From 66884e318f2edfb0dd66d76a7d3b080d0dd9e4c5 Mon Sep 17 00:00:00 2001 From: Joseph Ryan Date: Mon, 3 Aug 2020 12:45:27 -0500 Subject: Add json backend Respond to comments and start adding tests Fix re-exports and extern-crate Add expected output tests Add restricted paths Format Fix: associated methods missing in output Ignore stripped items Mark stripped items removing them creates dangling references Fix tests and update conversions Don't panic if JSON backend fails to create a file Fix attribute error in JSON testsuite Move rustdoc-json to rustdoc/ This way it doesn't have to build rustc twice. Eventually it should probably get its own suite, like rustdoc-js, but that can be fixed in a follow-up. Small cleanups Don't prettify json before printing This fully halves the size of the emitted JSON. Add comments [BREAKING CHANGE] rename version -> crate_version [BREAKING CHANGE] rename source -> span Use exhaustive matches Don't qualify imports for DefId Rename clean::{ItemEnum -> ItemKind}, clean::Item::{inner -> kind} Fix Visibility conversion clean::Visability was changed here: https://github.com/rust-lang/rust/pull/77820/files#diff-df9f90aae0b7769e1eb6ea6f1d73c1c3f649e1ac48a20e169662a8930eb427beL1467-R1509 More churn catchup Format --- 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 + 5 files changed, 779 insertions(+) create mode 100644 src/test/rustdoc/rustdoc-json/Makefile create mode 100644 src/test/rustdoc/rustdoc-json/check_missing_items.py create mode 100644 src/test/rustdoc/rustdoc-json/compare.py create mode 100644 src/test/rustdoc/rustdoc-json/structs.expected create mode 100644 src/test/rustdoc/rustdoc-json/structs.rs (limited to 'src/test') diff --git a/src/test/rustdoc/rustdoc-json/Makefile b/src/test/rustdoc/rustdoc-json/Makefile new file mode 100644 index 00000000000..ad517ae95eb --- /dev/null +++ b/src/test/rustdoc/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/rustdoc-json/check_missing_items.py b/src/test/rustdoc/rustdoc-json/check_missing_items.py new file mode 100644 index 00000000000..0004dc8fb14 --- /dev/null +++ b/src/test/rustdoc/rustdoc-json/check_missing_items.py @@ -0,0 +1,181 @@ +#!/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 new file mode 100644 index 00000000000..5daf8903e20 --- /dev/null +++ b/src/test/rustdoc/rustdoc-json/compare.py @@ -0,0 +1,107 @@ +#!/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 new file mode 100644 index 00000000000..45b23534bc7 --- /dev/null +++ b/src/test/rustdoc/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/rustdoc-json/structs.rs b/src/test/rustdoc/rustdoc-json/structs.rs new file mode 100644 index 00000000000..43fc4743503 --- /dev/null +++ b/src/test/rustdoc/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, +} -- cgit 1.4.1-3-g733a5 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 (limited to 'src/test') 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 From 40b5470b0db1997fe28bd774171b4f958fa7d240 Mon Sep 17 00:00:00 2001 From: Nixon Enraght-Moony Date: Mon, 30 Nov 2020 20:24:48 +0000 Subject: Address review comments. Go back to CRATE_DEF_INDEX Minor niceness improvements Don't output hidden items Remove striped items from fields Add $TEST_BASE_DIR Small catch --- src/librustdoc/clean/mod.rs | 2 +- src/librustdoc/json/conversions.rs | 76 +++++++++++++++++----------------- src/librustdoc/json/mod.rs | 18 ++++---- src/librustdoc/json/types.rs | 3 -- src/librustdoc/lib.rs | 1 + src/test/rustdoc-json/Makefile | 6 --- src/test/rustdoc-json/compare.py | 21 ++++------ src/test/rustdoc-json/structs.expected | 42 +++++++------------ src/tools/compiletest/src/runtest.rs | 14 +++---- 9 files changed, 80 insertions(+), 103 deletions(-) delete mode 100644 src/test/rustdoc-json/Makefile (limited to 'src/test') diff --git a/src/librustdoc/clean/mod.rs b/src/librustdoc/clean/mod.rs index 9d42705da82..d294d8f02a8 100644 --- a/src/librustdoc/clean/mod.rs +++ b/src/librustdoc/clean/mod.rs @@ -2281,7 +2281,7 @@ impl Clean> for doctree::Import<'_> { name: None, attrs: self.attrs.clean(cx), source: self.span.clean(cx), - def_id: cx.tcx.hir().local_def_id(self.id).to_def_id(), + def_id: DefId::local(CRATE_DEF_INDEX), visibility: self.vis.clean(cx), stability: None, const_stability: None, diff --git a/src/librustdoc/json/conversions.rs b/src/librustdoc/json/conversions.rs index 80192623d54..4d57b337879 100644 --- a/src/librustdoc/json/conversions.rs +++ b/src/librustdoc/json/conversions.rs @@ -12,43 +12,44 @@ use crate::doctree; use crate::formats::item_type::ItemType; use crate::json::types::*; -impl From for Item { +impl From for Option { fn from(item: clean::Item) -> Self { let item_type = ItemType::from(&item); let clean::Item { source, name, attrs, - kind: inner, + kind, visibility, def_id, stability: _, deprecation, } = item; - Item { - id: def_id.into(), - crate_id: def_id.krate.as_u32(), - name, - stripped: match inner { - clean::StrippedItem(_) => true, - _ => false, - }, - source: source.into(), - visibility: visibility.into(), - docs: attrs.collapsed_doc_value().unwrap_or_default(), - links: attrs - .links - .into_iter() - .filter_map(|clean::ItemLink { link, did, .. }| did.map(|did| (link, did.into()))) - .collect(), - attrs: attrs - .other_attrs - .iter() - .map(rustc_ast_pretty::pprust::attribute_to_string) - .collect(), - deprecation: deprecation.map(Into::into), - kind: item_type.into(), - inner: inner.into(), + match kind { + clean::StrippedItem(_) => None, + _ => Some(Item { + id: def_id.into(), + crate_id: def_id.krate.as_u32(), + name, + source: source.into(), + visibility: visibility.into(), + docs: attrs.collapsed_doc_value().unwrap_or_default(), + links: attrs + .links + .into_iter() + .filter_map(|clean::ItemLink { link, did, .. }| { + did.map(|did| (link, did.into())) + }) + .collect(), + attrs: attrs + .other_attrs + .iter() + .map(rustc_ast_pretty::pprust::attribute_to_string) + .collect(), + deprecation: deprecation.map(Into::into), + kind: item_type.into(), + inner: kind.into(), + }), } } } @@ -194,10 +195,7 @@ impl From for ItemEnum { impl From for Module { fn from(module: clean::Module) -> Self { - Module { - is_crate: module.is_crate, - items: module.items.into_iter().map(|i| i.def_id.into()).collect(), - } + Module { is_crate: module.is_crate, items: ids(module.items) } } } @@ -208,7 +206,7 @@ impl From for Struct { struct_type: struct_type.into(), generics: generics.into(), fields_stripped, - fields: fields.into_iter().map(|i| i.def_id.into()).collect(), + fields: ids(fields), impls: Vec::new(), // Added in JsonRenderer::item } } @@ -221,7 +219,7 @@ impl From for Struct { struct_type: struct_type.into(), generics: generics.into(), fields_stripped, - fields: fields.into_iter().map(|i| i.def_id.into()).collect(), + fields: ids(fields), impls: Vec::new(), // Added in JsonRenderer::item } } @@ -407,7 +405,7 @@ impl From for Trait { Trait { is_auto, is_unsafe: unsafety == rustc_hir::Unsafety::Unsafe, - items: items.into_iter().map(|i| i.def_id.into()).collect(), + items: ids(items), generics: generics.into(), bounds: bounds.into_iter().map(Into::into).collect(), implementors: Vec::new(), // Added in JsonRenderer::item @@ -434,7 +432,7 @@ impl From for Impl { provided_trait_methods: provided_trait_methods.into_iter().collect(), trait_: trait_.map(Into::into), for_: for_.into(), - items: items.into_iter().map(|i| i.def_id.into()).collect(), + items: ids(items), negative: polarity == Some(clean::ImplPolarity::Negative), synthetic, blanket_impl: blanket_impl.map(Into::into), @@ -460,7 +458,7 @@ impl From for Enum { Enum { generics: generics.into(), variants_stripped, - variants: variants.into_iter().map(|i| i.def_id.into()).collect(), + variants: ids(variants), impls: Vec::new(), // Added in JsonRenderer::item } } @@ -473,7 +471,7 @@ impl From for Struct { struct_type: struct_type.into(), generics: Default::default(), fields_stripped, - fields: fields.into_iter().map(|i| i.def_id.into()).collect(), + fields: ids(fields), impls: Vec::new(), } } @@ -485,7 +483,7 @@ impl From for Variant { match variant.kind { CLike => Variant::Plain, Tuple(t) => Variant::Tuple(t.into_iter().map(Into::into).collect()), - Struct(s) => Variant::Struct(s.fields.into_iter().map(|i| i.def_id.into()).collect()), + Struct(s) => Variant::Struct(ids(s.fields)), } } } @@ -594,3 +592,7 @@ impl From for ItemKind { } } } + +fn ids(items: impl IntoIterator) -> Vec { + items.into_iter().filter(|x| !x.is_stripped()).map(|i| i.def_id.into()).collect() +} diff --git a/src/librustdoc/json/mod.rs b/src/librustdoc/json/mod.rs index 20a8de144ad..c080ad21c0f 100644 --- a/src/librustdoc/json/mod.rs +++ b/src/librustdoc/json/mod.rs @@ -99,7 +99,6 @@ impl JsonRenderer { .0 .last() .map(Clone::clone), - stripped: false, visibility: types::Visibility::Public, kind: types::ItemKind::Trait, inner: types::ItemEnum::TraitItem(trait_item.clone().into()), @@ -144,16 +143,17 @@ impl FormatRenderer for JsonRenderer { item.kind.inner_items().for_each(|i| self.item(i.clone(), cache).unwrap()); let id = item.def_id; - let mut new_item: types::Item = item.into(); - if let types::ItemEnum::TraitItem(ref mut t) = new_item.inner { - t.implementors = self.get_trait_implementors(id, cache) - } else if let types::ItemEnum::StructItem(ref mut s) = new_item.inner { - s.impls = self.get_impls(id, cache) - } else if let types::ItemEnum::EnumItem(ref mut e) = new_item.inner { - e.impls = self.get_impls(id, cache) + if let Some(mut new_item) = item.into(): Option { + if let types::ItemEnum::TraitItem(ref mut t) = new_item.inner { + t.implementors = self.get_trait_implementors(id, cache) + } else if let types::ItemEnum::StructItem(ref mut s) = new_item.inner { + s.impls = self.get_impls(id, cache) + } else if let types::ItemEnum::EnumItem(ref mut e) = new_item.inner { + e.impls = self.get_impls(id, cache) + } + self.index.borrow_mut().insert(id.into(), new_item); } - self.index.borrow_mut().insert(id.into(), new_item); Ok(()) } diff --git a/src/librustdoc/json/types.rs b/src/librustdoc/json/types.rs index 62705affafc..10bf2a2acc5 100644 --- a/src/librustdoc/json/types.rs +++ b/src/librustdoc/json/types.rs @@ -62,9 +62,6 @@ pub struct Item { pub crate_id: u32, /// Some items such as impls don't have names. pub name: Option, - /// Whether this item is meant to be omitted from the generated documentation due to `#doc(hidden)`, - /// because it is private, or because it was inlined. - pub stripped: bool, /// The source location of this item (absent if it came from a macro expansion or inline /// assembly). pub source: Option, diff --git a/src/librustdoc/lib.rs b/src/librustdoc/lib.rs index 751f2301053..80a9c3811cf 100644 --- a/src/librustdoc/lib.rs +++ b/src/librustdoc/lib.rs @@ -14,6 +14,7 @@ #![feature(crate_visibility_modifier)] #![feature(never_type)] #![feature(once_cell)] +#![feature(type_ascription)] #![recursion_limit = "256"] #[macro_use] diff --git a/src/test/rustdoc-json/Makefile b/src/test/rustdoc-json/Makefile deleted file mode 100644 index ad517ae95eb..00000000000 --- a/src/test/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-json/compare.py b/src/test/rustdoc-json/compare.py index afc8066685c..422e48ea39a 100644 --- a/src/test/rustdoc-json/compare.py +++ b/src/test/rustdoc-json/compare.py @@ -34,23 +34,18 @@ def check_subset(expected_main, actual_main, base_dir): def _check_subset(expected, actual, trace): expected_type = type(expected) actual_type = type(actual) + + if actual_type is str: + actual = actual.replace(base_dir, "$TEST_BASE_DIR") + 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 in (int, bool, str) 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: diff --git a/src/test/rustdoc-json/structs.expected b/src/test/rustdoc-json/structs.expected index 45b23534bc7..799829de3fd 100644 --- a/src/test/rustdoc-json/structs.expected +++ b/src/test/rustdoc-json/structs.expected @@ -7,7 +7,7 @@ "crate_id": 0, "name": "Unit", "source": { - "filename": "structs.rs", + "filename": "$TEST_BASE_DIR/structs.rs", "begin": [ 7, 0 @@ -37,7 +37,7 @@ "crate_id": 0, "name": "1", "source": { - "filename": "structs.rs", + "filename": "$TEST_BASE_DIR/structs.rs", "begin": [ 5, 22 @@ -72,7 +72,7 @@ "crate_id": 0, "name": "stuff", "source": { - "filename": "structs.rs", + "filename": "$TEST_BASE_DIR/structs.rs", "begin": [ 15, 4 @@ -114,7 +114,7 @@ "crate_id": 0, "name": "WithPrimitives", "source": { - "filename": "structs.rs", + "filename": "$TEST_BASE_DIR/structs.rs", "begin": [ 9, 0 @@ -141,18 +141,14 @@ ], "where_predicates": [] }, - "fields_stripped": true, - "fields": [ - "0:13", - "0:14" - ] + "fields_stripped": true } }, "0:14": { "crate_id": 0, "name": "s", "source": { - "filename": "structs.rs", + "filename": "$TEST_BASE_DIR/structs.rs", "begin": [ 11, 4 @@ -184,7 +180,7 @@ "crate_id": 0, "name": "things", "source": { - "filename": "structs.rs", + "filename": "$TEST_BASE_DIR/structs.rs", "begin": [ 16, 4 @@ -232,7 +228,7 @@ "crate_id": 0, "name": "WithGenerics", "source": { - "filename": "structs.rs", + "filename": "$TEST_BASE_DIR/structs.rs", "begin": [ 14, 0 @@ -273,18 +269,14 @@ ], "where_predicates": [] }, - "fields_stripped": true, - "fields": [ - "0:18", - "0:19" - ] + "fields_stripped": true } }, "0:0": { "crate_id": 0, "name": "structs", "source": { - "filename": "structs.rs", + "filename": "$TEST_BASE_DIR/structs.rs", "begin": [ 1, 0 @@ -315,7 +307,7 @@ "crate_id": 0, "name": "num", "source": { - "filename": "structs.rs", + "filename": "$TEST_BASE_DIR/structs.rs", "begin": [ 10, 4 @@ -340,7 +332,7 @@ "crate_id": 0, "name": "Tuple", "source": { - "filename": "structs.rs", + "filename": "$TEST_BASE_DIR/structs.rs", "begin": [ 5, 0 @@ -362,18 +354,14 @@ "params": [], "where_predicates": [] }, - "fields_stripped": true, - "fields": [ - "0:7", - "0:8" - ] + "fields_stripped": true } }, "0:4": { "crate_id": 0, "name": "PlainEmpty", "source": { - "filename": "structs.rs", + "filename": "$TEST_BASE_DIR/structs.rs", "begin": [ 3, 0 @@ -403,7 +391,7 @@ "crate_id": 0, "name": "0", "source": { - "filename": "structs.rs", + "filename": "$TEST_BASE_DIR/structs.rs", "begin": [ 5, 17 diff --git a/src/tools/compiletest/src/runtest.rs b/src/tools/compiletest/src/runtest.rs index 88cb8544e47..e9089b4b15b 100644 --- a/src/tools/compiletest/src/runtest.rs +++ b/src/tools/compiletest/src/runtest.rs @@ -1565,7 +1565,7 @@ impl<'test> TestCx<'test> { self.compose_and_run_compiler(rustc, None) } - fn document(&self, out_dir: &Path, json: bool) -> ProcRes { + fn document(&self, out_dir: &Path) -> 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); @@ -1579,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, json); + let auxres = aux_cx.document(out_dir); if !auxres.status.success() { return auxres; } @@ -1601,7 +1601,7 @@ impl<'test> TestCx<'test> { .arg(&self.testpaths.file) .args(&self.props.compile_flags); - if json { + if self.config.mode == RustdocJson { rustdoc.arg("--output-format").arg("json"); } @@ -2336,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, false); + let proc_res = self.document(&out_dir); if !proc_res.status.success() { self.fatal_proc_rec("rustdoc failed!", &proc_res); } @@ -2392,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, false); + let proc_res = new_rustdoc.document(&compare_dir); if !proc_res.status.success() { proc_res.fatal(Some("failed to run nightly rustdoc"), || ()); } @@ -2482,7 +2482,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, true); + let proc_res = self.document(&out_dir); if !proc_res.status.success() { self.fatal_proc_rec("rustdoc failed!", &proc_res); } @@ -3052,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, false); + self.document(&out_dir); let root = self.config.find_rust_src_root().unwrap(); let file_stem = -- cgit 1.4.1-3-g733a5 From d619271f2bf90a4fce327ef868920ef39ee22a4c Mon Sep 17 00:00:00 2001 From: Nixon Enraght-Moony Date: Wed, 2 Dec 2020 20:41:20 +0000 Subject: Normalize windows path seperators. --- src/test/rustdoc-json/compare.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) (limited to 'src/test') diff --git a/src/test/rustdoc-json/compare.py b/src/test/rustdoc-json/compare.py index 422e48ea39a..b0c5b16a197 100644 --- a/src/test/rustdoc-json/compare.py +++ b/src/test/rustdoc-json/compare.py @@ -4,7 +4,8 @@ # 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`. +# and then create `yourtest.expected` by stripping unnecessary details from `yourtest.json`. If +# you're on windows, replace `\` with `/`. import copy import sys @@ -36,7 +37,7 @@ def check_subset(expected_main, actual_main, base_dir): actual_type = type(actual) if actual_type is str: - actual = actual.replace(base_dir, "$TEST_BASE_DIR") + actual = normalize(actual).replace(base_dir, "$TEST_BASE_DIR") if expected_type is not actual_type: raise SubsetException( @@ -118,9 +119,11 @@ def main(expected_fpath, actual_fpath, base_dir): check_subset(expected_main, actual_main, base_dir) print("all checks passed") +def normalize(s): + return s.replace('\\', '/') 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]) + main(sys.argv[1], sys.argv[2], normalize(sys.argv[3])) -- cgit 1.4.1-3-g733a5