@@ -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,12 +42,21 @@ 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 ) ;
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) ;
44
54
testcase. build ( manifest) ;
45
55
}
56
+ TestType :: CompileLib => {
57
+ cprint ! ( "Compiling lib {}..." , testcase. name) ;
58
+ testcase. build_lib ( manifest) ;
59
+ }
46
60
}
47
61
cprintln ! ( "<g>OK</g>" ) ;
48
62
}
@@ -51,58 +65,119 @@ impl Run for TestCommand {
51
65
52
66
impl TestCommand {
53
67
pub fn collect_testcases ( & self , manifest : & Manifest ) -> Vec < TestCase > {
54
- let mut result = vec ! [ ] ;
68
+ let mut tests = vec ! [ ] ;
55
69
56
70
// Examples
57
- for case in glob ( "example /*.rs" ) . unwrap ( ) {
71
+ for case in glob ( "examples /*.rs" ) . unwrap ( ) {
58
72
let case = case. unwrap ( ) ;
59
73
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 } )
66
77
}
67
78
68
79
// Codegen tests
69
80
for case in glob ( "tests/codegen/*.rs" ) . unwrap ( ) {
70
81
let case = case. unwrap ( ) ;
71
82
let filename = case. file_stem ( ) . unwrap ( ) ;
72
83
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
+ }
75
110
}
76
111
77
- result
112
+ // Compile auxiliary before the tests
113
+ let mut cases = auxiliary;
114
+ cases. extend ( tests) ;
115
+ cases
78
116
}
79
117
}
80
118
81
119
pub enum TestType {
120
+ /// Test an executable can be compiled
82
121
Compile ,
122
+ /// Test a library can be compiled
123
+ CompileLib ,
124
+ /// Run LLVM FileCheck on the generated code
83
125
FileCheck ,
126
+ /// Bless test - the output should be the same as the last run
127
+ Bless ,
84
128
}
85
129
86
130
pub struct TestCase {
87
131
pub name : String ,
88
132
pub source : PathBuf ,
89
- pub output : PathBuf ,
133
+ pub output_file : PathBuf ,
90
134
pub test : TestType ,
91
135
}
92
136
93
137
impl TestCase {
94
138
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 ( ) ;
96
141
let mut command = manifest. rustc ( ) ;
97
142
command
98
143
. args ( [ "--crate-type" , "bin" ] )
99
144
. arg ( "-O" )
100
145
. arg ( & self . source )
101
146
. arg ( "-o" )
102
- . arg ( & self . output ) ;
147
+ . arg ( & self . output_file ) ;
103
148
log:: debug!( "running {:?}" , command) ;
104
149
command. status ( ) . unwrap ( ) ;
105
150
}
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
+ }
106
181
}
107
182
108
183
struct FileChecker {
@@ -126,25 +201,41 @@ impl FileChecker {
126
201
Self { filecheck }
127
202
}
128
203
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 ( ) ;
144
206
let mut command = std:: process:: Command :: new ( & self . filecheck ) ;
145
- command. arg ( source) . stdin ( generated) ;
207
+ command. arg ( & case . source ) . stdin ( generated) ;
146
208
log:: debug!( "running {:?}" , command) ;
147
209
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
+ }
149
240
}
150
241
}
0 commit comments