@@ -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,12 +42,21 @@ impl Run for TestCommand {
3742 TestType :: FileCheck => {
3843 cprint ! ( "File checking {}..." , testcase. name) ;
3944 testcase. build ( manifest) ;
40- filechecker. run ( & testcase. source , & testcase. output ) ;
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) ;
4454 testcase. build ( manifest) ;
4555 }
56+ TestType :: CompileLib => {
57+ cprint ! ( "Compiling lib {}..." , testcase. name) ;
58+ testcase. build_lib ( manifest) ;
59+ }
4660 }
4761 cprintln ! ( "<g>OK</g>" ) ;
4862 }
@@ -51,58 +65,119 @@ impl Run for TestCommand {
5165
5266impl TestCommand {
5367 pub fn collect_testcases ( & self , manifest : & Manifest ) -> Vec < TestCase > {
54- let mut result = vec ! [ ] ;
68+ let mut tests = vec ! [ ] ;
5569
5670 // Examples
57- for case in glob ( "example /*.rs" ) . unwrap ( ) {
71+ for case in glob ( "examples /*.rs" ) . unwrap ( ) {
5872 let case = case. unwrap ( ) ;
5973 let filename = case. file_stem ( ) . unwrap ( ) ;
60- if filename == "mini_core" {
61- continue ;
62- }
63- let name = format ! ( "example/{}" , filename. to_string_lossy( ) ) ;
64- let output = manifest. out_dir . join ( "example" ) . join ( filename) ;
65- result. push ( TestCase { name, source : case, output, test : TestType :: Compile } )
74+ let name = format ! ( "examples/{}" , filename. to_string_lossy( ) ) ;
75+ let output_file = manifest. out_dir . join ( "examples" ) . join ( filename) ;
76+ tests. push ( TestCase { name, source : case, output_file, test : TestType :: Compile } )
6677 }
6778
6879 // Codegen tests
6980 for case in glob ( "tests/codegen/*.rs" ) . unwrap ( ) {
7081 let case = case. unwrap ( ) ;
7182 let filename = case. file_stem ( ) . unwrap ( ) ;
7283 let name = format ! ( "codegen/{}" , filename. to_string_lossy( ) ) ;
73- let output = manifest. out_dir . join ( "tests/codegen" ) . join ( filename) ;
74- result. push ( TestCase { name, source : case, output, test : TestType :: FileCheck } )
84+ let output_file = manifest. out_dir . join ( "tests/codegen" ) . join ( filename) ;
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 } )
95+ }
96+
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+ }
75110 }
76111
77- result
112+ // Compile auxiliary before the tests
113+ let mut cases = auxiliary;
114+ cases. extend ( tests) ;
115+ cases
78116 }
79117}
80118
81119pub enum TestType {
120+ /// Test an executable can be compiled
82121 Compile ,
122+ /// Test a library can be compiled
123+ CompileLib ,
124+ /// Run LLVM FileCheck on the generated code
83125 FileCheck ,
126+ /// Bless test - the output should be the same as the last run
127+ Bless ,
84128}
85129
86130pub struct TestCase {
87131 pub name : String ,
88132 pub source : PathBuf ,
89- pub output : PathBuf ,
133+ pub output_file : PathBuf ,
90134 pub test : TestType ,
91135}
92136
93137impl TestCase {
94138 pub fn build ( & self , manifest : & Manifest ) {
95- std:: fs:: create_dir_all ( self . output . parent ( ) . unwrap ( ) ) . unwrap ( ) ;
139+ let output_dir = self . output_file . parent ( ) . unwrap ( ) ;
140+ std:: fs:: create_dir_all ( output_dir) . unwrap ( ) ;
96141 let mut command = manifest. rustc ( ) ;
97142 command
98143 . args ( [ "--crate-type" , "bin" ] )
99144 . arg ( "-O" )
100145 . arg ( & self . source )
101146 . arg ( "-o" )
102- . arg ( & self . output ) ;
147+ . arg ( & self . output_file ) ;
103148 log:: debug!( "running {:?}" , command) ;
104149 command. status ( ) . unwrap ( ) ;
105150 }
151+
152+ pub fn build_lib ( & self , manifest : & Manifest ) {
153+ let output_dir = self . output_file . parent ( ) . unwrap ( ) ;
154+ std:: fs:: create_dir_all ( output_dir) . unwrap ( ) ;
155+ let mut command = manifest. rustc ( ) ;
156+ command
157+ . args ( [ "--crate-type" , "lib" ] )
158+ . arg ( "-O" )
159+ . arg ( & self . source )
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
162+ log:: debug!( "running {:?}" , command) ;
163+ command. status ( ) . unwrap ( ) ;
164+ }
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+ }
106181}
107182
108183struct FileChecker {
@@ -126,25 +201,41 @@ impl FileChecker {
126201 Self { filecheck }
127202 }
128203
129- fn run ( & self , source : & Path , output : & Path ) {
130- let case = source. file_stem ( ) . unwrap ( ) . to_string_lossy ( ) ;
131- let generated = std:: fs:: read_dir ( output. parent ( ) . unwrap ( ) )
132- . unwrap ( )
133- . filter_map ( |entry| entry. ok ( ) )
134- . find ( |entry| {
135- let filename = entry. file_name ( ) ;
136- let filename = filename. to_string_lossy ( ) ;
137- filename. ends_with ( ".c" ) && filename. starts_with ( case. as_ref ( ) )
138- } ) ;
139-
140- assert ! ( generated. is_some( ) , "could not find {case}'s generated file" ) ;
141- let generated = generated. unwrap ( ) ;
142-
143- let generated = File :: open ( generated. path ( ) ) . unwrap ( ) ;
204+ fn run ( & self , case : & TestCase ) {
205+ let generated = File :: open ( case. generated ( ) ) . unwrap ( ) ;
144206 let mut command = std:: process:: Command :: new ( & self . filecheck ) ;
145- command. arg ( source) . stdin ( generated) ;
207+ command. arg ( & case . source ) . stdin ( generated) ;
146208 log:: debug!( "running {:?}" , command) ;
147209 let output = command. output ( ) . unwrap ( ) ;
148- 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+ }
149240 }
150241}
0 commit comments