@@ -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,208 @@ 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 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) ;
482
689
}
483
690
484
691
fn parse_comments (
@@ -635,7 +842,7 @@ impl dyn TestStatus {
635
842
build_manager : & BuildManager < ' _ > ,
636
843
config : & Config ,
637
844
comments : & Comments ,
638
- ) -> TestResult {
845
+ ) -> Result < ( TestOk , Option < WriteBack > ) , Errored > {
639
846
let path = self . path ( ) ;
640
847
let revision = self . revision ( ) ;
641
848
@@ -669,7 +876,7 @@ impl dyn TestStatus {
669
876
let ( cmd, status, stderr, stdout) = self . run_command ( cmd) ?;
670
877
671
878
let mode = comments. mode ( revision) ?;
672
- let cmd = check_test_result (
879
+ let ( cmd, write_back ) = check_test_result (
673
880
cmd,
674
881
match * mode {
675
882
Mode :: Run { .. } => Mode :: Pass ,
@@ -685,13 +892,14 @@ impl dyn TestStatus {
685
892
) ?;
686
893
687
894
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 ) ) ;
689
897
}
690
898
691
899
run_rustfix (
692
900
& stderr, & stdout, path, comments, revision, & config, * mode, extra_args,
693
901
) ?;
694
- Ok ( TestOk :: Ok )
902
+ Ok ( ( TestOk :: Ok , write_back ) )
695
903
}
696
904
697
905
/// Run a command, and if it takes more than 100ms, start appending the last stderr/stdout
@@ -850,7 +1058,7 @@ fn run_rustfix(
850
1058
851
1059
let global_rustfix = match mode {
852
1060
Mode :: Pass | Mode :: Run { .. } | Mode :: Panic => RustfixMode :: Disabled ,
853
- Mode :: Fail { rustfix, .. } | Mode :: Yolo { rustfix } => rustfix,
1061
+ Mode :: Fail { rustfix, .. } | Mode :: Yolo { rustfix, .. } => rustfix,
854
1062
} ;
855
1063
856
1064
let fixed_code = ( no_run_rustfix. is_none ( ) && global_rustfix. enabled ( ) )
@@ -1009,7 +1217,7 @@ fn check_test_result(
1009
1217
status : ExitStatus ,
1010
1218
stdout : & [ u8 ] ,
1011
1219
stderr : & [ u8 ] ,
1012
- ) -> Result < Command , Errored > {
1220
+ ) -> Result < ( Command , Option < WriteBack > ) , Errored > {
1013
1221
let mut errors = vec ! [ ] ;
1014
1222
errors. extend ( mode. ok ( status) . err ( ) ) ;
1015
1223
// Always remove annotation comments from stderr.
@@ -1024,7 +1232,7 @@ fn check_test_result(
1024
1232
& diagnostics. rendered ,
1025
1233
) ;
1026
1234
// Check error annotations in the source against output
1027
- check_annotations (
1235
+ let write_back = check_annotations (
1028
1236
diagnostics. messages ,
1029
1237
diagnostics. messages_from_unknown_file_or_line ,
1030
1238
path,
@@ -1033,7 +1241,7 @@ fn check_test_result(
1033
1241
comments,
1034
1242
) ?;
1035
1243
if errors. is_empty ( ) {
1036
- Ok ( command)
1244
+ Ok ( ( command, write_back ) )
1037
1245
} else {
1038
1246
Err ( Errored {
1039
1247
command,
@@ -1066,7 +1274,7 @@ fn check_annotations(
1066
1274
errors : & mut Errors ,
1067
1275
revision : & str ,
1068
1276
comments : & Comments ,
1069
- ) -> Result < ( ) , Errored > {
1277
+ ) -> Result < Option < WriteBack > , Errored > {
1070
1278
let error_patterns = comments
1071
1279
. for_revision ( revision)
1072
1280
. flat_map ( |r| r. error_in_other_files . iter ( ) ) ;
@@ -1177,7 +1385,9 @@ fn check_annotations(
1177
1385
1178
1386
let mode = comments. mode ( revision) ?;
1179
1387
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 {
1181
1391
let messages_from_unknown_file_or_line = filter ( messages_from_unknown_file_or_line) ;
1182
1392
if !messages_from_unknown_file_or_line. is_empty ( ) {
1183
1393
errors. push ( Error :: ErrorsWithoutPattern {
@@ -1202,7 +1412,9 @@ fn check_annotations(
1202
1412
} ) ;
1203
1413
}
1204
1414
}
1205
- }
1415
+
1416
+ None
1417
+ } ;
1206
1418
1207
1419
match ( * mode, seen_error_match) {
1208
1420
( Mode :: Pass , Some ( span) ) | ( Mode :: Panic , Some ( span) ) => {
@@ -1220,7 +1432,7 @@ fn check_annotations(
1220
1432
) => errors. push ( Error :: NoPatternsFound ) ,
1221
1433
_ => { }
1222
1434
}
1223
- Ok ( ( ) )
1435
+ Ok ( write_back )
1224
1436
}
1225
1437
1226
1438
fn check_output (
0 commit comments