@@ -559,6 +559,83 @@ impl RunnableDocTest {
559559 }
560560}
561561
562+ fn compile_merged_doctest_and_caller_binary (
563+ mut child : process:: Child ,
564+ doctest : & RunnableDocTest ,
565+ rustdoc_options : & RustdocOptions ,
566+ rustc_binary : & Path ,
567+ output_file : & Path ,
568+ compiler_args : Vec < String > ,
569+ test_code : & str ,
570+ instant : Instant ,
571+ ) -> Result < process:: Output , ( Duration , Result < ( ) , RustdocResult > ) > {
572+ // compile-fail tests never get merged, so this should always pass
573+ let status = child. wait ( ) . expect ( "Failed to wait" ) ;
574+
575+ // the actual test runner is a separate component, built with nightly-only features;
576+ // build it now
577+ let runner_input_file = doctest. path_for_merged_doctest_runner ( ) ;
578+
579+ let mut runner_compiler =
580+ wrapped_rustc_command ( & rustdoc_options. test_builder_wrappers , rustc_binary) ;
581+ // the test runner does not contain any user-written code, so this doesn't allow
582+ // the user to exploit nightly-only features on stable
583+ runner_compiler. env ( "RUSTC_BOOTSTRAP" , "1" ) ;
584+ runner_compiler. args ( compiler_args) ;
585+ runner_compiler. args ( [ "--crate-type=bin" , "-o" ] ) . arg ( output_file) ;
586+ let mut extern_path = std:: ffi:: OsString :: from ( format ! (
587+ "--extern=doctest_bundle_{edition}=" ,
588+ edition = doctest. edition
589+ ) ) ;
590+
591+ // Deduplicate passed -L directory paths, since usually all dependencies will be in the
592+ // same directory (e.g. target/debug/deps from Cargo).
593+ let mut seen_search_dirs = FxHashSet :: default ( ) ;
594+ for extern_str in & rustdoc_options. extern_strs {
595+ if let Some ( ( _cratename, path) ) = extern_str. split_once ( '=' ) {
596+ // Direct dependencies of the tests themselves are
597+ // indirect dependencies of the test runner.
598+ // They need to be in the library search path.
599+ let dir = Path :: new ( path)
600+ . parent ( )
601+ . filter ( |x| x. components ( ) . count ( ) > 0 )
602+ . unwrap_or ( Path :: new ( "." ) ) ;
603+ if seen_search_dirs. insert ( dir) {
604+ runner_compiler. arg ( "-L" ) . arg ( dir) ;
605+ }
606+ }
607+ }
608+ let output_bundle_file = doctest
609+ . test_opts
610+ . outdir
611+ . path ( )
612+ . join ( format ! ( "libdoctest_bundle_{edition}.rlib" , edition = doctest. edition) ) ;
613+ extern_path. push ( & output_bundle_file) ;
614+ runner_compiler. arg ( extern_path) ;
615+ runner_compiler. arg ( & runner_input_file) ;
616+ if std:: fs:: write ( & runner_input_file, test_code) . is_err ( ) {
617+ // If we cannot write this file for any reason, we leave. All combined tests will be
618+ // tested as standalone tests.
619+ return Err ( ( instant. elapsed ( ) , Err ( RustdocResult :: CompileError ) ) ) ;
620+ }
621+ if !rustdoc_options. no_capture {
622+ // If `no_capture` is disabled, then we don't display rustc's output when compiling
623+ // the merged doctests.
624+ runner_compiler. stderr ( Stdio :: null ( ) ) ;
625+ }
626+ runner_compiler. arg ( "--error-format=short" ) ;
627+ debug ! ( "compiler invocation for doctest runner: {runner_compiler:?}" ) ;
628+
629+ let status = if !status. success ( ) {
630+ status
631+ } else {
632+ let mut child_runner = runner_compiler. spawn ( ) . expect ( "Failed to spawn rustc process" ) ;
633+ child_runner. wait ( ) . expect ( "Failed to wait" )
634+ } ;
635+
636+ Ok ( process:: Output { status, stdout : Vec :: new ( ) , stderr : Vec :: new ( ) } )
637+ }
638+
562639/// Execute a `RunnableDoctest`.
563640///
564641/// This is the function that calculates the compiler command line, invokes the compiler, then
@@ -674,7 +751,6 @@ fn run_test(
674751 . arg ( input_file) ;
675752 } else {
676753 compiler. arg ( "--crate-type=bin" ) . arg ( "-o" ) . arg ( & output_file) ;
677- // Setting these environment variables is unneeded if this is a merged doctest.
678754 compiler. env ( "UNSTABLE_RUSTDOC_TEST_PATH" , & doctest. test_opts . path ) ;
679755 compiler. env (
680756 "UNSTABLE_RUSTDOC_TEST_LINE" ,
@@ -689,71 +765,19 @@ fn run_test(
689765
690766 let mut child = compiler. spawn ( ) . expect ( "Failed to spawn rustc process" ) ;
691767 let output = if let Some ( merged_test_code) = & doctest. merged_test_code {
692- // compile-fail tests never get merged, so this should always pass
693- let status = child. wait ( ) . expect ( "Failed to wait" ) ;
694-
695- // the actual test runner is a separate component, built with nightly-only features;
696- // build it now
697- let runner_input_file = doctest. path_for_merged_doctest_runner ( ) ;
698-
699- let mut runner_compiler =
700- wrapped_rustc_command ( & rustdoc_options. test_builder_wrappers , rustc_binary) ;
701- // the test runner does not contain any user-written code, so this doesn't allow
702- // the user to exploit nightly-only features on stable
703- runner_compiler. env ( "RUSTC_BOOTSTRAP" , "1" ) ;
704- runner_compiler. args ( compiler_args) ;
705- runner_compiler. args ( [ "--crate-type=bin" , "-o" ] ) . arg ( & output_file) ;
706- let mut extern_path = std:: ffi:: OsString :: from ( format ! (
707- "--extern=doctest_bundle_{edition}=" ,
708- edition = doctest. edition
709- ) ) ;
710-
711- // Deduplicate passed -L directory paths, since usually all dependencies will be in the
712- // same directory (e.g. target/debug/deps from Cargo).
713- let mut seen_search_dirs = FxHashSet :: default ( ) ;
714- for extern_str in & rustdoc_options. extern_strs {
715- if let Some ( ( _cratename, path) ) = extern_str. split_once ( '=' ) {
716- // Direct dependencies of the tests themselves are
717- // indirect dependencies of the test runner.
718- // They need to be in the library search path.
719- let dir = Path :: new ( path)
720- . parent ( )
721- . filter ( |x| x. components ( ) . count ( ) > 0 )
722- . unwrap_or ( Path :: new ( "." ) ) ;
723- if seen_search_dirs. insert ( dir) {
724- runner_compiler. arg ( "-L" ) . arg ( dir) ;
725- }
726- }
727- }
728- let output_bundle_file = doctest
729- . test_opts
730- . outdir
731- . path ( )
732- . join ( format ! ( "libdoctest_bundle_{edition}.rlib" , edition = doctest. edition) ) ;
733- extern_path. push ( & output_bundle_file) ;
734- runner_compiler. arg ( extern_path) ;
735- runner_compiler. arg ( & runner_input_file) ;
736- if std:: fs:: write ( & runner_input_file, merged_test_code) . is_err ( ) {
737- // If we cannot write this file for any reason, we leave. All combined tests will be
738- // tested as standalone tests.
739- return ( instant. elapsed ( ) , Err ( RustdocResult :: CompileError ) ) ;
740- }
741- if !rustdoc_options. no_capture {
742- // If `no_capture` is disabled, then we don't display rustc's output when compiling
743- // the merged doctests.
744- runner_compiler. stderr ( Stdio :: null ( ) ) ;
768+ match compile_merged_doctest_and_caller_binary (
769+ child,
770+ & doctest,
771+ rustdoc_options,
772+ rustc_binary,
773+ & output_file,
774+ compiler_args,
775+ merged_test_code,
776+ instant,
777+ ) {
778+ Ok ( out) => out,
779+ Err ( err) => return err,
745780 }
746- runner_compiler. arg ( "--error-format=short" ) ;
747- debug ! ( "compiler invocation for doctest runner: {runner_compiler:?}" ) ;
748-
749- let status = if !status. success ( ) {
750- status
751- } else {
752- let mut child_runner = runner_compiler. spawn ( ) . expect ( "Failed to spawn rustc process" ) ;
753- child_runner. wait ( ) . expect ( "Failed to wait" )
754- } ;
755-
756- process:: Output { status, stdout : Vec :: new ( ) , stderr : Vec :: new ( ) }
757781 } else {
758782 let stdin = child. stdin . as_mut ( ) . expect ( "Failed to open stdin" ) ;
759783 stdin. write_all ( doctest. full_test_code . as_bytes ( ) ) . expect ( "could write out test sources" ) ;
0 commit comments