Skip to content

Commit 9ea5e0e

Browse files
committed
Add test linking
This adds the ability to link rust-lang/rust tests to reference rule annotations. Rules now have a link that pops up a list of tests that are associated with that rule. Additionally, there is a new appendix that lists the number of rules in each chapter, how many tests are associated, and various summaries. This requires a local checkout of rust-lang/rust which is pointed to by the `SPEC_RUST_ROOT` environment variable.
1 parent 84414cc commit 9ea5e0e

File tree

13 files changed

+403
-9
lines changed

13 files changed

+403
-9
lines changed

.github/workflows/main.yml

+10-4
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 \

README.md

+4
Original file line numberDiff line numberDiff line change
@@ -81,3 +81,7 @@ The published site at <https://doc.rust-lang.org/reference/> (or local docs usin
8181
### `SPEC_DENY_WARNINGS`
8282

8383
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

+12
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,18 @@ 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+
You *should* when possible make sure every rule has a test associated with it. This is beneficial for reviewers to see the behavior and 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).
109+
110+
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`.
111+
112+
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.
113+
102114
### Standard library links
103115

104116
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

+57-3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
#![deny(rust_2018_idioms, unused_lifetimes)]
22

33
use crate::rules::Rules;
4-
use anyhow::Result;
4+
use anyhow::{bail, Context, Result};
55
use mdbook::book::{Book, Chapter};
66
use mdbook::errors::Error;
77
use mdbook::preprocess::{CmdPreprocessor, Preprocessor, PreprocessorContext};
@@ -10,9 +10,11 @@ use once_cell::sync::Lazy;
1010
use regex::{Captures, Regex};
1111
use semver::{Version, VersionReq};
1212
use std::io;
13+
use std::path::PathBuf;
1314

1415
mod rules;
1516
mod std_links;
17+
mod test_links;
1618

1719
/// The Regex for the syntax for blockquotes that have a specific CSS class,
1820
/// like `> [!WARNING]`.
@@ -47,12 +49,37 @@ pub struct Spec {
4749
/// Whether or not warnings should be errors (set by SPEC_DENY_WARNINGS
4850
/// environment variable).
4951
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,
5057
}
5158

5259
impl Spec {
5360
fn new() -> Result<Spec> {
5461
let deny_warnings = std::env::var("SPEC_DENY_WARNINGS").as_deref() == Ok("1");
55-
Ok(Spec { deny_warnings })
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");
65+
}
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()
75+
}
76+
}
77+
};
78+
Ok(Spec {
79+
deny_warnings,
80+
rust_root,
81+
git_ref,
82+
})
5683
}
5784

5885
/// Generates link references to all rules on all pages, so you can easily
@@ -115,13 +142,37 @@ fn to_initial_case(s: &str) -> String {
115142
format!("{first}{rest}")
116143
}
117144

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+
118167
impl Preprocessor for Spec {
119168
fn name(&self) -> &str {
120169
"spec"
121170
}
122171

123172
fn run(&self, _ctx: &PreprocessorContext, mut book: Book) -> Result<Book, Error> {
124173
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);
125176

126177
book.for_each_mut(|item| {
127178
let BookItem::Chapter(ch) = item else {
@@ -132,7 +183,10 @@ impl Preprocessor for Spec {
132183
}
133184
ch.content = self.admonitions(&ch);
134185
ch.content = self.auto_link_references(&ch, &rules);
135-
ch.content = self.render_rule_definitions(&ch.content);
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);
189+
}
136190
});
137191

138192
// Final pass will resolve everything as a std link (or error if the

mdbook-spec/src/rules.rs

+25-1
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
//! Handling for rule identifiers.
22
3+
use crate::test_links::RuleToTests;
34
use crate::Spec;
45
use mdbook::book::Book;
56
use mdbook::BookItem;
67
use once_cell::sync::Lazy;
78
use regex::{Captures, Regex};
89
use std::collections::{BTreeMap, HashSet};
10+
use std::fmt::Write;
911
use std::path::PathBuf;
1012

1113
/// The Regex for rules like `r[foo]`.
@@ -76,13 +78,35 @@ impl Spec {
7678

7779
/// Converts lines that start with `r[…]` into a "rule" which has special
7880
/// styling and can be linked to.
79-
pub fn render_rule_definitions(&self, content: &str) -> String {
81+
pub fn render_rule_definitions(&self, content: &str, tests: &RuleToTests) -> String {
8082
RULE_RE
8183
.replace_all(content, |caps: &Captures<'_>| {
8284
let rule_id = &caps[1];
85+
let mut test_html = String::new();
86+
if let Some(tests) = tests.get(rule_id) {
87+
test_html = format!(
88+
"<span class=\"popup-container\">\n\
89+
&nbsp;&nbsp;&nbsp;&nbsp;<a href=\"javascript:void(0)\" onclick=\"spec_toggle_tests('{rule_id}');\">\
90+
Tests</a>\n\
91+
<div id=\"tests-{rule_id}\" class=\"tests-popup popup-hidden\">\n\
92+
Tests with this rule:
93+
<ul>");
94+
for test in tests {
95+
writeln!(
96+
test_html,
97+
"<li><a href=\"https://github.com/rust-lang/rust/blob/{git_ref}/{test_path}\">{test_path}</a></li>",
98+
test_path = test.path,
99+
git_ref = self.git_ref
100+
)
101+
.unwrap();
102+
}
103+
104+
test_html.push_str("</ul></div></span>");
105+
}
83106
format!(
84107
"<div class=\"rule\" id=\"r-{rule_id}\">\
85108
<a class=\"rule-link\" href=\"#r-{rule_id}\">[{rule_id}]</a>\
109+
{test_html}\
86110
</div>\n"
87111
)
88112
})

0 commit comments

Comments
 (0)