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
99109All conditions can be negated with `!`. `@!has foo/type.NoSuch.html`
137147
138148channel = os .environ ["DOC_RUST_LANG_ORG_CHANNEL" ]
139149
150+ # Initialized in main
151+ rust_test_path = None
152+ bless = None
153+
140154class 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+
390430def 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
485549if __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 ("\n Encountered {} errors" .format (ERR_COUNT ))
493565 raise SystemExit (1 )
0 commit comments