@@ -2,14 +2,15 @@ use std::collections::BTreeMap;
2
2
use std:: io:: { stdout, Write } ;
3
3
use std:: path:: { Path , PathBuf } ;
4
4
use std:: sync:: Arc ;
5
- use std:: time:: Instant ;
5
+ use std:: time:: { Duration , Instant } ;
6
6
7
7
use anyhow:: { anyhow, Context , Result } ;
8
8
use async_trait:: async_trait;
9
9
use clap:: { ArgEnum , Parser } ;
10
10
use console:: style;
11
11
use futures:: StreamExt ;
12
12
use itertools:: Itertools ;
13
+ use quick_junit:: { NonSuccessKind , Report , TestCase , TestCaseStatus , TestSuite } ;
13
14
use sqllogictest:: { Control , Record , TestErrorKind } ;
14
15
15
16
#[ derive( Copy , Clone , Debug , PartialEq , ArgEnum ) ]
@@ -58,6 +59,7 @@ struct Opt {
58
59
#[ clap( short = 'w' , long, default_value = "postgres" ) ]
59
60
pass : String ,
60
61
62
+ /// Whether to enable colorful output.
61
63
#[ clap(
62
64
long,
63
65
arg_enum,
@@ -71,6 +73,10 @@ struct Opt {
71
73
/// database will be created for each test file.
72
74
#[ clap( long, short) ]
73
75
jobs : Option < usize > ,
76
+
77
+ /// Report to junit XML.
78
+ #[ clap( long) ]
79
+ junit : Option < String > ,
74
80
}
75
81
76
82
#[ tokio:: main]
@@ -120,7 +126,14 @@ async fn main() -> Result<()> {
120
126
return Err ( anyhow ! ( "no test case found" ) ) ;
121
127
}
122
128
123
- if let Some ( job) = & opt. jobs {
129
+ let mut report = Report :: new (
130
+ opt. junit
131
+ . clone ( )
132
+ . unwrap_or_else ( || "sqllogictest" . to_string ( ) ) ,
133
+ ) ;
134
+ let mut test_suite = TestSuite :: new ( "sqllogictest" ) ;
135
+
136
+ let result = if let Some ( job) = & opt. jobs {
124
137
let mut create_databases = BTreeMap :: new ( ) ;
125
138
for file in files {
126
139
let db_name = file
@@ -170,11 +183,20 @@ async fn main() -> Result<()> {
170
183
let start = Instant :: now ( ) ;
171
184
172
185
while let Some ( ( file, res, mut buf) ) = stream. next ( ) . await {
173
- if let Err ( e) = res {
174
- writeln ! ( buf, "{}\n \n {:?}" , style( "[FAILED]" ) . red( ) . bold( ) , e) ?;
175
- writeln ! ( buf) ?;
176
- failed_case. push ( file) ;
177
- }
186
+ let case = match res {
187
+ Ok ( duration) => {
188
+ let mut case = TestCase :: new ( file, TestCaseStatus :: success ( ) ) ;
189
+ case. set_time ( duration) ;
190
+ case
191
+ }
192
+ Err ( e) => {
193
+ writeln ! ( buf, "{}\n \n {:?}" , style( "[FAILED]" ) . red( ) . bold( ) , e) ?;
194
+ writeln ! ( buf) ?;
195
+ failed_case. push ( file. clone ( ) ) ;
196
+ TestCase :: new ( file, TestCaseStatus :: non_success ( NonSuccessKind :: Failure ) )
197
+ }
198
+ } ;
199
+ test_suite. add_test_case ( case) ;
178
200
tokio:: task:: block_in_place ( || stdout ( ) . write_all ( & buf) ) ?;
179
201
}
180
202
@@ -184,7 +206,7 @@ async fn main() -> Result<()> {
184
206
) ;
185
207
186
208
if !failed_case. is_empty ( ) {
187
- Err ( anyhow ! ( "some test case failed:\n {:#?}" , failed_case) )
209
+ return Err ( anyhow ! ( "some test case failed:\n {:#?}" , failed_case) ) ;
188
210
} else {
189
211
Ok ( ( ) )
190
212
}
@@ -194,19 +216,40 @@ async fn main() -> Result<()> {
194
216
let mut failed_case = vec ! [ ] ;
195
217
196
218
for file in files {
197
- if let Err ( e) = run_test_file ( & mut std:: io:: stdout ( ) , pg. clone ( ) , & file) . await {
198
- println ! ( "{}\n \n {:?}" , style( "[FAILED]" ) . red( ) . bold( ) , e) ;
199
- println ! ( ) ;
200
- failed_case. push ( file. to_string_lossy ( ) . to_string ( ) ) ;
201
- }
219
+ let filename = file. to_string_lossy ( ) . to_string ( ) ;
220
+ let case = match run_test_file ( & mut std:: io:: stdout ( ) , pg. clone ( ) , & file) . await {
221
+ Ok ( duration) => {
222
+ let mut case = TestCase :: new ( filename, TestCaseStatus :: success ( ) ) ;
223
+ case. set_time ( duration) ;
224
+ case
225
+ }
226
+ Err ( e) => {
227
+ println ! ( "{}\n \n {:?}" , style( "[FAILED]" ) . red( ) . bold( ) , e) ;
228
+ println ! ( ) ;
229
+ failed_case. push ( filename. clone ( ) ) ;
230
+ TestCase :: new (
231
+ filename,
232
+ TestCaseStatus :: non_success ( NonSuccessKind :: Failure ) ,
233
+ )
234
+ }
235
+ } ;
236
+ test_suite. add_test_case ( case) ;
202
237
}
203
238
204
239
if !failed_case. is_empty ( ) {
205
240
Err ( anyhow ! ( "some test case failed:\n {:#?}" , failed_case) )
206
241
} else {
207
242
Ok ( ( ) )
208
243
}
244
+ } ;
245
+
246
+ report. add_test_suite ( test_suite) ;
247
+
248
+ if let Some ( junit_file) = opt. junit {
249
+ tokio:: fs:: write ( format ! ( "{}-junit.xml" , junit_file) , report. to_string ( ) ?) . await ?;
209
250
}
251
+
252
+ result
210
253
}
211
254
212
255
async fn flush ( out : & mut impl std:: io:: Write ) -> std:: io:: Result < ( ) > {
@@ -218,7 +261,7 @@ async fn run_test_file_on_db(
218
261
filename : PathBuf ,
219
262
db_name : String ,
220
263
opt : Opt ,
221
- ) -> Result < ( ) > {
264
+ ) -> Result < Duration > {
222
265
let ( client, connection) = tokio_postgres:: Config :: new ( )
223
266
. host ( & opt. host )
224
267
. port ( opt. port )
@@ -240,18 +283,18 @@ async fn run_test_file_on_db(
240
283
engine_name : opt. engine . clone ( ) ,
241
284
} ;
242
285
243
- run_test_file ( out, pg, filename) . await ?;
286
+ let result = run_test_file ( out, pg, filename) . await ?;
244
287
245
288
handle. abort ( ) ;
246
289
247
- Ok ( ( ) )
290
+ Ok ( result )
248
291
}
249
292
250
293
async fn run_test_file < T : std:: io:: Write > (
251
294
out : & mut T ,
252
295
engine : Postgres ,
253
296
filename : impl AsRef < Path > ,
254
- ) -> Result < ( ) > {
297
+ ) -> Result < Duration > {
255
298
let filename = filename. as_ref ( ) ;
256
299
let mut runner = sqllogictest:: Runner :: new ( engine) ;
257
300
let records = tokio:: task:: block_in_place ( || {
@@ -341,6 +384,8 @@ async fn run_test_file<T: std::io::Write>(
341
384
) ) ?;
342
385
}
343
386
387
+ let duration = begin_times[ 0 ] . elapsed ( ) ;
388
+
344
389
finish (
345
390
out,
346
391
& mut begin_times,
@@ -350,7 +395,7 @@ async fn run_test_file<T: std::io::Write>(
350
395
351
396
writeln ! ( out) ?;
352
397
353
- Ok ( ( ) )
398
+ Ok ( duration )
354
399
}
355
400
356
401
#[ derive( Clone ) ]
0 commit comments