@@ -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 ) ;
@@ -72,6 +72,13 @@ impl Run for TestCommand {
7272 self . log_action_context ( "output" , & testcase. output_file . display ( ) ) ;
7373 testcase. build_lib ( manifest) ;
7474 }
75+ TestType :: Runtime => {
76+ self . log_action_start ( "TEST Runtime" , & testcase. name ) ;
77+ self . log_action_context ( "source" , & testcase. source . display ( ) ) ;
78+ self . log_action_context ( "output" , & testcase. output_file . display ( ) ) ;
79+ testcase. build ( manifest) ;
80+ self . run_and_check_output ( & testcase) ;
81+ }
7582 }
7683 }
7784 }
@@ -84,7 +91,6 @@ impl Run for TestCommand {
8491impl TestCommand {
8592 pub fn collect_testcases ( & self , manifest : & Manifest ) -> Vec < TestCase > {
8693 let mut cases = vec ! [ ] ;
87-
8894 let verbose = self . verbose ;
8995
9096 // Examples
@@ -117,6 +123,20 @@ impl TestCommand {
117123 cases. push ( testcase) ;
118124 }
119125
126+ // Runtime tests - compile, run and compare output
127+ for case in glob ( "tests/runit/*.rs" ) . unwrap ( ) {
128+ let case = case. unwrap ( ) ;
129+ let filename = case. file_stem ( ) . unwrap ( ) ;
130+ // Skip the test runner
131+ if filename == "runner" {
132+ continue ;
133+ }
134+ let name = format ! ( "runit/{}" , filename. to_string_lossy( ) ) ;
135+ let output_file = manifest. out_dir . join ( "tests/runit" ) . join ( filename) ;
136+ let testcase = TestCase :: new ( name, case, output_file, TestType :: Runtime , verbose) ;
137+ cases. push ( testcase) ;
138+ }
139+
120140 // Collect test-auxiliary
121141 let aux_use = regex:: Regex :: new ( r"\s*//@\s*aux-build:(?P<fname>.*)" ) . unwrap ( ) ;
122142 let mut auxiliaries = vec ! [ ] ;
@@ -145,6 +165,134 @@ impl TestCommand {
145165 testcases. extend ( cases) ;
146166 testcases
147167 }
168+
169+ fn bless ( & self , update : bool , case : & TestCase ) {
170+ let output = case. generated ( ) ;
171+ let blessed = case. source . with_extension ( "c" ) ;
172+
173+ self . log_action_context ( "checking" , & blessed. display ( ) ) ;
174+ if update {
175+ self . log_action_context ( "updating" , & blessed. display ( ) ) ;
176+ std:: fs:: copy ( output, & blessed) . unwrap ( ) ;
177+ self . log_action_context ( "result" , "updated" ) ;
178+ } else {
179+ let output = std:: fs:: read_to_string ( output) . unwrap ( ) ;
180+ let blessed = std:: fs:: read_to_string ( & blessed) . unwrap ( ) ;
181+
182+ let diff = TextDiff :: from_lines ( & blessed, & output) ;
183+ if diff. ratio ( ) < 1.0 {
184+ cprintln ! ( "<r,s>output does not match blessed output</r,s>" ) ;
185+ for change in diff. iter_all_changes ( ) {
186+ let lineno = change. old_index ( ) . unwrap_or ( change. new_index ( ) . unwrap_or ( 0 ) ) ;
187+ match change. tag ( ) {
188+ ChangeTag :: Equal => print ! ( " {:4}| {}" , lineno, change) ,
189+ ChangeTag :: Insert => cprint ! ( "<g>+{:4}| {}</g>" , lineno, change) ,
190+ ChangeTag :: Delete => cprint ! ( "<r>-{:4}| {}</r>" , lineno, change) ,
191+ }
192+ }
193+ std:: process:: exit ( 1 ) ;
194+ }
195+ self . log_action_context ( "result" , "passed" ) ;
196+ }
197+ }
198+
199+ /// Run a runtime test and compare its output with the expected output
200+ fn run_and_check_output ( & self , testcase : & TestCase ) {
201+ // Run the test
202+ self . log_action_context ( "running" , & testcase. output_file . display ( ) ) ;
203+ let output = std:: process:: Command :: new ( & testcase. output_file )
204+ . output ( )
205+ . unwrap_or_else ( |e| panic ! ( "failed to run {}: {}" , testcase. output_file. display( ) , e) ) ;
206+
207+ // Check return value
208+ let actual_return = output. status . code ( ) . unwrap_or_else ( || {
209+ panic ! ( "Process terminated by signal: {}" , testcase. output_file. display( ) )
210+ } ) ;
211+
212+ let expected_return_path = testcase. source . with_extension ( "ret" ) ;
213+ if expected_return_path. exists ( ) {
214+ self . log_action_context ( "checking return value" , & expected_return_path. display ( ) ) ;
215+ let expected_return = std:: fs:: read_to_string ( & expected_return_path)
216+ . unwrap_or_else ( |e| {
217+ panic ! ( "failed to read {}: {}" , expected_return_path. display( ) , e)
218+ } )
219+ . trim ( )
220+ . parse :: < i32 > ( )
221+ . unwrap_or_else ( |e| {
222+ panic ! ( "invalid return value in {}: {}" , expected_return_path. display( ) , e)
223+ } ) ;
224+
225+ if actual_return != expected_return {
226+ cprintln ! ( "<r,s>return value does not match expected value</r,s>" ) ;
227+ cprintln ! ( "expected: {}" , expected_return) ;
228+ cprintln ! ( "actual: {}" , actual_return) ;
229+ std:: process:: exit ( 1 ) ;
230+ }
231+ self . log_action_context ( "return value" , "passed" ) ;
232+ }
233+
234+ // Check stdout
235+ let actual_output = String :: from_utf8_lossy ( & output. stdout ) . into_owned ( ) ;
236+ let expected_output_path = testcase. source . with_extension ( "out" ) ;
237+
238+ if !expected_output_path. exists ( ) {
239+ panic ! ( "expected output file {} does not exist" , expected_output_path. display( ) ) ;
240+ }
241+
242+ self . log_action_context ( "checking stdout" , & expected_output_path. display ( ) ) ;
243+ let expected_output = std:: fs:: read_to_string ( & expected_output_path)
244+ . unwrap_or_else ( |e| panic ! ( "failed to read {}: {}" , expected_output_path. display( ) , e) ) ;
245+
246+ let diff = TextDiff :: from_lines ( & expected_output, & actual_output) ;
247+ if diff. ratio ( ) < 1.0 {
248+ cprintln ! ( "<r,s>stdout does not match expected output</r,s>" ) ;
249+ for change in diff. iter_all_changes ( ) {
250+ let lineno = change. old_index ( ) . unwrap_or ( change. new_index ( ) . unwrap_or ( 0 ) ) ;
251+ match change. tag ( ) {
252+ ChangeTag :: Equal => print ! ( " {:4}| {}" , lineno, change) ,
253+ ChangeTag :: Insert => cprint ! ( "<g>+{:4}| {}</g>" , lineno, change) ,
254+ ChangeTag :: Delete => cprint ! ( "<r>-{:4}| {}</r>" , lineno, change) ,
255+ }
256+ }
257+ std:: process:: exit ( 1 ) ;
258+ }
259+ self . log_action_context ( "stdout" , "passed" ) ;
260+
261+ // Check stderr if there's any output
262+ if !output. stderr . is_empty ( ) {
263+ let stderr_str = String :: from_utf8_lossy ( & output. stderr ) . into_owned ( ) ;
264+ let expected_stderr_path = testcase. source . with_extension ( "err" ) ;
265+
266+ if expected_stderr_path. exists ( ) {
267+ self . log_action_context ( "checking stderr" , & expected_stderr_path. display ( ) ) ;
268+ let expected_stderr = std:: fs:: read_to_string ( & expected_stderr_path)
269+ . unwrap_or_else ( |e| {
270+ panic ! ( "failed to read {}: {}" , expected_stderr_path. display( ) , e)
271+ } ) ;
272+
273+ let diff = TextDiff :: from_lines ( & expected_stderr, & stderr_str) ;
274+ if diff. ratio ( ) < 1.0 {
275+ cprintln ! ( "<r,s>stderr does not match expected output</r,s>" ) ;
276+ for change in diff. iter_all_changes ( ) {
277+ let lineno = change. old_index ( ) . unwrap_or ( change. new_index ( ) . unwrap_or ( 0 ) ) ;
278+ match change. tag ( ) {
279+ ChangeTag :: Equal => print ! ( " {:4}| {}" , lineno, change) ,
280+ ChangeTag :: Insert => cprint ! ( "<g>+{:4}| {}</g>" , lineno, change) ,
281+ ChangeTag :: Delete => cprint ! ( "<r>-{:4}| {}</r>" , lineno, change) ,
282+ }
283+ }
284+ std:: process:: exit ( 1 ) ;
285+ }
286+ self . log_action_context ( "stderr" , "passed" ) ;
287+ } else if !stderr_str. trim ( ) . is_empty ( ) {
288+ // If there's no .err file but we got stderr output, that's unexpected
289+ cprintln ! ( "<r,s>unexpected stderr output:</r,s>" ) ;
290+ cprintln ! ( "{}" , stderr_str) ;
291+ std:: process:: exit ( 1 ) ;
292+ }
293+ }
294+ self . log_action_context ( "result" , "all checks passed" ) ;
295+ }
148296}
149297
150298#[ derive( Debug ) ]
@@ -157,14 +305,18 @@ pub enum TestType {
157305 FileCheck ,
158306 /// Bless test - the output should be the same as the last run
159307 Bless ,
308+ /// Runtime test - compile, run and compare output
309+ Runtime ,
160310}
311+
161312impl TestType {
162313 pub fn as_str ( & self ) -> & ' static str {
163314 match self {
164315 TestType :: Compile => "compile" ,
165316 TestType :: CompileLib => "compile-lib" ,
166317 TestType :: FileCheck => "filecheck" ,
167318 TestType :: Bless => "bless" ,
319+ TestType :: Runtime => "runtime" ,
168320 }
169321 }
170322}
@@ -286,28 +438,3 @@ impl FileChecker {
286438 ) ;
287439 }
288440}
289-
290- fn bless ( update : bool , case : & TestCase ) {
291- let output = case. generated ( ) ;
292- let blessed = case. source . with_extension ( "c" ) ;
293- if update {
294- std:: fs:: copy ( output, blessed) . unwrap ( ) ;
295- } else {
296- let output = std:: fs:: read_to_string ( output) . unwrap ( ) ;
297- let blessed = std:: fs:: read_to_string ( blessed) . unwrap ( ) ;
298-
299- let diff = TextDiff :: from_lines ( & blessed, & output) ;
300- if diff. ratio ( ) < 1.0 {
301- cprintln ! ( "<r,s>output does not match blessed output</r,s>" ) ;
302- for change in diff. iter_all_changes ( ) {
303- let lineno = change. old_index ( ) . unwrap_or ( change. new_index ( ) . unwrap_or ( 0 ) ) ;
304- match change. tag ( ) {
305- ChangeTag :: Equal => print ! ( " {:4}| {}" , lineno, change) ,
306- ChangeTag :: Insert => cprint ! ( "<g>+{:4}| {}</g>" , lineno, change) ,
307- ChangeTag :: Delete => cprint ! ( "<r>-{:4}| {}</r>" , lineno, change) ,
308- }
309- }
310- std:: process:: exit ( 1 ) ;
311- }
312- }
313- }
0 commit comments