about summary refs log tree commit diff
path: root/src/test/rustdoc/rustdoc-json/compare.py
diff options
context:
space:
mode:
Diffstat (limited to 'src/test/rustdoc/rustdoc-json/compare.py')
-rw-r--r--src/test/rustdoc/rustdoc-json/compare.py107
1 files changed, 107 insertions, 0 deletions
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])