@@ -2,19 +2,19 @@ mod traits;
2
2
mod ui;
3
3
mod validate;
4
4
5
- use std:: any:: { TypeId , type_name} ;
5
+ use std:: any:: type_name;
6
6
use std:: cmp:: min;
7
7
use std:: ops:: RangeInclusive ;
8
8
use std:: process:: ExitCode ;
9
+ use std:: sync:: OnceLock ;
9
10
use std:: sync:: atomic:: { AtomicU64 , Ordering } ;
10
- use std:: sync:: { OnceLock , mpsc} ;
11
11
use std:: { fmt, time} ;
12
12
13
- use indicatif:: { MultiProgress , ProgressBar } ;
14
13
use rand:: distributions:: { Distribution , Standard } ;
15
14
use rayon:: prelude:: * ;
16
15
use time:: { Duration , Instant } ;
17
16
use traits:: { Float , Generator , Int } ;
17
+ use validate:: CheckError ;
18
18
19
19
/// Test generators.
20
20
mod gen {
@@ -43,7 +43,7 @@ const HUGE_TEST_CUTOFF: u64 = 5_000_000;
43
43
/// Seed for tests that use a deterministic RNG.
44
44
const SEED : [ u8 ; 32 ] = * b"3.141592653589793238462643383279" ;
45
45
46
- /// Global configuration
46
+ /// Global configuration.
47
47
#[ derive( Debug ) ]
48
48
pub struct Config {
49
49
pub timeout : Duration ,
@@ -104,9 +104,9 @@ pub fn run(cfg: Config, include: &[String], exclude: &[String]) -> ExitCode {
104
104
println ! ( "Skipping test '{exc}'" ) ;
105
105
}
106
106
107
- println ! ( "launching " ) ;
107
+ println ! ( "Launching all " ) ;
108
108
let elapsed = launch_tests ( & mut tests, & cfg) ;
109
- ui:: finish ( & tests, elapsed, & cfg)
109
+ ui:: finish_all ( & tests, elapsed, & cfg)
110
110
}
111
111
112
112
/// Enumerate tests to run but don't actually run them.
@@ -160,18 +160,18 @@ where
160
160
#[ derive( Debug ) ]
161
161
pub struct TestInfo {
162
162
pub name : String ,
163
- /// Tests are identified by the type ID of `(F, G)` (tuple of the float and generator type).
164
- /// This gives an easy way to associate messages with tests.
165
- id : TypeId ,
166
163
float_name : & ' static str ,
164
+ float_bits : u32 ,
167
165
gen_name : & ' static str ,
168
166
/// Name for display in the progress bar.
169
167
short_name : String ,
168
+ /// Pad the short name to a common width for progress bar use.
169
+ short_name_padded : String ,
170
170
total_tests : u64 ,
171
171
/// Function to launch this test.
172
- launch : fn ( & mpsc :: Sender < Msg > , & TestInfo , & Config ) ,
172
+ launch : fn ( & TestInfo , & Config ) ,
173
173
/// Progress bar to be updated.
174
- pb : Option < ProgressBar > ,
174
+ progress : Option < ui :: Progress > ,
175
175
/// Once completed, this will be set.
176
176
completed : OnceLock < Completed > ,
177
177
}
@@ -187,121 +187,37 @@ impl TestInfo {
187
187
let f_name = type_name :: < F > ( ) ;
188
188
let gen_name = G :: NAME ;
189
189
let gen_short_name = G :: SHORT_NAME ;
190
+ let name = format ! ( "{f_name} {gen_name}" ) ;
191
+ let short_name = format ! ( "{f_name} {gen_short_name}" ) ;
192
+ let short_name_padded = format ! ( "{short_name:18}" ) ;
190
193
191
194
let info = TestInfo {
192
- id : TypeId :: of :: < ( F , G ) > ( ) ,
193
195
float_name : f_name,
196
+ float_bits : F :: BITS ,
194
197
gen_name,
195
- pb : None ,
196
- name : format ! ( "{f_name} {gen_name}" ) ,
197
- short_name : format ! ( "{f_name} {gen_short_name}" ) ,
198
+ progress : None ,
199
+ name,
200
+ short_name_padded,
201
+ short_name,
198
202
launch : test_runner :: < F , G > ,
199
203
total_tests : G :: total_tests ( ) ,
200
204
completed : OnceLock :: new ( ) ,
201
205
} ;
202
206
v. push ( info) ;
203
207
}
204
208
205
- /// Pad the short name to a common width for progress bar use.
206
- fn short_name_padded ( & self ) -> String {
207
- format ! ( "{:18}" , self . short_name)
208
- }
209
-
210
- /// Create a progress bar for this test within a multiprogress bar.
211
- fn register_pb ( & mut self , mp : & MultiProgress , drop_bars : & mut Vec < ProgressBar > ) {
212
- self . pb = Some ( ui:: create_pb ( mp, self . total_tests , & self . short_name_padded ( ) , drop_bars) ) ;
213
- }
214
-
215
- /// When the test is finished, update progress bar messages and finalize.
216
- fn finalize_pb ( & self , c : & Completed ) {
217
- let pb = self . pb . as_ref ( ) . unwrap ( ) ;
218
- ui:: finalize_pb ( pb, & self . short_name_padded ( ) , c) ;
219
- }
220
-
221
209
/// True if this should be run after all others.
222
210
fn is_huge_test ( & self ) -> bool {
223
211
self . total_tests >= HUGE_TEST_CUTOFF
224
212
}
225
- }
226
-
227
- /// A message sent from test runner threads to the UI/log thread.
228
- #[ derive( Clone , Debug ) ]
229
- struct Msg {
230
- id : TypeId ,
231
- update : Update ,
232
- }
233
213
234
- impl Msg {
235
- /// Wrap an `Update` into a message for the specified type. We use the `TypeId` of `(F, G)` to
236
- /// identify which test a message in the channel came from.
237
- fn new < F : Float , G : Generator < F > > ( u : Update ) -> Self {
238
- Self { id : TypeId :: of :: < ( F , G ) > ( ) , update : u }
239
- }
240
-
241
- /// Get the matching test from a list. Panics if not found.
242
- fn find_test < ' a > ( & self , tests : & ' a [ TestInfo ] ) -> & ' a TestInfo {
243
- tests. iter ( ) . find ( |t| t. id == self . id ) . unwrap ( )
244
- }
245
-
246
- /// Update UI as needed for a single message received from the test runners.
247
- fn handle ( self , tests : & [ TestInfo ] , mp : & MultiProgress ) {
248
- let test = self . find_test ( tests) ;
249
- let pb = test. pb . as_ref ( ) . unwrap ( ) ;
250
-
251
- match self . update {
252
- Update :: Started => {
253
- mp. println ( format ! ( "Testing '{}'" , test. name) ) . unwrap ( ) ;
254
- }
255
- Update :: Progress { executed, failures } => {
256
- pb. set_message ( format ! { "{failures}" } ) ;
257
- pb. set_position ( executed) ;
258
- }
259
- Update :: Failure { fail, input, float_res } => {
260
- mp. println ( format ! (
261
- "Failure in '{}': {fail}. parsing '{input}'. Parsed as: {float_res}" ,
262
- test. name
263
- ) )
264
- . unwrap ( ) ;
265
- }
266
- Update :: Completed ( c) => {
267
- test. finalize_pb ( & c) ;
268
-
269
- let prefix = match c. result {
270
- Ok ( FinishedAll ) => "Completed tests for" ,
271
- Err ( EarlyExit :: Timeout ) => "Timed out" ,
272
- Err ( EarlyExit :: MaxFailures ) => "Max failures reached for" ,
273
- } ;
274
-
275
- mp. println ( format ! (
276
- "{prefix} generator '{}' in {:?}. {} tests run, {} failures" ,
277
- test. name, c. elapsed, c. executed, c. failures
278
- ) )
279
- . unwrap ( ) ;
280
- test. completed . set ( c) . unwrap ( ) ;
281
- }
282
- } ;
214
+ /// When the test is finished, update progress bar messages and finalize.
215
+ fn complete ( & self , c : Completed ) {
216
+ self . progress . as_ref ( ) . unwrap ( ) . complete ( & c, 0 ) ;
217
+ self . completed . set ( c) . unwrap ( ) ;
283
218
}
284
219
}
285
220
286
- /// Status sent with a message.
287
- #[ derive( Clone , Debug ) ]
288
- enum Update {
289
- /// Starting a new test runner.
290
- Started ,
291
- /// Completed a out of b tests.
292
- Progress { executed : u64 , failures : u64 } ,
293
- /// Received a failed test.
294
- Failure {
295
- fail : CheckFailure ,
296
- /// String for which parsing was attempted.
297
- input : Box < str > ,
298
- /// The parsed & decomposed `FloatRes`, already stringified so we don't need generics here.
299
- float_res : Box < str > ,
300
- } ,
301
- /// Exited with an unexpected condition.
302
- Completed ( Completed ) ,
303
- }
304
-
305
221
/// Result of an input did not parsing successfully.
306
222
#[ derive( Clone , Debug ) ]
307
223
enum CheckFailure {
@@ -329,6 +245,10 @@ enum CheckFailure {
329
245
/// two representable values.
330
246
incorrect_midpoint_rounding : bool ,
331
247
} ,
248
+ /// String did not parse successfully.
249
+ ParsingFailed ( Box < str > ) ,
250
+ /// A panic was caught.
251
+ Panic ( Box < str > ) ,
332
252
}
333
253
334
254
impl fmt:: Display for CheckFailure {
@@ -363,6 +283,8 @@ impl fmt::Display for CheckFailure {
363
283
}
364
284
Ok ( ( ) )
365
285
}
286
+ CheckFailure :: ParsingFailed ( e) => write ! ( f, "parsing failed: {e}" ) ,
287
+ CheckFailure :: Panic ( e) => write ! ( f, "function panicked: {e}" ) ,
366
288
}
367
289
}
368
290
}
@@ -398,71 +320,34 @@ enum EarlyExit {
398
320
/// This launches a main thread that receives messages and handlees UI updates, and uses the
399
321
/// rest of the thread pool to execute the tests.
400
322
fn launch_tests ( tests : & mut [ TestInfo ] , cfg : & Config ) -> Duration {
401
- // Run shorter tests first
402
- tests. sort_unstable_by_key ( |test| test. total_tests ) ;
323
+ // Run shorter tests and smaller float types first.
324
+ tests. sort_unstable_by_key ( |test| ( test. total_tests , test . float_bits ) ) ;
403
325
404
326
for test in tests. iter ( ) {
405
327
println ! ( "Launching test '{}'" , test. name) ;
406
328
}
407
329
408
- // Configure progress bars
409
330
let mut all_progress_bars = Vec :: new ( ) ;
410
- let mp = MultiProgress :: new ( ) ;
411
- mp. set_move_cursor ( true ) ;
412
- for test in tests. iter_mut ( ) {
413
- test. register_pb ( & mp, & mut all_progress_bars) ;
414
- }
415
-
416
- ui:: set_panic_hook ( all_progress_bars) ;
417
-
418
- let ( tx, rx) = mpsc:: channel :: < Msg > ( ) ;
419
331
let start = Instant :: now ( ) ;
420
332
421
- rayon:: scope ( |scope| {
422
- // Thread that updates the UI
423
- scope. spawn ( |_scope| {
424
- let rx = rx; // move rx
425
-
426
- loop {
427
- if tests. iter ( ) . all ( |t| t. completed . get ( ) . is_some ( ) ) {
428
- break ;
429
- }
430
-
431
- let msg = rx. recv ( ) . unwrap ( ) ;
432
- msg. handle ( tests, & mp) ;
433
- }
434
-
435
- // All tests completed; finish things up
436
- drop ( mp) ;
437
- assert_eq ! ( rx. try_recv( ) . unwrap_err( ) , mpsc:: TryRecvError :: Empty ) ;
438
- } ) ;
439
-
440
- // Don't let the thread pool be starved by huge tests. Run faster tests first in parallel,
441
- // then parallelize only within the rest of the tests.
442
- let ( huge_tests, normal_tests) : ( Vec < _ > , Vec < _ > ) =
443
- tests. iter ( ) . partition ( |t| t. is_huge_test ( ) ) ;
444
-
445
- // Run the actual tests
446
- normal_tests. par_iter ( ) . for_each ( |test| ( ( test. launch ) ( & tx, test, cfg) ) ) ;
447
-
448
- huge_tests. par_iter ( ) . for_each ( |test| ( ( test. launch ) ( & tx, test, cfg) ) ) ;
449
- } ) ;
333
+ for test in tests. iter_mut ( ) {
334
+ test. progress = Some ( ui:: Progress :: new ( test, & mut all_progress_bars) ) ;
335
+ ui:: set_panic_hook ( & all_progress_bars) ;
336
+ ( ( test. launch ) ( test, cfg) ) ;
337
+ }
450
338
451
339
start. elapsed ( )
452
340
}
453
341
454
342
/// Test runer for a single generator.
455
343
///
456
344
/// This calls the generator's iterator multiple times (in parallel) and validates each output.
457
- fn test_runner < F : Float , G : Generator < F > > ( tx : & mpsc:: Sender < Msg > , _info : & TestInfo , cfg : & Config ) {
458
- tx. send ( Msg :: new :: < F , G > ( Update :: Started ) ) . unwrap ( ) ;
459
-
460
- let total = G :: total_tests ( ) ;
345
+ fn test_runner < F : Float , G : Generator < F > > ( test : & TestInfo , cfg : & Config ) {
461
346
let gen = G :: new ( ) ;
462
347
let executed = AtomicU64 :: new ( 0 ) ;
463
348
let failures = AtomicU64 :: new ( 0 ) ;
464
349
465
- let checks_per_update = min ( total , 1000 ) ;
350
+ let checks_per_update = min ( test . total_tests , 1000 ) ;
466
351
let started = Instant :: now ( ) ;
467
352
468
353
// Function to execute for a single test iteration.
@@ -474,7 +359,12 @@ fn test_runner<F: Float, G: Generator<F>>(tx: &mpsc::Sender<Msg>, _info: &TestIn
474
359
match validate:: validate :: < F > ( buf) {
475
360
Ok ( ( ) ) => ( ) ,
476
361
Err ( e) => {
477
- tx. send ( Msg :: new :: < F , G > ( e) ) . unwrap ( ) ;
362
+ let CheckError { fail, input, float_res } = e;
363
+ test. progress . as_ref ( ) . unwrap ( ) . println ( & format ! (
364
+ "Failure in '{}': {fail}. parsing '{input}'. Parsed as: {float_res}" ,
365
+ test. name
366
+ ) ) ;
367
+
478
368
let f = failures. fetch_add ( 1 , Ordering :: Relaxed ) ;
479
369
// End early if the limit is exceeded.
480
370
if f >= cfg. max_failures {
@@ -486,9 +376,7 @@ fn test_runner<F: Float, G: Generator<F>>(tx: &mpsc::Sender<Msg>, _info: &TestIn
486
376
// Send periodic updates
487
377
if executed % checks_per_update == 0 {
488
378
let failures = failures. load ( Ordering :: Relaxed ) ;
489
-
490
- tx. send ( Msg :: new :: < F , G > ( Update :: Progress { executed, failures } ) ) . unwrap ( ) ;
491
-
379
+ test. progress . as_ref ( ) . unwrap ( ) . update ( executed, failures) ;
492
380
if started. elapsed ( ) > cfg. timeout {
493
381
return Err ( EarlyExit :: Timeout ) ;
494
382
}
@@ -499,28 +387,25 @@ fn test_runner<F: Float, G: Generator<F>>(tx: &mpsc::Sender<Msg>, _info: &TestIn
499
387
500
388
// Run the test iterations in parallel. Each thread gets a string buffer to write
501
389
// its check values to.
502
- let res = gen. par_bridge ( ) . try_for_each_init ( || String :: with_capacity ( 100 ) , check_one) ;
390
+ let res = gen. par_bridge ( ) . try_for_each_init ( String :: new , check_one) ;
503
391
504
392
let elapsed = started. elapsed ( ) ;
505
393
let executed = executed. into_inner ( ) ;
506
394
let failures = failures. into_inner ( ) ;
507
395
508
396
// Warn about bad estimates if relevant.
509
- let warning = if executed != total && res. is_ok ( ) {
510
- let msg = format ! ( "executed tests != estimated ({executed} != {total}) for {}" , G :: NAME ) ;
397
+ let warning = if executed != test. total_tests && res. is_ok ( ) {
398
+ let msg = format ! (
399
+ "executed tests != estimated ({executed} != {}) for {}" ,
400
+ test. total_tests,
401
+ G :: NAME
402
+ ) ;
511
403
512
404
Some ( msg. into ( ) )
513
405
} else {
514
406
None
515
407
} ;
516
408
517
409
let result = res. map ( |( ) | FinishedAll ) ;
518
- tx. send ( Msg :: new :: < F , G > ( Update :: Completed ( Completed {
519
- executed,
520
- failures,
521
- result,
522
- warning,
523
- elapsed,
524
- } ) ) )
525
- . unwrap ( ) ;
410
+ test. complete ( Completed { executed, failures, result, warning, elapsed } ) ;
526
411
}
0 commit comments