@@ -49,7 +49,7 @@ pub mod test {
4949 pub use crate :: time:: { TestExecTime , TestTimeOptions } ;
5050 pub use crate :: types:: {
5151 DynTestFn , DynTestName , StaticBenchFn , StaticTestFn , StaticTestName , TestDesc ,
52- TestDescAndFn , TestId , TestName , TestType ,
52+ TestDescAndFn , TestId , TestList , TestListOrder , TestName , TestType ,
5353 } ;
5454 pub use crate :: { assert_test_result, filter_tests, run_test, test_main, test_main_static} ;
5555}
@@ -106,6 +106,16 @@ pub fn test_main_with_exit_callback<F: FnOnce()>(
106106 tests : Vec < TestDescAndFn > ,
107107 options : Option < Options > ,
108108 exit_callback : F ,
109+ ) {
110+ let tests = TestList :: new ( tests, TestListOrder :: Unsorted ) ;
111+ test_main_inner ( args, tests, options, exit_callback)
112+ }
113+
114+ fn test_main_inner < F : FnOnce ( ) > (
115+ args : & [ String ] ,
116+ tests : TestList ,
117+ options : Option < Options > ,
118+ exit_callback : F ,
109119) {
110120 let mut opts = match cli:: parse_opts ( args) {
111121 Some ( Ok ( o) ) => o,
@@ -180,7 +190,9 @@ pub fn test_main_with_exit_callback<F: FnOnce()>(
180190pub fn test_main_static ( tests : & [ & TestDescAndFn ] ) {
181191 let args = env:: args ( ) . collect :: < Vec < _ > > ( ) ;
182192 let owned_tests: Vec < _ > = tests. iter ( ) . map ( make_owned_test) . collect ( ) ;
183- test_main ( & args, owned_tests, None )
193+ // Tests are sorted by name at compile time by mk_tests_slice.
194+ let tests = TestList :: new ( owned_tests, TestListOrder :: Sorted ) ;
195+ test_main_inner ( & args, tests, None , || { } )
184196}
185197
186198/// A variant optimized for invocation with a static test vector.
@@ -229,7 +241,9 @@ pub fn test_main_static_abort(tests: &[&TestDescAndFn]) {
229241
230242 let args = env:: args ( ) . collect :: < Vec < _ > > ( ) ;
231243 let owned_tests: Vec < _ > = tests. iter ( ) . map ( make_owned_test) . collect ( ) ;
232- test_main ( & args, owned_tests, Some ( Options :: new ( ) . panic_abort ( true ) ) )
244+ // Tests are sorted by name at compile time by mk_tests_slice.
245+ let tests = TestList :: new ( owned_tests, TestListOrder :: Sorted ) ;
246+ test_main_inner ( & args, tests, Some ( Options :: new ( ) . panic_abort ( true ) ) , || { } )
233247}
234248
235249/// Clones static values for putting into a dynamic vector, which test_main()
@@ -298,7 +312,7 @@ impl FilteredTests {
298312
299313pub fn run_tests < F > (
300314 opts : & TestOpts ,
301- tests : Vec < TestDescAndFn > ,
315+ tests : TestList ,
302316 mut notify_about_test_event : F ,
303317) -> io:: Result < ( ) >
304318where
@@ -334,7 +348,7 @@ where
334348 timeout : Instant ,
335349 }
336350
337- let tests_len = tests. len ( ) ;
351+ let tests_len = tests. tests . len ( ) ;
338352
339353 let mut filtered = FilteredTests { tests : Vec :: new ( ) , benches : Vec :: new ( ) , next_id : 0 } ;
340354
@@ -512,25 +526,48 @@ where
512526 Ok ( ( ) )
513527}
514528
515- pub fn filter_tests ( opts : & TestOpts , tests : Vec < TestDescAndFn > ) -> Vec < TestDescAndFn > {
529+ pub fn filter_tests ( opts : & TestOpts , tests : TestList ) -> Vec < TestDescAndFn > {
530+ let TestList { tests, order } = tests;
516531 let mut filtered = tests;
517- let matches_filter = |test : & TestDescAndFn , filter : & str | {
518- let test_name = test. desc . name . as_slice ( ) ;
519-
520- match opts. filter_exact {
521- true => test_name == filter,
522- false => test_name. contains ( filter) ,
523- }
524- } ;
525532
526- // Remove tests that don't match the test filter
533+ // Remove tests that don't match the test filter.
527534 if !opts. filters . is_empty ( ) {
528- filtered. retain ( |test| opts. filters . iter ( ) . any ( |filter| matches_filter ( test, filter) ) ) ;
535+ if opts. filter_exact && order == TestListOrder :: Sorted {
536+ // Let's say that `f` is the number of filters and `n` is the number
537+ // of tests.
538+ //
539+ // The test array is sorted by name (guaranteed by the caller via
540+ // TestListOrder::Sorted), so use binary search for O(f log n)
541+ // exact-match lookups instead of an O(n) linear scan.
542+ //
543+ // This is important for Miri, where the interpreted execution makes
544+ // the linear scan very expensive.
545+ filtered = filter_exact_match ( filtered, & opts. filters ) ;
546+ } else {
547+ filtered. retain ( |test| {
548+ let test_name = test. desc . name . as_slice ( ) ;
549+ opts. filters . iter ( ) . any ( |filter| {
550+ if opts. filter_exact {
551+ test_name == filter. as_str ( )
552+ } else {
553+ test_name. contains ( filter. as_str ( ) )
554+ }
555+ } )
556+ } ) ;
557+ }
529558 }
530559
531560 // Skip tests that match any of the skip filters
561+ //
562+ // After exact positive filtering above, the filtered set is small, so a
563+ // linear scan is acceptable even under Miri.
532564 if !opts. skip . is_empty ( ) {
533- filtered. retain ( |test| !opts. skip . iter ( ) . any ( |sf| matches_filter ( test, sf) ) ) ;
565+ filtered. retain ( |test| {
566+ let name = test. desc . name . as_slice ( ) ;
567+ !opts. skip . iter ( ) . any ( |sf| {
568+ if opts. filter_exact { name == sf. as_str ( ) } else { name. contains ( sf. as_str ( ) ) }
569+ } )
570+ } ) ;
534571 }
535572
536573 // Excludes #[should_panic] tests
@@ -553,6 +590,30 @@ pub fn filter_tests(opts: &TestOpts, tests: Vec<TestDescAndFn>) -> Vec<TestDescA
553590 filtered
554591}
555592
593+ /// Extract tests whose names exactly match one of the given `filters`, using
594+ /// binary search on the (assumed sorted) test list.
595+ fn filter_exact_match ( mut tests : Vec < TestDescAndFn > , filters : & [ String ] ) -> Vec < TestDescAndFn > {
596+ // Binary search for each filter in the sorted test list.
597+ let mut indexes: Vec < usize > = filters
598+ . iter ( )
599+ . filter_map ( |f| tests. binary_search_by ( |t| t. desc . name . as_slice ( ) . cmp ( f. as_str ( ) ) ) . ok ( ) )
600+ . collect ( ) ;
601+ indexes. sort_unstable ( ) ;
602+ indexes. dedup ( ) ;
603+
604+ // Extract matching tests. Process indexes in descending order so that
605+ // swap_remove (which replaces the removed element with the last) does not
606+ // invalidate indexes we haven't visited yet.
607+ let mut result = Vec :: with_capacity ( indexes. len ( ) ) ;
608+ for & idx in indexes. iter ( ) . rev ( ) {
609+ result. push ( tests. swap_remove ( idx) ) ;
610+ }
611+ // Reverse to restore the original sorted order, since we extracted the
612+ // matching tests in descending index order.
613+ result. reverse ( ) ;
614+ result
615+ }
616+
556617pub fn convert_benchmarks_to_tests ( tests : Vec < TestDescAndFn > ) -> Vec < TestDescAndFn > {
557618 // convert benchmarks to tests, if we're not benchmarking them
558619 tests
0 commit comments