xref: /aosp_15_r20/external/bazelbuild-rules_testing/lib/private/dict_subject.bzl (revision d605057434dcabba796c020773aab68d9790ff9f)
1*d6050574SRomain Jobredeaux# Copyright 2023 The Bazel Authors. All rights reserved.
2*d6050574SRomain Jobredeaux#
3*d6050574SRomain Jobredeaux# Licensed under the Apache License, Version 2.0 (the "License");
4*d6050574SRomain Jobredeaux# you may not use this file except in compliance with the License.
5*d6050574SRomain Jobredeaux# You may obtain a copy of the License at
6*d6050574SRomain Jobredeaux#
7*d6050574SRomain Jobredeaux#     http://www.apache.org/licenses/LICENSE-2.0
8*d6050574SRomain Jobredeaux#
9*d6050574SRomain Jobredeaux# Unless required by applicable law or agreed to in writing, software
10*d6050574SRomain Jobredeaux# distributed under the License is distributed on an "AS IS" BASIS,
11*d6050574SRomain Jobredeaux# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12*d6050574SRomain Jobredeaux# See the License for the specific language governing permissions and
13*d6050574SRomain Jobredeaux# limitations under the License.
14*d6050574SRomain Jobredeaux
15*d6050574SRomain Jobredeaux"""# DictSubject"""
16*d6050574SRomain Jobredeaux
17*d6050574SRomain Jobredeauxload(":collection_subject.bzl", "CollectionSubject")
18*d6050574SRomain Jobredeauxload(":compare_util.bzl", "compare_dicts")
19*d6050574SRomain Jobredeauxload(
20*d6050574SRomain Jobredeaux    ":failure_messages.bzl",
21*d6050574SRomain Jobredeaux    "format_dict_as_lines",
22*d6050574SRomain Jobredeaux    "format_problem_dict_expected",
23*d6050574SRomain Jobredeaux)
24*d6050574SRomain Jobredeaux
25*d6050574SRomain Jobredeauxdef _dict_subject_new(actual, meta, container_name = "dict", key_plural_name = "keys"):
26*d6050574SRomain Jobredeaux    """Creates a new `DictSubject`.
27*d6050574SRomain Jobredeaux
28*d6050574SRomain Jobredeaux    Method: DictSubject.new
29*d6050574SRomain Jobredeaux
30*d6050574SRomain Jobredeaux    Args:
31*d6050574SRomain Jobredeaux        actual: ([`dict`]) the dict to assert against.
32*d6050574SRomain Jobredeaux        meta: ([`ExpectMeta`]) of call chain information.
33*d6050574SRomain Jobredeaux        container_name: ([`str`]) conceptual name of the dict.
34*d6050574SRomain Jobredeaux        key_plural_name: ([`str`]) the plural word for the keys of the dict.
35*d6050574SRomain Jobredeaux
36*d6050574SRomain Jobredeaux    Returns:
37*d6050574SRomain Jobredeaux        New `DictSubject` struct.
38*d6050574SRomain Jobredeaux    """
39*d6050574SRomain Jobredeaux
40*d6050574SRomain Jobredeaux    # buildifier: disable=uninitialized
41*d6050574SRomain Jobredeaux    public = struct(
42*d6050574SRomain Jobredeaux        # keep sorted start
43*d6050574SRomain Jobredeaux        contains_exactly = lambda *a, **k: _dict_subject_contains_exactly(self, *a, **k),
44*d6050574SRomain Jobredeaux        contains_at_least = lambda *a, **k: _dict_subject_contains_at_least(self, *a, **k),
45*d6050574SRomain Jobredeaux        contains_none_of = lambda *a, **k: _dict_subject_contains_none_of(self, *a, **k),
46*d6050574SRomain Jobredeaux        get = lambda *a, **k: _dict_subject_get(self, *a, **k),
47*d6050574SRomain Jobredeaux        keys = lambda *a, **k: _dict_subject_keys(self, *a, **k),
48*d6050574SRomain Jobredeaux        # keep sorted end
49*d6050574SRomain Jobredeaux    )
50*d6050574SRomain Jobredeaux    self = struct(
51*d6050574SRomain Jobredeaux        actual = actual,
52*d6050574SRomain Jobredeaux        meta = meta,
53*d6050574SRomain Jobredeaux        container_name = container_name,
54*d6050574SRomain Jobredeaux        key_plural_name = key_plural_name,
55*d6050574SRomain Jobredeaux    )
56*d6050574SRomain Jobredeaux    return public
57*d6050574SRomain Jobredeaux
58*d6050574SRomain Jobredeauxdef _dict_subject_contains_at_least(self, at_least):
59*d6050574SRomain Jobredeaux    """Assert the dict has at least the entries from `at_least`.
60*d6050574SRomain Jobredeaux
61*d6050574SRomain Jobredeaux    Method: DictSubject.contains_at_least
62*d6050574SRomain Jobredeaux
63*d6050574SRomain Jobredeaux    Args:
64*d6050574SRomain Jobredeaux        self: implicitly added.
65*d6050574SRomain Jobredeaux        at_least: ([`dict`]) the subset of keys/values that must exist. Extra
66*d6050574SRomain Jobredeaux            keys are allowed. Order is not checked.
67*d6050574SRomain Jobredeaux    """
68*d6050574SRomain Jobredeaux    result = compare_dicts(
69*d6050574SRomain Jobredeaux        expected = at_least,
70*d6050574SRomain Jobredeaux        actual = self.actual,
71*d6050574SRomain Jobredeaux    )
72*d6050574SRomain Jobredeaux    if not result.missing_keys and not result.incorrect_entries:
73*d6050574SRomain Jobredeaux        return
74*d6050574SRomain Jobredeaux
75*d6050574SRomain Jobredeaux    self.meta.add_failure(
76*d6050574SRomain Jobredeaux        problem = format_problem_dict_expected(
77*d6050574SRomain Jobredeaux            expected = at_least,
78*d6050574SRomain Jobredeaux            missing_keys = result.missing_keys,
79*d6050574SRomain Jobredeaux            unexpected_keys = [],
80*d6050574SRomain Jobredeaux            incorrect_entries = result.incorrect_entries,
81*d6050574SRomain Jobredeaux            container_name = self.container_name,
82*d6050574SRomain Jobredeaux            key_plural_name = self.key_plural_name,
83*d6050574SRomain Jobredeaux        ),
84*d6050574SRomain Jobredeaux        actual = "actual: {{\n{}\n}}".format(format_dict_as_lines(self.actual)),
85*d6050574SRomain Jobredeaux    )
86*d6050574SRomain Jobredeaux
87*d6050574SRomain Jobredeauxdef _dict_subject_contains_exactly(self, expected):
88*d6050574SRomain Jobredeaux    """Assert the dict has exactly the provided values.
89*d6050574SRomain Jobredeaux
90*d6050574SRomain Jobredeaux    Method: DictSubject.contains_exactly
91*d6050574SRomain Jobredeaux
92*d6050574SRomain Jobredeaux    Args:
93*d6050574SRomain Jobredeaux        self: implicitly added
94*d6050574SRomain Jobredeaux        expected: ([`dict`]) the values that must exist. Missing values or
95*d6050574SRomain Jobredeaux            extra values are not allowed. Order is not checked.
96*d6050574SRomain Jobredeaux    """
97*d6050574SRomain Jobredeaux    result = compare_dicts(
98*d6050574SRomain Jobredeaux        expected = expected,
99*d6050574SRomain Jobredeaux        actual = self.actual,
100*d6050574SRomain Jobredeaux    )
101*d6050574SRomain Jobredeaux
102*d6050574SRomain Jobredeaux    if (not result.missing_keys and not result.unexpected_keys and
103*d6050574SRomain Jobredeaux        not result.incorrect_entries):
104*d6050574SRomain Jobredeaux        return
105*d6050574SRomain Jobredeaux
106*d6050574SRomain Jobredeaux    self.meta.add_failure(
107*d6050574SRomain Jobredeaux        problem = format_problem_dict_expected(
108*d6050574SRomain Jobredeaux            expected = expected,
109*d6050574SRomain Jobredeaux            missing_keys = result.missing_keys,
110*d6050574SRomain Jobredeaux            unexpected_keys = result.unexpected_keys,
111*d6050574SRomain Jobredeaux            incorrect_entries = result.incorrect_entries,
112*d6050574SRomain Jobredeaux            container_name = self.container_name,
113*d6050574SRomain Jobredeaux            key_plural_name = self.key_plural_name,
114*d6050574SRomain Jobredeaux        ),
115*d6050574SRomain Jobredeaux        actual = "actual: {{\n{}\n}}".format(format_dict_as_lines(self.actual)),
116*d6050574SRomain Jobredeaux    )
117*d6050574SRomain Jobredeaux
118*d6050574SRomain Jobredeauxdef _dict_subject_contains_none_of(self, none_of):
119*d6050574SRomain Jobredeaux    """Assert the dict contains none of `none_of` keys/values.
120*d6050574SRomain Jobredeaux
121*d6050574SRomain Jobredeaux    Method: DictSubject.contains_none_of
122*d6050574SRomain Jobredeaux
123*d6050574SRomain Jobredeaux    Args:
124*d6050574SRomain Jobredeaux        self: implicitly added
125*d6050574SRomain Jobredeaux        none_of: ([`dict`]) the keys/values that must not exist. Order is not
126*d6050574SRomain Jobredeaux            checked.
127*d6050574SRomain Jobredeaux    """
128*d6050574SRomain Jobredeaux    result = compare_dicts(
129*d6050574SRomain Jobredeaux        expected = none_of,
130*d6050574SRomain Jobredeaux        actual = self.actual,
131*d6050574SRomain Jobredeaux    )
132*d6050574SRomain Jobredeaux    none_of_keys = sorted(none_of.keys())
133*d6050574SRomain Jobredeaux    if (sorted(result.missing_keys) == none_of_keys or
134*d6050574SRomain Jobredeaux        sorted(result.incorrect_entries.keys()) == none_of_keys):
135*d6050574SRomain Jobredeaux        return
136*d6050574SRomain Jobredeaux
137*d6050574SRomain Jobredeaux    incorrect_entries = {}
138*d6050574SRomain Jobredeaux    for key, not_expected in none_of.items():
139*d6050574SRomain Jobredeaux        actual = self.actual[key]
140*d6050574SRomain Jobredeaux        if actual == not_expected:
141*d6050574SRomain Jobredeaux            incorrect_entries[key] = struct(
142*d6050574SRomain Jobredeaux                actual = actual,
143*d6050574SRomain Jobredeaux                expected = "<not {}>".format(not_expected),
144*d6050574SRomain Jobredeaux            )
145*d6050574SRomain Jobredeaux
146*d6050574SRomain Jobredeaux    self.meta.add_failure(
147*d6050574SRomain Jobredeaux        problem = format_problem_dict_expected(
148*d6050574SRomain Jobredeaux            expected = none_of,
149*d6050574SRomain Jobredeaux            missing_keys = [],
150*d6050574SRomain Jobredeaux            unexpected_keys = [],
151*d6050574SRomain Jobredeaux            incorrect_entries = incorrect_entries,
152*d6050574SRomain Jobredeaux            container_name = self.container_name + " to be missing",
153*d6050574SRomain Jobredeaux            key_plural_name = self.key_plural_name,
154*d6050574SRomain Jobredeaux        ),
155*d6050574SRomain Jobredeaux        actual = "actual: {{\n{}\n}}".format(format_dict_as_lines(self.actual)),
156*d6050574SRomain Jobredeaux    )
157*d6050574SRomain Jobredeaux
158*d6050574SRomain Jobredeauxdef _dict_subject_get(self, key, *, factory):
159*d6050574SRomain Jobredeaux    """Gets `key` from the actual dict wrapped in a subject.
160*d6050574SRomain Jobredeaux
161*d6050574SRomain Jobredeaux    Args:
162*d6050574SRomain Jobredeaux        self: implicitly added.
163*d6050574SRomain Jobredeaux        key: ([`object`]) the key to fetch.
164*d6050574SRomain Jobredeaux        factory: ([`callable`]) subject factory function, with the signature
165*d6050574SRomain Jobredeaux            of `def factory(value, *, meta)`, and returns the wrapped value.
166*d6050574SRomain Jobredeaux
167*d6050574SRomain Jobredeaux    Returns:
168*d6050574SRomain Jobredeaux        The return value of the `factory` arg.
169*d6050574SRomain Jobredeaux    """
170*d6050574SRomain Jobredeaux    if key not in self.actual:
171*d6050574SRomain Jobredeaux        fail("KeyError: '{key}' not found in {expr}".format(
172*d6050574SRomain Jobredeaux            key = key,
173*d6050574SRomain Jobredeaux            expr = self.meta.current_expr(),
174*d6050574SRomain Jobredeaux        ))
175*d6050574SRomain Jobredeaux    return factory(self.actual[key], meta = self.meta.derive("get({})".format(key)))
176*d6050574SRomain Jobredeaux
177*d6050574SRomain Jobredeauxdef _dict_subject_keys(self):
178*d6050574SRomain Jobredeaux    """Returns a `CollectionSubject` for the dict's keys.
179*d6050574SRomain Jobredeaux
180*d6050574SRomain Jobredeaux    Method: DictSubject.keys
181*d6050574SRomain Jobredeaux
182*d6050574SRomain Jobredeaux    Args:
183*d6050574SRomain Jobredeaux        self: implicitly added
184*d6050574SRomain Jobredeaux
185*d6050574SRomain Jobredeaux    Returns:
186*d6050574SRomain Jobredeaux        [`CollectionSubject`] of the keys.
187*d6050574SRomain Jobredeaux    """
188*d6050574SRomain Jobredeaux    return CollectionSubject.new(
189*d6050574SRomain Jobredeaux        self.actual.keys(),
190*d6050574SRomain Jobredeaux        meta = self.meta.derive("keys()"),
191*d6050574SRomain Jobredeaux        container_name = "dict keys",
192*d6050574SRomain Jobredeaux        element_plural_name = "keys",
193*d6050574SRomain Jobredeaux    )
194*d6050574SRomain Jobredeaux
195*d6050574SRomain Jobredeaux# We use this name so it shows up nice in docs.
196*d6050574SRomain Jobredeaux# buildifier: disable=name-conventions
197*d6050574SRomain JobredeauxDictSubject = struct(
198*d6050574SRomain Jobredeaux    new = _dict_subject_new,
199*d6050574SRomain Jobredeaux    contains_at_least = _dict_subject_contains_at_least,
200*d6050574SRomain Jobredeaux    contains_exactly = _dict_subject_contains_exactly,
201*d6050574SRomain Jobredeaux    contains_none_of = _dict_subject_contains_none_of,
202*d6050574SRomain Jobredeaux    keys = _dict_subject_keys,
203*d6050574SRomain Jobredeaux)
204