@@ -75,8 +75,9 @@ impl DocTestRunner {
75
75
#![allow(internal_features)]
76
76
#![feature(test)]
77
77
#![feature(rustc_attrs)]
78
- #![feature(coverage_attribute)]\n "
79
- . to_string ( ) ;
78
+ #![feature(coverage_attribute)]
79
+ "
80
+ . to_string ( ) ;
80
81
81
82
for crate_attr in & self . crate_attrs {
82
83
code. push_str ( crate_attr) ;
@@ -104,15 +105,67 @@ impl DocTestRunner {
104
105
code,
105
106
"\
106
107
{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
+
107
134
#[rustc_main]
108
135
#[coverage(off)]
109
- fn main() {{
136
+ fn main() -> std::process::ExitCode {{
110
137
const 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\" );
116
169
}}" ,
117
170
nb_tests = self . nb_tests,
118
171
output = self . output,
@@ -156,6 +209,10 @@ fn generate_mergeable_doctest(
156
209
} else {
157
210
writeln ! ( output, "mod {test_id} {{\n {}{}" , doctest. crates, doctest. maybe_crate_attrs)
158
211
. 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
+ }
159
216
if doctest. has_main_fn {
160
217
output. push_str ( & doctest. everything_else ) ;
161
218
} else {
@@ -167,14 +224,15 @@ fn generate_mergeable_doctest(
167
224
write ! (
168
225
output,
169
226
"\
170
- fn main() {returns_result} {{
171
- {}
172
- }}",
227
+ fn main() {returns_result} {{
228
+ {}
229
+ }}" ,
173
230
doctest. everything_else
174
231
)
175
232
. unwrap ( ) ;
176
233
}
177
234
}
235
+ let not_running = ignore || scraped_test. langstr . no_run ;
178
236
writeln ! (
179
237
output,
180
238
"
@@ -196,7 +254,7 @@ pub const TEST: test::TestDescAndFn = test::TestDescAndFn {{
196
254
}},
197
255
testfn: test::StaticTestFn(
198
256
#[coverage(off)]
199
- || test::assert_test_result({ runner}) ,
257
+ || {{{ runner}}} ,
200
258
)
201
259
}};
202
260
}}" ,
@@ -211,10 +269,18 @@ pub const TEST: test::TestDescAndFn = test::TestDescAndFn {{
211
269
} ,
212
270
// Setting `no_run` to `true` in `TestDesc` still makes the test run, so we simply
213
271
// 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 ( )
216
274
} 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
+ )
218
284
} ,
219
285
)
220
286
. unwrap ( ) ;
0 commit comments