@@ -34,7 +34,7 @@ use nextest_runner::{
3434 redact:: Redactor ,
3535 reporter:: {
3636 FinalStatusLevel , ReporterBuilder , StatusLevel , TestOutputDisplay , TestOutputErrorSlice ,
37- events:: { FinalRunStats , RunStatsFailureKind } ,
37+ events:: { FinalRunStats , RunStatsFailureKind , TestEventKind } ,
3838 highlight_end, structured,
3939 } ,
4040 reuse_build:: { ArchiveReporter , PathMapper , ReuseBuildInfo , archive_to_file} ,
@@ -54,7 +54,7 @@ use std::{
5454 env:: VarError ,
5555 fmt,
5656 io:: { Cursor , Write } ,
57- sync:: { Arc , OnceLock } ,
57+ sync:: { Arc , OnceLock , Mutex } ,
5858} ;
5959use swrite:: { SWrite , swrite} ;
6060use tracing:: { Level , debug, info, warn} ;
@@ -587,6 +587,18 @@ struct TestBuildFilter {
587587 #[ arg( long) ]
588588 ignore_default_filter : bool ,
589589
590+ /// Only run tests that failed in the last run
591+ #[ arg( long, visible_alias = "lf" , conflicts_with_all = [ "failed_last" , "clear_failed" ] ) ]
592+ last_failed : bool ,
593+
594+ /// Run failed tests first, then other tests
595+ #[ arg( long, visible_alias = "fl" , conflicts_with_all = [ "last_failed" , "clear_failed" ] ) ]
596+ failed_last : bool ,
597+
598+ /// Clear the list of failed tests without running tests
599+ #[ arg( long, conflicts_with_all = [ "last_failed" , "failed_last" ] ) ]
600+ clear_failed : bool ,
601+
590602 /// Test name filters.
591603 #[ arg( help_heading = None , name = "FILTERS" ) ]
592604 pre_double_dash_filters : Vec < String > ,
@@ -648,12 +660,61 @@ impl TestBuildFilter {
648660 . map_err ( |err| ExpectedError :: CreateTestListError { err } )
649661 }
650662
651- fn make_test_filter_builder ( & self , filter_exprs : Vec < Filterset > ) -> Result < TestFilterBuilder > {
663+ fn make_test_filter_builder ( & self , filter_exprs : Vec < Filterset > , profile_name : & str , profile : & EarlyProfile < ' _ > ) -> Result < TestFilterBuilder > {
652664 // Merge the test binary args into the patterns.
653665 let mut run_ignored = self . run_ignored . map ( Into :: into) ;
654666 let mut patterns = TestFilterPatterns :: new ( self . pre_double_dash_filters . clone ( ) ) ;
655667 self . merge_test_binary_args ( & mut run_ignored, & mut patterns) ?;
656668
669+ // Handle --last-failed and --failed-last options
670+ if self . last_failed || self . failed_last {
671+ use nextest_runner:: reporter:: last_failed:: FailedTestStore ;
672+
673+ let store = FailedTestStore :: new ( profile. store_dir ( ) , profile_name) ;
674+ match store. load ( ) {
675+ Ok ( Some ( snapshot) ) => {
676+ if snapshot. failed_tests . is_empty ( ) {
677+ eprintln ! ( "No failed tests found from previous run for profile '{}'" , profile_name) ;
678+ if self . last_failed {
679+ // For --last-failed with no failed tests, we should run no tests
680+ // Create a pattern that matches nothing
681+ patterns = TestFilterPatterns :: default ( ) ;
682+ patterns. add_exact_pattern ( "__nextest_internal_no_tests_to_run__" . to_string ( ) ) ;
683+ }
684+ // For --failed-last, we continue with the normal filtering
685+ } else {
686+ eprintln ! ( "Found {} failed test(s) from previous run" , snapshot. failed_tests. len( ) ) ;
687+
688+ if self . last_failed {
689+ // Only run failed tests - replace all patterns
690+ patterns = TestFilterPatterns :: default ( ) ;
691+ for failed_test in & snapshot. failed_tests {
692+ // Add exact pattern for each failed test
693+ patterns. add_exact_pattern ( failed_test. test_name . clone ( ) ) ;
694+ }
695+ } else {
696+ // --failed-last: prioritize failed tests
697+ // This will be handled in the test runner by sorting tests
698+ // For now, we pass the failed tests information through some mechanism
699+ // TODO: Add a way to pass failed test info to the runner for prioritization
700+ }
701+ }
702+ }
703+ Ok ( None ) => {
704+ eprintln ! ( "No previous test run found for profile '{}'" , profile_name) ;
705+ if self . last_failed {
706+ // For --last-failed with no history, run no tests
707+ patterns = TestFilterPatterns :: default ( ) ;
708+ patterns. add_exact_pattern ( "__nextest_internal_no_tests_to_run__" . to_string ( ) ) ;
709+ }
710+ }
711+ Err ( err) => {
712+ eprintln ! ( "Warning: Failed to load test history: {}" , err) ;
713+ // Continue with normal filtering on error
714+ }
715+ }
716+ }
717+
657718 Ok ( TestFilterBuilder :: new (
658719 run_ignored. unwrap_or_default ( ) ,
659720 self . partition . clone ( ) ,
@@ -1649,8 +1710,15 @@ impl App {
16491710
16501711 let ( version_only_config, config) = self . base . load_config ( & pcx) ?;
16511712 let profile = self . base . load_profile ( & config) ?;
1713+ let profile_name = self . base . config_opts . profile . as_deref ( ) . unwrap_or_else ( || {
1714+ if std:: env:: var_os ( "MIRI_SYSROOT" ) . is_some ( ) {
1715+ NextestConfig :: DEFAULT_MIRI_PROFILE
1716+ } else {
1717+ NextestConfig :: DEFAULT_PROFILE
1718+ }
1719+ } ) ;
16521720 let filter_exprs = self . build_filtering_expressions ( & pcx) ?;
1653- let test_filter_builder = self . build_filter . make_test_filter_builder ( filter_exprs) ?;
1721+ let test_filter_builder = self . build_filter . make_test_filter_builder ( filter_exprs, profile_name , & profile ) ?;
16541722
16551723 let binary_list = self . base . build_binary_list ( ) ?;
16561724
@@ -1710,6 +1778,13 @@ impl App {
17101778 let pcx = ParseContext :: new ( self . base . graph ( ) ) ;
17111779 let ( _, config) = self . base . load_config ( & pcx) ?;
17121780 let profile = self . base . load_profile ( & config) ?;
1781+ let profile_name = self . base . config_opts . profile . as_deref ( ) . unwrap_or_else ( || {
1782+ if std:: env:: var_os ( "MIRI_SYSROOT" ) . is_some ( ) {
1783+ NextestConfig :: DEFAULT_MIRI_PROFILE
1784+ } else {
1785+ NextestConfig :: DEFAULT_PROFILE
1786+ }
1787+ } ) ;
17131788
17141789 // Validate test groups before doing any other work.
17151790 let mode = if groups. is_empty ( ) {
@@ -1721,7 +1796,7 @@ impl App {
17211796 let settings = ShowTestGroupSettings { mode, show_default } ;
17221797
17231798 let filter_exprs = self . build_filtering_expressions ( & pcx) ?;
1724- let test_filter_builder = self . build_filter . make_test_filter_builder ( filter_exprs) ?;
1799+ let test_filter_builder = self . build_filter . make_test_filter_builder ( filter_exprs, profile_name , & profile ) ?;
17251800
17261801 let binary_list = self . base . build_binary_list ( ) ?;
17271802 let build_platforms = binary_list. rust_build_meta . build_platforms . clone ( ) ;
@@ -1765,6 +1840,26 @@ impl App {
17651840 let pcx = ParseContext :: new ( self . base . graph ( ) ) ;
17661841 let ( version_only_config, config) = self . base . load_config ( & pcx) ?;
17671842 let profile = self . base . load_profile ( & config) ?;
1843+ let profile_name = self . base . config_opts . profile . as_deref ( ) . unwrap_or_else ( || {
1844+ if std:: env:: var_os ( "MIRI_SYSROOT" ) . is_some ( ) {
1845+ NextestConfig :: DEFAULT_MIRI_PROFILE
1846+ } else {
1847+ NextestConfig :: DEFAULT_PROFILE
1848+ }
1849+ } ) ;
1850+
1851+ // Handle clearing failed tests early if requested
1852+ if self . build_filter . clear_failed {
1853+ use nextest_runner:: reporter:: last_failed:: FailedTestStore ;
1854+ let store = FailedTestStore :: new ( profile. store_dir ( ) , profile_name) ;
1855+ store
1856+ . clear ( )
1857+ . map_err ( |err| ExpectedError :: ClearFailedTestsError {
1858+ error : err. to_string ( ) ,
1859+ } ) ?;
1860+ eprintln ! ( "Cleared failed test history for profile '{}'" , profile_name) ;
1861+ return Ok ( 0 ) ;
1862+ }
17681863
17691864 // Construct this here so that errors are reported before the build step.
17701865 let mut structured_reporter = structured:: StructuredReporter :: new ( ) ;
@@ -1818,7 +1913,7 @@ impl App {
18181913 reporter_builder. set_verbose ( self . base . output . verbose ) ;
18191914
18201915 let filter_exprs = self . build_filtering_expressions ( & pcx) ?;
1821- let test_filter_builder = self . build_filter . make_test_filter_builder ( filter_exprs) ?;
1916+ let test_filter_builder = self . build_filter . make_test_filter_builder ( filter_exprs, profile_name , & profile ) ?;
18221917
18231918 let binary_list = self . base . build_binary_list ( ) ?;
18241919 let build_platforms = & binary_list. rust_build_meta . build_platforms . clone ( ) ;
@@ -1870,11 +1965,44 @@ impl App {
18701965 ) ;
18711966
18721967 configure_handle_inheritance ( no_capture) ?;
1968+
1969+ // Track failed tests during the run
1970+ use nextest_runner:: reporter:: last_failed:: { FailedTest , FailedTestStore , FailedTestsSnapshot } ;
1971+ let failed_tests = Arc :: new ( Mutex :: new ( Vec :: < FailedTest > :: new ( ) ) ) ;
1972+ let failed_tests_for_callback = Arc :: clone ( & failed_tests) ;
1973+
18731974 let run_stats = runner. try_execute ( |event| {
1975+ // Track failed tests for persistence
1976+ if let TestEventKind :: TestFinished { test_instance, run_statuses, .. } = & event. kind {
1977+ if !run_statuses. last_status ( ) . result . is_success ( ) {
1978+ let mut failed = failed_tests_for_callback. lock ( ) . unwrap ( ) ;
1979+ failed. push ( FailedTest :: from_test_instance_id ( test_instance. id ( ) ) ) ;
1980+ }
1981+ }
1982+
18741983 // Write and flush the event.
18751984 reporter. report_event ( event)
18761985 } ) ?;
18771986 reporter. finish ( ) ;
1987+
1988+ // After the run completes, persist failed tests if we're not in no-run mode
1989+ if !runner_opts. no_run {
1990+ let store = FailedTestStore :: new ( profile. store_dir ( ) , profile_name) ;
1991+
1992+ let failed = failed_tests. lock ( ) . unwrap ( ) ;
1993+ let snapshot = FailedTestsSnapshot {
1994+ version : 1 ,
1995+ created_at : chrono:: Utc :: now ( ) ,
1996+ profile_name : profile_name. to_owned ( ) ,
1997+ failed_tests : failed. iter ( ) . cloned ( ) . collect ( ) ,
1998+ } ;
1999+
2000+ if let Err ( err) = store. save ( & snapshot) {
2001+ eprintln ! ( "Warning: Failed to save failed test history: {}" , err) ;
2002+ // Don't fail the entire test run if we can't save the history
2003+ }
2004+ }
2005+
18782006 self . base
18792007 . check_version_config_final ( version_only_config. nextest_version ( ) ) ?;
18802008
@@ -2734,7 +2862,17 @@ mod tests {
27342862 fn get_test_filter_builder ( cmd : & str ) -> Result < TestFilterBuilder > {
27352863 let app = TestCli :: try_parse_from ( shell_words:: split ( cmd) . expect ( "valid command line" ) )
27362864 . unwrap_or_else ( |_| panic ! ( "{cmd} should have successfully parsed" ) ) ;
2737- app. build_filter . make_test_filter_builder ( vec ! [ ] )
2865+ // For tests, skip the failed test loading functionality
2866+ let mut run_ignored = app. build_filter . run_ignored . map ( Into :: into) ;
2867+ let mut patterns = TestFilterPatterns :: new ( app. build_filter . pre_double_dash_filters . clone ( ) ) ;
2868+ app. build_filter . merge_test_binary_args ( & mut run_ignored, & mut patterns) ?;
2869+
2870+ Ok ( TestFilterBuilder :: new (
2871+ run_ignored. unwrap_or_default ( ) ,
2872+ app. build_filter . partition . clone ( ) ,
2873+ patterns,
2874+ vec ! [ ] ,
2875+ ) ?)
27382876 }
27392877
27402878 let valid = & [
0 commit comments