@@ -58,7 +58,7 @@ impl Run for TestCommand {
58
58
self . log_action_context ( "source" , & testcase. source . display ( ) ) ;
59
59
self . log_action_context ( "output" , & testcase. output_file . display ( ) ) ;
60
60
testcase. build ( manifest) ;
61
- bless ( self . bless , & testcase) ;
61
+ self . bless ( self . bless , & testcase) ;
62
62
}
63
63
TestType :: Compile => {
64
64
self . log_action_start ( "TEST Compile" , & testcase. name ) ;
@@ -72,6 +72,13 @@ impl Run for TestCommand {
72
72
self . log_action_context ( "output" , & testcase. output_file . display ( ) ) ;
73
73
testcase. build_lib ( manifest) ;
74
74
}
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
+ }
75
82
}
76
83
}
77
84
}
@@ -84,7 +91,6 @@ impl Run for TestCommand {
84
91
impl TestCommand {
85
92
pub fn collect_testcases ( & self , manifest : & Manifest ) -> Vec < TestCase > {
86
93
let mut cases = vec ! [ ] ;
87
-
88
94
let verbose = self . verbose ;
89
95
90
96
// Examples
@@ -117,6 +123,20 @@ impl TestCommand {
117
123
cases. push ( testcase) ;
118
124
}
119
125
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
+
120
140
// Collect test-auxiliary
121
141
let aux_use = regex:: Regex :: new ( r"\s*//@\s*aux-build:(?P<fname>.*)" ) . unwrap ( ) ;
122
142
let mut auxiliaries = vec ! [ ] ;
@@ -145,6 +165,134 @@ impl TestCommand {
145
165
testcases. extend ( cases) ;
146
166
testcases
147
167
}
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
+ }
148
296
}
149
297
150
298
#[ derive( Debug ) ]
@@ -157,14 +305,18 @@ pub enum TestType {
157
305
FileCheck ,
158
306
/// Bless test - the output should be the same as the last run
159
307
Bless ,
308
+ /// Runtime test - compile, run and compare output
309
+ Runtime ,
160
310
}
311
+
161
312
impl TestType {
162
313
pub fn as_str ( & self ) -> & ' static str {
163
314
match self {
164
315
TestType :: Compile => "compile" ,
165
316
TestType :: CompileLib => "compile-lib" ,
166
317
TestType :: FileCheck => "filecheck" ,
167
318
TestType :: Bless => "bless" ,
319
+ TestType :: Runtime => "runtime" ,
168
320
}
169
321
}
170
322
}
@@ -286,28 +438,3 @@ impl FileChecker {
286
438
) ;
287
439
}
288
440
}
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