@@ -5,14 +5,19 @@ use anstream::{eprint as print, eprintln as println};
55use clap:: Args ;
66use color_print:: { cprint, cprintln} ;
77use glob:: glob;
8+ use similar:: { ChangeTag , TextDiff } ;
89use which:: which;
910
1011use crate :: manifest:: Manifest ;
1112use crate :: Run ;
1213
1314/// Run tests
1415#[ derive( Args , Debug ) ]
15- pub struct TestCommand { }
16+ pub struct TestCommand {
17+ /// Update the blessed output
18+ #[ clap( long) ]
19+ pub bless : bool ,
20+ }
1621
1722impl Run for TestCommand {
1823 fn run ( & self , manifest : & Manifest ) {
@@ -37,7 +42,12 @@ impl Run for TestCommand {
3742 TestType :: FileCheck => {
3843 cprint ! ( "File checking {}..." , testcase. name) ;
3944 testcase. build ( manifest) ;
40- filechecker. run ( & testcase. source , & testcase. output_file ) ;
45+ filechecker. run ( & testcase) ;
46+ }
47+ TestType :: Bless => {
48+ cprint ! ( "Blessing {}..." , testcase. name) ;
49+ testcase. build ( manifest) ;
50+ bless ( self . bless , & testcase) ;
4151 }
4252 TestType :: Compile => {
4353 cprint ! ( "Compiling {}..." , testcase. name) ;
@@ -55,24 +65,15 @@ impl Run for TestCommand {
5565
5666impl TestCommand {
5767 pub fn collect_testcases ( & self , manifest : & Manifest ) -> Vec < TestCase > {
58- let mut result = vec ! [ ] ;
59-
60- // Test auxiliary (should compile first)
61- for case in glob ( "tests/auxiliary/*.rs" ) . unwrap ( ) {
62- let case = case. unwrap ( ) ;
63- let filename = case. file_stem ( ) . unwrap ( ) ;
64- let name = format ! ( "auxiliary/{}" , filename. to_string_lossy( ) ) ;
65- let output_file = manifest. out_dir . join ( filename) ;
66- result. push ( TestCase { name, source : case, output_file, test : TestType :: CompileLib } )
67- }
68+ let mut tests = vec ! [ ] ;
6869
6970 // Examples
7071 for case in glob ( "examples/*.rs" ) . unwrap ( ) {
7172 let case = case. unwrap ( ) ;
7273 let filename = case. file_stem ( ) . unwrap ( ) ;
7374 let name = format ! ( "examples/{}" , filename. to_string_lossy( ) ) ;
7475 let output_file = manifest. out_dir . join ( "examples" ) . join ( filename) ;
75- result . push ( TestCase { name, source : case, output_file, test : TestType :: Compile } )
76+ tests . push ( TestCase { name, source : case, output_file, test : TestType :: Compile } )
7677 }
7778
7879 // Codegen tests
@@ -81,18 +82,49 @@ impl TestCommand {
8182 let filename = case. file_stem ( ) . unwrap ( ) ;
8283 let name = format ! ( "codegen/{}" , filename. to_string_lossy( ) ) ;
8384 let output_file = manifest. out_dir . join ( "tests/codegen" ) . join ( filename) ;
84- result. push ( TestCase { name, source : case, output_file, test : TestType :: FileCheck } )
85+ tests. push ( TestCase { name, source : case, output_file, test : TestType :: FileCheck } )
86+ }
87+
88+ // Bless tests - the output should be the same as the last run
89+ for case in glob ( "tests/bless/*.rs" ) . unwrap ( ) {
90+ let case = case. unwrap ( ) ;
91+ let filename = case. file_stem ( ) . unwrap ( ) ;
92+ let name = format ! ( "bless/{}" , filename. to_string_lossy( ) ) ;
93+ let output_file = manifest. out_dir . join ( "tests/bless" ) . join ( filename) ;
94+ tests. push ( TestCase { name, source : case, output_file, test : TestType :: Bless } )
8595 }
8696
87- result
97+ // Collect test-auxiliary
98+ let aux_use = regex:: Regex :: new ( r"^//@\s*aux-build:(?P<fname>.*)" ) . unwrap ( ) ;
99+ let mut auxiliary = vec ! [ ] ;
100+ for case in tests. iter ( ) {
101+ let source = std:: fs:: read_to_string ( & case. source ) . unwrap ( ) ;
102+ for cap in aux_use. captures_iter ( & source) {
103+ let fname = cap. name ( "fname" ) . unwrap ( ) . as_str ( ) ;
104+ let source = Path :: new ( "tests/auxiliary" ) . join ( fname) ;
105+ let filename = source. file_stem ( ) . unwrap ( ) ;
106+ let name = format ! ( "auxiliary/{}" , filename. to_string_lossy( ) ) ;
107+ let output_file = manifest. out_dir . join ( filename) ; // aux files are output to the base directory
108+ auxiliary. push ( TestCase { name, source, output_file, test : TestType :: CompileLib } )
109+ }
110+ }
111+
112+ // Compile auxiliary before the tests
113+ let mut cases = auxiliary;
114+ cases. extend ( tests) ;
115+ cases
88116 }
89117}
90118
91119pub enum TestType {
92120 /// Test an executable can be compiled
93121 Compile ,
122+ /// Test a library can be compiled
94123 CompileLib ,
124+ /// Run LLVM FileCheck on the generated code
95125 FileCheck ,
126+ /// Bless test - the output should be the same as the last run
127+ Bless ,
96128}
97129
98130pub struct TestCase {
@@ -125,11 +157,27 @@ impl TestCase {
125157 . args ( [ "--crate-type" , "lib" ] )
126158 . arg ( "-O" )
127159 . arg ( & self . source )
128- . arg ( "--out-dir" )
129- . arg ( self . output_file . parent ( ) . unwrap ( ) ) ;
160+ . arg ( "--out-dir" ) // we use `--out-dir` to integrate with the default name convention
161+ . arg ( output_dir ) ; // so here we ignore the filename and just use the directory
130162 log:: debug!( "running {:?}" , command) ;
131163 command. status ( ) . unwrap ( ) ;
132164 }
165+
166+ /// Get the generated C file f
167+ pub fn generated ( & self ) -> PathBuf {
168+ let case = self . source . file_stem ( ) . unwrap ( ) . to_string_lossy ( ) ;
169+ let generated = std:: fs:: read_dir ( self . output_file . parent ( ) . unwrap ( ) )
170+ . unwrap ( )
171+ . filter_map ( |entry| entry. ok ( ) )
172+ . find ( |entry| {
173+ let filename = entry. file_name ( ) ;
174+ let filename = filename. to_string_lossy ( ) ;
175+ filename. ends_with ( ".c" ) && filename. starts_with ( case. as_ref ( ) )
176+ } ) ;
177+
178+ assert ! ( generated. is_some( ) , "could not find {case}'s generated file" ) ;
179+ generated. unwrap ( ) . path ( )
180+ }
133181}
134182
135183struct FileChecker {
@@ -153,25 +201,41 @@ impl FileChecker {
153201 Self { filecheck }
154202 }
155203
156- fn run ( & self , source : & Path , output : & Path ) {
157- let case = source. file_stem ( ) . unwrap ( ) . to_string_lossy ( ) ;
158- let generated = std:: fs:: read_dir ( output. parent ( ) . unwrap ( ) )
159- . unwrap ( )
160- . filter_map ( |entry| entry. ok ( ) )
161- . find ( |entry| {
162- let filename = entry. file_name ( ) ;
163- let filename = filename. to_string_lossy ( ) ;
164- filename. ends_with ( ".c" ) && filename. starts_with ( case. as_ref ( ) )
165- } ) ;
166-
167- assert ! ( generated. is_some( ) , "could not find {case}'s generated file" ) ;
168- let generated = generated. unwrap ( ) ;
169-
170- let generated = File :: open ( generated. path ( ) ) . unwrap ( ) ;
204+ fn run ( & self , case : & TestCase ) {
205+ let generated = File :: open ( case. generated ( ) ) . unwrap ( ) ;
171206 let mut command = std:: process:: Command :: new ( & self . filecheck ) ;
172- command. arg ( source) . stdin ( generated) ;
207+ command. arg ( & case . source ) . stdin ( generated) ;
173208 log:: debug!( "running {:?}" , command) ;
174209 let output = command. output ( ) . unwrap ( ) ;
175- assert ! ( output. status. success( ) , "failed to run FileCheck on {case}" ) ;
210+ assert ! (
211+ output. status. success( ) ,
212+ "failed to run FileCheck on {}" ,
213+ case. source. file_stem( ) . unwrap( ) . to_string_lossy( )
214+ ) ;
215+ }
216+ }
217+
218+ fn bless ( update : bool , case : & TestCase ) {
219+ let output = case. generated ( ) ;
220+ let blessed = case. source . with_extension ( "c" ) ;
221+ if update {
222+ std:: fs:: copy ( output, blessed) . unwrap ( ) ;
223+ } else {
224+ let output = std:: fs:: read_to_string ( output) . unwrap ( ) ;
225+ let blessed = std:: fs:: read_to_string ( blessed) . unwrap ( ) ;
226+
227+ let diff = TextDiff :: from_lines ( & blessed, & output) ;
228+ if diff. ratio ( ) < 1.0 {
229+ cprintln ! ( "<r,s>output does not match blessed output</r,s>" ) ;
230+ for change in diff. iter_all_changes ( ) {
231+ let lineno = change. old_index ( ) . unwrap_or ( change. new_index ( ) . unwrap_or ( 0 ) ) ;
232+ match change. tag ( ) {
233+ ChangeTag :: Equal => print ! ( " {:4}| {}" , lineno, change) ,
234+ ChangeTag :: Insert => cprint ! ( "<g>+{:4}| {}</g>" , lineno, change) ,
235+ ChangeTag :: Delete => cprint ! ( "<r>-{:4}| {}</r>" , lineno, change) ,
236+ }
237+ }
238+ std:: process:: exit ( 1 ) ;
239+ }
176240 }
177241}
0 commit comments