From 1098cce27acb2d52cb3b5ddbcf28c3a06e38dc7c Mon Sep 17 00:00:00 2001 From: Nixon Enraght-Moony Date: Sun, 29 Nov 2020 16:16:25 +0000 Subject: Add tests for rustdoc json Move rustdoc/rustdoc-json to rustdoc-json Scaffold rustdoc-json test mode Implement run_rustdoc_json_test Fix up python Make tidy happy --- src/bootstrap/builder.rs | 1 + src/bootstrap/test.rs | 7 + src/test/rustdoc-json/Makefile | 6 + src/test/rustdoc-json/check_missing_items.py | 187 ++++++++ src/test/rustdoc-json/compare.py | 131 ++++++ src/test/rustdoc-json/structs.expected | 468 +++++++++++++++++++++ src/test/rustdoc-json/structs.rs | 17 + src/test/rustdoc/rustdoc-json/Makefile | 6 - .../rustdoc/rustdoc-json/check_missing_items.py | 181 -------- src/test/rustdoc/rustdoc-json/compare.py | 107 ----- src/test/rustdoc/rustdoc-json/structs.expected | 468 --------------------- src/test/rustdoc/rustdoc-json/structs.rs | 17 - src/tools/compiletest/src/common.rs | 3 + src/tools/compiletest/src/main.rs | 2 +- src/tools/compiletest/src/runtest.rs | 67 ++- 15 files changed, 879 insertions(+), 789 deletions(-) create mode 100644 src/test/rustdoc-json/Makefile create mode 100644 src/test/rustdoc-json/check_missing_items.py create mode 100644 src/test/rustdoc-json/compare.py create mode 100644 src/test/rustdoc-json/structs.expected create mode 100644 src/test/rustdoc-json/structs.rs delete mode 100644 src/test/rustdoc/rustdoc-json/Makefile delete mode 100644 src/test/rustdoc/rustdoc-json/check_missing_items.py delete mode 100644 src/test/rustdoc/rustdoc-json/compare.py delete mode 100644 src/test/rustdoc/rustdoc-json/structs.expected delete mode 100644 src/test/rustdoc/rustdoc-json/structs.rs diff --git a/src/bootstrap/builder.rs b/src/bootstrap/builder.rs index b5bbb6372ee..6d97943548d 100644 --- a/src/bootstrap/builder.rs +++ b/src/bootstrap/builder.rs @@ -425,6 +425,7 @@ impl<'a> Builder<'a> { test::RustdocJSNotStd, test::RustdocTheme, test::RustdocUi, + test::RustdocJson, // Run bootstrap close to the end as it's unlikely to fail test::Bootstrap, // Run run-make last, since these won't pass without make on Windows diff --git a/src/bootstrap/test.rs b/src/bootstrap/test.rs index 1df50322a07..78b5de7897d 100644 --- a/src/bootstrap/test.rs +++ b/src/bootstrap/test.rs @@ -904,6 +904,12 @@ host_test!(UiFullDeps { path: "src/test/ui-fulldeps", mode: "ui", suite: "ui-ful host_test!(Rustdoc { path: "src/test/rustdoc", mode: "rustdoc", suite: "rustdoc" }); host_test!(RustdocUi { path: "src/test/rustdoc-ui", mode: "ui", suite: "rustdoc-ui" }); +host_test!(RustdocJson { + path: "src/test/rustdoc-json", + mode: "rustdoc-json", + suite: "rustdoc-json" +}); + host_test!(Pretty { path: "src/test/pretty", mode: "pretty", suite: "pretty" }); default_test!(RunMake { path: "src/test/run-make", mode: "run-make", suite: "run-make" }); @@ -1001,6 +1007,7 @@ note: if you're sure you want to do this, please open an issue as to why. In the || (mode == "run-make" && suite.ends_with("fulldeps")) || (mode == "ui" && is_rustdoc) || mode == "js-doc-test" + || mode == "rustdoc-json" { cmd.arg("--rustdoc-path").arg(builder.rustdoc(compiler)); } diff --git a/src/test/rustdoc-json/Makefile b/src/test/rustdoc-json/Makefile new file mode 100644 index 00000000000..ad517ae95eb --- /dev/null +++ b/src/test/rustdoc-json/Makefile @@ -0,0 +1,6 @@ +-include ../tools.mk + +tests: *.rs + $(RUSTDOC) $< -o $(TMPDIR) --output-format json + $(PYTHON) check_missing_items.py $(TMPDIR)/$(basename $<).json + $(PYTHON) compare.py $(basename $<).expected $(TMPDIR)/$(basename $<).json diff --git a/src/test/rustdoc-json/check_missing_items.py b/src/test/rustdoc-json/check_missing_items.py new file mode 100644 index 00000000000..3a3bf7fa3ed --- /dev/null +++ b/src/test/rustdoc-json/check_missing_items.py @@ -0,0 +1,187 @@ +#!/usr/bin/env python + +# This test ensures that every ID in the produced json actually resolves to an item either in +# `index` or `paths`. It DOES NOT check that the structure of the produced json is actually in +# any way correct, for example an empty map would pass. + +import sys +import json + +crate = json.load(open(sys.argv[1])) + + +def get_local_item(item_id): + if item_id in crate["index"]: + return crate["index"][item_id] + print("Missing local ID:", item_id) + sys.exit(1) + + +# local IDs have to be in `index`, external ones can sometimes be in `index` but otherwise have +# to be in `paths` +def valid_id(item_id): + return item_id in crate["index"] or item_id[0] != "0" and item_id in crate["paths"] + + +def check_generics(generics): + for param in generics["params"]: + check_generic_param(param) + for where_predicate in generics["where_predicates"]: + if "bound_predicate" in where_predicate: + pred = where_predicate["bound_predicate"] + check_type(pred["ty"]) + for bound in pred["bounds"]: + check_generic_bound(bound) + elif "region_predicate" in where_predicate: + pred = where_predicate["region_predicate"] + for bound in pred["bounds"]: + check_generic_bound(bound) + elif "eq_predicate" in where_predicate: + pred = where_predicate["eq_predicate"] + check_type(pred["rhs"]) + check_type(pred["lhs"]) + + +def check_generic_param(param): + if "type" in param["kind"]: + ty = param["kind"]["type"] + if ty["default"]: + check_type(ty["default"]) + for bound in ty["bounds"]: + check_generic_bound(bound) + elif "const" in param["kind"]: + check_type(param["kind"]["const"]) + + +def check_generic_bound(bound): + if "trait_bound" in bound: + for param in bound["trait_bound"]["generic_params"]: + check_generic_param(param) + check_type(bound["trait_bound"]["trait"]) + + +def check_decl(decl): + for (_name, ty) in decl["inputs"]: + check_type(ty) + if decl["output"]: + check_type(decl["output"]) + + +def check_type(ty): + if ty["kind"] == "resolved_path": + for bound in ty["inner"]["param_names"]: + check_generic_bound(bound) + args = ty["inner"]["args"] + if args: + if "angle_bracketed" in args: + for arg in args["angle_bracketed"]["args"]: + if "type" in arg: + check_type(arg["type"]) + elif "const" in arg: + check_type(arg["const"]["type"]) + for binding in args["angle_bracketed"]["bindings"]: + if "equality" in binding["binding"]: + check_type(binding["binding"]["equality"]) + elif "constraint" in binding["binding"]: + for bound in binding["binding"]["constraint"]: + check_generic_bound(bound) + elif "parenthesized" in args: + for ty in args["parenthesized"]["inputs"]: + check_type(ty) + if args["parenthesized"]["output"]: + check_type(args["parenthesized"]["output"]) + if not valid_id(ty["inner"]["id"]): + print("Type contained an invalid ID:", ty["inner"]["id"]) + sys.exit(1) + elif ty["kind"] == "tuple": + for ty in ty["inner"]: + check_type(ty) + elif ty["kind"] == "slice": + check_type(ty["inner"]) + elif ty["kind"] == "impl_trait": + for bound in ty["inner"]: + check_generic_bound(bound) + elif ty["kind"] in ("raw_pointer", "borrowed_ref", "array"): + check_type(ty["inner"]["type"]) + elif ty["kind"] == "function_pointer": + for param in ty["inner"]["generic_params"]: + check_generic_param(param) + check_decl(ty["inner"]["inner"]) + elif ty["kind"] == "qualified_path": + check_type(ty["inner"]["self_type"]) + check_type(ty["inner"]["trait"]) + + +work_list = set([crate["root"]]) +visited = work_list.copy() + +while work_list: + current = work_list.pop() + visited.add(current) + item = get_local_item(current) + # check intradoc links + for (_name, link) in item["links"].items(): + if not valid_id(link): + print("Intra-doc link contains invalid ID:", link) + + # check all fields that reference types such as generics as well as nested items + # (modules, structs, traits, and enums) + if item["kind"] == "module": + work_list |= set(item["inner"]["items"]) - visited + elif item["kind"] == "struct": + check_generics(item["inner"]["generics"]) + work_list |= ( + set(item["inner"]["fields"]) | set(item["inner"]["impls"]) + ) - visited + elif item["kind"] == "struct_field": + check_type(item["inner"]) + elif item["kind"] == "enum": + check_generics(item["inner"]["generics"]) + work_list |= ( + set(item["inner"]["variants"]) | set(item["inner"]["impls"]) + ) - visited + elif item["kind"] == "variant": + if item["inner"]["variant_kind"] == "tuple": + for ty in item["inner"]["variant_inner"]: + check_type(ty) + elif item["inner"]["variant_kind"] == "struct": + work_list |= set(item["inner"]["variant_inner"]) - visited + elif item["kind"] in ("function", "method"): + check_generics(item["inner"]["generics"]) + check_decl(item["inner"]["decl"]) + elif item["kind"] in ("static", "constant", "assoc_const"): + check_type(item["inner"]["type"]) + elif item["kind"] == "typedef": + check_type(item["inner"]["type"]) + check_generics(item["inner"]["generics"]) + elif item["kind"] == "opaque_ty": + check_generics(item["inner"]["generics"]) + for bound in item["inner"]["bounds"]: + check_generic_bound(bound) + elif item["kind"] == "trait_alias": + check_generics(item["inner"]["params"]) + for bound in item["inner"]["bounds"]: + check_generic_bound(bound) + elif item["kind"] == "trait": + check_generics(item["inner"]["generics"]) + for bound in item["inner"]["bounds"]: + check_generic_bound(bound) + work_list |= ( + set(item["inner"]["items"]) | set(item["inner"]["implementors"]) + ) - visited + elif item["kind"] == "impl": + check_generics(item["inner"]["generics"]) + if item["inner"]["trait"]: + check_type(item["inner"]["trait"]) + if item["inner"]["blanket_impl"]: + check_type(item["inner"]["blanket_impl"]) + check_type(item["inner"]["for"]) + for assoc_item in item["inner"]["items"]: + if not valid_id(assoc_item): + print("Impl block referenced a missing ID:", assoc_item) + sys.exit(1) + elif item["kind"] == "assoc_type": + for bound in item["inner"]["bounds"]: + check_generic_bound(bound) + if item["inner"]["default"]: + check_type(item["inner"]["default"]) diff --git a/src/test/rustdoc-json/compare.py b/src/test/rustdoc-json/compare.py new file mode 100644 index 00000000000..afc8066685c --- /dev/null +++ b/src/test/rustdoc-json/compare.py @@ -0,0 +1,131 @@ +#!/usr/bin/env python + +# This script can check that an expected json blob is a subset of what actually gets produced. +# The comparison is independent of the value of IDs (which are unstable) and instead uses their +# relative ordering to check them against eachother by looking them up in their respective blob's +# `index` or `paths` mappings. To add a new test run `rustdoc --output-format json -o . yourtest.rs` +# and then create `yourtest.expected` by stripping unnecessary details from `yourtest.json`. + +import copy +import sys +import json +import types + +# Used instead of the string ids when used as references. +# Not used as keys in `index` or `paths` +class ID(str): + pass + + +class SubsetException(Exception): + def __init__(self, msg, trace): + self.msg = msg + self.trace = msg + super().__init__("{}: {}".format(trace, msg)) + + +def check_subset(expected_main, actual_main, base_dir): + expected_index = expected_main["index"] + expected_paths = expected_main["paths"] + actual_index = actual_main["index"] + actual_paths = actual_main["paths"] + already_checked = set() + + def _check_subset(expected, actual, trace): + expected_type = type(expected) + actual_type = type(actual) + if expected_type is not actual_type: + raise SubsetException( + "expected type `{}`, got `{}`".format(expected_type, actual_type), trace + ) + if expected_type in (str, int, bool) and expected != actual: + if expected_type == str and actual.startswith(base_dir): + if actual.replace(base_dir + "/", "") != expected: + raise SubsetException( + "expected `{}`, got: `{}`".format( + expected, actual.replace(base_dir + "/", "") + ), + trace, + ) + else: + raise SubsetException( + "expected `{}`, got: `{}`".format(expected, actual), trace + ) + if expected_type is dict: + for key in expected: + if key not in actual: + raise SubsetException( + "Key `{}` not found in output".format(key), trace + ) + new_trace = copy.deepcopy(trace) + new_trace.append(key) + _check_subset(expected[key], actual[key], new_trace) + elif expected_type is list: + expected_elements = len(expected) + actual_elements = len(actual) + if expected_elements != actual_elements: + raise SubsetException( + "Found {} items, expected {}".format( + expected_elements, actual_elements + ), + trace, + ) + for expected, actual in zip(expected, actual): + new_trace = copy.deepcopy(trace) + new_trace.append(expected) + _check_subset(expected, actual, new_trace) + elif expected_type is ID and expected not in already_checked: + already_checked.add(expected) + _check_subset( + expected_index.get(expected, {}), actual_index.get(actual, {}), trace + ) + _check_subset( + expected_paths.get(expected, {}), actual_paths.get(actual, {}), trace + ) + + _check_subset(expected_main["root"], actual_main["root"], []) + + +def rustdoc_object_hook(obj): + # No need to convert paths, index and external_crates keys to ids, since + # they are the target of resolution, and never a source itself. + if "id" in obj and obj["id"]: + obj["id"] = ID(obj["id"]) + if "root" in obj: + obj["root"] = ID(obj["root"]) + if "items" in obj: + obj["items"] = [ID(id) for id in obj["items"]] + if "variants" in obj: + obj["variants"] = [ID(id) for id in obj["variants"]] + if "fields" in obj: + obj["fields"] = [ID(id) for id in obj["fields"]] + if "impls" in obj: + obj["impls"] = [ID(id) for id in obj["impls"]] + if "implementors" in obj: + obj["implementors"] = [ID(id) for id in obj["implementors"]] + if "links" in obj: + obj["links"] = {s: ID(id) for s, id in obj["links"]} + if "variant_kind" in obj and obj["variant_kind"] == "struct": + obj["variant_inner"] = [ID(id) for id in obj["variant_inner"]] + return obj + + +def main(expected_fpath, actual_fpath, base_dir): + print( + "checking that {} is a logical subset of {}".format( + expected_fpath, actual_fpath + ) + ) + with open(expected_fpath) as expected_file: + expected_main = json.load(expected_file, object_hook=rustdoc_object_hook) + with open(actual_fpath) as actual_file: + actual_main = json.load(actual_file, object_hook=rustdoc_object_hook) + check_subset(expected_main, actual_main, base_dir) + print("all checks passed") + + +if __name__ == "__main__": + if len(sys.argv) < 4: + print("Usage: `compare.py expected.json actual.json test-dir`") + else: + main(sys.argv[1], sys.argv[2], sys.argv[3]) diff --git a/src/test/rustdoc-json/structs.expected b/src/test/rustdoc-json/structs.expected new file mode 100644 index 00000000000..45b23534bc7 --- /dev/null +++ b/src/test/rustdoc-json/structs.expected @@ -0,0 +1,468 @@ +{ + "root": "0:0", + "version": null, + "includes_private": false, + "index": { + "0:9": { + "crate_id": 0, + "name": "Unit", + "source": { + "filename": "structs.rs", + "begin": [ + 7, + 0 + ], + "end": [ + 7, + 16 + ] + }, + "visibility": "public", + "docs": "", + "links": {}, + "attrs": [], + "deprecation": null, + "kind": "struct", + "inner": { + "struct_type": "unit", + "generics": { + "params": [], + "where_predicates": [] + }, + "fields_stripped": false, + "fields": [] + } + }, + "0:8": { + "crate_id": 0, + "name": "1", + "source": { + "filename": "structs.rs", + "begin": [ + 5, + 22 + ], + "end": [ + 5, + 28 + ] + }, + "visibility": "default", + "docs": "", + "links": {}, + "attrs": [], + "deprecation": null, + "kind": "struct_field", + "inner": { + "kind": "resolved_path", + "inner": { + "name": "String", + "id": "5:5035", + "args": { + "angle_bracketed": { + "args": [], + "bindings": [] + } + }, + "param_names": [] + } + } + }, + "0:18": { + "crate_id": 0, + "name": "stuff", + "source": { + "filename": "structs.rs", + "begin": [ + 15, + 4 + ], + "end": [ + 15, + 17 + ] + }, + "visibility": "default", + "docs": "", + "links": {}, + "attrs": [], + "deprecation": null, + "kind": "struct_field", + "inner": { + "kind": "resolved_path", + "inner": { + "name": "Vec", + "id": "5:4322", + "args": { + "angle_bracketed": { + "args": [ + { + "type": { + "kind": "generic", + "inner": "T" + } + } + ], + "bindings": [] + } + }, + "param_names": [] + } + } + }, + "0:11": { + "crate_id": 0, + "name": "WithPrimitives", + "source": { + "filename": "structs.rs", + "begin": [ + 9, + 0 + ], + "end": [ + 12, + 1 + ] + }, + "visibility": "public", + "docs": "", + "links": {}, + "attrs": [], + "deprecation": null, + "kind": "struct", + "inner": { + "struct_type": "plain", + "generics": { + "params": [ + { + "name": "'a", + "kind": "lifetime" + } + ], + "where_predicates": [] + }, + "fields_stripped": true, + "fields": [ + "0:13", + "0:14" + ] + } + }, + "0:14": { + "crate_id": 0, + "name": "s", + "source": { + "filename": "structs.rs", + "begin": [ + 11, + 4 + ], + "end": [ + 11, + 14 + ] + }, + "visibility": "default", + "docs": "", + "links": {}, + "attrs": [], + "deprecation": null, + "kind": "struct_field", + "inner": { + "kind": "borrowed_ref", + "inner": { + "lifetime": "'a", + "mutable": false, + "type": { + "kind": "primitive", + "inner": "str" + } + } + } + }, + "0:19": { + "crate_id": 0, + "name": "things", + "source": { + "filename": "structs.rs", + "begin": [ + 16, + 4 + ], + "end": [ + 16, + 25 + ] + }, + "visibility": "default", + "docs": "", + "links": {}, + "attrs": [], + "deprecation": null, + "kind": "struct_field", + "inner": { + "kind": "resolved_path", + "inner": { + "name": "HashMap", + "id": "1:6600", + "args": { + "angle_bracketed": { + "args": [ + { + "type": { + "kind": "generic", + "inner": "U" + } + }, + { + "type": { + "kind": "generic", + "inner": "U" + } + } + ], + "bindings": [] + } + }, + "param_names": [] + } + } + }, + "0:15": { + "crate_id": 0, + "name": "WithGenerics", + "source": { + "filename": "structs.rs", + "begin": [ + 14, + 0 + ], + "end": [ + 17, + 1 + ] + }, + "visibility": "public", + "docs": "", + "links": {}, + "attrs": [], + "deprecation": null, + "kind": "struct", + "inner": { + "struct_type": "plain", + "generics": { + "params": [ + { + "name": "T", + "kind": { + "type": { + "bounds": [], + "default": null + } + } + }, + { + "name": "U", + "kind": { + "type": { + "bounds": [], + "default": null + } + } + } + ], + "where_predicates": [] + }, + "fields_stripped": true, + "fields": [ + "0:18", + "0:19" + ] + } + }, + "0:0": { + "crate_id": 0, + "name": "structs", + "source": { + "filename": "structs.rs", + "begin": [ + 1, + 0 + ], + "end": [ + 17, + 1 + ] + }, + "visibility": "public", + "docs": "", + "links": {}, + "attrs": [], + "deprecation": null, + "kind": "module", + "inner": { + "is_crate": true, + "items": [ + "0:4", + "0:5", + "0:9", + "0:11", + "0:15" + ] + } + }, + "0:13": { + "crate_id": 0, + "name": "num", + "source": { + "filename": "structs.rs", + "begin": [ + 10, + 4 + ], + "end": [ + 10, + 12 + ] + }, + "visibility": "default", + "docs": "", + "links": {}, + "attrs": [], + "deprecation": null, + "kind": "struct_field", + "inner": { + "kind": "primitive", + "inner": "u32" + } + }, + "0:5": { + "crate_id": 0, + "name": "Tuple", + "source": { + "filename": "structs.rs", + "begin": [ + 5, + 0 + ], + "end": [ + 5, + 30 + ] + }, + "visibility": "public", + "docs": "", + "links": {}, + "attrs": [], + "deprecation": null, + "kind": "struct", + "inner": { + "struct_type": "tuple", + "generics": { + "params": [], + "where_predicates": [] + }, + "fields_stripped": true, + "fields": [ + "0:7", + "0:8" + ] + } + }, + "0:4": { + "crate_id": 0, + "name": "PlainEmpty", + "source": { + "filename": "structs.rs", + "begin": [ + 3, + 0 + ], + "end": [ + 3, + 24 + ] + }, + "visibility": "public", + "docs": "", + "links": {}, + "attrs": [], + "deprecation": null, + "kind": "struct", + "inner": { + "struct_type": "plain", + "generics": { + "params": [], + "where_predicates": [] + }, + "fields_stripped": false, + "fields": [] + } + }, + "0:7": { + "crate_id": 0, + "name": "0", + "source": { + "filename": "structs.rs", + "begin": [ + 5, + 17 + ], + "end": [ + 5, + 20 + ] + }, + "visibility": "default", + "docs": "", + "links": {}, + "attrs": [], + "deprecation": null, + "kind": "struct_field", + "inner": { + "kind": "primitive", + "inner": "u32" + } + } + }, + "paths": { + "5:4322": { + "crate_id": 5, + "path": [ + "alloc", + "vec", + "Vec" + ], + "kind": "struct" + }, + "5:5035": { + "crate_id": 5, + "path": [ + "alloc", + "string", + "String" + ], + "kind": "struct" + }, + "1:6600": { + "crate_id": 1, + "path": [ + "std", + "collections", + "hash", + "map", + "HashMap" + ], + "kind": "struct" + } + }, + "external_crates": { + "1": { + "name": "std" + }, + "5": { + "name": "alloc" + } + }, + "format_version": 1 +} diff --git a/src/test/rustdoc-json/structs.rs b/src/test/rustdoc-json/structs.rs new file mode 100644 index 00000000000..43fc4743503 --- /dev/null +++ b/src/test/rustdoc-json/structs.rs @@ -0,0 +1,17 @@ +use std::collections::HashMap; + +pub struct PlainEmpty {} + +pub struct Tuple(u32, String); + +pub struct Unit; + +pub struct WithPrimitives<'a> { + num: u32, + s: &'a str, +} + +pub struct WithGenerics { + stuff: Vec, + things: HashMap, +} diff --git a/src/test/rustdoc/rustdoc-json/Makefile b/src/test/rustdoc/rustdoc-json/Makefile deleted file mode 100644 index ad517ae95eb..00000000000 --- a/src/test/rustdoc/rustdoc-json/Makefile +++ /dev/null @@ -1,6 +0,0 @@ --include ../tools.mk - -tests: *.rs - $(RUSTDOC) $< -o $(TMPDIR) --output-format json - $(PYTHON) check_missing_items.py $(TMPDIR)/$(basename $<).json - $(PYTHON) compare.py $(basename $<).expected $(TMPDIR)/$(basename $<).json diff --git a/src/test/rustdoc/rustdoc-json/check_missing_items.py b/src/test/rustdoc/rustdoc-json/check_missing_items.py deleted file mode 100644 index 0004dc8fb14..00000000000 --- a/src/test/rustdoc/rustdoc-json/check_missing_items.py +++ /dev/null @@ -1,181 +0,0 @@ -#!/usr/bin/env python - -# This test ensures that every ID in the produced json actually resolves to an item either in -# `index` or `paths`. It DOES NOT check that the structure of the produced json is actually in -# any way correct, for example an empty map would pass. - -import sys -import json - -crate = json.load(open(sys.argv[1])) - - -def get_local_item(item_id): - if item_id in crate["index"]: - return crate["index"][item_id] - print("Missing local ID:", item_id) - sys.exit(1) - - -# local IDs have to be in `index`, external ones can sometimes be in `index` but otherwise have -# to be in `paths` -def valid_id(item_id): - return item_id in crate["index"] or item_id[0] != "0" and item_id in crate["paths"] - - -def check_generics(generics): - for param in generics["params"]: - check_generic_param(param) - for where_predicate in generics["where_predicates"]: - if "bound_predicate" in where_predicate: - pred = where_predicate["bound_predicate"] - check_type(pred["ty"]) - for bound in pred["bounds"]: - check_generic_bound(bound) - elif "region_predicate" in where_predicate: - pred = where_predicate["region_predicate"] - for bound in pred["bounds"]: - check_generic_bound(bound) - elif "eq_predicate" in where_predicate: - pred = where_predicate["eq_predicate"] - check_type(pred["rhs"]) - check_type(pred["lhs"]) - - -def check_generic_param(param): - if "type" in param["kind"]: - ty = param["kind"]["type"] - if ty["default"]: - check_type(ty["default"]) - for bound in ty["bounds"]: - check_generic_bound(bound) - elif "const" in param["kind"]: - check_type(param["kind"]["const"]) - - -def check_generic_bound(bound): - if "trait_bound" in bound: - for param in bound["trait_bound"]["generic_params"]: - check_generic_param(param) - check_type(bound["trait_bound"]["trait"]) - - -def check_decl(decl): - for (_name, ty) in decl["inputs"]: - check_type(ty) - if decl["output"]: - check_type(decl["output"]) - - -def check_type(ty): - if ty["kind"] == "resolved_path": - for bound in ty["inner"]["param_names"]: - check_generic_bound(bound) - args = ty["inner"]["args"] - if args: - if "angle_bracketed" in args: - for arg in args["angle_bracketed"]["args"]: - if "type" in arg: - check_type(arg["type"]) - elif "const" in arg: - check_type(arg["const"]["type"]) - for binding in args["angle_bracketed"]["bindings"]: - if "equality" in binding["binding"]: - check_type(binding["binding"]["equality"]) - elif "constraint" in binding["binding"]: - for bound in binding["binding"]["constraint"]: - check_generic_bound(bound) - elif "parenthesized" in args: - for ty in args["parenthesized"]["inputs"]: - check_type(ty) - if args["parenthesized"]["output"]: - check_type(args["parenthesized"]["output"]) - if not valid_id(ty["inner"]["id"]): - print("Type contained an invalid ID:", ty["inner"]["id"]) - sys.exit(1) - elif ty["kind"] == "tuple": - for ty in ty["inner"]: - check_type(ty) - elif ty["kind"] == "slice": - check_type(ty["inner"]) - elif ty["kind"] == "impl_trait": - for bound in ty["inner"]: - check_generic_bound(bound) - elif ty["kind"] in ("raw_pointer", "borrowed_ref", "array"): - check_type(ty["inner"]["type"]) - elif ty["kind"] == "function_pointer": - for param in ty["inner"]["generic_params"]: - check_generic_param(param) - check_decl(ty["inner"]["inner"]) - elif ty["kind"] == "qualified_path": - check_type(ty["inner"]["self_type"]) - check_type(ty["inner"]["trait"]) - - -work_list = set([crate["root"]]) -visited = work_list.copy() - -while work_list: - current = work_list.pop() - visited.add(current) - item = get_local_item(current) - # check intradoc links - for (_name, link) in item["links"].items(): - if not valid_id(link): - print("Intra-doc link contains invalid ID:", link) - - # check all fields that reference types such as generics as well as nested items - # (modules, structs, traits, and enums) - if item["kind"] == "module": - work_list |= set(item["inner"]["items"]) - visited - elif item["kind"] == "struct": - check_generics(item["inner"]["generics"]) - work_list |= (set(item["inner"]["fields"]) | set(item["inner"]["impls"])) - visited - elif item["kind"] == "struct_field": - check_type(item["inner"]) - elif item["kind"] == "enum": - check_generics(item["inner"]["generics"]) - work_list |= (set(item["inner"]["variants"]) | set(item["inner"]["impls"])) - visited - elif item["kind"] == "variant": - if item["inner"]["variant_kind"] == "tuple": - for ty in item["inner"]["variant_inner"]: - check_type(ty) - elif item["inner"]["variant_kind"] == "struct": - work_list |= set(item["inner"]["variant_inner"]) - visited - elif item["kind"] in ("function", "method"): - check_generics(item["inner"]["generics"]) - check_decl(item["inner"]["decl"]) - elif item["kind"] in ("static", "constant", "assoc_const"): - check_type(item["inner"]["type"]) - elif item["kind"] == "typedef": - check_type(item["inner"]["type"]) - check_generics(item["inner"]["generics"]) - elif item["kind"] == "opaque_ty": - check_generics(item["inner"]["generics"]) - for bound in item["inner"]["bounds"]: - check_generic_bound(bound) - elif item["kind"] == "trait_alias": - check_generics(item["inner"]["params"]) - for bound in item["inner"]["bounds"]: - check_generic_bound(bound) - elif item["kind"] == "trait": - check_generics(item["inner"]["generics"]) - for bound in item["inner"]["bounds"]: - check_generic_bound(bound) - work_list |= (set(item["inner"]["items"]) | set(item["inner"]["implementors"])) - visited - elif item["kind"] == "impl": - check_generics(item["inner"]["generics"]) - if item["inner"]["trait"]: - check_type(item["inner"]["trait"]) - if item["inner"]["blanket_impl"]: - check_type(item["inner"]["blanket_impl"]) - check_type(item["inner"]["for"]) - for assoc_item in item["inner"]["items"]: - if not valid_id(assoc_item): - print("Impl block referenced a missing ID:", assoc_item) - sys.exit(1) - elif item["kind"] == "assoc_type": - for bound in item["inner"]["bounds"]: - check_generic_bound(bound) - if item["inner"]["default"]: - check_type(item["inner"]["default"]) diff --git a/src/test/rustdoc/rustdoc-json/compare.py b/src/test/rustdoc/rustdoc-json/compare.py deleted file mode 100644 index 5daf8903e20..00000000000 --- a/src/test/rustdoc/rustdoc-json/compare.py +++ /dev/null @@ -1,107 +0,0 @@ -#!/usr/bin/env python - -# This script can check that an expected json blob is a subset of what actually gets produced. -# The comparison is independent of the value of IDs (which are unstable) and instead uses their -# relative ordering to check them against eachother by looking them up in their respective blob's -# `index` or `paths` mappings. To add a new test run `rustdoc --output-format json -o . yourtest.rs` -# and then create `yourtest.expected` by stripping unnecessary details from `yourtest.json`. - -import copy -import sys -import json -import types - -# Used instead of the string ids when used as references. -# Not used as keys in `index` or `paths` -class ID(str): - pass - - -class SubsetException(Exception): - def __init__(self, msg, trace): - self.msg = msg - self.trace = msg - super().__init__("{}: {}".format(trace, msg)) - - -def check_subset(expected_main, actual_main): - expected_index = expected_main["index"] - expected_paths = expected_main["paths"] - actual_index = actual_main["index"] - actual_paths = actual_main["paths"] - already_checked = set() - - def _check_subset(expected, actual, trace): - expected_type = type(expected) - actual_type = type(actual) - if expected_type is not actual_type: - raise SubsetException( - "expected type `{}`, got `{}`".format(expected_type, actual_type), trace - ) - if expected_type in (str, int, bool) and expected != actual: - raise SubsetException("expected `{}`, got: `{}`".format(expected, actual), trace) - if expected_type is dict: - for key in expected: - if key not in actual: - raise SubsetException("Key `{}` not found in output".format(key), trace) - new_trace = copy.deepcopy(trace) - new_trace.append(key) - _check_subset(expected[key], actual[key], new_trace) - elif expected_type is list: - expected_elements = len(expected) - actual_elements = len(actual) - if expected_elements != actual_elements: - raise SubsetException( - "Found {} items, expected {}".format(expected_elements, actual_elements), trace - ) - for expected, actual in zip(expected, actual): - new_trace = copy.deepcopy(trace) - new_trace.append(expected) - _check_subset(expected, actual, new_trace) - elif expected_type is ID and expected not in already_checked: - already_checked.add(expected) - _check_subset(expected_index.get(expected, {}), actual_index.get(actual, {}), trace) - _check_subset(expected_paths.get(expected, {}), actual_paths.get(actual, {}), trace) - - _check_subset(expected_main["root"], actual_main["root"], []) - - -def rustdoc_object_hook(obj): - # No need to convert paths, index and external_crates keys to ids, since - # they are the target of resolution, and never a source itself. - if "id" in obj and obj["id"]: - obj["id"] = ID(obj["id"]) - if "root" in obj: - obj["root"] = ID(obj["root"]) - if "items" in obj: - obj["items"] = [ID(id) for id in obj["items"]] - if "variants" in obj: - obj["variants"] = [ID(id) for id in obj["variants"]] - if "fields" in obj: - obj["fields"] = [ID(id) for id in obj["fields"]] - if "impls" in obj: - obj["impls"] = [ID(id) for id in obj["impls"]] - if "implementors" in obj: - obj["implementors"] = [ID(id) for id in obj["implementors"]] - if "links" in obj: - obj["links"] = {s: ID(id) for s, id in obj["links"]} - if "variant_kind" in obj and obj["variant_kind"] == "struct": - obj["variant_inner"] = [ID(id) for id in obj["variant_inner"]] - return obj - - -def main(expected_fpath, actual_fpath): - print("checking that {} is a logical subset of {}".format(expected_fpath, actual_fpath)) - with open(expected_fpath) as expected_file: - expected_main = json.load(expected_file, object_hook=rustdoc_object_hook) - with open(actual_fpath) as actual_file: - actual_main = json.load(actual_file, object_hook=rustdoc_object_hook) - check_subset(expected_main, actual_main) - print("all checks passed") - - -if __name__ == "__main__": - if len(sys.argv) < 3: - print("Usage: `compare.py expected.json actual.json`") - else: - main(sys.argv[1], sys.argv[2]) diff --git a/src/test/rustdoc/rustdoc-json/structs.expected b/src/test/rustdoc/rustdoc-json/structs.expected deleted file mode 100644 index 45b23534bc7..00000000000 --- a/src/test/rustdoc/rustdoc-json/structs.expected +++ /dev/null @@ -1,468 +0,0 @@ -{ - "root": "0:0", - "version": null, - "includes_private": false, - "index": { - "0:9": { - "crate_id": 0, - "name": "Unit", - "source": { - "filename": "structs.rs", - "begin": [ - 7, - 0 - ], - "end": [ - 7, - 16 - ] - }, - "visibility": "public", - "docs": "", - "links": {}, - "attrs": [], - "deprecation": null, - "kind": "struct", - "inner": { - "struct_type": "unit", - "generics": { - "params": [], - "where_predicates": [] - }, - "fields_stripped": false, - "fields": [] - } - }, - "0:8": { - "crate_id": 0, - "name": "1", - "source": { - "filename": "structs.rs", - "begin": [ - 5, - 22 - ], - "end": [ - 5, - 28 - ] - }, - "visibility": "default", - "docs": "", - "links": {}, - "attrs": [], - "deprecation": null, - "kind": "struct_field", - "inner": { - "kind": "resolved_path", - "inner": { - "name": "String", - "id": "5:5035", - "args": { - "angle_bracketed": { - "args": [], - "bindings": [] - } - }, - "param_names": [] - } - } - }, - "0:18": { - "crate_id": 0, - "name": "stuff", - "source": { - "filename": "structs.rs", - "begin": [ - 15, - 4 - ], - "end": [ - 15, - 17 - ] - }, - "visibility": "default", - "docs": "", - "links": {}, - "attrs": [], - "deprecation": null, - "kind": "struct_field", - "inner": { - "kind": "resolved_path", - "inner": { - "name": "Vec", - "id": "5:4322", - "args": { - "angle_bracketed": { - "args": [ - { - "type": { - "kind": "generic", - "inner": "T" - } - } - ], - "bindings": [] - } - }, - "param_names": [] - } - } - }, - "0:11": { - "crate_id": 0, - "name": "WithPrimitives", - "source": { - "filename": "structs.rs", - "begin": [ - 9, - 0 - ], - "end": [ - 12, - 1 - ] - }, - "visibility": "public", - "docs": "", - "links": {}, - "attrs": [], - "deprecation": null, - "kind": "struct", - "inner": { - "struct_type": "plain", - "generics": { - "params": [ - { - "name": "'a", - "kind": "lifetime" - } - ], - "where_predicates": [] - }, - "fields_stripped": true, - "fields": [ - "0:13", - "0:14" - ] - } - }, - "0:14": { - "crate_id": 0, - "name": "s", - "source": { - "filename": "structs.rs", - "begin": [ - 11, - 4 - ], - "end": [ - 11, - 14 - ] - }, - "visibility": "default", - "docs": "", - "links": {}, - "attrs": [], - "deprecation": null, - "kind": "struct_field", - "inner": { - "kind": "borrowed_ref", - "inner": { - "lifetime": "'a", - "mutable": false, - "type": { - "kind": "primitive", - "inner": "str" - } - } - } - }, - "0:19": { - "crate_id": 0, - "name": "things", - "source": { - "filename": "structs.rs", - "begin": [ - 16, - 4 - ], - "end": [ - 16, - 25 - ] - }, - "visibility": "default", - "docs": "", - "links": {}, - "attrs": [], - "deprecation": null, - "kind": "struct_field", - "inner": { - "kind": "resolved_path", - "inner": { - "name": "HashMap", - "id": "1:6600", - "args": { - "angle_bracketed": { - "args": [ - { - "type": { - "kind": "generic", - "inner": "U" - } - }, - { - "type": { - "kind": "generic", - "inner": "U" - } - } - ], - "bindings": [] - } - }, - "param_names": [] - } - } - }, - "0:15": { - "crate_id": 0, - "name": "WithGenerics", - "source": { - "filename": "structs.rs", - "begin": [ - 14, - 0 - ], - "end": [ - 17, - 1 - ] - }, - "visibility": "public", - "docs": "", - "links": {}, - "attrs": [], - "deprecation": null, - "kind": "struct", - "inner": { - "struct_type": "plain", - "generics": { - "params": [ - { - "name": "T", - "kind": { - "type": { - "bounds": [], - "default": null - } - } - }, - { - "name": "U", - "kind": { - "type": { - "bounds": [], - "default": null - } - } - } - ], - "where_predicates": [] - }, - "fields_stripped": true, - "fields": [ - "0:18", - "0:19" - ] - } - }, - "0:0": { - "crate_id": 0, - "name": "structs", - "source": { - "filename": "structs.rs", - "begin": [ - 1, - 0 - ], - "end": [ - 17, - 1 - ] - }, - "visibility": "public", - "docs": "", - "links": {}, - "attrs": [], - "deprecation": null, - "kind": "module", - "inner": { - "is_crate": true, - "items": [ - "0:4", - "0:5", - "0:9", - "0:11", - "0:15" - ] - } - }, - "0:13": { - "crate_id": 0, - "name": "num", - "source": { - "filename": "structs.rs", - "begin": [ - 10, - 4 - ], - "end": [ - 10, - 12 - ] - }, - "visibility": "default", - "docs": "", - "links": {}, - "attrs": [], - "deprecation": null, - "kind": "struct_field", - "inner": { - "kind": "primitive", - "inner": "u32" - } - }, - "0:5": { - "crate_id": 0, - "name": "Tuple", - "source": { - "filename": "structs.rs", - "begin": [ - 5, - 0 - ], - "end": [ - 5, - 30 - ] - }, - "visibility": "public", - "docs": "", - "links": {}, - "attrs": [], - "deprecation": null, - "kind": "struct", - "inner": { - "struct_type": "tuple", - "generics": { - "params": [], - "where_predicates": [] - }, - "fields_stripped": true, - "fields": [ - "0:7", - "0:8" - ] - } - }, - "0:4": { - "crate_id": 0, - "name": "PlainEmpty", - "source": { - "filename": "structs.rs", - "begin": [ - 3, - 0 - ], - "end": [ - 3, - 24 - ] - }, - "visibility": "public", - "docs": "", - "links": {}, - "attrs": [], - "deprecation": null, - "kind": "struct", - "inner": { - "struct_type": "plain", - "generics": { - "params": [], - "where_predicates": [] - }, - "fields_stripped": false, - "fields": [] - } - }, - "0:7": { - "crate_id": 0, - "name": "0", - "source": { - "filename": "structs.rs", - "begin": [ - 5, - 17 - ], - "end": [ - 5, - 20 - ] - }, - "visibility": "default", - "docs": "", - "links": {}, - "attrs": [], - "deprecation": null, - "kind": "struct_field", - "inner": { - "kind": "primitive", - "inner": "u32" - } - } - }, - "paths": { - "5:4322": { - "crate_id": 5, - "path": [ - "alloc", - "vec", - "Vec" - ], - "kind": "struct" - }, - "5:5035": { - "crate_id": 5, - "path": [ - "alloc", - "string", - "String" - ], - "kind": "struct" - }, - "1:6600": { - "crate_id": 1, - "path": [ - "std", - "collections", - "hash", - "map", - "HashMap" - ], - "kind": "struct" - } - }, - "external_crates": { - "1": { - "name": "std" - }, - "5": { - "name": "alloc" - } - }, - "format_version": 1 -} diff --git a/src/test/rustdoc/rustdoc-json/structs.rs b/src/test/rustdoc/rustdoc-json/structs.rs deleted file mode 100644 index 43fc4743503..00000000000 --- a/src/test/rustdoc/rustdoc-json/structs.rs +++ /dev/null @@ -1,17 +0,0 @@ -use std::collections::HashMap; - -pub struct PlainEmpty {} - -pub struct Tuple(u32, String); - -pub struct Unit; - -pub struct WithPrimitives<'a> { - num: u32, - s: &'a str, -} - -pub struct WithGenerics { - stuff: Vec, - things: HashMap, -} diff --git a/src/tools/compiletest/src/common.rs b/src/tools/compiletest/src/common.rs index 24ef98cd784..eba02333c8c 100644 --- a/src/tools/compiletest/src/common.rs +++ b/src/tools/compiletest/src/common.rs @@ -17,6 +17,7 @@ pub enum Mode { DebugInfo, Codegen, Rustdoc, + RustdocJson, CodegenUnits, Incremental, RunMake, @@ -48,6 +49,7 @@ impl FromStr for Mode { "debuginfo" => Ok(DebugInfo), "codegen" => Ok(Codegen), "rustdoc" => Ok(Rustdoc), + "rustdoc-json" => Ok(RustdocJson), "codegen-units" => Ok(CodegenUnits), "incremental" => Ok(Incremental), "run-make" => Ok(RunMake), @@ -70,6 +72,7 @@ impl fmt::Display for Mode { DebugInfo => "debuginfo", Codegen => "codegen", Rustdoc => "rustdoc", + RustdocJson => "rustdoc-json", CodegenUnits => "codegen-units", Incremental => "incremental", RunMake => "run-make", diff --git a/src/tools/compiletest/src/main.rs b/src/tools/compiletest/src/main.rs index 32347db5dbb..0541548aefd 100644 --- a/src/tools/compiletest/src/main.rs +++ b/src/tools/compiletest/src/main.rs @@ -68,7 +68,7 @@ pub fn parse_config(args: Vec) -> Config { "mode", "which sort of compile tests to run", "compile-fail | run-fail | run-pass-valgrind | pretty | debug-info | codegen | rustdoc \ - codegen-units | incremental | run-make | ui | js-doc-test | mir-opt | assembly", + | rustdoc-json | codegen-units | incremental | run-make | ui | js-doc-test | mir-opt | assembly", ) .reqopt( "", diff --git a/src/tools/compiletest/src/runtest.rs b/src/tools/compiletest/src/runtest.rs index 1b9f0089dce..88cb8544e47 100644 --- a/src/tools/compiletest/src/runtest.rs +++ b/src/tools/compiletest/src/runtest.rs @@ -2,7 +2,7 @@ use crate::common::{expected_output_path, UI_EXTENSIONS, UI_FIXED, UI_STDERR, UI_STDOUT}; use crate::common::{output_base_dir, output_base_name, output_testname_unique}; -use crate::common::{Assembly, Incremental, JsDocTest, MirOpt, RunMake, Ui}; +use crate::common::{Assembly, Incremental, JsDocTest, MirOpt, RunMake, RustdocJson, Ui}; use crate::common::{Codegen, CodegenUnits, DebugInfo, Debugger, Rustdoc}; use crate::common::{CompareMode, FailMode, PassMode}; use crate::common::{CompileFail, Pretty, RunFail, RunPassValgrind}; @@ -342,6 +342,7 @@ impl<'test> TestCx<'test> { DebugInfo => self.run_debuginfo_test(), Codegen => self.run_codegen_test(), Rustdoc => self.run_rustdoc_test(), + RustdocJson => self.run_rustdoc_json_test(), CodegenUnits => self.run_codegen_units_test(), Incremental => self.run_incremental_test(), RunMake => self.run_rmake_test(), @@ -1564,7 +1565,7 @@ impl<'test> TestCx<'test> { self.compose_and_run_compiler(rustc, None) } - fn document(&self, out_dir: &Path) -> ProcRes { + fn document(&self, out_dir: &Path, json: bool) -> ProcRes { if self.props.build_aux_docs { for rel_ab in &self.props.aux_builds { let aux_testpaths = self.compute_aux_test_paths(rel_ab); @@ -1578,7 +1579,7 @@ impl<'test> TestCx<'test> { }; // Create the directory for the stdout/stderr files. create_dir_all(aux_cx.output_base_dir()).unwrap(); - let auxres = aux_cx.document(out_dir); + let auxres = aux_cx.document(out_dir, json); if !auxres.status.success() { return auxres; } @@ -1600,6 +1601,10 @@ impl<'test> TestCx<'test> { .arg(&self.testpaths.file) .args(&self.props.compile_flags); + if json { + rustdoc.arg("--output-format").arg("json"); + } + if let Some(ref linker) = self.config.linker { rustdoc.arg(format!("-Clinker={}", linker)); } @@ -1887,7 +1892,9 @@ impl<'test> TestCx<'test> { } fn is_rustdoc(&self) -> bool { - self.config.src_base.ends_with("rustdoc-ui") || self.config.src_base.ends_with("rustdoc-js") + self.config.src_base.ends_with("rustdoc-ui") + || self.config.src_base.ends_with("rustdoc-js") + || self.config.src_base.ends_with("rustdoc-json") } fn make_compile_args( @@ -1968,8 +1975,8 @@ impl<'test> TestCx<'test> { rustc.arg(dir_opt); } - RunFail | RunPassValgrind | Pretty | DebugInfo | Codegen | Rustdoc | RunMake - | CodegenUnits | JsDocTest | Assembly => { + RunFail | RunPassValgrind | Pretty | DebugInfo | Codegen | Rustdoc | RustdocJson + | RunMake | CodegenUnits | JsDocTest | Assembly => { // do not use JSON output } } @@ -2329,7 +2336,7 @@ impl<'test> TestCx<'test> { let _ = fs::remove_dir_all(&out_dir); create_dir_all(&out_dir).unwrap(); - let proc_res = self.document(&out_dir); + let proc_res = self.document(&out_dir, false); if !proc_res.status.success() { self.fatal_proc_rec("rustdoc failed!", &proc_res); } @@ -2385,7 +2392,7 @@ impl<'test> TestCx<'test> { rustc.arg("-L").arg(&new_rustdoc.aux_output_dir_name()); new_rustdoc.build_all_auxiliary(&mut rustc); - let proc_res = new_rustdoc.document(&compare_dir); + let proc_res = new_rustdoc.document(&compare_dir, false); if !proc_res.status.success() { proc_res.fatal(Some("failed to run nightly rustdoc"), || ()); } @@ -2466,6 +2473,48 @@ impl<'test> TestCx<'test> { eprintln!("{}", String::from_utf8_lossy(&output.stderr)); } + fn run_rustdoc_json_test(&self) { + //FIXME: Add bless option. + + assert!(self.revision.is_none(), "revisions not relevant here"); + + let out_dir = self.output_base_dir(); + let _ = fs::remove_dir_all(&out_dir); + create_dir_all(&out_dir).unwrap(); + + let proc_res = self.document(&out_dir, true); + if !proc_res.status.success() { + self.fatal_proc_rec("rustdoc failed!", &proc_res); + } + + let root = self.config.find_rust_src_root().unwrap(); + let mut json_out = out_dir.join(self.testpaths.file.file_stem().unwrap()); + json_out.set_extension("json"); + let res = self.cmd2procres( + Command::new(&self.config.docck_python) + .arg(root.join("src/test/rustdoc-json/check_missing_items.py")) + .arg(&json_out), + ); + + if !res.status.success() { + self.fatal_proc_rec("check_missing_items failed!", &res); + } + + let mut expected = self.testpaths.file.clone(); + expected.set_extension("expected"); + let res = self.cmd2procres( + Command::new(&self.config.docck_python) + .arg(root.join("src/test/rustdoc-json/compare.py")) + .arg(&expected) + .arg(&json_out) + .arg(&expected.parent().unwrap()), + ); + + if !res.status.success() { + self.fatal_proc_rec("compare failed!", &res); + } + } + fn get_lines>( &self, path: &P, @@ -3003,7 +3052,7 @@ impl<'test> TestCx<'test> { if let Some(nodejs) = &self.config.nodejs { let out_dir = self.output_base_dir(); - self.document(&out_dir); + self.document(&out_dir, false); let root = self.config.find_rust_src_root().unwrap(); let file_stem = -- cgit 1.4.1-3-g733a5