Skip to content

Commit 87b0c90

Browse files
committed
Auto merge of #60387 - Goirad:test-expansion, r=ollie27
Allow cross-compiling doctests This PR allows doctest to receive a --runtool argument, as well as possibly many --runtool-arg arguments, which are then used to run cross compiled doctests. Also, functionality has been added to rustdoc to allow it to skip testing doctests on a per-target basis, in the same way that compiletest does it. For example, tagging the doctest with "ignore-sgx" disables testing on any targets that contain "sgx". A plain "ignore" still skips testing on all targets. See [here](rust-lang/cargo#6892) for the companion PR in the cargo project that extends functionality in Cargo so that it passes the appropriate parameters to rustdoc when cross compiling and testing doctests. Part of [#6460](rust-lang/cargo#6460)
2 parents 403c0de + 4a2094c commit 87b0c90

File tree

9 files changed

+190
-47
lines changed

9 files changed

+190
-47
lines changed

Diff for: src/doc/rustdoc/src/unstable-features.md

+50
Original file line numberDiff line numberDiff line change
@@ -471,3 +471,53 @@ Some methodology notes about what rustdoc counts in this metric:
471471

472472
Public items that are not documented can be seen with the built-in `missing_docs` lint. Private
473473
items that are not documented can be seen with Clippy's `missing_docs_in_private_items` lint.
474+
475+
### `--enable-per-target-ignores`: allow `ignore-foo` style filters for doctests
476+
477+
Using this flag looks like this:
478+
479+
```bash
480+
$ rustdoc src/lib.rs -Z unstable-options --enable-per-target-ignores
481+
```
482+
483+
This flag allows you to tag doctests with compiltest style `ignore-foo` filters that prevent
484+
rustdoc from running that test if the target triple string contains foo. For example:
485+
486+
```rust
487+
///```ignore-foo,ignore-bar
488+
///assert!(2 == 2);
489+
///```
490+
struct Foo;
491+
```
492+
493+
This will not be run when the build target is `super-awesome-foo` or `less-bar-awesome`.
494+
If the flag is not enabled, then rustdoc will consume the filter, but do nothing with it, and
495+
the above example will be run for all targets.
496+
If you want to preserve backwards compatibility for older versions of rustdoc, you can use
497+
498+
```rust
499+
///```ignore,ignore-foo
500+
///assert!(2 == 2);
501+
///```
502+
struct Foo;
503+
```
504+
505+
In older versions, this will be ignored on all targets, but on newer versions `ignore-gnu` will
506+
override `ignore`.
507+
508+
### `--runtool`, `--runtool-arg`: program to run tests with; args to pass to it
509+
510+
Using thses options looks like this:
511+
512+
```bash
513+
$ rustdoc src/lib.rs -Z unstable-options --runtool runner --runtool-arg --do-thing --runtool-arg --do-other-thing
514+
```
515+
516+
These options can be used to run the doctest under a program, and also pass arguments to
517+
that program. For example, if you want to run your doctests under valgrind you might run
518+
519+
```bash
520+
$ rustdoc src/lib.rs -Z unstable-options --runtool valgrind
521+
```
522+
523+
Another use case would be to run a test inside an emulator, or through a Virtual Machine.

Diff for: src/librustdoc/config.rs

+22-3
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use rustc::session;
99
use rustc::session::config::{CrateType, parse_crate_types_from_list};
1010
use rustc::session::config::{CodegenOptions, DebuggingOptions, ErrorOutputType, Externs};
1111
use rustc::session::config::{nightly_options, build_codegen_options, build_debugging_options,
12-
get_cmd_lint_options, ExternEntry};
12+
get_cmd_lint_options, host_triple, ExternEntry};
1313
use rustc::session::search_paths::SearchPath;
1414
use rustc_driver;
1515
use rustc_target::spec::TargetTriple;
@@ -54,7 +54,7 @@ pub struct Options {
5454
/// Debugging (`-Z`) options to pass to the compiler.
5555
pub debugging_options: DebuggingOptions,
5656
/// The target used to compile the crate against.
57-
pub target: Option<TargetTriple>,
57+
pub target: TargetTriple,
5858
/// Edition used when reading the crate. Defaults to "2015". Also used by default when
5959
/// compiling doctests from the crate.
6060
pub edition: Edition,
@@ -77,6 +77,14 @@ pub struct Options {
7777
/// Optional path to persist the doctest executables to, defaults to a
7878
/// temporary directory if not set.
7979
pub persist_doctests: Option<PathBuf>,
80+
/// Runtool to run doctests with
81+
pub runtool: Option<String>,
82+
/// Arguments to pass to the runtool
83+
pub runtool_args: Vec<String>,
84+
/// Whether to allow ignoring doctests on a per-target basis
85+
/// For example, using ignore-foo to ignore running the doctest on any target that
86+
/// contains "foo" as a substring
87+
pub enable_per_target_ignores: bool,
8088

8189
// Options that affect the documentation process
8290

@@ -140,6 +148,9 @@ impl fmt::Debug for Options {
140148
.field("show_coverage", &self.show_coverage)
141149
.field("crate_version", &self.crate_version)
142150
.field("render_options", &self.render_options)
151+
.field("runtool", &self.runtool)
152+
.field("runtool_args", &self.runtool_args)
153+
.field("enable-per-target-ignores", &self.enable_per_target_ignores)
143154
.finish()
144155
}
145156
}
@@ -414,7 +425,9 @@ impl Options {
414425
}
415426
}
416427

417-
let target = matches.opt_str("target").map(|target| {
428+
let target = matches.opt_str("target").map_or(
429+
TargetTriple::from_triple(host_triple()),
430+
|target| {
418431
if target.ends_with(".json") {
419432
TargetTriple::TargetPath(PathBuf::from(target))
420433
} else {
@@ -466,6 +479,9 @@ impl Options {
466479
let codegen_options_strs = matches.opt_strs("C");
467480
let lib_strs = matches.opt_strs("L");
468481
let extern_strs = matches.opt_strs("extern");
482+
let runtool = matches.opt_str("runtool");
483+
let runtool_args = matches.opt_strs("runtool-arg");
484+
let enable_per_target_ignores = matches.opt_present("enable-per-target-ignores");
469485

470486
let (lint_opts, describe_lints, lint_cap) = get_cmd_lint_options(matches, error_format);
471487

@@ -496,6 +512,9 @@ impl Options {
496512
show_coverage,
497513
crate_version,
498514
persist_doctests,
515+
runtool,
516+
runtool_args,
517+
enable_per_target_ignores,
499518
render_options: RenderOptions {
500519
output,
501520
external_html,

Diff for: src/librustdoc/core.rs

+1-3
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ use rustc_interface::interface;
1313
use rustc_driver::abort_on_err;
1414
use rustc_resolve as resolve;
1515
use rustc_metadata::cstore::CStore;
16-
use rustc_target::spec::TargetTriple;
1716

1817
use syntax::source_map;
1918
use syntax::attr;
@@ -294,7 +293,6 @@ pub fn run_core(options: RustdocOptions) -> (clean::Crate, RenderInfo, RenderOpt
294293
}
295294
}).collect();
296295

297-
let host_triple = TargetTriple::from_triple(config::host_triple());
298296
let crate_types = if proc_macro_crate {
299297
vec![config::CrateType::ProcMacro]
300298
} else {
@@ -313,7 +311,7 @@ pub fn run_core(options: RustdocOptions) -> (clean::Crate, RenderInfo, RenderOpt
313311
lint_cap: Some(lint_cap.unwrap_or_else(|| lint::Forbid)),
314312
cg: codegen_options,
315313
externs,
316-
target_triple: target.unwrap_or(host_triple),
314+
target_triple: target,
317315
// Ensure that rustdoc works even if rustc is feature-staged
318316
unstable_features: UnstableFeatures::Allow,
319317
actually_rustdoc: true,

Diff for: src/librustdoc/html/markdown.rs

+32-11
Original file line numberDiff line numberDiff line change
@@ -199,7 +199,7 @@ impl<'a, I: Iterator<Item = Event<'a>>> Iterator for CodeBlocks<'_, 'a, I> {
199199
let ignore;
200200
let edition;
201201
if let Some(Event::Start(Tag::CodeBlock(lang))) = event {
202-
let parse_result = LangString::parse(&lang, self.check_error_codes);
202+
let parse_result = LangString::parse(&lang, self.check_error_codes, false);
203203
if !parse_result.rust {
204204
return Some(Event::Start(Tag::CodeBlock(lang)));
205205
}
@@ -272,7 +272,7 @@ impl<'a, I: Iterator<Item = Event<'a>>> Iterator for CodeBlocks<'_, 'a, I> {
272272
))
273273
});
274274

275-
let tooltip = if ignore {
275+
let tooltip = if ignore != Ignore::None {
276276
Some(("This example is not tested".to_owned(), "ignore"))
277277
} else if compile_fail {
278278
Some(("This example deliberately fails to compile".to_owned(), "compile_fail"))
@@ -286,7 +286,7 @@ impl<'a, I: Iterator<Item = Event<'a>>> Iterator for CodeBlocks<'_, 'a, I> {
286286
s.push_str(&highlight::render_with_highlighting(
287287
&text,
288288
Some(&format!("rust-example-rendered{}",
289-
if ignore { " ignore" }
289+
if ignore != Ignore::None { " ignore" }
290290
else if compile_fail { " compile_fail" }
291291
else if explicit_edition { " edition " }
292292
else { "" })),
@@ -297,7 +297,7 @@ impl<'a, I: Iterator<Item = Event<'a>>> Iterator for CodeBlocks<'_, 'a, I> {
297297
s.push_str(&highlight::render_with_highlighting(
298298
&text,
299299
Some(&format!("rust-example-rendered{}",
300-
if ignore { " ignore" }
300+
if ignore != Ignore::None { " ignore" }
301301
else if compile_fail { " compile_fail" }
302302
else if explicit_edition { " edition " }
303303
else { "" })),
@@ -551,7 +551,8 @@ impl<'a, I: Iterator<Item = Event<'a>>> Iterator for Footnotes<'a, I> {
551551
}
552552
}
553553

554-
pub fn find_testable_code<T: test::Tester>(doc: &str, tests: &mut T, error_codes: ErrorCodes) {
554+
pub fn find_testable_code<T: test::Tester>(doc: &str, tests: &mut T, error_codes: ErrorCodes,
555+
enable_per_target_ignores: bool) {
555556
let mut parser = Parser::new(doc);
556557
let mut prev_offset = 0;
557558
let mut nb_lines = 0;
@@ -564,7 +565,7 @@ pub fn find_testable_code<T: test::Tester>(doc: &str, tests: &mut T, error_codes
564565
let block_info = if s.is_empty() {
565566
LangString::all_false()
566567
} else {
567-
LangString::parse(&*s, error_codes)
568+
LangString::parse(&*s, error_codes, enable_per_target_ignores)
568569
};
569570
if !block_info.rust {
570571
continue;
@@ -607,7 +608,7 @@ pub struct LangString {
607608
original: String,
608609
pub should_panic: bool,
609610
pub no_run: bool,
610-
pub ignore: bool,
611+
pub ignore: Ignore,
611612
pub rust: bool,
612613
pub test_harness: bool,
613614
pub compile_fail: bool,
@@ -616,13 +617,20 @@ pub struct LangString {
616617
pub edition: Option<Edition>
617618
}
618619

620+
#[derive(Eq, PartialEq, Clone, Debug)]
621+
pub enum Ignore {
622+
All,
623+
None,
624+
Some(Vec<String>),
625+
}
626+
619627
impl LangString {
620628
fn all_false() -> LangString {
621629
LangString {
622630
original: String::new(),
623631
should_panic: false,
624632
no_run: false,
625-
ignore: false,
633+
ignore: Ignore::None,
626634
rust: true, // NB This used to be `notrust = false`
627635
test_harness: false,
628636
compile_fail: false,
@@ -632,11 +640,16 @@ impl LangString {
632640
}
633641
}
634642

635-
fn parse(string: &str, allow_error_code_check: ErrorCodes) -> LangString {
643+
fn parse(
644+
string: &str,
645+
allow_error_code_check: ErrorCodes,
646+
enable_per_target_ignores: bool
647+
) -> LangString {
636648
let allow_error_code_check = allow_error_code_check.as_bool();
637649
let mut seen_rust_tags = false;
638650
let mut seen_other_tags = false;
639651
let mut data = LangString::all_false();
652+
let mut ignores = vec![];
640653

641654
data.original = string.to_owned();
642655
let tokens = string.split(|c: char|
@@ -651,7 +664,11 @@ impl LangString {
651664
seen_rust_tags = seen_other_tags == false;
652665
}
653666
"no_run" => { data.no_run = true; seen_rust_tags = !seen_other_tags; }
654-
"ignore" => { data.ignore = true; seen_rust_tags = !seen_other_tags; }
667+
"ignore" => { data.ignore = Ignore::All; seen_rust_tags = !seen_other_tags; }
668+
x if x.starts_with("ignore-") => if enable_per_target_ignores {
669+
ignores.push(x.trim_start_matches("ignore-").to_owned());
670+
seen_rust_tags = !seen_other_tags;
671+
}
655672
"allow_fail" => { data.allow_fail = true; seen_rust_tags = !seen_other_tags; }
656673
"rust" => { data.rust = true; seen_rust_tags = true; }
657674
"test_harness" => {
@@ -679,6 +696,10 @@ impl LangString {
679696
_ => { seen_other_tags = true }
680697
}
681698
}
699+
// ignore-foo overrides ignore
700+
if !ignores.is_empty() {
701+
data.ignore = Ignore::Some(ignores);
702+
}
682703

683704
data.rust &= !seen_other_tags || seen_rust_tags;
684705

@@ -919,7 +940,7 @@ crate fn rust_code_blocks(md: &str) -> Vec<RustCodeBlock> {
919940
let lang_string = if syntax.is_empty() {
920941
LangString::all_false()
921942
} else {
922-
LangString::parse(&*syntax, ErrorCodes::Yes)
943+
LangString::parse(&*syntax, ErrorCodes::Yes, false)
923944
};
924945

925946
if lang_string.rust {

Diff for: src/librustdoc/html/markdown/tests.rs

+23-21
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use super::{ErrorCodes, LangString, Markdown, MarkdownHtml, IdMap};
1+
use super::{ErrorCodes, LangString, Markdown, MarkdownHtml, IdMap, Ignore};
22
use super::plain_summary_line;
33
use std::cell::RefCell;
44
use syntax::edition::{Edition, DEFAULT_EDITION};
@@ -26,10 +26,10 @@ fn test_unique_id() {
2626
#[test]
2727
fn test_lang_string_parse() {
2828
fn t(s: &str,
29-
should_panic: bool, no_run: bool, ignore: bool, rust: bool, test_harness: bool,
29+
should_panic: bool, no_run: bool, ignore: Ignore, rust: bool, test_harness: bool,
3030
compile_fail: bool, allow_fail: bool, error_codes: Vec<String>,
31-
edition: Option<Edition>) {
32-
assert_eq!(LangString::parse(s, ErrorCodes::Yes), LangString {
31+
edition: Option<Edition>) {
32+
assert_eq!(LangString::parse(s, ErrorCodes::Yes, true), LangString {
3333
should_panic,
3434
no_run,
3535
ignore,
@@ -42,6 +42,7 @@ fn test_lang_string_parse() {
4242
edition,
4343
})
4444
}
45+
let ignore_foo = Ignore::Some(vec!("foo".to_string()));
4546

4647
fn v() -> Vec<String> {
4748
Vec::new()
@@ -50,23 +51,24 @@ fn test_lang_string_parse() {
5051
// ignore-tidy-linelength
5152
// marker | should_panic | no_run | ignore | rust | test_harness
5253
// | compile_fail | allow_fail | error_codes | edition
53-
t("", false, false, false, true, false, false, false, v(), None);
54-
t("rust", false, false, false, true, false, false, false, v(), None);
55-
t("sh", false, false, false, false, false, false, false, v(), None);
56-
t("ignore", false, false, true, true, false, false, false, v(), None);
57-
t("should_panic", true, false, false, true, false, false, false, v(), None);
58-
t("no_run", false, true, false, true, false, false, false, v(), None);
59-
t("test_harness", false, false, false, true, true, false, false, v(), None);
60-
t("compile_fail", false, true, false, true, false, true, false, v(), None);
61-
t("allow_fail", false, false, false, true, false, false, true, v(), None);
62-
t("{.no_run .example}", false, true, false, true, false, false, false, v(), None);
63-
t("{.sh .should_panic}", true, false, false, false, false, false, false, v(), None);
64-
t("{.example .rust}", false, false, false, true, false, false, false, v(), None);
65-
t("{.test_harness .rust}", false, false, false, true, true, false, false, v(), None);
66-
t("text, no_run", false, true, false, false, false, false, false, v(), None);
67-
t("text,no_run", false, true, false, false, false, false, false, v(), None);
68-
t("edition2015", false, false, false, true, false, false, false, v(), Some(Edition::Edition2015));
69-
t("edition2018", false, false, false, true, false, false, false, v(), Some(Edition::Edition2018));
54+
t("", false, false, Ignore::None, true, false, false, false, v(), None);
55+
t("rust", false, false, Ignore::None, true, false, false, false, v(), None);
56+
t("sh", false, false, Ignore::None, false, false, false, false, v(), None);
57+
t("ignore", false, false, Ignore::All, true, false, false, false, v(), None);
58+
t("ignore-foo", false, false, ignore_foo, true, false, false, false, v(), None);
59+
t("should_panic", true, false, Ignore::None, true, false, false, false, v(), None);
60+
t("no_run", false, true, Ignore::None, true, false, false, false, v(), None);
61+
t("test_harness", false, false, Ignore::None, true, true, false, false, v(), None);
62+
t("compile_fail", false, true, Ignore::None, true, false, true, false, v(), None);
63+
t("allow_fail", false, false, Ignore::None, true, false, false, true, v(), None);
64+
t("{.no_run .example}", false, true, Ignore::None, true, false, false, false, v(), None);
65+
t("{.sh .should_panic}", true, false, Ignore::None, false, false, false, false, v(), None);
66+
t("{.example .rust}", false, false, Ignore::None, true, false, false, false, v(), None);
67+
t("{.test_harness .rust}", false, false, Ignore::None, true, true, false, false, v(), None);
68+
t("text, no_run", false, true, Ignore::None, false, false, false, false, v(), None);
69+
t("text,no_run", false, true, Ignore::None, false, false, false, false, v(), None);
70+
t("edition2015", false, false, Ignore::None, true, false, false, false, v(), Some(Edition::Edition2015));
71+
t("edition2018", false, false, Ignore::None, true, false, false, false, v(), Some(Edition::Edition2018));
7072
}
7173

7274
#[test]

Diff for: src/librustdoc/lib.rs

+17
Original file line numberDiff line numberDiff line change
@@ -356,6 +356,23 @@ fn opts() -> Vec<RustcOptGroup> {
356356
"show-coverage",
357357
"calculate percentage of public items with documentation")
358358
}),
359+
unstable("enable-per-target-ignores", |o| {
360+
o.optflag("",
361+
"enable-per-target-ignores",
362+
"parse ignore-foo for ignoring doctests on a per-target basis")
363+
}),
364+
unstable("runtool", |o| {
365+
o.optopt("",
366+
"runtool",
367+
"",
368+
"The tool to run tests with when building for a different target than host")
369+
}),
370+
unstable("runtool-arg", |o| {
371+
o.optmulti("",
372+
"runtool-arg",
373+
"",
374+
"One (of possibly many) arguments to pass to the runtool")
375+
}),
359376
]
360377
}
361378

Diff for: src/librustdoc/markdown.rs

+3-2
Original file line numberDiff line numberDiff line change
@@ -143,11 +143,12 @@ pub fn test(mut options: Options, diag: &errors::Handler) -> i32 {
143143
opts.no_crate_inject = true;
144144
opts.display_warnings = options.display_warnings;
145145
let mut collector = Collector::new(options.input.display().to_string(), options.clone(),
146-
true, opts, None, Some(options.input));
146+
true, opts, None, Some(options.input),
147+
options.enable_per_target_ignores);
147148
collector.set_position(DUMMY_SP);
148149
let codes = ErrorCodes::from(UnstableFeatures::from_environment().is_nightly_build());
149150

150-
find_testable_code(&input_str, &mut collector, codes);
151+
find_testable_code(&input_str, &mut collector, codes, options.enable_per_target_ignores);
151152

152153
options.test_args.insert(0, "rustdoctest".to_string());
153154
testing::test_main(&options.test_args, collector.tests,

0 commit comments

Comments
 (0)