Skip to content

Commit e2846a7

Browse files
committed
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
1 parent d2c24aa commit e2846a7

File tree

2 files changed

+82
-10
lines changed

2 files changed

+82
-10
lines changed

src/etc/htmldocck.py

Lines changed: 76 additions & 4 deletions
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)

src/tools/compiletest/src/runtest.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2217,12 +2217,12 @@ impl<'test> TestCx<'test> {
22172217
self.check_rustdoc_test_option(proc_res);
22182218
} else {
22192219
let root = self.config.find_rust_src_root().unwrap();
2220-
let res = self.cmd2procres(
2221-
Command::new(&self.config.docck_python)
2222-
.arg(root.join("src/etc/htmldocck.py"))
2223-
.arg(&out_dir)
2224-
.arg(&self.testpaths.file),
2225-
);
2220+
let mut cmd = Command::new(&self.config.docck_python);
2221+
cmd.arg(root.join("src/etc/htmldocck.py")).arg(&out_dir).arg(&self.testpaths.file);
2222+
if self.config.bless {
2223+
cmd.arg("--bless");
2224+
}
2225+
let res = self.cmd2procres(&mut cmd);
22262226
if !res.status.success() {
22272227
self.fatal_proc_rec_with_ctx("htmldocck failed!", &res, |mut this| {
22282228
this.compare_to_default_rustdoc(&out_dir)

0 commit comments

Comments
 (0)