Skip to content

Commit

Permalink
Add chapters on dependency resolution and SemVer compatibility.
Browse files Browse the repository at this point in the history
  • Loading branch information
ehuss committed Aug 10, 2020
1 parent 449743b commit fd6a84a
Show file tree
Hide file tree
Showing 9 changed files with 1,982 additions and 0 deletions.
1 change: 1 addition & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ jobs:
- run: rustup update nightly && rustup default nightly
- run: rustup component add rust-docs
- run: ci/validate-man.sh
- run: cd src/doc/semver-check && cargo run
- run: |
mkdir mdbook
curl -Lf https://github.com/rust-lang/mdBook/releases/download/v0.3.7/mdbook-v0.3.7-x86_64-unknown-linux-gnu.tar.gz | tar -xz --directory=./mdbook
Expand Down
6 changes: 6 additions & 0 deletions src/doc/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,12 @@ To rebuild the man pages, run the script `build-man.sh` in the `src/doc` directo
$ ./build-man.sh
```

### SemVer chapter tests

There is a script to verify that the examples in the SemVer chapter work as
intended. To run the tests, go into the `semver-check` directory and run
`cargo run`.

## Contributing

We'd love your help with improving the documentation! Please feel free to
Expand Down
10 changes: 10 additions & 0 deletions src/doc/semver-check/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
[package]
name = "semver-check"
version = "0.1.0"
authors = ["Eric Huss"]
edition = "2018"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
tempfile = "3.1.0"
217 changes: 217 additions & 0 deletions src/doc/semver-check/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,217 @@
//! Test runner for the semver compatibility doc chapter.
//!
//! This extracts all the "rust" annotated code blocks and tests that they
//! either fail or succeed as expected. This also checks that the examples are
//! formatted correctly.
//!
//! An example with the word "MINOR" at the top is expected to successfully
//! build against the before and after. Otherwise it should fail. A comment of
//! "// Error:" will check that the given message appears in the error output.

use std::error::Error;
use std::fs;
use std::path::Path;
use std::process::Command;

fn main() {
if let Err(e) = doit() {
println!("error: {}", e);
std::process::exit(1);
}
}

const SEPARATOR: &str = "///////////////////////////////////////////////////////////";

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

loop {
// Find a rust block.
let block_start = loop {
match lines.next() {
Some((lineno, line)) => {
if line.trim().starts_with("```rust") && !line.contains("skip") {
break lineno + 1;
}
}
None => return Ok(()),
}
};
// Read in the code block.
let mut block = Vec::new();
loop {
match lines.next() {
Some((_, line)) => {
if line.trim() == "```" {
break;
}
block.push(line);
}
None => {
return Err(format!(
"rust block did not end for example starting on line {}",
block_start
)
.into());
}
}
}
// Split it into the separate source files.
let parts: Vec<_> = block.split(|line| line.trim() == SEPARATOR).collect();
if parts.len() != 4 {
return Err(format!(
"expected 4 sections in example starting on line {}, got {}:\n{:?}",
block_start,
parts.len(),
parts
)
.into());
}
let join = |part: &[&str]| {
let mut result = String::new();
result.push_str("#![allow(unused)]\n#![deny(warnings)]\n");
result.push_str(&part.join("\n"));
if !result.ends_with('\n') {
result.push('\n');
}
result
};
let expect_success = parts[0][0].contains("MINOR");
println!("Running test from line {}", block_start);
if let Err(e) = run_test(
join(parts[1]),
join(parts[2]),
join(parts[3]),
expect_success,
) {
return Err(format!(
"test failed for example starting on line {}: {}",
block_start, e
)
.into());
}
}
}

const CRATE_NAME: &str = "updated_crate";

fn run_test(
before: String,
after: String,
example: String,
expect_success: bool,
) -> Result<(), Box<dyn Error>> {
let tempdir = tempfile::TempDir::new()?;
let before_p = tempdir.path().join("before.rs");
let after_p = tempdir.path().join("after.rs");
let example_p = tempdir.path().join("example.rs");
compile(before, &before_p, CRATE_NAME, false, true)?;
compile(example.clone(), &example_p, "example", true, true)?;
compile(after, &after_p, CRATE_NAME, false, true)?;
compile(example, &example_p, "example", true, expect_success)?;
Ok(())
}

fn check_formatting(path: &Path) -> Result<(), Box<dyn Error>> {
match Command::new("rustfmt")
.args(&["--edition=2018", "--check"])
.arg(path)
.status()
{
Ok(status) => {
if !status.success() {
return Err(format!("failed to run rustfmt: {}", status).into());
}
return Ok(());
}
Err(e) => {
return Err(format!("failed to run rustfmt: {}", e).into());
}
}
}

fn compile(
mut contents: String,
path: &Path,
crate_name: &str,
extern_path: bool,
expect_success: bool,
) -> Result<(), Box<dyn Error>> {
// If the example has an error message, remove it so that it can be
// compared with the actual output, and also to avoid issues with rustfmt
// moving it around.
let expected_error = match contents.find("// Error:") {
Some(index) => {
let start = contents[..index].rfind(|ch| ch != ' ').unwrap();
let end = contents[index..].find('\n').unwrap();
let error = contents[index + 9..index + end].trim().to_string();
contents.replace_range(start + 1..index + end, "");
Some(error)
}
None => None,
};
let crate_type = if contents.contains("fn main()") {
"bin"
} else {
"rlib"
};

fs::write(path, &contents)?;
check_formatting(path)?;
let out_dir = path.parent().unwrap();
let mut cmd = Command::new("rustc");
cmd.args(&[
"--edition=2018",
"--crate-type",
crate_type,
"--crate-name",
crate_name,
"--out-dir",
]);
cmd.arg(&out_dir);
if extern_path {
let epath = out_dir.join(format!("lib{}.rlib", CRATE_NAME));
cmd.arg("--extern")
.arg(format!("{}={}", CRATE_NAME, epath.display()));
}
cmd.arg(path);
let output = cmd.output()?;
let stderr = std::str::from_utf8(&output.stderr).unwrap();
match (output.status.success(), expect_success) {
(true, true) => Ok(()),
(true, false) => Err(format!(
"expected failure, got success {}\n===== Contents:\n{}\n===== Output:\n{}\n",
path.display(),
contents,
stderr
)
.into()),
(false, true) => Err(format!(
"expected success, got error {}\n===== Contents:\n{}\n===== Output:\n{}\n",
path.display(),
contents,
stderr
)
.into()),
(false, false) => {
if expected_error.is_none() {
return Err("failing test should have an \"// Error:\" annotation ".into());
}
let expected_error = expected_error.unwrap();
if !stderr.contains(&expected_error) {
Err(format!(
"expected error message not found in compiler output\nExpected: {}\nGot:\n{}\n",
expected_error, stderr
)
.into())
} else {
Ok(())
}
}
}
}
2 changes: 2 additions & 0 deletions src/doc/src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@
* [Source Replacement](reference/source-replacement.md)
* [External Tools](reference/external-tools.md)
* [Registries](reference/registries.md)
* [Dependency Resolution](reference/resolver.md)
* [SemVer Compatibility](reference/semver.md)
* [Unstable Features](reference/unstable.md)

* [Cargo Commands](commands/index.md)
Expand Down
2 changes: 2 additions & 0 deletions src/doc/src/reference/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,6 @@ The reference covers the details of various areas of Cargo.
* [Source Replacement](source-replacement.md)
* [External Tools](external-tools.md)
* [Registries](registries.md)
* [Dependency Resolution](resolver.md)
* [SemVer Compatibility](semver.md)
* [Unstable Features](unstable.md)
8 changes: 8 additions & 0 deletions src/doc/src/reference/manifest.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,14 @@ Versioning](https://semver.org/), so make sure you follow some basic rules:
traits, fields, types, functions, methods or anything else.
* Use version numbers with three numeric parts such as 1.0.0 rather than 1.0.

See the [Resolver] chapter for more information on how Cargo uses versions to
resolve dependencies, and for guidelines on setting your own version. See the
[Semver compatibility] chapter for more details on exactly what constitutes a
breaking change.

[Resolver]: resolver.md
[Semver compatibility]: semver.md

<a id="the-authors-field-optional"></a>
#### The `authors` field

Expand Down
Loading

0 comments on commit fd6a84a

Please sign in to comment.