@@ -5,14 +5,19 @@ use anstream::{eprint as print, eprintln as println};
5
5
use clap:: Args ;
6
6
use color_print:: { cprint, cprintln} ;
7
7
use glob:: glob;
8
+ use similar:: { ChangeTag , TextDiff } ;
8
9
use which:: which;
9
10
10
11
use crate :: manifest:: Manifest ;
11
12
use crate :: Run ;
12
13
13
14
/// Run tests
14
15
#[ derive( Args , Debug ) ]
15
- pub struct TestCommand { }
16
+ pub struct TestCommand {
17
+ /// Update the blessed output
18
+ #[ clap( long) ]
19
+ pub bless : bool ,
20
+ }
16
21
17
22
impl Run for TestCommand {
18
23
fn run ( & self , manifest : & Manifest ) {
@@ -37,7 +42,12 @@ impl Run for TestCommand {
37
42
TestType :: FileCheck => {
38
43
cprint ! ( "File checking {}..." , testcase. name) ;
39
44
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) ;
41
51
}
42
52
TestType :: Compile => {
43
53
cprint ! ( "Compiling {}..." , testcase. name) ;
@@ -55,24 +65,15 @@ impl Run for TestCommand {
55
65
56
66
impl TestCommand {
57
67
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 ! [ ] ;
68
69
69
70
// Examples
70
71
for case in glob ( "examples/*.rs" ) . unwrap ( ) {
71
72
let case = case. unwrap ( ) ;
72
73
let filename = case. file_stem ( ) . unwrap ( ) ;
73
74
let name = format ! ( "examples/{}" , filename. to_string_lossy( ) ) ;
74
75
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 } )
76
77
}
77
78
78
79
// Codegen tests
@@ -81,18 +82,49 @@ impl TestCommand {
81
82
let filename = case. file_stem ( ) . unwrap ( ) ;
82
83
let name = format ! ( "codegen/{}" , filename. to_string_lossy( ) ) ;
83
84
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 } )
85
95
}
86
96
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
88
116
}
89
117
}
90
118
91
119
pub enum TestType {
92
120
/// Test an executable can be compiled
93
121
Compile ,
122
+ /// Test a library can be compiled
94
123
CompileLib ,
124
+ /// Run LLVM FileCheck on the generated code
95
125
FileCheck ,
126
+ /// Bless test - the output should be the same as the last run
127
+ Bless ,
96
128
}
97
129
98
130
pub struct TestCase {
@@ -125,11 +157,27 @@ impl TestCase {
125
157
. args ( [ "--crate-type" , "lib" ] )
126
158
. arg ( "-O" )
127
159
. 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
130
162
log:: debug!( "running {:?}" , command) ;
131
163
command. status ( ) . unwrap ( ) ;
132
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
+ }
133
181
}
134
182
135
183
struct FileChecker {
@@ -153,25 +201,41 @@ impl FileChecker {
153
201
Self { filecheck }
154
202
}
155
203
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 ( ) ;
171
206
let mut command = std:: process:: Command :: new ( & self . filecheck ) ;
172
- command. arg ( source) . stdin ( generated) ;
207
+ command. arg ( & case . source ) . stdin ( generated) ;
173
208
log:: debug!( "running {:?}" , command) ;
174
209
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
+ }
176
240
}
177
241
}
0 commit comments