@@ -2,19 +2,19 @@ mod traits;
22mod ui;
33mod validate;
44
5- use std:: any:: { TypeId , type_name} ;
5+ use std:: any:: type_name;
66use std:: cmp:: min;
77use std:: ops:: RangeInclusive ;
88use std:: process:: ExitCode ;
9+ use std:: sync:: OnceLock ;
910use std:: sync:: atomic:: { AtomicU64 , Ordering } ;
10- use std:: sync:: { OnceLock , mpsc} ;
1111use std:: { fmt, time} ;
1212
13- use indicatif:: { MultiProgress , ProgressBar } ;
1413use rand:: distributions:: { Distribution , Standard } ;
1514use rayon:: prelude:: * ;
1615use time:: { Duration , Instant } ;
1716use traits:: { Float , Generator , Int } ;
17+ use validate:: CheckError ;
1818
1919/// Test generators.
2020mod gen {
@@ -43,7 +43,7 @@ const HUGE_TEST_CUTOFF: u64 = 5_000_000;
4343/// Seed for tests that use a deterministic RNG.
4444const SEED : [ u8 ; 32 ] = * b"3.141592653589793238462643383279" ;
4545
46- /// Global configuration
46+ /// Global configuration.
4747#[ derive( Debug ) ]
4848pub struct Config {
4949 pub timeout : Duration ,
@@ -104,9 +104,9 @@ pub fn run(cfg: Config, include: &[String], exclude: &[String]) -> ExitCode {
104104 println ! ( "Skipping test '{exc}'" ) ;
105105 }
106106
107- println ! ( "launching " ) ;
107+ println ! ( "Launching all " ) ;
108108 let elapsed = launch_tests ( & mut tests, & cfg) ;
109- ui:: finish ( & tests, elapsed, & cfg)
109+ ui:: finish_all ( & tests, elapsed, & cfg)
110110}
111111
112112/// Enumerate tests to run but don't actually run them.
@@ -160,18 +160,18 @@ where
160160#[ derive( Debug ) ]
161161pub struct TestInfo {
162162 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 ,
166163 float_name : & ' static str ,
164+ float_bits : u32 ,
167165 gen_name : & ' static str ,
168166 /// Name for display in the progress bar.
169167 short_name : String ,
168+ /// Pad the short name to a common width for progress bar use.
169+ short_name_padded : String ,
170170 total_tests : u64 ,
171171 /// Function to launch this test.
172- launch : fn ( & mpsc :: Sender < Msg > , & TestInfo , & Config ) ,
172+ launch : fn ( & TestInfo , & Config ) ,
173173 /// Progress bar to be updated.
174- pb : Option < ProgressBar > ,
174+ progress : Option < ui :: Progress > ,
175175 /// Once completed, this will be set.
176176 completed : OnceLock < Completed > ,
177177}
@@ -187,121 +187,37 @@ impl TestInfo {
187187 let f_name = type_name :: < F > ( ) ;
188188 let gen_name = G :: NAME ;
189189 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}" ) ;
190193
191194 let info = TestInfo {
192- id : TypeId :: of :: < ( F , G ) > ( ) ,
193195 float_name : f_name,
196+ float_bits : F :: BITS ,
194197 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,
198202 launch : test_runner :: < F , G > ,
199203 total_tests : G :: total_tests ( ) ,
200204 completed : OnceLock :: new ( ) ,
201205 } ;
202206 v. push ( info) ;
203207 }
204208
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-
221209 /// True if this should be run after all others.
222210 fn is_huge_test ( & self ) -> bool {
223211 self . total_tests >= HUGE_TEST_CUTOFF
224212 }
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- }
233213
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 ( ) ;
283218 }
284219}
285220
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-
305221/// Result of an input did not parsing successfully.
306222#[ derive( Clone , Debug ) ]
307223enum CheckFailure {
@@ -329,6 +245,10 @@ enum CheckFailure {
329245 /// two representable values.
330246 incorrect_midpoint_rounding : bool ,
331247 } ,
248+ /// String did not parse successfully.
249+ ParsingFailed ( Box < str > ) ,
250+ /// A panic was caught.
251+ Panic ( Box < str > ) ,
332252}
333253
334254impl fmt:: Display for CheckFailure {
@@ -363,6 +283,8 @@ impl fmt::Display for CheckFailure {
363283 }
364284 Ok ( ( ) )
365285 }
286+ CheckFailure :: ParsingFailed ( e) => write ! ( f, "parsing failed: {e}" ) ,
287+ CheckFailure :: Panic ( e) => write ! ( f, "function panicked: {e}" ) ,
366288 }
367289 }
368290}
@@ -398,71 +320,34 @@ enum EarlyExit {
398320/// This launches a main thread that receives messages and handlees UI updates, and uses the
399321/// rest of the thread pool to execute the tests.
400322fn 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 ) ) ;
403325
404326 for test in tests. iter ( ) {
405327 println ! ( "Launching test '{}'" , test. name) ;
406328 }
407329
408- // Configure progress bars
409330 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 > ( ) ;
419331 let start = Instant :: now ( ) ;
420332
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+ }
450338
451339 start. elapsed ( )
452340}
453341
454342/// Test runer for a single generator.
455343///
456344/// 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 ) {
461346 let gen = G :: new ( ) ;
462347 let executed = AtomicU64 :: new ( 0 ) ;
463348 let failures = AtomicU64 :: new ( 0 ) ;
464349
465- let checks_per_update = min ( total , 1000 ) ;
350+ let checks_per_update = min ( test . total_tests , 1000 ) ;
466351 let started = Instant :: now ( ) ;
467352
468353 // 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
474359 match validate:: validate :: < F > ( buf) {
475360 Ok ( ( ) ) => ( ) ,
476361 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+
478368 let f = failures. fetch_add ( 1 , Ordering :: Relaxed ) ;
479369 // End early if the limit is exceeded.
480370 if f >= cfg. max_failures {
@@ -486,9 +376,7 @@ fn test_runner<F: Float, G: Generator<F>>(tx: &mpsc::Sender<Msg>, _info: &TestIn
486376 // Send periodic updates
487377 if executed % checks_per_update == 0 {
488378 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) ;
492380 if started. elapsed ( ) > cfg. timeout {
493381 return Err ( EarlyExit :: Timeout ) ;
494382 }
@@ -499,28 +387,25 @@ fn test_runner<F: Float, G: Generator<F>>(tx: &mpsc::Sender<Msg>, _info: &TestIn
499387
500388 // Run the test iterations in parallel. Each thread gets a string buffer to write
501389 // 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) ;
503391
504392 let elapsed = started. elapsed ( ) ;
505393 let executed = executed. into_inner ( ) ;
506394 let failures = failures. into_inner ( ) ;
507395
508396 // 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+ ) ;
511403
512404 Some ( msg. into ( ) )
513405 } else {
514406 None
515407 } ;
516408
517409 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 } ) ;
526411}
0 commit comments