diff options
| author | Nixon Enraght-Moony <nixon.emoony@gmail.com> | 2020-11-29 16:16:25 +0000 |
|---|---|---|
| committer | Nixon Enraght-Moony <nixon.emoony@gmail.com> | 2020-12-01 18:34:39 +0000 |
| commit | 1098cce27acb2d52cb3b5ddbcf28c3a06e38dc7c (patch) | |
| tree | 672e12f1004ced7aa2858d6f829c48d06e78cb84 /src/test/rustdoc-json | |
| parent | 66884e318f2edfb0dd66d76a7d3b080d0dd9e4c5 (diff) | |
| download | rust-1098cce27acb2d52cb3b5ddbcf28c3a06e38dc7c.tar.gz rust-1098cce27acb2d52cb3b5ddbcf28c3a06e38dc7c.zip | |
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
Diffstat (limited to 'src/test/rustdoc-json')
| -rw-r--r-- | src/test/rustdoc-json/Makefile | 6 | ||||
| -rw-r--r-- | src/test/rustdoc-json/check_missing_items.py | 187 | ||||
| -rw-r--r-- | src/test/rustdoc-json/compare.py | 131 | ||||
| -rw-r--r-- | src/test/rustdoc-json/structs.expected | 468 | ||||
| -rw-r--r-- | src/test/rustdoc-json/structs.rs | 17 |
5 files changed, 809 insertions, 0 deletions
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<T, U> { + stuff: Vec<T>, + things: HashMap<U, U>, +} |
