@@ -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,208 @@ 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 write_under_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 single_line = true ;
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, _) | single_line &= 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+ single_line &= 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+ single_line &= !txt. trim_end ( ) . ends_with ( b"{" ) && !txt. contains_str ( b"//" ) ;
607+
608+ match & * print_msgs {
609+ [ ] => {
610+ write_under_matcher = false ;
611+ buf. extend ( txt)
612+ }
613+ [ ( _, _, code, rev) ]
614+ if single_line && txt. len ( ) + code. len ( ) + rev. len ( ) < 65 =>
615+ {
616+ write_under_matcher = false ;
617+ let ( txt, end) : ( _ , & [ u8 ] ) = if let Some ( txt) = txt. strip_suffix ( b"\r \n " ) {
618+ ( txt, b"\r \n " )
619+ } else if let Some ( txt) = txt. strip_suffix ( b"\n " ) {
620+ ( txt, b"\n " )
621+ } else {
622+ ( txt, & [ ] )
623+ } ;
624+
625+ buf. extend ( txt) ;
626+ buf. extend ( b" //~" ) ;
627+ if !rev. is_empty ( ) {
628+ buf. push ( b'[' ) ;
629+ buf. extend ( rev. as_bytes ( ) ) ;
630+ buf. push ( b']' ) ;
631+ }
632+ buf. push ( b' ' ) ;
633+ buf. extend ( code. as_bytes ( ) ) ;
634+ buf. extend ( end) ;
635+ }
636+ [ ..] => {
637+ if single_line {
638+ buf. extend ( txt) ;
639+ write_under_matcher = true ;
640+ if !buf. ends_with ( b"\n " ) {
641+ buf. push ( b'\n' ) ;
642+ }
643+ }
644+ let indent = & txt[ ..txt
645+ . iter ( )
646+ . position ( |x| !matches ! ( x, b' ' | b'\t' ) )
647+ . unwrap_or ( txt. len ( ) ) ] ;
648+ let end: & [ u8 ] = if txt. ends_with ( b"\r \n " ) {
649+ b"\r \n "
650+ } else {
651+ b"\n "
652+ } ;
653+ if !single_line && write_under_matcher {
654+ write_under_matcher = false ;
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 ( single_line, msg_num) {
669+ ( true , 1 ) => b'^' ,
670+ ( false , 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 !single_line {
680+ buf. extend ( txt) ;
681+ }
682+ }
683+ }
684+ }
685+ }
686+ }
687+
688+ let _ = std:: fs:: write ( file_path, buf) ;
482689}
483690
484691fn parse_comments (
@@ -635,7 +842,7 @@ impl dyn TestStatus {
635842 build_manager : & BuildManager < ' _ > ,
636843 config : & Config ,
637844 comments : & Comments ,
638- ) -> TestResult {
845+ ) -> Result < ( TestOk , Option < WriteBack > ) , Errored > {
639846 let path = self . path ( ) ;
640847 let revision = self . revision ( ) ;
641848
@@ -669,7 +876,7 @@ impl dyn TestStatus {
669876 let ( cmd, status, stderr, stdout) = self . run_command ( cmd) ?;
670877
671878 let mode = comments. mode ( revision) ?;
672- let cmd = check_test_result (
879+ let ( cmd, write_back ) = check_test_result (
673880 cmd,
674881 match * mode {
675882 Mode :: Run { .. } => Mode :: Pass ,
@@ -685,13 +892,14 @@ impl dyn TestStatus {
685892 ) ?;
686893
687894 if let Mode :: Run { .. } = * mode {
688- return run_test_binary ( mode, path, revision, comments, cmd, & config) ;
895+ return run_test_binary ( mode, path, revision, comments, cmd, & config)
896+ . map ( |x| ( x, None ) ) ;
689897 }
690898
691899 run_rustfix (
692900 & stderr, & stdout, path, comments, revision, & config, * mode, extra_args,
693901 ) ?;
694- Ok ( TestOk :: Ok )
902+ Ok ( ( TestOk :: Ok , write_back ) )
695903 }
696904
697905 /// Run a command, and if it takes more than 100ms, start appending the last stderr/stdout
@@ -850,7 +1058,7 @@ fn run_rustfix(
8501058
8511059 let global_rustfix = match mode {
8521060 Mode :: Pass | Mode :: Run { .. } | Mode :: Panic => RustfixMode :: Disabled ,
853- Mode :: Fail { rustfix, .. } | Mode :: Yolo { rustfix } => rustfix,
1061+ Mode :: Fail { rustfix, .. } | Mode :: Yolo { rustfix, .. } => rustfix,
8541062 } ;
8551063
8561064 let fixed_code = ( no_run_rustfix. is_none ( ) && global_rustfix. enabled ( ) )
@@ -1009,7 +1217,7 @@ fn check_test_result(
10091217 status : ExitStatus ,
10101218 stdout : & [ u8 ] ,
10111219 stderr : & [ u8 ] ,
1012- ) -> Result < Command , Errored > {
1220+ ) -> Result < ( Command , Option < WriteBack > ) , Errored > {
10131221 let mut errors = vec ! [ ] ;
10141222 errors. extend ( mode. ok ( status) . err ( ) ) ;
10151223 // Always remove annotation comments from stderr.
@@ -1024,7 +1232,7 @@ fn check_test_result(
10241232 & diagnostics. rendered ,
10251233 ) ;
10261234 // Check error annotations in the source against output
1027- check_annotations (
1235+ let write_back = check_annotations (
10281236 diagnostics. messages ,
10291237 diagnostics. messages_from_unknown_file_or_line ,
10301238 path,
@@ -1033,7 +1241,7 @@ fn check_test_result(
10331241 comments,
10341242 ) ?;
10351243 if errors. is_empty ( ) {
1036- Ok ( command)
1244+ Ok ( ( command, write_back ) )
10371245 } else {
10381246 Err ( Errored {
10391247 command,
@@ -1066,7 +1274,7 @@ fn check_annotations(
10661274 errors : & mut Errors ,
10671275 revision : & str ,
10681276 comments : & Comments ,
1069- ) -> Result < ( ) , Errored > {
1277+ ) -> Result < Option < WriteBack > , Errored > {
10701278 let error_patterns = comments
10711279 . for_revision ( revision)
10721280 . flat_map ( |r| r. error_in_other_files . iter ( ) ) ;
@@ -1177,7 +1385,9 @@ fn check_annotations(
11771385
11781386 let mode = comments. mode ( revision) ?;
11791387
1180- if !matches ! ( * mode, Mode :: Yolo { .. } ) {
1388+ let write_back = if let Mode :: Yolo { write_back, .. } = * mode {
1389+ write_back. map ( |level| WriteBack { level, messages } )
1390+ } else {
11811391 let messages_from_unknown_file_or_line = filter ( messages_from_unknown_file_or_line) ;
11821392 if !messages_from_unknown_file_or_line. is_empty ( ) {
11831393 errors. push ( Error :: ErrorsWithoutPattern {
@@ -1202,7 +1412,9 @@ fn check_annotations(
12021412 } ) ;
12031413 }
12041414 }
1205- }
1415+
1416+ None
1417+ } ;
12061418
12071419 match ( * mode, seen_error_match) {
12081420 ( Mode :: Pass , Some ( span) ) | ( Mode :: Panic , Some ( span) ) => {
@@ -1220,7 +1432,7 @@ fn check_annotations(
12201432 ) => errors. push ( Error :: NoPatternsFound ) ,
12211433 _ => { }
12221434 }
1223- Ok ( ( ) )
1435+ Ok ( write_back )
12241436}
12251437
12261438fn check_output (
0 commit comments