Skip to content

Commit d43a507

Browse files
committed
Auto merge of #10287 - Nilstrieb:docs-semver-check-run-fail, r=ehuss
Add `run-fail` to semver-check for docs I encountered this missing feature in #10276 and therefore added it here in this separate PR. If the breaking change does not involve a compilation error but a change in runtime behaviour, you can add `run-fail` to the codeblock. The "before" code must return exit code 0, and the "after" code must be nonzero (like a panic). Example case that I tested (ignore the trailing dot, it's for github markdown to not hate me) ``` ```rust,ignore,run-fail // MAJOR CHANGE /////////////////////////////////////////////////////////// // Before pub fn foo() {} /////////////////////////////////////////////////////////// // After pub fn foo() { panic!("hey!"); } /////////////////////////////////////////////////////////// // Example usage that will break. fn main() { updated_crate::foo(); } ```. ```
2 parents c6745a3 + 6865218 commit d43a507

File tree

1 file changed

+98
-33
lines changed

1 file changed

+98
-33
lines changed

src/doc/semver-check/src/main.rs

Lines changed: 98 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
use std::error::Error;
1212
use std::fs;
1313
use std::path::Path;
14-
use std::process::Command;
14+
use std::process::{Command, Output};
1515

1616
fn main() {
1717
if let Err(e) = doit() {
@@ -24,19 +24,18 @@ const SEPARATOR: &str = "///////////////////////////////////////////////////////
2424

2525
fn doit() -> Result<(), Box<dyn Error>> {
2626
let filename = std::env::args()
27-
.skip(1)
28-
.next()
27+
.nth(1)
2928
.unwrap_or_else(|| "../src/reference/semver.md".to_string());
3029
let contents = fs::read_to_string(filename)?;
3130
let mut lines = contents.lines().enumerate();
3231

3332
loop {
3433
// Find a rust block.
35-
let block_start = loop {
34+
let (block_start, run_program) = loop {
3635
match lines.next() {
3736
Some((lineno, line)) => {
3837
if line.trim().starts_with("```rust") && !line.contains("skip") {
39-
break lineno + 1;
38+
break (lineno + 1, line.contains("run-fail"));
4039
}
4140
}
4241
None => return Ok(()),
@@ -83,12 +82,16 @@ fn doit() -> Result<(), Box<dyn Error>> {
8382
};
8483
let expect_success = parts[0][0].contains("MINOR");
8584
println!("Running test from line {}", block_start);
86-
if let Err(e) = run_test(
85+
86+
let result = run_test(
8787
join(parts[1]),
8888
join(parts[2]),
8989
join(parts[3]),
9090
expect_success,
91-
) {
91+
run_program,
92+
);
93+
94+
if let Err(e) = result {
9295
return Err(format!(
9396
"test failed for example starting on line {}: {}",
9497
block_start, e
@@ -105,15 +108,23 @@ fn run_test(
105108
after: String,
106109
example: String,
107110
expect_success: bool,
111+
run_program: bool,
108112
) -> Result<(), Box<dyn Error>> {
109113
let tempdir = tempfile::TempDir::new()?;
110114
let before_p = tempdir.path().join("before.rs");
111115
let after_p = tempdir.path().join("after.rs");
112116
let example_p = tempdir.path().join("example.rs");
113-
compile(before, &before_p, CRATE_NAME, false, true)?;
114-
compile(example.clone(), &example_p, "example", true, true)?;
115-
compile(after, &after_p, CRATE_NAME, false, true)?;
116-
compile(example, &example_p, "example", true, expect_success)?;
117+
118+
let check_fn = if run_program {
119+
run_check
120+
} else {
121+
compile_check
122+
};
123+
124+
compile_check(before, &before_p, CRATE_NAME, false, true)?;
125+
check_fn(example.clone(), &example_p, "example", true, true)?;
126+
compile_check(after, &after_p, CRATE_NAME, false, true)?;
127+
check_fn(example, &example_p, "example", true, expect_success)?;
117128
Ok(())
118129
}
119130

@@ -127,34 +138,18 @@ fn check_formatting(path: &Path) -> Result<(), Box<dyn Error>> {
127138
if !status.success() {
128139
return Err(format!("failed to run rustfmt: {}", status).into());
129140
}
130-
return Ok(());
131-
}
132-
Err(e) => {
133-
return Err(format!("failed to run rustfmt: {}", e).into());
141+
Ok(())
134142
}
143+
Err(e) => Err(format!("failed to run rustfmt: {}", e).into()),
135144
}
136145
}
137146

138147
fn compile(
139-
mut contents: String,
148+
contents: &str,
140149
path: &Path,
141150
crate_name: &str,
142151
extern_path: bool,
143-
expect_success: bool,
144-
) -> Result<(), Box<dyn Error>> {
145-
// If the example has an error message, remove it so that it can be
146-
// compared with the actual output, and also to avoid issues with rustfmt
147-
// moving it around.
148-
let expected_error = match contents.find("// Error:") {
149-
Some(index) => {
150-
let start = contents[..index].rfind(|ch| ch != ' ').unwrap();
151-
let end = contents[index..].find('\n').unwrap();
152-
let error = contents[index + 9..index + end].trim().to_string();
153-
contents.replace_range(start + 1..index + end, "");
154-
Some(error)
155-
}
156-
None => None,
157-
};
152+
) -> Result<Output, Box<dyn Error>> {
158153
let crate_type = if contents.contains("fn main()") {
159154
"bin"
160155
} else {
@@ -166,7 +161,7 @@ fn compile(
166161
let out_dir = path.parent().unwrap();
167162
let mut cmd = Command::new("rustc");
168163
cmd.args(&[
169-
"--edition=2018",
164+
"--edition=2021",
170165
"--crate-type",
171166
crate_type,
172167
"--crate-name",
@@ -180,7 +175,32 @@ fn compile(
180175
.arg(format!("{}={}", CRATE_NAME, epath.display()));
181176
}
182177
cmd.arg(path);
183-
let output = cmd.output()?;
178+
cmd.output().map_err(Into::into)
179+
}
180+
181+
fn compile_check(
182+
mut contents: String,
183+
path: &Path,
184+
crate_name: &str,
185+
extern_path: bool,
186+
expect_success: bool,
187+
) -> Result<(), Box<dyn Error>> {
188+
// If the example has an error message, remove it so that it can be
189+
// compared with the actual output, and also to avoid issues with rustfmt
190+
// moving it around.
191+
let expected_error = match contents.find("// Error:") {
192+
Some(index) => {
193+
let start = contents[..index].rfind(|ch| ch != ' ').unwrap();
194+
let end = contents[index..].find('\n').unwrap();
195+
let error = contents[index + 9..index + end].trim().to_string();
196+
contents.replace_range(start + 1..index + end, "");
197+
Some(error)
198+
}
199+
None => None,
200+
};
201+
202+
let output = compile(&contents, path, crate_name, extern_path)?;
203+
184204
let stderr = std::str::from_utf8(&output.stderr).unwrap();
185205
match (output.status.success(), expect_success) {
186206
(true, true) => Ok(()),
@@ -215,3 +235,48 @@ fn compile(
215235
}
216236
}
217237
}
238+
239+
fn run_check(
240+
contents: String,
241+
path: &Path,
242+
crate_name: &str,
243+
extern_path: bool,
244+
expect_success: bool,
245+
) -> Result<(), Box<dyn Error>> {
246+
let compile_output = compile(&contents, path, crate_name, extern_path)?;
247+
248+
if !compile_output.status.success() {
249+
let stderr = std::str::from_utf8(&compile_output.stderr).unwrap();
250+
return Err(format!(
251+
"expected success, got error {}\n===== Contents:\n{}\n===== Output:\n{}\n",
252+
path.display(),
253+
contents,
254+
stderr
255+
)
256+
.into());
257+
}
258+
259+
let binary_path = path.parent().unwrap().join(crate_name);
260+
261+
let output = Command::new(binary_path).output()?;
262+
263+
let stderr = std::str::from_utf8(&output.stderr).unwrap();
264+
265+
match (output.status.success(), expect_success) {
266+
(true, false) => Err(format!(
267+
"expected panic, got success {}\n===== Contents:\n{}\n===== Output:\n{}\n",
268+
path.display(),
269+
contents,
270+
stderr
271+
)
272+
.into()),
273+
(false, true) => Err(format!(
274+
"expected success, got panic {}\n===== Contents:\n{}\n===== Output:\n{}\n",
275+
path.display(),
276+
contents,
277+
stderr,
278+
)
279+
.into()),
280+
(_, _) => Ok(()),
281+
}
282+
}

0 commit comments

Comments
 (0)