@@ -96,7 +96,7 @@ pub(crate) fn generate_args_file(file_path: &Path, options: &RustdocOptions) ->
96
96
. map_err ( |error| format ! ( "failed to create args file: {error:?}" ) ) ?;
97
97
98
98
// We now put the common arguments into the file we created.
99
- let mut content = vec ! [ "--crate-type=bin" . to_string ( ) ] ;
99
+ let mut content = vec ! [ ] ;
100
100
101
101
for cfg in & options. cfgs {
102
102
content. push ( format ! ( "--cfg={cfg}" ) ) ;
@@ -513,12 +513,18 @@ pub(crate) struct RunnableDocTest {
513
513
line : usize ,
514
514
edition : Edition ,
515
515
no_run : bool ,
516
- is_multiple_tests : bool ,
516
+ merged_test_code : Option < String > ,
517
517
}
518
518
519
519
impl RunnableDocTest {
520
- fn path_for_merged_doctest ( & self ) -> PathBuf {
521
- self . test_opts . outdir . path ( ) . join ( format ! ( "doctest_{}.rs" , self . edition) )
520
+ fn path_for_merged_doctest_bundle ( & self ) -> PathBuf {
521
+ self . test_opts . outdir . path ( ) . join ( format ! ( "doctest_bundle_{}.rs" , self . edition) )
522
+ }
523
+ fn path_for_merged_doctest_runner ( & self ) -> PathBuf {
524
+ self . test_opts . outdir . path ( ) . join ( format ! ( "doctest_runner_{}.rs" , self . edition) )
525
+ }
526
+ fn is_multiple_tests ( & self ) -> bool {
527
+ self . merged_test_code . is_some ( )
522
528
}
523
529
}
524
530
@@ -543,90 +549,100 @@ fn run_test(
543
549
. unwrap_or_else ( || rustc_interface:: util:: rustc_path ( ) . expect ( "found rustc" ) ) ;
544
550
let mut compiler = wrapped_rustc_command ( & rustdoc_options. test_builder_wrappers , rustc_binary) ;
545
551
546
- compiler. arg ( format ! ( "@{}" , doctest. global_opts. args_file. display( ) ) ) ;
552
+ let mut compiler_args = vec ! [ ] ;
553
+
554
+ compiler_args. push ( format ! ( "@{}" , doctest. global_opts. args_file. display( ) ) ) ;
547
555
548
556
if let Some ( sysroot) = & rustdoc_options. maybe_sysroot {
549
- compiler . arg ( format ! ( "--sysroot={}" , sysroot. display( ) ) ) ;
557
+ compiler_args . push ( format ! ( "--sysroot={}" , sysroot. display( ) ) ) ;
550
558
}
551
559
552
- compiler. arg ( "--edition" ) . arg ( doctest. edition . to_string ( ) ) ;
553
- if doctest. is_multiple_tests {
554
- // The merged test harness uses the `test` crate, so we need to actually allow it.
555
- // This will not expose nightly features on stable, because crate attrs disable
556
- // merging, and `#![feature]` is required to be a crate attr.
557
- compiler. env ( "RUSTC_BOOTSTRAP" , "1" ) ;
558
- } else {
559
- // Setting these environment variables is unneeded if this is a merged doctest.
560
- compiler. env ( "UNSTABLE_RUSTDOC_TEST_PATH" , & doctest. test_opts . path ) ;
561
- compiler. env (
562
- "UNSTABLE_RUSTDOC_TEST_LINE" ,
563
- format ! ( "{}" , doctest. line as isize - doctest. full_test_line_offset as isize ) ,
564
- ) ;
565
- }
566
- compiler. arg ( "-o" ) . arg ( & output_file) ;
560
+ compiler_args. extend_from_slice ( & [ "--edition" . to_owned ( ) , doctest. edition . to_string ( ) ] ) ;
567
561
if langstr. test_harness {
568
- compiler . arg ( "--test" ) ;
562
+ compiler_args . push ( "--test" . to_owned ( ) ) ;
569
563
}
570
564
if rustdoc_options. json_unused_externs . is_enabled ( ) && !langstr. compile_fail {
571
- compiler . arg ( "--error-format=json" ) ;
572
- compiler . arg ( "--json" ) . arg ( "unused-externs" ) ;
573
- compiler . arg ( "-W" ) . arg ( "unused_crate_dependencies" ) ;
574
- compiler . arg ( "-Z" ) . arg ( "unstable-options" ) ;
565
+ compiler_args . push ( "--error-format=json" . to_owned ( ) ) ;
566
+ compiler_args . extend_from_slice ( & [ "--json" . to_owned ( ) , "unused-externs" . to_owned ( ) ] ) ;
567
+ compiler_args . extend_from_slice ( & [ "-W" . to_owned ( ) , "unused_crate_dependencies" . to_owned ( ) ] ) ;
568
+ compiler_args . extend_from_slice ( & [ "-Z" . to_owned ( ) , "unstable-options" . to_owned ( ) ] ) ;
575
569
}
576
570
577
571
if doctest. no_run && !langstr. compile_fail && rustdoc_options. persist_doctests . is_none ( ) {
578
572
// FIXME: why does this code check if it *shouldn't* persist doctests
579
573
// -- shouldn't it be the negation?
580
- compiler . arg ( "--emit=metadata" ) ;
574
+ compiler_args . push ( "--emit=metadata" . to_owned ( ) ) ;
581
575
}
582
- compiler. arg ( "--target" ) . arg ( match & rustdoc_options. target {
583
- TargetTuple :: TargetTuple ( s) => s,
584
- TargetTuple :: TargetJson { path_for_rustdoc, .. } => {
585
- path_for_rustdoc. to_str ( ) . expect ( "target path must be valid unicode" )
586
- }
587
- } ) ;
576
+ compiler_args. extend_from_slice ( & [
577
+ "--target" . to_owned ( ) ,
578
+ match & rustdoc_options. target {
579
+ TargetTuple :: TargetTuple ( s) => s. clone ( ) ,
580
+ TargetTuple :: TargetJson { path_for_rustdoc, .. } => {
581
+ path_for_rustdoc. to_str ( ) . expect ( "target path must be valid unicode" ) . to_owned ( )
582
+ }
583
+ } ,
584
+ ] ) ;
588
585
if let ErrorOutputType :: HumanReadable ( kind, color_config) = rustdoc_options. error_format {
589
586
let short = kind. short ( ) ;
590
587
let unicode = kind == HumanReadableErrorType :: Unicode ;
591
588
592
589
if short {
593
- compiler . arg ( "--error-format" ) . arg ( "short" ) ;
590
+ compiler_args . extend_from_slice ( & [ "--error-format" . to_owned ( ) , "short" . to_owned ( ) ] ) ;
594
591
}
595
592
if unicode {
596
- compiler. arg ( "--error-format" ) . arg ( "human-unicode" ) ;
593
+ compiler_args
594
+ . extend_from_slice ( & [ "--error-format" . to_owned ( ) , "human-unicode" . to_owned ( ) ] ) ;
597
595
}
598
596
599
597
match color_config {
600
598
ColorConfig :: Never => {
601
- compiler . arg ( "--color" ) . arg ( "never" ) ;
599
+ compiler_args . extend_from_slice ( & [ "--color" . to_owned ( ) , "never" . to_owned ( ) ] ) ;
602
600
}
603
601
ColorConfig :: Always => {
604
- compiler . arg ( "--color" ) . arg ( "always" ) ;
602
+ compiler_args . extend_from_slice ( & [ "--color" . to_owned ( ) , "always" . to_owned ( ) ] ) ;
605
603
}
606
604
ColorConfig :: Auto => {
607
- compiler. arg ( "--color" ) . arg ( if supports_color { "always" } else { "never" } ) ;
605
+ compiler_args. extend_from_slice ( & [
606
+ "--color" . to_owned ( ) ,
607
+ if supports_color { "always" } else { "never" } . to_owned ( ) ,
608
+ ] ) ;
608
609
}
609
610
}
610
611
}
611
612
613
+ compiler. args ( & compiler_args) ;
614
+
612
615
// If this is a merged doctest, we need to write it into a file instead of using stdin
613
616
// because if the size of the merged doctests is too big, it'll simply break stdin.
614
- if doctest. is_multiple_tests {
617
+ let output_bundle_file = doctest
618
+ . test_opts
619
+ . outdir
620
+ . path ( )
621
+ . join ( format ! ( "librustdoc_tests_merged_{edition}.rlib" , edition = doctest. edition) ) ;
622
+ if doctest. is_multiple_tests ( ) {
615
623
// It makes the compilation failure much faster if it is for a combined doctest.
616
624
compiler. arg ( "--error-format=short" ) ;
617
- let input_file = doctest. path_for_merged_doctest ( ) ;
625
+ let input_file = doctest. path_for_merged_doctest_bundle ( ) ;
618
626
if std:: fs:: write ( & input_file, & doctest. full_test_code ) . is_err ( ) {
619
627
// If we cannot write this file for any reason, we leave. All combined tests will be
620
628
// tested as standalone tests.
621
629
return Err ( TestFailure :: CompileError ) ;
622
630
}
623
- compiler. arg ( input_file) ;
624
631
if !rustdoc_options. nocapture {
625
632
// If `nocapture` is disabled, then we don't display rustc's output when compiling
626
633
// the merged doctests.
627
634
compiler. stderr ( Stdio :: null ( ) ) ;
628
635
}
636
+ // bundled tests are an rlib, loaded by a separate runner executable
637
+ compiler. arg ( "--crate-type=lib" ) . arg ( "-o" ) . arg ( & output_bundle_file) . arg ( input_file) ;
629
638
} else {
639
+ compiler. arg ( "--crate-type=bin" ) . arg ( "-o" ) . arg ( & output_file) ;
640
+ // Setting these environment variables is unneeded if this is a merged doctest.
641
+ compiler. env ( "UNSTABLE_RUSTDOC_TEST_PATH" , & doctest. test_opts . path ) ;
642
+ compiler. env (
643
+ "UNSTABLE_RUSTDOC_TEST_LINE" ,
644
+ format ! ( "{}" , doctest. line as isize - doctest. full_test_line_offset as isize ) ,
645
+ ) ;
630
646
compiler. arg ( "-" ) ;
631
647
compiler. stdin ( Stdio :: piped ( ) ) ;
632
648
compiler. stderr ( Stdio :: piped ( ) ) ;
@@ -635,8 +651,45 @@ fn run_test(
635
651
debug ! ( "compiler invocation for doctest: {compiler:?}" ) ;
636
652
637
653
let mut child = compiler. spawn ( ) . expect ( "Failed to spawn rustc process" ) ;
638
- let output = if doctest. is_multiple_tests {
654
+ let output = if let Some ( merged_test_code) = & doctest. merged_test_code {
655
+ // compile-fail tests never get merged, so this should always pass
639
656
let status = child. wait ( ) . expect ( "Failed to wait" ) ;
657
+
658
+ // the actual test runner is a separate component, built with nightly-only features;
659
+ // build it now
660
+ let runner_input_file = doctest. path_for_merged_doctest_runner ( ) ;
661
+
662
+ let mut compiler_runner =
663
+ wrapped_rustc_command ( & rustdoc_options. test_builder_wrappers , rustc_binary) ;
664
+ compiler_runner. args ( compiler_args) ;
665
+ compiler_runner. args ( & [ "--crate-type=bin" , "-o" ] ) . arg ( & output_file) ;
666
+ let mut extern_path = std:: ffi:: OsString :: from ( format ! (
667
+ "--extern=rustdoc_tests_merged_{edition}=" ,
668
+ edition = doctest. edition
669
+ ) ) ;
670
+ extern_path. push ( & output_bundle_file) ;
671
+ compiler_runner. arg ( extern_path) ;
672
+ compiler_runner. arg ( & runner_input_file) ;
673
+ if std:: fs:: write ( & runner_input_file, & merged_test_code) . is_err ( ) {
674
+ // If we cannot write this file for any reason, we leave. All combined tests will be
675
+ // tested as standalone tests.
676
+ return Err ( TestFailure :: CompileError ) ;
677
+ }
678
+ if !rustdoc_options. nocapture {
679
+ // If `nocapture` is disabled, then we don't display rustc's output when compiling
680
+ // the merged doctests.
681
+ compiler_runner. stderr ( Stdio :: null ( ) ) ;
682
+ }
683
+ compiler_runner. arg ( "--error-format=short" ) ;
684
+ debug ! ( "compiler invocation for doctest bundle: {compiler_runner:?}" ) ;
685
+
686
+ let status = if !status. success ( ) {
687
+ status
688
+ } else {
689
+ let mut child_runner = compiler_runner. spawn ( ) . expect ( "Failed to spawn rustc process" ) ;
690
+ child_runner. wait ( ) . expect ( "Failed to wait" )
691
+ } ;
692
+
640
693
process:: Output { status, stdout : Vec :: new ( ) , stderr : Vec :: new ( ) }
641
694
} else {
642
695
let stdin = child. stdin . as_mut ( ) . expect ( "Failed to open stdin" ) ;
@@ -713,15 +766,15 @@ fn run_test(
713
766
cmd. arg ( & output_file) ;
714
767
} else {
715
768
cmd = Command :: new ( & output_file) ;
716
- if doctest. is_multiple_tests {
769
+ if doctest. is_multiple_tests ( ) {
717
770
cmd. env ( "RUSTDOC_DOCTEST_BIN_PATH" , & output_file) ;
718
771
}
719
772
}
720
773
if let Some ( run_directory) = & rustdoc_options. test_run_directory {
721
774
cmd. current_dir ( run_directory) ;
722
775
}
723
776
724
- let result = if doctest. is_multiple_tests || rustdoc_options. nocapture {
777
+ let result = if doctest. is_multiple_tests ( ) || rustdoc_options. nocapture {
725
778
cmd. status ( ) . map ( |status| process:: Output {
726
779
status,
727
780
stdout : Vec :: new ( ) ,
@@ -1008,7 +1061,7 @@ fn doctest_run_fn(
1008
1061
line : scraped_test. line ,
1009
1062
edition : scraped_test. edition ( & rustdoc_options) ,
1010
1063
no_run : scraped_test. no_run ( & rustdoc_options) ,
1011
- is_multiple_tests : false ,
1064
+ merged_test_code : None ,
1012
1065
} ;
1013
1066
let res =
1014
1067
run_test ( runnable_test, & rustdoc_options, doctest. supports_color , report_unused_externs) ;
0 commit comments