Skip to content

Commit cf88463

Browse files
authored
Merge pull request #1646 from ehuss/test-summary
Add test linking
2 parents 0c96434 + 4d83b0b commit cf88463

File tree

14 files changed

+524
-71
lines changed

14 files changed

+524
-71
lines changed

.github/workflows/main.yml

+17-5
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,11 @@ jobs:
3535
runs-on: ubuntu-latest
3636
steps:
3737
- uses: actions/checkout@master
38+
- name: Checkout rust-lang/rust
39+
uses: actions/checkout@master
40+
with:
41+
repository: rust-lang/rust
42+
path: rust
3843
- name: Update rustup
3944
run: rustup self update
4045
- name: Install Rust
@@ -52,16 +57,17 @@ jobs:
5257
rustup --version
5358
rustc -Vv
5459
mdbook --version
55-
- name: Verify the book builds
56-
env:
57-
SPEC_DENY_WARNINGS: 1
58-
run: mdbook build
5960
- name: Style checks
6061
working-directory: style-check
6162
run: cargo run --locked -- ../src
6263
- name: Style fmt
6364
working-directory: style-check
6465
run: cargo fmt --check
66+
- name: Verify the book builds
67+
env:
68+
SPEC_DENY_WARNINGS: 1
69+
SPEC_RUST_ROOT: ${{ github.workspace }}/rust
70+
run: mdbook build
6571
- name: Check for broken links
6672
run: |
6773
curl -sSLo linkcheck.sh \
@@ -103,6 +109,11 @@ jobs:
103109
runs-on: ubuntu-latest
104110
steps:
105111
- uses: actions/checkout@master
112+
- name: Checkout rust-lang/rust
113+
uses: actions/checkout@master
114+
with:
115+
repository: rust-lang/rust
116+
path: rust
106117
- name: Update rustup
107118
run: rustup self update
108119
- name: Install Rust
@@ -117,7 +128,8 @@ jobs:
117128
echo "$(pwd)/bin" >> $GITHUB_PATH
118129
- name: Build the book
119130
env:
120-
SPEC_RELATIVE: 0
131+
SPEC_RELATIVE: 0
132+
SPEC_RUST_ROOT: ${{ github.workspace }}/rust
121133
run: mdbook build --dest-dir dist/preview-${{ github.event.pull_request.number }}
122134
- name: Upload artifact
123135
uses: actions/upload-artifact@v4

README.md

+14-2
Original file line numberDiff line numberDiff line change
@@ -66,10 +66,22 @@ SPEC_RELATIVE=0 mdbook build --open
6666

6767
This will open a browser with a websocket live-link to automatically reload whenever the source is updated.
6868

69-
The `SPEC_RELATIVE=0` environment variable makes links to the standard library go to <https://doc.rust-lang.org/> instead of being relative, which is useful when viewing locally since you normally don't have a copy of the standard library.
70-
7169
You can also use mdbook's live webserver option, which will automatically rebuild the book and reload your web browser whenever a source file is modified:
7270

7371
```sh
7472
SPEC_RELATIVE=0 mdbook serve --open
7573
```
74+
75+
### `SPEC_RELATIVE`
76+
77+
The `SPEC_RELATIVE=0` environment variable makes links to the standard library go to <https://doc.rust-lang.org/> instead of being relative, which is useful when viewing locally since you normally don't have a copy of the standard library.
78+
79+
The published site at <https://doc.rust-lang.org/reference/> (or local docs using `rustup doc`) does not set this, which means it will use relative links which supports offline viewing and links to the correct version (for example, links in <https://doc.rust-lang.org/1.81.0/reference/> will stay within the 1.81.0 directory).
80+
81+
### `SPEC_DENY_WARNINGS`
82+
83+
The `SPEC_DENY_WARNINGS=1` environment variable will turn all warnings generated by `mdbook-spec` to errors. This is used in CI to ensure that there aren't any problems with the book content.
84+
85+
### `SPEC_RUST_ROOT`
86+
87+
The `SPEC_RUST_ROOT` can be used to point to the directory of a checkout of <https://github.com./rust-lang/rust>. This is used by the test-linking feature so that it can find tests linked to reference rules. If this is not set, then the tests won't be linked.

book.toml

+1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ author = "The Rust Project Developers"
55

66
[output.html]
77
additional-css = ["theme/reference.css"]
8+
additional-js = ["theme/reference.js"]
89
git-repository-url = "https://github.com./rust-lang/reference/"
910
edit-url-template = "https://github.com./rust-lang/reference/edit/master/{path}"
1011
smart-punctuation = true

docs/authoring.md

+16
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,22 @@ When assigning rules to new paragraphs, or when modifying rule names, use the fo
9999
* Target specific admonitions should typically be named by the least specific target property to which they apply (e.g. if a rule affects all x86 CPUs, the rule name should include `x86` rather than separately listing `i586`, `i686` and `x86_64`, and if a rule applies to all ELF platforms, it should be named `elf` rather than listing every ELF OS).
100100
* Use an appropriately descriptive, but short, name if the language does not provide one.
101101

102+
#### Test rule annotations
103+
104+
Tests in <https://github.com./rust-lang/rust> can be linked to rules in the reference. The rule will include a link to the tests, and there is also an [appendix] which tracks how the rules are currently linked.
105+
106+
Tests in the `tests` directory can be annotated with the `//@ reference: x.y.z` header to link it to a rule. The header can be specified multiple times if a single file covers multiple rules.
107+
108+
Compiler developers are not expected to add `reference` annotations to tests. However, if they do want to help, their cooperation is very welcome. Reference authors and editors are responsible for making sure every rule has a test associated with it.
109+
110+
The tests are beneficial for reviewers to see the behavior of a rule. It is also a benefit to readers who may want to see examples of particular behaviors. When adding new rules, you should wait until the reference side is approved before submitting a PR to `rust-lang/rust` (to avoid churn if we decide on different names).
111+
112+
Prefixed rule names should not be used in tests. That is, do not use something like `asm.rules` when there are specific rules like `asm.rules.reg-not-input`.
113+
114+
We are not expecting 100% coverage at any time. Although it would be nice, it is unrealistic due to the sequence things are developed, and resources available.
115+
116+
[appendix]: https://doc.rust-lang.org/nightly/reference/test-summary.html
117+
102118
### Standard library links
103119

104120
You should link to the standard library without specifying a URL in a fashion similar to [rustdoc intra-doc links][intra]. Some examples:

mdbook-spec/Cargo.lock

+30-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

mdbook-spec/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,4 @@ regex = "1.9.4"
1919
semver = "1.0.21"
2020
serde_json = "1.0.113"
2121
tempfile = "3.10.1"
22+
walkdir = "2.5.0"

mdbook-spec/src/lib.rs

+66-60
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,29 @@
11
#![deny(rust_2018_idioms, unused_lifetimes)]
22

3+
use crate::rules::Rules;
4+
use anyhow::{bail, Context, Result};
35
use mdbook::book::{Book, Chapter};
46
use mdbook::errors::Error;
57
use mdbook::preprocess::{CmdPreprocessor, Preprocessor, PreprocessorContext};
68
use mdbook::BookItem;
79
use once_cell::sync::Lazy;
810
use regex::{Captures, Regex};
911
use semver::{Version, VersionReq};
10-
use std::collections::BTreeMap;
1112
use std::io;
1213
use std::path::PathBuf;
1314

15+
mod rules;
1416
mod std_links;
15-
16-
/// The Regex for rules like `r[foo]`.
17-
static RULE_RE: Lazy<Regex> = Lazy::new(|| Regex::new(r"(?m)^r\[([^]]+)]$").unwrap());
17+
mod test_links;
1818

1919
/// The Regex for the syntax for blockquotes that have a specific CSS class,
2020
/// like `> [!WARNING]`.
2121
static ADMONITION_RE: Lazy<Regex> = Lazy::new(|| {
2222
Regex::new(r"(?m)^ *> \[!(?<admon>[^]]+)\]\n(?<blockquote>(?: *>.*\n)+)").unwrap()
2323
});
2424

25-
pub fn handle_preprocessing(pre: &dyn Preprocessor) -> Result<(), Error> {
25+
pub fn handle_preprocessing() -> Result<(), Error> {
26+
let pre = Spec::new()?;
2627
let (ctx, book) = CmdPreprocessor::parse_input(io::stdin())?;
2728

2829
let book_version = Version::parse(&ctx.mdbook_version)?;
@@ -48,59 +49,45 @@ pub struct Spec {
4849
/// Whether or not warnings should be errors (set by SPEC_DENY_WARNINGS
4950
/// environment variable).
5051
deny_warnings: bool,
52+
/// Path to the rust-lang/rust git repository (set by SPEC_RUST_ROOT
53+
/// environment variable).
54+
rust_root: Option<PathBuf>,
55+
/// The git ref that can be used in a URL to the rust-lang/rust repository.
56+
git_ref: String,
5157
}
5258

5359
impl Spec {
54-
pub fn new() -> Spec {
55-
Spec {
56-
deny_warnings: std::env::var("SPEC_DENY_WARNINGS").as_deref() == Ok("1"),
60+
fn new() -> Result<Spec> {
61+
let deny_warnings = std::env::var("SPEC_DENY_WARNINGS").as_deref() == Ok("1");
62+
let rust_root = std::env::var_os("SPEC_RUST_ROOT").map(PathBuf::from);
63+
if deny_warnings && rust_root.is_none() {
64+
bail!("SPEC_RUST_ROOT environment variable must be set");
5765
}
58-
}
59-
60-
/// Converts lines that start with `r[…]` into a "rule" which has special
61-
/// styling and can be linked to.
62-
fn rule_definitions(
63-
&self,
64-
chapter: &Chapter,
65-
found_rules: &mut BTreeMap<String, (PathBuf, PathBuf)>,
66-
) -> String {
67-
let source_path = chapter.source_path.clone().unwrap_or_default();
68-
let path = chapter.path.clone().unwrap_or_default();
69-
RULE_RE
70-
.replace_all(&chapter.content, |caps: &Captures<'_>| {
71-
let rule_id = &caps[1];
72-
if let Some((old, _)) =
73-
found_rules.insert(rule_id.to_string(), (source_path.clone(), path.clone()))
74-
{
75-
let message = format!(
76-
"rule `{rule_id}` defined multiple times\n\
77-
First location: {old:?}\n\
78-
Second location: {source_path:?}"
79-
);
80-
if self.deny_warnings {
81-
panic!("error: {message}");
82-
} else {
83-
eprintln!("warning: {message}");
84-
}
66+
let git_ref = match git_ref(&rust_root) {
67+
Ok(s) => s,
68+
Err(e) => {
69+
if deny_warnings {
70+
eprintln!("error: {e:?}");
71+
std::process::exit(1);
72+
} else {
73+
eprintln!("warning: {e:?}");
74+
"master".into()
8575
}
86-
format!(
87-
"<div class=\"rule\" id=\"r-{rule_id}\">\
88-
<a class=\"rule-link\" href=\"#r-{rule_id}\">[{rule_id}]</a>\
89-
</div>\n"
90-
)
91-
})
92-
.to_string()
76+
}
77+
};
78+
Ok(Spec {
79+
deny_warnings,
80+
rust_root,
81+
git_ref,
82+
})
9383
}
9484

9585
/// Generates link references to all rules on all pages, so you can easily
9686
/// refer to rules anywhere in the book.
97-
fn auto_link_references(
98-
&self,
99-
chapter: &Chapter,
100-
found_rules: &BTreeMap<String, (PathBuf, PathBuf)>,
101-
) -> String {
87+
fn auto_link_references(&self, chapter: &Chapter, rules: &Rules) -> String {
10288
let current_path = chapter.path.as_ref().unwrap().parent().unwrap();
103-
let definitions: String = found_rules
89+
let definitions: String = rules
90+
.def_paths
10491
.iter()
10592
.map(|(rule_id, (_, path))| {
10693
let relative = pathdiff::diff_paths(path, current_path).unwrap();
@@ -155,34 +142,53 @@ fn to_initial_case(s: &str) -> String {
155142
format!("{first}{rest}")
156143
}
157144

145+
/// Determines the git ref used for linking to a particular branch/tag in GitHub.
146+
fn git_ref(rust_root: &Option<PathBuf>) -> Result<String> {
147+
let Some(rust_root) = rust_root else {
148+
return Ok("master".into());
149+
};
150+
let channel = std::fs::read_to_string(rust_root.join("src/ci/channel"))
151+
.context("failed to read src/ci/channel")?;
152+
let git_ref = match channel.trim() {
153+
// nightly/beta are branches, not stable references. Should be ok
154+
// because we're not expecting those channels to be long-lived.
155+
"nightly" => "master".into(),
156+
"beta" => "beta".into(),
157+
"stable" => {
158+
let version = std::fs::read_to_string(rust_root.join("src/version"))
159+
.context("|| failed to read src/version")?;
160+
version.trim().into()
161+
}
162+
ch => bail!("unknown channel {ch}"),
163+
};
164+
Ok(git_ref)
165+
}
166+
158167
impl Preprocessor for Spec {
159168
fn name(&self) -> &str {
160169
"spec"
161170
}
162171

163172
fn run(&self, _ctx: &PreprocessorContext, mut book: Book) -> Result<Book, Error> {
164-
let mut found_rules = BTreeMap::new();
173+
let rules = self.collect_rules(&book);
174+
let tests = self.collect_tests(&rules);
175+
let summary_table = test_links::make_summary_table(&book, &tests, &rules);
176+
165177
book.for_each_mut(|item| {
166178
let BookItem::Chapter(ch) = item else {
167179
return;
168180
};
169181
if ch.is_draft_chapter() {
170182
return;
171183
}
172-
ch.content = self.rule_definitions(&ch, &mut found_rules);
173184
ch.content = self.admonitions(&ch);
174-
});
175-
// This is a separate pass because it relies on the modifications of
176-
// the previous passes.
177-
book.for_each_mut(|item| {
178-
let BookItem::Chapter(ch) = item else {
179-
return;
180-
};
181-
if ch.is_draft_chapter() {
182-
return;
185+
ch.content = self.auto_link_references(&ch, &rules);
186+
ch.content = self.render_rule_definitions(&ch.content, &tests);
187+
if ch.name == "Test summary" {
188+
ch.content = ch.content.replace("{{summary-table}}", &summary_table);
183189
}
184-
ch.content = self.auto_link_references(&ch, &found_rules);
185190
});
191+
186192
// Final pass will resolve everything as a std link (or error if the
187193
// link is unknown).
188194
std_links::std_links(&mut book);

mdbook-spec/src/main.rs

+1-3
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,7 @@ fn main() {
1212
None => {}
1313
}
1414

15-
let preprocessor = mdbook_spec::Spec::new();
16-
17-
if let Err(e) = mdbook_spec::handle_preprocessing(&preprocessor) {
15+
if let Err(e) = mdbook_spec::handle_preprocessing() {
1816
eprintln!("{}", e);
1917
std::process::exit(1);
2018
}

0 commit comments

Comments
 (0)