Skip to content

Commit 341eb6d

Browse files
committed
Give a better error when rustdoc tests fail
- Run the default rustdoc against the current rustdoc - Diff output recursively - Colorize diff output
1 parent a1f7ca7 commit 341eb6d

File tree

3 files changed

+113
-8
lines changed

3 files changed

+113
-8
lines changed

src/tools/compiletest/src/json.rs

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -153,11 +153,14 @@ fn parse_line(file_name: &str, line: &str, output: &str, proc_res: &ProcRes) ->
153153
if serde_json::from_str::<FutureIncompatReport>(line).is_ok() {
154154
vec![]
155155
} else {
156-
proc_res.fatal(Some(&format!(
157-
"failed to decode compiler output as json: \
156+
proc_res.fatal(
157+
Some(&format!(
158+
"failed to decode compiler output as json: \
158159
`{}`\nline: {}\noutput: {}",
159-
error, line, output
160-
)));
160+
error, line, output
161+
)),
162+
|| (),
163+
);
161164
}
162165
}
163166
}

src/tools/compiletest/src/runtest.rs

Lines changed: 68 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,7 @@ pub fn compute_stamp_hash(config: &Config) -> String {
287287
format!("{:x}", hash.finish())
288288
}
289289

290+
#[derive(Copy, Clone)]
290291
struct TestCx<'test> {
291292
config: &'test Config,
292293
props: &'test TestProps,
@@ -2208,7 +2209,12 @@ impl<'test> TestCx<'test> {
22082209

22092210
fn fatal_proc_rec(&self, err: &str, proc_res: &ProcRes) -> ! {
22102211
self.error(err);
2211-
proc_res.fatal(None);
2212+
proc_res.fatal(None, || ());
2213+
}
2214+
2215+
fn fatal_proc_rec_with_ctx(&self, err: &str, proc_res: &ProcRes, ctx: impl FnOnce(Self)) -> ! {
2216+
self.error(err);
2217+
proc_res.fatal(None, || ctx(*self));
22122218
}
22132219

22142220
// codegen tests (using FileCheck)
@@ -2325,15 +2331,72 @@ impl<'test> TestCx<'test> {
23252331
let res = self.cmd2procres(
23262332
Command::new(&self.config.docck_python)
23272333
.arg(root.join("src/etc/htmldocck.py"))
2328-
.arg(out_dir)
2334+
.arg(&out_dir)
23292335
.arg(&self.testpaths.file),
23302336
);
23312337
if !res.status.success() {
2332-
self.fatal_proc_rec("htmldocck failed!", &res);
2338+
self.fatal_proc_rec_with_ctx("htmldocck failed!", &res, |mut this| {
2339+
this.compare_to_default_rustdoc(&out_dir)
2340+
});
23332341
}
23342342
}
23352343
}
23362344

2345+
fn compare_to_default_rustdoc(&mut self, out_dir: &Path) {
2346+
println!("info: generating a diff against nightly rustdoc");
2347+
2348+
let suffix =
2349+
self.safe_revision().map_or("nightly".into(), |path| path.to_owned() + "-nightly");
2350+
let compare_dir = output_base_dir(self.config, self.testpaths, Some(&suffix));
2351+
let _ = fs::remove_dir_all(&compare_dir);
2352+
create_dir_all(&compare_dir).unwrap();
2353+
2354+
// We need to create a new struct for the lifetimes on `config` to work.
2355+
let new_rustdoc = TestCx {
2356+
config: &Config {
2357+
// FIXME: use beta or a user-specified rustdoc instead of hardcoding
2358+
// the default toolchain
2359+
rustdoc_path: Some("rustdoc".into()),
2360+
..self.config.clone()
2361+
},
2362+
..*self
2363+
};
2364+
let proc_res = new_rustdoc.document(&compare_dir);
2365+
if !proc_res.status.success() {
2366+
proc_res.fatal(Some("failed to run nightly rustdoc"), || ());
2367+
}
2368+
2369+
// NOTE: this is fine since compiletest never runs out-of-tree
2370+
let tidy = concat!(env!("CARGO_MANIFEST_DIR"), "/tidy-rustdoc.sh");
2371+
// FIXME: this overwrites `out_dir` in place, maybe we should make a copy?
2372+
let status = Command::new(tidy)
2373+
.arg(out_dir)
2374+
.spawn()
2375+
.expect("tidy-rustdoc not found")
2376+
.wait()
2377+
.unwrap();
2378+
if !status.success() {
2379+
self.fatal("failed to run tidy - is installed?");
2380+
}
2381+
let status = Command::new(tidy).arg(&compare_dir).spawn().unwrap().wait().unwrap();
2382+
if !status.success() {
2383+
self.fatal("failed to run tidy");
2384+
}
2385+
2386+
let diff_pid = Command::new("diff")
2387+
.args(&["-u", "-r"])
2388+
.args(&[out_dir, &compare_dir])
2389+
.stdout(Stdio::piped())
2390+
.spawn()
2391+
.expect("failed to run `diff`");
2392+
Command::new("delta")
2393+
.stdin(diff_pid.stdout.unwrap())
2394+
.spawn()
2395+
.expect("delta not found")
2396+
.wait()
2397+
.unwrap();
2398+
}
2399+
23372400
fn get_lines<P: AsRef<Path>>(
23382401
&self,
23392402
path: &P,
@@ -3590,7 +3653,7 @@ pub struct ProcRes {
35903653
}
35913654

35923655
impl ProcRes {
3593-
pub fn fatal(&self, err: Option<&str>) -> ! {
3656+
pub fn fatal(&self, err: Option<&str>, ctx: impl FnOnce()) -> ! {
35943657
if let Some(e) = err {
35953658
println!("\nerror: {}", e);
35963659
}
@@ -3612,6 +3675,7 @@ impl ProcRes {
36123675
json::extract_rendered(&self.stdout),
36133676
json::extract_rendered(&self.stderr),
36143677
);
3678+
ctx();
36153679
// Use resume_unwind instead of panic!() to prevent a panic message + backtrace from
36163680
// compiletest, which is unnecessary noise.
36173681
std::panic::resume_unwind(Box::new(()));

src/tools/compiletest/tidy-rustdoc.sh

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
#!/usr/bin/env bash
2+
3+
set -euo pipefail
4+
5+
indir="${1:?Missing argument 1: input directory}"
6+
7+
tidy () {
8+
{
9+
# new-inline-tags is workaround for:
10+
# https://github.com/rust-lang/stdarch/issues/945
11+
# https://github.com/rust-lang/mdBook/issues/1372
12+
command tidy \
13+
--indent yes \
14+
--indent-spaces 2 \
15+
--wrap 0 \
16+
--show-warnings no \
17+
--markup yes \
18+
--quiet yes \
19+
--new-inline-tags 'c t' \
20+
"$@" \
21+
>/dev/null \
22+
|| {
23+
# tidy exits with code 1 if there were any warnings :facepalm:
24+
status=$?
25+
if [ $status != 0 ] && [ $status != 1 ]
26+
then
27+
echo "While tidying $1" >&2
28+
exit 1
29+
fi
30+
}
31+
} | sed -E 's/#[0-9]+(-[0-9]+)?/#line/g'
32+
}
33+
34+
find "$indir" -type f -name '*.html' -print0 \
35+
| while IFS= read -d '' -r file
36+
do
37+
tidy -modify "$file"
38+
done

0 commit comments

Comments
 (0)