Skip to content

Commit 420ddd0

Browse files
authored
Rollup merge of #91209 - camelid:snapshot, r=jyn514
Implement `@snapshot` check for htmldocck This form of check allows performing snapshot tests (à la `src/test/ui`) on rustdoc HTML output, making it easier to create and update tests. See [this Zulip thread][1] for more information about the motivation for this change. [1]: https://zulip-archive.rust-lang.org/stream/266220-rustdoc/topic/HTML.20snapshot.20tests.html#262651142 r? `@GuillaumeGomez`
2 parents 0bd4ee7 + fe88fcf commit 420ddd0

File tree

5 files changed

+92
-20
lines changed

5 files changed

+92
-20
lines changed

src/etc/htmldocck.py

+76-4
Original file line numberDiff line numberDiff line change
@@ -90,10 +90,20 @@
9090
highlights for example. If you want to simply check for the presence of
9191
a given node or attribute, use an empty string (`""`) as a `PATTERN`.
9292
93-
* `@count PATH XPATH COUNT' checks for the occurrence of the given XPath
93+
* `@count PATH XPATH COUNT` checks for the occurrence of the given XPath
9494
in the specified file. The number of occurrences must match the given
9595
count.
9696
97+
* `@snapshot NAME PATH XPATH` creates a snapshot test named NAME.
98+
A snapshot test captures a subtree of the DOM, at the location
99+
determined by the XPath, and compares it to a pre-recorded value
100+
in a file. The file's name is the test's name with the `.rs` extension
101+
replaced with `.NAME.html`, where NAME is the snapshot's name.
102+
103+
htmldocck supports the `--bless` option to accept the current subtree
104+
as expected, saving it to the file determined by the snapshot's name.
105+
compiletest's `--bless` flag is forwarded to htmldocck.
106+
97107
* `@has-dir PATH` checks for the existence of the given directory.
98108
99109
All conditions can be negated with `!`. `@!has foo/type.NoSuch.html`
@@ -137,6 +147,10 @@
137147

138148
channel = os.environ["DOC_RUST_LANG_ORG_CHANNEL"]
139149

150+
# Initialized in main
151+
rust_test_path = None
152+
bless = None
153+
140154
class CustomHTMLParser(HTMLParser):
141155
"""simplified HTML parser.
142156
@@ -387,6 +401,32 @@ def get_tree_count(tree, path):
387401
return len(tree.findall(path))
388402

389403

404+
def check_snapshot(snapshot_name, tree):
405+
assert rust_test_path.endswith('.rs')
406+
snapshot_path = '{}.{}.{}'.format(rust_test_path[:-3], snapshot_name, 'html')
407+
try:
408+
with open(snapshot_path, 'r') as snapshot_file:
409+
expected_str = snapshot_file.read()
410+
except FileNotFoundError:
411+
if bless:
412+
expected_str = None
413+
else:
414+
raise FailedCheck('No saved snapshot value')
415+
416+
actual_str = ET.tostring(tree).decode('utf-8')
417+
418+
if expected_str != actual_str:
419+
if bless:
420+
with open(snapshot_path, 'w') as snapshot_file:
421+
snapshot_file.write(actual_str)
422+
else:
423+
print('--- expected ---\n')
424+
print(expected_str)
425+
print('\n\n--- actual ---\n')
426+
print(actual_str)
427+
print()
428+
raise FailedCheck('Actual snapshot value is different than expected')
429+
390430
def stderr(*args):
391431
if sys.version_info.major < 3:
392432
file = codecs.getwriter('utf-8')(sys.stderr)
@@ -448,6 +488,28 @@ def check_command(c, cache):
448488
ret = expected == found
449489
else:
450490
raise InvalidCheck('Invalid number of @{} arguments'.format(c.cmd))
491+
492+
elif c.cmd == 'snapshot': # snapshot test
493+
if len(c.args) == 3: # @snapshot <snapshot-name> <html-path> <xpath>
494+
[snapshot_name, html_path, pattern] = c.args
495+
tree = cache.get_tree(html_path)
496+
xpath = normalize_xpath(pattern)
497+
subtrees = tree.findall(xpath)
498+
if len(subtrees) == 1:
499+
[subtree] = subtrees
500+
try:
501+
check_snapshot(snapshot_name, subtree)
502+
ret = True
503+
except FailedCheck as err:
504+
cerr = str(err)
505+
ret = False
506+
elif len(subtrees) == 0:
507+
raise FailedCheck('XPATH did not match')
508+
else:
509+
raise FailedCheck('Expected 1 match, but found {}'.format(len(subtrees)))
510+
else:
511+
raise InvalidCheck('Invalid number of @{} arguments'.format(c.cmd))
512+
451513
elif c.cmd == 'has-dir': # has-dir test
452514
if len(c.args) == 1: # @has-dir <path> = has-dir test
453515
try:
@@ -458,11 +520,13 @@ def check_command(c, cache):
458520
ret = False
459521
else:
460522
raise InvalidCheck('Invalid number of @{} arguments'.format(c.cmd))
523+
461524
elif c.cmd == 'valid-html':
462525
raise InvalidCheck('Unimplemented @valid-html')
463526

464527
elif c.cmd == 'valid-links':
465528
raise InvalidCheck('Unimplemented @valid-links')
529+
466530
else:
467531
raise InvalidCheck('Unrecognized @{}'.format(c.cmd))
468532

@@ -483,11 +547,19 @@ def check(target, commands):
483547

484548

485549
if __name__ == '__main__':
486-
if len(sys.argv) != 3:
487-
stderr('Usage: {} <doc dir> <template>'.format(sys.argv[0]))
550+
if len(sys.argv) not in [3, 4]:
551+
stderr('Usage: {} <doc dir> <template> [--bless]'.format(sys.argv[0]))
488552
raise SystemExit(1)
489553

490-
check(sys.argv[1], get_commands(sys.argv[2]))
554+
rust_test_path = sys.argv[2]
555+
if len(sys.argv) > 3 and sys.argv[3] == '--bless':
556+
bless = True
557+
else:
558+
# We only support `--bless` at the end of the arguments.
559+
# This assert is to prevent silent failures.
560+
assert '--bless' not in sys.argv
561+
bless = False
562+
check(sys.argv[1], get_commands(rust_test_path))
491563
if ERR_COUNT:
492564
stderr("\nEncountered {} errors".format(ERR_COUNT))
493565
raise SystemExit(1)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
<div class="docblock"><p>Hello world!
2+
Goodbye!
3+
Hello again!</p>
4+
</div>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
<div class="docblock"><p>Hello world!</p>
2+
<p>Goodbye!
3+
Hello again!</p>
4+
</div>

src/test/rustdoc/mixing-doc-comments-and-attrs.rs

+2-10
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,15 @@
11
#![crate_name = "foo"]
22

33
// @has 'foo/struct.S1.html'
4-
// @count - '//details[@class="rustdoc-toggle top-doc"]/div[@class="docblock"]/p' \
5-
// 1
6-
// @has - '//details[@class="rustdoc-toggle top-doc"]/div[@class="docblock"]/p[1]' \
7-
// 'Hello world! Goodbye! Hello again!'
4+
// @snapshot S1_top-doc - '//details[@class="rustdoc-toggle top-doc"]/div[@class="docblock"]'
85

96
#[doc = "Hello world!\n\n"]
107
/// Goodbye!
118
#[doc = " Hello again!\n"]
129
pub struct S1;
1310

1411
// @has 'foo/struct.S2.html'
15-
// @count - '//details[@class="rustdoc-toggle top-doc"]/div[@class="docblock"]/p' \
16-
// 2
17-
// @has - '//details[@class="rustdoc-toggle top-doc"]/div[@class="docblock"]/p[1]' \
18-
// 'Hello world!'
19-
// @has - '//details[@class="rustdoc-toggle top-doc"]/div[@class="docblock"]/p[2]' \
20-
// 'Goodbye! Hello again!'
12+
// @snapshot S2_top-doc - '//details[@class="rustdoc-toggle top-doc"]/div[@class="docblock"]'
2113

2214
/// Hello world!
2315
///

src/tools/compiletest/src/runtest.rs

+6-6
Original file line numberDiff line numberDiff line change
@@ -2219,12 +2219,12 @@ impl<'test> TestCx<'test> {
22192219
self.check_rustdoc_test_option(proc_res);
22202220
} else {
22212221
let root = self.config.find_rust_src_root().unwrap();
2222-
let res = self.cmd2procres(
2223-
Command::new(&self.config.docck_python)
2224-
.arg(root.join("src/etc/htmldocck.py"))
2225-
.arg(&out_dir)
2226-
.arg(&self.testpaths.file),
2227-
);
2222+
let mut cmd = Command::new(&self.config.docck_python);
2223+
cmd.arg(root.join("src/etc/htmldocck.py")).arg(&out_dir).arg(&self.testpaths.file);
2224+
if self.config.bless {
2225+
cmd.arg("--bless");
2226+
}
2227+
let res = self.cmd2procres(&mut cmd);
22282228
if !res.status.success() {
22292229
self.fatal_proc_rec_with_ctx("htmldocck failed!", &res, |mut this| {
22302230
this.compare_to_default_rustdoc(&out_dir)

0 commit comments

Comments
 (0)