| 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
 | #!/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`. If
# you're on windows, replace `\` with `/`.
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 actual_type is str:
            actual = normalize(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 (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:
                    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")
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], normalize(sys.argv[3]))
 |