@@ -228,6 +228,12 @@ struct TestRun {
228228 status : Box < dyn status_emitter:: TestStatus > ,
229229}
230230
231+ #[ derive( Debug ) ]
232+ struct WriteBack {
233+ level : WriteBackLevel ,
234+ messages : Vec < Vec < Message > > ,
235+ }
236+
231237/// A version of `run_tests` that allows more fine-grained control over running tests.
232238///
233239/// All `configs` are being run in parallel.
@@ -319,7 +325,7 @@ pub fn run_tests_generic(
319325 let mut config = config. clone ( ) ;
320326 per_file_config ( & mut config, path, & file_contents) ;
321327 let result = match std:: panic:: catch_unwind ( || {
322- parse_and_test_file ( & build_manager, & status, config, file_contents)
328+ parse_and_test_file ( & build_manager, & status, config, file_contents, path )
323329 } ) {
324330 Ok ( Ok ( res) ) => res,
325331 Ok ( Err ( err) ) => {
@@ -438,6 +444,7 @@ fn parse_and_test_file(
438444 status : & dyn TestStatus ,
439445 mut config : Config ,
440446 file_contents : Vec < u8 > ,
447+ file_path : & Path ,
441448) -> Result < Vec < TestRun > , Errored > {
442449 let comments = parse_comments (
443450 & file_contents,
@@ -448,7 +455,9 @@ fn parse_and_test_file(
448455 // Run the test for all revisions
449456 let revisions = comments. revisions . as_deref ( ) . unwrap_or ( EMPTY ) ;
450457 let mut built_deps = false ;
451- Ok ( revisions
458+ let mut write_backs = Vec :: new ( ) ;
459+ let mut success = true ;
460+ let results: Vec < _ > = revisions
452461 . iter ( )
453462 . map ( |revision| {
454463 let status = status. for_revision ( revision) ;
@@ -475,10 +484,210 @@ fn parse_and_test_file(
475484 built_deps = true ;
476485 }
477486
478- let result = status. run_test ( build_manager, & config, & comments) ;
479- TestRun { result, status }
487+ match status. run_test ( build_manager, & config, & comments) {
488+ Ok ( ( result, Some ( write_back) ) ) => {
489+ write_backs. push ( ( & * * revision, write_back) ) ;
490+ TestRun {
491+ result : Ok ( result) ,
492+ status,
493+ }
494+ }
495+ Ok ( ( result, None ) ) => TestRun {
496+ result : Ok ( result) ,
497+ status,
498+ } ,
499+ Err ( e) => {
500+ success = false ;
501+ TestRun {
502+ result : Err ( e) ,
503+ status,
504+ }
505+ }
506+ }
480507 } )
481- . collect ( ) )
508+ . collect ( ) ;
509+
510+ if success && !write_backs. is_empty ( ) {
511+ write_back_annotations ( file_path, & file_contents, & comments, & write_backs) ;
512+ }
513+
514+ Ok ( results)
515+ }
516+
517+ fn write_back_annotations (
518+ file_path : & Path ,
519+ file_contents : & [ u8 ] ,
520+ comments : & Comments ,
521+ write_backs : & [ ( & str , WriteBack ) ] ,
522+ ) {
523+ let mut buf = Vec :: < u8 > :: with_capacity ( file_contents. len ( ) * 2 ) ;
524+ let ( first_rev, revs) = write_backs. split_first ( ) . unwrap ( ) ;
525+ let mut counters = Vec :: new ( ) ;
526+ let mut print_msgs = Vec :: new ( ) ;
527+ let prefix = comments
528+ . base_immut ( )
529+ . diagnostic_code_prefix
530+ . as_ref ( )
531+ . map_or ( "" , |x| x. as_str ( ) ) ;
532+ let mut skip_line_before_over_matcher = false ;
533+
534+ match first_rev. 1 . level {
535+ WriteBackLevel :: Code => {
536+ for ( line, txt) in file_contents. lines_with_terminator ( ) . enumerate ( ) {
537+ let mut use_over_matcher = false ;
538+ let first_msgs: & [ Message ] =
539+ first_rev. 1 . messages . get ( line + 1 ) . map_or ( & [ ] , |m| & * * m) ;
540+
541+ print_msgs. clear ( ) ;
542+ print_msgs. extend (
543+ first_msgs
544+ . iter ( )
545+ . filter ( |m| m. level == Level :: Error )
546+ . filter_map ( |m| {
547+ m. line_col
548+ . as_ref ( )
549+ . zip ( m. code . as_deref ( ) . and_then ( |c| c. strip_prefix ( prefix) ) )
550+ } )
551+ . inspect ( |( span, _) | use_over_matcher |= span. line_start != span. line_end )
552+ . enumerate ( )
553+ . map ( |( i, ( span, code) ) | ( i, span, code, first_rev. 0 ) ) ,
554+ ) ;
555+ counters. clear ( ) ;
556+ counters. resize ( print_msgs. len ( ) , 0 ) ;
557+
558+ for rev in revs {
559+ let msgs: & [ Message ] = rev. 1 . messages . get ( line + 1 ) . map_or ( & [ ] , |m| & * * m) ;
560+
561+ for ( span, code) in
562+ msgs. iter ( )
563+ . filter ( |m| m. level == Level :: Error )
564+ . filter_map ( |m| {
565+ m. line_col
566+ . as_ref ( )
567+ . zip ( m. code . as_deref ( ) . and_then ( |c| c. strip_prefix ( prefix) ) )
568+ } )
569+ {
570+ let i = if let Some ( & ( i, ..) ) = print_msgs[ ..counters. len ( ) ] . iter ( ) . find (
571+ |& & ( _, prev_span, prev_code, _) | span == prev_span && code == prev_code,
572+ ) {
573+ counters[ i] += 1 ;
574+ i
575+ } else {
576+ use_over_matcher |= span. line_start != span. line_end ;
577+ usize:: MAX
578+ } ;
579+ print_msgs. push ( ( i, span, code, rev. 0 ) ) ;
580+ }
581+ }
582+
583+ // partition the first revision's messages
584+ // in all revisions => only some revisions
585+ let mut i = 0 ;
586+ let mut j = counters. len ( ) ;
587+ while i < j {
588+ if counters[ i] == revs. len ( ) {
589+ print_msgs[ i] . 3 = "" ;
590+ i += 1 ;
591+ } else if counters[ j - 1 ] == revs. len ( ) {
592+ print_msgs. swap ( i, j - 1 ) ;
593+ print_msgs[ i] . 3 = "" ;
594+ i += 1 ;
595+ j -= 1 ;
596+ } else {
597+ j -= 1 ;
598+ }
599+ }
600+ // For all other revision's messages, remove the ones that exist in all revisions.
601+ print_msgs. retain ( |& ( i, _, _, rev) | {
602+ rev. is_empty ( ) || counters. get ( i) . map_or ( true , |& x| x != revs. len ( ) )
603+ } ) ;
604+
605+ // rustfmt behaves poorly when putting a comment underneath in these cases.
606+ use_over_matcher |= txt. trim_end ( ) . ends_with ( b"{" ) || txt. contains_str ( b"//" ) ;
607+
608+ match & * print_msgs {
609+ [ ] => {
610+ skip_line_before_over_matcher =
611+ !txt. trim_start ( ) . starts_with ( b"//" ) && txt. contains_str ( b"//" ) ;
612+ buf. extend ( txt)
613+ }
614+ [ ( _, _, code, rev) ]
615+ if !use_over_matcher && txt. len ( ) + code. len ( ) + rev. len ( ) < 65 =>
616+ {
617+ skip_line_before_over_matcher = true ;
618+ let ( txt, end) : ( _ , & [ u8 ] ) = if let Some ( txt) = txt. strip_suffix ( b"\r \n " ) {
619+ ( txt, b"\r \n " )
620+ } else if let Some ( txt) = txt. strip_suffix ( b"\n " ) {
621+ ( txt, b"\n " )
622+ } else {
623+ ( txt, & [ ] )
624+ } ;
625+
626+ buf. extend ( txt) ;
627+ buf. extend ( b" //~" ) ;
628+ if !rev. is_empty ( ) {
629+ buf. push ( b'[' ) ;
630+ buf. extend ( rev. as_bytes ( ) ) ;
631+ buf. push ( b']' ) ;
632+ }
633+ buf. push ( b' ' ) ;
634+ buf. extend ( code. as_bytes ( ) ) ;
635+ buf. extend ( end) ;
636+ }
637+ [ ..] => {
638+ if !use_over_matcher {
639+ buf. extend ( txt) ;
640+ skip_line_before_over_matcher = true ;
641+ if !buf. ends_with ( b"\n " ) {
642+ buf. push ( b'\n' ) ;
643+ }
644+ }
645+ let indent = & txt[ ..txt
646+ . iter ( )
647+ . position ( |x| !matches ! ( x, b' ' | b'\t' ) )
648+ . unwrap_or ( txt. len ( ) ) ] ;
649+ let end: & [ u8 ] = if txt. ends_with ( b"\r \n " ) {
650+ b"\r \n "
651+ } else {
652+ b"\n "
653+ } ;
654+ if use_over_matcher && skip_line_before_over_matcher {
655+ buf. extend ( end) ;
656+ }
657+
658+ let mut msg_num = 1 ;
659+ let msg_end = print_msgs. len ( ) ;
660+ for ( _, _, code, rev) in & print_msgs {
661+ buf. extend ( indent) ;
662+ buf. extend ( b"//~" ) ;
663+ if !rev. is_empty ( ) {
664+ buf. push ( b'[' ) ;
665+ buf. extend ( rev. as_bytes ( ) ) ;
666+ buf. push ( b']' ) ;
667+ }
668+ buf. push ( match ( use_over_matcher, msg_num) {
669+ ( false , 1 ) => b'^' ,
670+ ( true , x) if x == msg_end => b'v' ,
671+ _ => b'|' ,
672+ } ) ;
673+ buf. push ( b' ' ) ;
674+ buf. extend ( code. as_bytes ( ) ) ;
675+ buf. extend ( end) ;
676+ msg_num += 1 ;
677+ }
678+
679+ if use_over_matcher {
680+ skip_line_before_over_matcher =
681+ !txt. trim_start ( ) . starts_with ( b"//" ) && txt. contains_str ( b"//" ) ;
682+ buf. extend ( txt) ;
683+ }
684+ }
685+ }
686+ }
687+ }
688+ }
689+
690+ let _ = std:: fs:: write ( file_path, buf) ;
482691}
483692
484693fn parse_comments (
@@ -635,7 +844,7 @@ impl dyn TestStatus {
635844 build_manager : & BuildManager < ' _ > ,
636845 config : & Config ,
637846 comments : & Comments ,
638- ) -> TestResult {
847+ ) -> Result < ( TestOk , Option < WriteBack > ) , Errored > {
639848 let path = self . path ( ) ;
640849 let revision = self . revision ( ) ;
641850
@@ -669,7 +878,7 @@ impl dyn TestStatus {
669878 let ( cmd, status, stderr, stdout) = self . run_command ( cmd) ?;
670879
671880 let mode = comments. mode ( revision) ?;
672- let cmd = check_test_result (
881+ let ( cmd, write_back ) = check_test_result (
673882 cmd,
674883 match * mode {
675884 Mode :: Run { .. } => Mode :: Pass ,
@@ -685,13 +894,14 @@ impl dyn TestStatus {
685894 ) ?;
686895
687896 if let Mode :: Run { .. } = * mode {
688- return run_test_binary ( mode, path, revision, comments, cmd, & config) ;
897+ return run_test_binary ( mode, path, revision, comments, cmd, & config)
898+ . map ( |x| ( x, None ) ) ;
689899 }
690900
691901 run_rustfix (
692902 & stderr, & stdout, path, comments, revision, & config, * mode, extra_args,
693903 ) ?;
694- Ok ( TestOk :: Ok )
904+ Ok ( ( TestOk :: Ok , write_back ) )
695905 }
696906
697907 /// Run a command, and if it takes more than 100ms, start appending the last stderr/stdout
@@ -850,7 +1060,7 @@ fn run_rustfix(
8501060
8511061 let global_rustfix = match mode {
8521062 Mode :: Pass | Mode :: Run { .. } | Mode :: Panic => RustfixMode :: Disabled ,
853- Mode :: Fail { rustfix, .. } | Mode :: Yolo { rustfix } => rustfix,
1063+ Mode :: Fail { rustfix, .. } | Mode :: Yolo { rustfix, .. } => rustfix,
8541064 } ;
8551065
8561066 let fixed_code = ( no_run_rustfix. is_none ( ) && global_rustfix. enabled ( ) )
@@ -1009,7 +1219,7 @@ fn check_test_result(
10091219 status : ExitStatus ,
10101220 stdout : & [ u8 ] ,
10111221 stderr : & [ u8 ] ,
1012- ) -> Result < Command , Errored > {
1222+ ) -> Result < ( Command , Option < WriteBack > ) , Errored > {
10131223 let mut errors = vec ! [ ] ;
10141224 errors. extend ( mode. ok ( status) . err ( ) ) ;
10151225 // Always remove annotation comments from stderr.
@@ -1024,7 +1234,7 @@ fn check_test_result(
10241234 & diagnostics. rendered ,
10251235 ) ;
10261236 // Check error annotations in the source against output
1027- check_annotations (
1237+ let write_back = check_annotations (
10281238 diagnostics. messages ,
10291239 diagnostics. messages_from_unknown_file_or_line ,
10301240 path,
@@ -1033,7 +1243,7 @@ fn check_test_result(
10331243 comments,
10341244 ) ?;
10351245 if errors. is_empty ( ) {
1036- Ok ( command)
1246+ Ok ( ( command, write_back ) )
10371247 } else {
10381248 Err ( Errored {
10391249 command,
@@ -1066,7 +1276,7 @@ fn check_annotations(
10661276 errors : & mut Errors ,
10671277 revision : & str ,
10681278 comments : & Comments ,
1069- ) -> Result < ( ) , Errored > {
1279+ ) -> Result < Option < WriteBack > , Errored > {
10701280 let error_patterns = comments
10711281 . for_revision ( revision)
10721282 . flat_map ( |r| r. error_in_other_files . iter ( ) ) ;
@@ -1177,7 +1387,9 @@ fn check_annotations(
11771387
11781388 let mode = comments. mode ( revision) ?;
11791389
1180- if !matches ! ( * mode, Mode :: Yolo { .. } ) {
1390+ let write_back = if let Mode :: Yolo { write_back, .. } = * mode {
1391+ write_back. map ( |level| WriteBack { level, messages } )
1392+ } else {
11811393 let messages_from_unknown_file_or_line = filter ( messages_from_unknown_file_or_line) ;
11821394 if !messages_from_unknown_file_or_line. is_empty ( ) {
11831395 errors. push ( Error :: ErrorsWithoutPattern {
@@ -1202,7 +1414,9 @@ fn check_annotations(
12021414 } ) ;
12031415 }
12041416 }
1205- }
1417+
1418+ None
1419+ } ;
12061420
12071421 match ( * mode, seen_error_match) {
12081422 ( Mode :: Pass , Some ( span) ) | ( Mode :: Panic , Some ( span) ) => {
@@ -1220,7 +1434,7 @@ fn check_annotations(
12201434 ) => errors. push ( Error :: NoPatternsFound ) ,
12211435 _ => { }
12221436 }
1223- Ok ( ( ) )
1437+ Ok ( write_back )
12241438}
12251439
12261440fn check_output (
0 commit comments