@@ -228,6 +228,12 @@ struct TestRun {
228
228
status : Box < dyn status_emitter:: TestStatus > ,
229
229
}
230
230
231
+ #[ derive( Debug ) ]
232
+ struct WriteBack {
233
+ level : WriteBackLevel ,
234
+ messages : Vec < Vec < Message > > ,
235
+ }
236
+
231
237
/// A version of `run_tests` that allows more fine-grained control over running tests.
232
238
///
233
239
/// All `configs` are being run in parallel.
@@ -319,7 +325,7 @@ pub fn run_tests_generic(
319
325
let mut config = config. clone ( ) ;
320
326
per_file_config ( & mut config, path, & file_contents) ;
321
327
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 )
323
329
} ) {
324
330
Ok ( Ok ( res) ) => res,
325
331
Ok ( Err ( err) ) => {
@@ -438,6 +444,7 @@ fn parse_and_test_file(
438
444
status : & dyn TestStatus ,
439
445
mut config : Config ,
440
446
file_contents : Vec < u8 > ,
447
+ file_path : & Path ,
441
448
) -> Result < Vec < TestRun > , Errored > {
442
449
let comments = parse_comments (
443
450
& file_contents,
@@ -448,7 +455,9 @@ fn parse_and_test_file(
448
455
// Run the test for all revisions
449
456
let revisions = comments. revisions . as_deref ( ) . unwrap_or ( EMPTY ) ;
450
457
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
452
461
. iter ( )
453
462
. map ( |revision| {
454
463
let status = status. for_revision ( revision) ;
@@ -475,10 +484,210 @@ fn parse_and_test_file(
475
484
built_deps = true ;
476
485
}
477
486
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
+ }
480
507
} )
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) ;
482
691
}
483
692
484
693
fn parse_comments (
@@ -635,7 +844,7 @@ impl dyn TestStatus {
635
844
build_manager : & BuildManager < ' _ > ,
636
845
config : & Config ,
637
846
comments : & Comments ,
638
- ) -> TestResult {
847
+ ) -> Result < ( TestOk , Option < WriteBack > ) , Errored > {
639
848
let path = self . path ( ) ;
640
849
let revision = self . revision ( ) ;
641
850
@@ -669,7 +878,7 @@ impl dyn TestStatus {
669
878
let ( cmd, status, stderr, stdout) = self . run_command ( cmd) ?;
670
879
671
880
let mode = comments. mode ( revision) ?;
672
- let cmd = check_test_result (
881
+ let ( cmd, write_back ) = check_test_result (
673
882
cmd,
674
883
match * mode {
675
884
Mode :: Run { .. } => Mode :: Pass ,
@@ -685,13 +894,14 @@ impl dyn TestStatus {
685
894
) ?;
686
895
687
896
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 ) ) ;
689
899
}
690
900
691
901
run_rustfix (
692
902
& stderr, & stdout, path, comments, revision, & config, * mode, extra_args,
693
903
) ?;
694
- Ok ( TestOk :: Ok )
904
+ Ok ( ( TestOk :: Ok , write_back ) )
695
905
}
696
906
697
907
/// Run a command, and if it takes more than 100ms, start appending the last stderr/stdout
@@ -850,7 +1060,7 @@ fn run_rustfix(
850
1060
851
1061
let global_rustfix = match mode {
852
1062
Mode :: Pass | Mode :: Run { .. } | Mode :: Panic => RustfixMode :: Disabled ,
853
- Mode :: Fail { rustfix, .. } | Mode :: Yolo { rustfix } => rustfix,
1063
+ Mode :: Fail { rustfix, .. } | Mode :: Yolo { rustfix, .. } => rustfix,
854
1064
} ;
855
1065
856
1066
let fixed_code = ( no_run_rustfix. is_none ( ) && global_rustfix. enabled ( ) )
@@ -1009,7 +1219,7 @@ fn check_test_result(
1009
1219
status : ExitStatus ,
1010
1220
stdout : & [ u8 ] ,
1011
1221
stderr : & [ u8 ] ,
1012
- ) -> Result < Command , Errored > {
1222
+ ) -> Result < ( Command , Option < WriteBack > ) , Errored > {
1013
1223
let mut errors = vec ! [ ] ;
1014
1224
errors. extend ( mode. ok ( status) . err ( ) ) ;
1015
1225
// Always remove annotation comments from stderr.
@@ -1024,7 +1234,7 @@ fn check_test_result(
1024
1234
& diagnostics. rendered ,
1025
1235
) ;
1026
1236
// Check error annotations in the source against output
1027
- check_annotations (
1237
+ let write_back = check_annotations (
1028
1238
diagnostics. messages ,
1029
1239
diagnostics. messages_from_unknown_file_or_line ,
1030
1240
path,
@@ -1033,7 +1243,7 @@ fn check_test_result(
1033
1243
comments,
1034
1244
) ?;
1035
1245
if errors. is_empty ( ) {
1036
- Ok ( command)
1246
+ Ok ( ( command, write_back ) )
1037
1247
} else {
1038
1248
Err ( Errored {
1039
1249
command,
@@ -1066,7 +1276,7 @@ fn check_annotations(
1066
1276
errors : & mut Errors ,
1067
1277
revision : & str ,
1068
1278
comments : & Comments ,
1069
- ) -> Result < ( ) , Errored > {
1279
+ ) -> Result < Option < WriteBack > , Errored > {
1070
1280
let error_patterns = comments
1071
1281
. for_revision ( revision)
1072
1282
. flat_map ( |r| r. error_in_other_files . iter ( ) ) ;
@@ -1177,7 +1387,9 @@ fn check_annotations(
1177
1387
1178
1388
let mode = comments. mode ( revision) ?;
1179
1389
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 {
1181
1393
let messages_from_unknown_file_or_line = filter ( messages_from_unknown_file_or_line) ;
1182
1394
if !messages_from_unknown_file_or_line. is_empty ( ) {
1183
1395
errors. push ( Error :: ErrorsWithoutPattern {
@@ -1202,7 +1414,9 @@ fn check_annotations(
1202
1414
} ) ;
1203
1415
}
1204
1416
}
1205
- }
1417
+
1418
+ None
1419
+ } ;
1206
1420
1207
1421
match ( * mode, seen_error_match) {
1208
1422
( Mode :: Pass , Some ( span) ) | ( Mode :: Panic , Some ( span) ) => {
@@ -1220,7 +1434,7 @@ fn check_annotations(
1220
1434
) => errors. push ( Error :: NoPatternsFound ) ,
1221
1435
_ => { }
1222
1436
}
1223
- Ok ( ( ) )
1437
+ Ok ( write_back )
1224
1438
}
1225
1439
1226
1440
fn check_output (
0 commit comments