@@ -58,7 +58,7 @@ impl Run for TestCommand {
5858 self . log_action_context ( "source" , & testcase. source . display ( ) ) ;
5959 self . log_action_context ( "output" , & testcase. output_file . display ( ) ) ;
6060 testcase. build ( manifest) ;
61- bless ( self . bless , & testcase) ;
61+ self . bless ( self . bless , & testcase) ;
6262 }
6363 TestType :: Compile => {
6464 self . log_action_start ( "TEST Compile" , & testcase. name ) ;
@@ -73,6 +73,7 @@ impl Run for TestCommand {
7373 testcase. build_lib ( manifest) ;
7474 }
7575 }
76+ self . check_and_run_directives ( & testcase) ;
7677 }
7778 }
7879
@@ -84,7 +85,6 @@ impl Run for TestCommand {
8485impl TestCommand {
8586 pub fn collect_testcases ( & self , manifest : & Manifest ) -> Vec < TestCase > {
8687 let mut cases = vec ! [ ] ;
87-
8888 let verbose = self . verbose ;
8989
9090 // Examples
@@ -107,7 +107,7 @@ impl TestCommand {
107107 cases. push ( testcase) ;
108108 }
109109
110- // Bless tests - the output should be the same as the last run
110+ // Bless tests
111111 for case in glob ( "tests/bless/*.rs" ) . unwrap ( ) {
112112 let case = case. unwrap ( ) ;
113113 let filename = case. file_stem ( ) . unwrap ( ) ;
@@ -117,27 +117,26 @@ impl TestCommand {
117117 cases. push ( testcase) ;
118118 }
119119
120- // Collect test-auxiliary
121- let aux_use = regex:: Regex :: new ( r"(?m)//@\s*aux-build:(?P<fname>.*)" ) . unwrap ( ) ;
120+ // Collect and process auxiliary builds from directives
122121 let mut auxiliaries = vec ! [ ] ;
123122 for case in cases. iter ( ) {
124- let content = std:: fs:: read_to_string ( & case. source ) . unwrap ( ) ;
125- for cap in aux_use. captures_iter ( & content) {
126- println ! ( "{:?}" , case. source) ;
127- let fname = cap. name ( "fname" ) . unwrap ( ) . as_str ( ) ;
128- let source = Path :: new ( "tests/auxiliary" ) . join ( fname) ;
129- let filename = source. file_stem ( ) . unwrap ( ) ;
130- let name = format ! ( "auxiliary/{}" , filename. to_string_lossy( ) ) ;
131-
132- // deduplication
133- if auxiliaries. iter ( ) . any ( |aux : & TestCase | aux. name == name) {
134- continue ;
123+ let directives = case. parse_directives ( ) ;
124+ for directive in directives {
125+ if let TestDirective :: AuxBuild ( fname) = directive {
126+ let source = Path :: new ( "tests/auxiliary" ) . join ( & fname) ;
127+ let filename = source. file_stem ( ) . unwrap ( ) ;
128+ let name = format ! ( "auxiliary/{}" , filename. to_string_lossy( ) ) ;
129+
130+ // deduplication
131+ if auxiliaries. iter ( ) . any ( |aux : & TestCase | aux. name == name) {
132+ continue ;
133+ }
134+
135+ let output_file = manifest. out_dir . join ( filename) ; // aux files are output to the base directory
136+ let testcase =
137+ TestCase :: new ( name, source, output_file, TestType :: CompileLib , verbose) ;
138+ auxiliaries. push ( testcase) ;
135139 }
136-
137- let output_file = manifest. out_dir . join ( filename) ; // aux files are output to the base directory
138- let testcase =
139- TestCase :: new ( name, source, output_file, TestType :: CompileLib , verbose) ;
140- auxiliaries. push ( testcase) ;
141140 }
142141 }
143142
@@ -146,6 +145,141 @@ impl TestCommand {
146145 testcases. extend ( cases) ;
147146 testcases
148147 }
148+
149+ fn bless ( & self , update : bool , case : & TestCase ) {
150+ let output = case. generated ( ) ;
151+ let blessed = case. source . with_extension ( "c" ) ;
152+
153+ self . log_action_context ( "checking" , & blessed. display ( ) ) ;
154+ if update {
155+ self . log_action_context ( "updating" , & blessed. display ( ) ) ;
156+ std:: fs:: copy ( output, & blessed) . unwrap ( ) ;
157+ self . log_action_context ( "result" , "updated" ) ;
158+ } else {
159+ let output = std:: fs:: read_to_string ( output) . unwrap ( ) ;
160+ let blessed = std:: fs:: read_to_string ( & blessed) . unwrap ( ) ;
161+
162+ let diff = TextDiff :: from_lines ( & blessed, & output) ;
163+ if diff. ratio ( ) < 1.0 {
164+ cprintln ! ( "<r,s>output does not match blessed output</r,s>" ) ;
165+ for change in diff. iter_all_changes ( ) {
166+ let lineno = change. old_index ( ) . unwrap_or ( change. new_index ( ) . unwrap_or ( 0 ) ) ;
167+ match change. tag ( ) {
168+ ChangeTag :: Equal => print ! ( " {:4}| {}" , lineno, change) ,
169+ ChangeTag :: Insert => cprint ! ( "<g>+{:4}| {}</g>" , lineno, change) ,
170+ ChangeTag :: Delete => cprint ! ( "<r>-{:4}| {}</r>" , lineno, change) ,
171+ }
172+ }
173+ std:: process:: exit ( 1 ) ;
174+ }
175+ self . log_action_context ( "result" , "passed" ) ;
176+ }
177+ }
178+
179+ /// Run a runtime test and check its output against directives
180+ fn check_and_run_directives ( & self , testcase : & TestCase ) {
181+ // Parse directives from source
182+ let directives = testcase. parse_directives ( ) ;
183+ self . log_action_context ( "directives" , & format ! ( "found {} directives" , directives. len( ) ) ) ;
184+
185+ let mut runpass = false ;
186+ let mut exitcode = None ;
187+ let mut stdout = None ;
188+ let mut stderr = None ;
189+
190+ // Check each directive
191+ for directive in directives {
192+ match directive {
193+ TestDirective :: RunPass => runpass = true ,
194+ TestDirective :: CheckStdout ( expected) => stdout = Some ( expected) ,
195+ TestDirective :: CheckStderr ( expected) => stderr = Some ( expected) ,
196+ TestDirective :: ExitCode ( expected) => exitcode = Some ( expected) ,
197+ TestDirective :: AuxBuild ( _) => {
198+ // AuxBuild directives are handled during test collection
199+ // No need to check them during test execution
200+ }
201+ }
202+ }
203+
204+ if !runpass && ( exitcode. is_some ( ) | stdout. is_some ( ) | stderr. is_some ( ) ) {
205+ panic ! ( "Directives conflicts, lack of '//@ run-pass'" ) ;
206+ }
207+
208+ if runpass {
209+ self . run_and_check_output ( testcase, exitcode, stdout, stderr) ;
210+ }
211+
212+ self . log_action_context ( "result" , "all checks passed" ) ;
213+ }
214+
215+ fn run_and_check_output (
216+ & self ,
217+ testcase : & TestCase ,
218+ expected_exit : Option < i32 > ,
219+ expected_stdout : Option < String > ,
220+ expected_stderr : Option < String > ,
221+ ) {
222+ // Run the test
223+ self . log_action_context ( "running" , & testcase. output_file . display ( ) ) ;
224+ let output = std:: process:: Command :: new ( & testcase. output_file )
225+ . output ( )
226+ . unwrap_or_else ( |e| panic ! ( "failed to run {}: {}" , testcase. output_file. display( ) , e) ) ;
227+
228+ // Get actual outputs
229+ let actual_return = output. status . code ( ) . unwrap_or_else ( || {
230+ panic ! ( "Process terminated by signal: {}" , testcase. output_file. display( ) )
231+ } ) ;
232+ let actual_stdout = String :: from_utf8_lossy ( & output. stdout ) . into_owned ( ) ;
233+ let actual_stderr = String :: from_utf8_lossy ( & output. stderr ) . into_owned ( ) ;
234+
235+ {
236+ let expected_exit = expected_exit. unwrap_or ( 0 ) ;
237+ self . log_action_context ( "checking exit code" , & expected_exit. to_string ( ) ) ;
238+ if actual_return != expected_exit {
239+ cprintln ! ( "<r,s>exit code does not match expected value</r,s>" ) ;
240+ cprintln ! ( "expected: {}" , expected_exit) ;
241+ cprintln ! ( "actual: {}" , actual_return) ;
242+ std:: process:: exit ( 1 ) ;
243+ }
244+ self . log_action_context ( "exit code" , "passed" ) ;
245+ }
246+
247+ if let Some ( expected_stdout) = expected_stdout {
248+ self . log_action_context ( "checking stdout" , & expected_stdout) ;
249+ let diff = TextDiff :: from_lines ( & expected_stdout, & actual_stdout) ;
250+ if diff. ratio ( ) < 1.0 {
251+ cprintln ! ( "<r,s>stdout does not match expected output</r,s>" ) ;
252+ for change in diff. iter_all_changes ( ) {
253+ let lineno = change. old_index ( ) . unwrap_or ( change. new_index ( ) . unwrap_or ( 0 ) ) ;
254+ match change. tag ( ) {
255+ ChangeTag :: Equal => print ! ( " {:4}| {}" , lineno, change) ,
256+ ChangeTag :: Insert => cprint ! ( "<g>+{:4}| {}</g>" , lineno, change) ,
257+ ChangeTag :: Delete => cprint ! ( "<r>-{:4}| {}</r>" , lineno, change) ,
258+ }
259+ }
260+ std:: process:: exit ( 1 ) ;
261+ }
262+ self . log_action_context ( "stdout" , "passed" ) ;
263+ }
264+
265+ if let Some ( expected_stderr) = expected_stderr {
266+ self . log_action_context ( "checking stderr" , & expected_stderr) ;
267+ let diff = TextDiff :: from_lines ( & expected_stderr, & actual_stderr) ;
268+ if diff. ratio ( ) < 1.0 {
269+ cprintln ! ( "<r,s>stderr does not match expected output</r,s>" ) ;
270+ for change in diff. iter_all_changes ( ) {
271+ let lineno = change. old_index ( ) . unwrap_or ( change. new_index ( ) . unwrap_or ( 0 ) ) ;
272+ match change. tag ( ) {
273+ ChangeTag :: Equal => print ! ( " {:4}| {}" , lineno, change) ,
274+ ChangeTag :: Insert => cprint ! ( "<g>+{:4}| {}</g>" , lineno, change) ,
275+ ChangeTag :: Delete => cprint ! ( "<r>-{:4}| {}</r>" , lineno, change) ,
276+ }
277+ }
278+ std:: process:: exit ( 1 ) ;
279+ }
280+ self . log_action_context ( "stderr" , "passed" ) ;
281+ }
282+ }
149283}
150284
151285#[ derive( Debug ) ]
@@ -159,6 +293,7 @@ pub enum TestType {
159293 /// Bless test - the output should be the same as the last run
160294 Bless ,
161295}
296+
162297impl TestType {
163298 pub fn as_str ( & self ) -> & ' static str {
164299 match self {
@@ -241,6 +376,58 @@ impl TestCase {
241376 assert ! ( generated. is_some( ) , "could not find {case}'s generated file" ) ;
242377 generated. unwrap ( ) . path ( )
243378 }
379+
380+ /// Parse test directives from the source file
381+ fn parse_directives ( & self ) -> Vec < TestDirective > {
382+ let source = std:: fs:: read_to_string ( & self . source )
383+ . unwrap_or_else ( |e| panic ! ( "failed to read {}: {}" , self . source. display( ) , e) ) ;
384+
385+ let mut directives = Vec :: new ( ) ;
386+
387+ // Regular expressions for matching directives
388+ let run_pass = regex:: Regex :: new ( r"^//@\s*run-pass" ) . unwrap ( ) ;
389+ let stdout_re = regex:: Regex :: new ( r"^//@\s*check-stdout:\s*(.*)" ) . unwrap ( ) ;
390+ let stderr_re = regex:: Regex :: new ( r"^//@\s*check-stderr:\s*(.*)" ) . unwrap ( ) ;
391+ let exit_re = regex:: Regex :: new ( r"^//@\s*exit-code:\s*(\d+)" ) . unwrap ( ) ;
392+ let aux_re = regex:: Regex :: new ( r"^//@\s*aux-build:\s*(.*)" ) . unwrap ( ) ;
393+ // Regex to match any directive pattern
394+ let directive_re = regex:: Regex :: new ( r"^//@\s*([^:]+)" ) . unwrap ( ) ;
395+
396+ for ( line_num, line) in source. lines ( ) . enumerate ( ) {
397+ if let Some ( _cap) = run_pass. captures ( line) {
398+ directives. push ( TestDirective :: RunPass ) ;
399+ } else if let Some ( cap) = stdout_re. captures ( line) {
400+ let content = cap[ 1 ] . trim ( ) . to_string ( ) ;
401+ directives. push ( TestDirective :: CheckStdout ( content) ) ;
402+ } else if let Some ( cap) = stderr_re. captures ( line) {
403+ let content = cap[ 1 ] . trim ( ) . to_string ( ) ;
404+ directives. push ( TestDirective :: CheckStderr ( content) ) ;
405+ } else if let Some ( cap) = exit_re. captures ( line) {
406+ if let Ok ( code) = cap[ 1 ] . parse ( ) {
407+ directives. push ( TestDirective :: ExitCode ( code) ) ;
408+ } else {
409+ panic ! (
410+ "{}:{}: invalid exit code in directive" ,
411+ self . source. display( ) ,
412+ line_num + 1
413+ ) ;
414+ }
415+ } else if let Some ( cap) = aux_re. captures ( line) {
416+ let fname = cap[ 1 ] . trim ( ) . to_string ( ) ;
417+ directives. push ( TestDirective :: AuxBuild ( fname) ) ;
418+ } else if let Some ( cap) = directive_re. captures ( line) {
419+ let directive_name = cap[ 1 ] . trim ( ) ;
420+ panic ! (
421+ "{}:{}: unknown directive '{}', supported directives are: check-stdout, check-stderr, exit-code, aux-build" ,
422+ self . source. display( ) ,
423+ line_num + 1 ,
424+ directive_name
425+ ) ;
426+ }
427+ }
428+
429+ directives
430+ }
244431}
245432
246433struct FileChecker {
@@ -288,27 +475,18 @@ impl FileChecker {
288475 }
289476}
290477
291- fn bless ( update : bool , case : & TestCase ) {
292- let output = case. generated ( ) ;
293- let blessed = case. source . with_extension ( "c" ) ;
294- if update {
295- std:: fs:: copy ( output, blessed) . unwrap ( ) ;
296- } else {
297- let output = std:: fs:: read_to_string ( output) . unwrap ( ) ;
298- let blessed = std:: fs:: read_to_string ( blessed) . unwrap ( ) ;
299-
300- let diff = TextDiff :: from_lines ( & blessed, & output) ;
301- if diff. ratio ( ) < 1.0 {
302- cprintln ! ( "<r,s>output does not match blessed output</r,s>" ) ;
303- for change in diff. iter_all_changes ( ) {
304- let lineno = change. old_index ( ) . unwrap_or ( change. new_index ( ) . unwrap_or ( 0 ) ) ;
305- match change. tag ( ) {
306- ChangeTag :: Equal => print ! ( " {:4}| {}" , lineno, change) ,
307- ChangeTag :: Insert => cprint ! ( "<g>+{:4}| {}</g>" , lineno, change) ,
308- ChangeTag :: Delete => cprint ! ( "<r>-{:4}| {}</r>" , lineno, change) ,
309- }
310- }
311- std:: process:: exit ( 1 ) ;
312- }
313- }
478+ /// Test directives that can appear in source files
479+ #[ derive( Debug ) ]
480+ enum TestDirective {
481+ /// Compile and run a testcase,
482+ /// expect a success (exit with 0)
483+ RunPass ,
484+ /// Expected stdout content
485+ CheckStdout ( String ) ,
486+ /// Expected stderr content
487+ CheckStderr ( String ) ,
488+ /// Expected exit code
489+ ExitCode ( i32 ) ,
490+ /// Auxiliary build requirement
491+ AuxBuild ( String ) ,
314492}
0 commit comments