@@ -75,8 +75,9 @@ impl DocTestRunner {
7575#![allow(internal_features)]
7676#![feature(test)]
7777#![feature(rustc_attrs)]
78- #![feature(coverage_attribute)]\n "
79- . to_string ( ) ;
78+ #![feature(coverage_attribute)]
79+ "
80+ . to_string ( ) ;
8081
8182 for crate_attr in & self . crate_attrs {
8283 code. push_str ( crate_attr) ;
@@ -104,15 +105,67 @@ impl DocTestRunner {
104105 code,
105106 "\
106107 {output}
108+
109+ mod __doctest_mod {{
110+ pub static mut BINARY_PATH: Option<std::path::PathBuf> = None;
111+ pub const RUN_OPTION: &str = \" *doctest-inner-test\" ;
112+ pub const BIN_OPTION: &str = \" *doctest-bin-path\" ;
113+
114+ #[allow(unused)]
115+ pub fn get_doctest_path() -> Option<&'static std::path::Path> {{
116+ unsafe {{ self::BINARY_PATH.as_deref() }}
117+ }}
118+
119+ #[allow(unused)]
120+ pub fn doctest_runner(bin: &std::path::Path, test_nb: usize) -> Result<(), String> {{
121+ let out = std::process::Command::new(bin)
122+ .arg(self::RUN_OPTION)
123+ .arg(test_nb.to_string())
124+ .output()
125+ .expect(\" failed to run command\" );
126+ if !out.status.success() {{
127+ Err(String::from_utf8_lossy(&out.stderr).to_string())
128+ }} else {{
129+ Ok(())
130+ }}
131+ }}
132+ }}
133+
107134#[rustc_main]
108135#[coverage(off)]
109- fn main() {{
136+ fn main() -> std::process::ExitCode {{
110137const TESTS: [test::TestDescAndFn; {nb_tests}] = [{ids}];
111- test::test_main(
112- &[{test_args}],
113- Vec::from(TESTS),
114- None,
115- );
138+ let bin_marker = std::ffi::OsStr::new(__doctest_mod::BIN_OPTION);
139+ let test_marker = std::ffi::OsStr::new(__doctest_mod::RUN_OPTION);
140+
141+ let mut args = std::env::args_os().skip(1);
142+ while let Some(arg) = args.next() {{
143+ if arg == bin_marker {{
144+ let Some(binary) = args.next() else {{
145+ panic!(\" missing argument after `{{}}`\" , __doctest_mod::BIN_OPTION);
146+ }};
147+ unsafe {{ crate::__doctest_mod::BINARY_PATH = Some(binary.into()); }}
148+ return std::process::Termination::report(test::test_main(
149+ &[{test_args}],
150+ Vec::from(TESTS),
151+ None,
152+ ));
153+ }} else if arg == test_marker {{
154+ let Some(nb_test) = args.next() else {{
155+ panic!(\" missing argument after `{{}}`\" , __doctest_mod::RUN_OPTION);
156+ }};
157+ if let Some(nb_test) = nb_test.to_str().and_then(|nb| nb.parse::<usize>().ok()) {{
158+ if let Some(test) = TESTS.get(nb_test) {{
159+ if let test::StaticTestFn(f) = test.testfn {{
160+ return std::process::Termination::report(f());
161+ }}
162+ }}
163+ }}
164+ panic!(\" Unexpected value after `{{}}`\" , __doctest_mod::RUN_OPTION);
165+ }}
166+ }}
167+
168+ panic!(\" missing argument for merged doctest binary\" );
116169}}" ,
117170 nb_tests = self . nb_tests,
118171 output = self . output,
@@ -156,6 +209,10 @@ fn generate_mergeable_doctest(
156209 } else {
157210 writeln ! ( output, "mod {test_id} {{\n {}{}" , doctest. crates, doctest. maybe_crate_attrs)
158211 . unwrap ( ) ;
212+ if scraped_test. langstr . no_run {
213+ // To prevent having warnings about unused items since they're not called.
214+ writeln ! ( output, "#![allow(unused)]" ) . unwrap ( ) ;
215+ }
159216 if doctest. has_main_fn {
160217 output. push_str ( & doctest. everything_else ) ;
161218 } else {
@@ -167,14 +224,15 @@ fn generate_mergeable_doctest(
167224 write ! (
168225 output,
169226 "\
170- fn main() {returns_result} {{
171- {}
172- }}",
227+ fn main() {returns_result} {{
228+ {}
229+ }}" ,
173230 doctest. everything_else
174231 )
175232 . unwrap ( ) ;
176233 }
177234 }
235+ let not_running = ignore || scraped_test. langstr . no_run ;
178236 writeln ! (
179237 output,
180238 "
@@ -196,7 +254,7 @@ pub const TEST: test::TestDescAndFn = test::TestDescAndFn {{
196254 }},
197255 testfn: test::StaticTestFn(
198256 #[coverage(off)]
199- || test::assert_test_result({ runner}) ,
257+ || {{{ runner}}} ,
200258 )
201259}};
202260}}" ,
@@ -211,10 +269,18 @@ pub const TEST: test::TestDescAndFn = test::TestDescAndFn {{
211269 } ,
212270 // Setting `no_run` to `true` in `TestDesc` still makes the test run, so we simply
213271 // don't give it the function to run.
214- runner = if ignore || scraped_test . langstr . no_run {
215- "Ok::<(), String>(())"
272+ runner = if not_running {
273+ "test::assert_test_result( Ok::<(), String>(()))" . to_string ( )
216274 } else {
217- "self::main()"
275+ format!(
276+ "
277+ if let Some(bin_path) = crate::__doctest_mod::get_doctest_path() {{
278+ test::assert_test_result(crate::__doctest_mod::doctest_runner(bin_path, {id}))
279+ }} else {{
280+ test::assert_test_result(self::main())
281+ }}
282+ " ,
283+ )
218284 } ,
219285 )
220286 . unwrap ( ) ;
0 commit comments