xref: /aosp_15_r20/external/bazelbuild-rules_testing/lib/private/depset_file_subject.bzl (revision d605057434dcabba796c020773aab68d9790ff9f)
1# Copyright 2023 The Bazel Authors. All rights reserved.
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7#     http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
15"""# DepsetFileSubject"""
16
17load("//lib:util.bzl", "is_file")
18load(
19    ":check_util.bzl",
20    "check_contains_at_least_predicates",
21    "check_contains_exactly",
22    "check_contains_predicate",
23    "check_not_contains_predicate",
24)
25load(":collection_subject.bzl", "CollectionSubject")
26load(
27    ":failure_messages.bzl",
28    "format_actual_collection",
29    "format_problem_expected_exactly",
30    "format_problem_matched_out_of_order",
31    "format_problem_missing_any_values",
32    "format_problem_missing_required_values",
33    "format_problem_predicates_did_not_match",
34    "format_problem_unexpected_values",
35)
36load(":matching.bzl", "matching")
37load(":truth_common.bzl", "to_list")
38
39def _depset_file_subject_new(files, meta, container_name = "depset", element_plural_name = "files"):
40    """Creates a DepsetFileSubject asserting on `files`.
41
42    Method: DepsetFileSubject.new
43
44    Args:
45        files: ([`depset`] of [`File`]) the values to assert on.
46        meta: ([`ExpectMeta`]) of call chain information.
47        container_name: ([`str`]) conceptual name of the container.
48        element_plural_name: ([`str`]) the plural word for the values in the container.
49
50    Returns:
51        [`DepsetFileSubject`] object.
52    """
53
54    # buildifier: disable=uninitialized
55    public = struct(
56        # keep sorted start
57        contains = lambda *a, **k: _depset_file_subject_contains(self, *a, **k),
58        contains_any_in = lambda *a, **k: _depset_file_subject_contains_any_in(self, *a, **k),
59        contains_at_least = lambda *a, **k: _depset_file_subject_contains_at_least(self, *a, **k),
60        contains_at_least_predicates = lambda *a, **k: _depset_file_subject_contains_at_least_predicates(self, *a, **k),
61        contains_exactly = lambda *a, **k: _depset_file_subject_contains_exactly(self, *a, **k),
62        contains_predicate = lambda *a, **k: _depset_file_subject_contains_predicate(self, *a, **k),
63        not_contains = lambda *a, **k: _depset_file_subject_not_contains(self, *a, **k),
64        not_contains_predicate = lambda *a, **k: _depset_file_subject_not_contains_predicate(self, *a, **k),
65        # keep sorted end
66    )
67    self = struct(
68        files = to_list(files),
69        meta = meta,
70        public = public,
71        actual_paths = sorted([f.short_path for f in to_list(files)]),
72        container_name = container_name,
73        element_plural_name = element_plural_name,
74    )
75    return public
76
77def _depset_file_subject_contains(self, expected):
78    """Asserts that the depset of files contains the provided path/file.
79
80    Method: DepsetFileSubject.contains
81
82    Args:
83        self: implicitly added
84        expected: ([`str`] | [`File`]) If a string path is provided, it is
85            compared to the short path of the files and are formatted using
86            [`ExpectMeta.format_str`] and its current contextual keywords. Note
87            that, when using `File` objects, two files' configurations must be
88            the same for them to be considered equal.
89    """
90    if is_file(expected):
91        actual = self.files
92    else:
93        expected = self.meta.format_str(expected)
94        actual = self.actual_paths
95
96    CollectionSubject.new(
97        actual,
98        meta = self.meta,
99        container_name = self.container_name,
100        element_plural_name = self.element_plural_name,
101    ).contains(expected)
102
103def _depset_file_subject_contains_at_least(self, expected):
104    """Asserts that the depset of files contains at least the provided paths.
105
106    Method: DepsetFileSubject.contains_at_least
107
108    Args:
109        self: implicitly added
110        expected: ([`collection`] of [`str`] | collection of [`File`]) multiplicity
111            is respected. If string paths are provided, they are compared to the
112            short path of the files and are formatted using
113            `ExpectMeta.format_str` and its current contextual keywords. Note
114            that, when using `File` objects, two files' configurations must be the
115            same for them to be considered equal.
116
117    Returns:
118        [`Ordered`] (see `_ordered_incorrectly_new`).
119    """
120    expected = to_list(expected)
121    if len(expected) < 1 or is_file(expected[0]):
122        actual = self.files
123    else:
124        expected = [self.meta.format_str(v) for v in expected]
125        actual = self.actual_paths
126
127    return CollectionSubject.new(
128        actual,
129        meta = self.meta,
130        container_name = self.container_name,
131        element_plural_name = self.element_plural_name,
132    ).contains_at_least(expected)
133
134def _depset_file_subject_contains_any_in(self, expected):
135    """Asserts that any of the values in `expected` exist.
136
137    Method: DepsetFileSubject.contains_any_in
138
139    Args:
140        self: implicitly added.
141        expected: ([`collection`] of [`str`] paths | [`collection`] of [`File`])
142            at least one of the values must exist. Note that, when using `File`
143            objects, two files' configurations must be the same for them to be
144            considered equal. When string paths are provided, they are compared
145            to `File.short_path`.
146    """
147    expected = to_list(expected)
148    if len(expected) < 1 or is_file(expected[0]):
149        actual = self.files
150    else:
151        actual = self.actual_paths
152
153    expected_map = {value: None for value in expected}
154
155    check_contains_predicate(
156        actual,
157        matcher = matching.is_in(expected_map),
158        format_problem = lambda: format_problem_missing_any_values(expected),
159        format_actual = lambda: format_actual_collection(
160            actual,
161            container_name = self.container_name,
162        ),
163        meta = self.meta,
164    )
165
166def _depset_file_subject_contains_at_least_predicates(self, matchers):
167    """Assert that the depset is a subset of the given predicates.
168
169    Method: DepsetFileSubject.contains_at_least_predicates
170
171    The depset must match all the predicates. It can contain extra elements.
172    The multiplicity of matchers is respected. Checking that the relative order
173    of matches is the same as the passed-in matchers order can done by calling
174    `in_order()`.
175
176    Args:
177        self: implicitly added.
178        matchers: ([`list`] of [`Matcher`]) (see `matchers` struct) that
179            accept [`File`] objects.
180
181    Returns:
182        [`Ordered`] (see `_ordered_incorrectly_new`).
183    """
184    ordered = check_contains_at_least_predicates(
185        self.files,
186        matchers,
187        format_missing = lambda missing: format_problem_predicates_did_not_match(
188            missing,
189            element_plural_name = self.element_plural_name,
190            container_name = self.container_name,
191        ),
192        format_out_of_order = format_problem_matched_out_of_order,
193        format_actual = lambda: format_actual_collection(
194            self.files,
195            name = self.container_name,
196        ),
197        meta = self.meta,
198    )
199    return ordered
200
201def _depset_file_subject_contains_predicate(self, matcher):
202    """Asserts that `matcher` matches at least one value.
203
204    Method: DepsetFileSubject.contains_predicate
205
206    Args:
207        self: implicitly added.
208        matcher: [`Matcher`] (see `matching` struct) that accepts `File` objects.
209    """
210    check_contains_predicate(
211        self.files,
212        matcher = matcher,
213        format_problem = matcher.desc,
214        format_actual = lambda: format_actual_collection(
215            self.files,
216            name = self.container_name,
217        ),
218        meta = self.meta,
219    )
220
221def _depset_file_subject_contains_exactly(self, paths):
222    """Asserts the depset of files contains exactly the given paths.
223
224    Method: DepsetFileSubject.contains_exactly
225
226    Args:
227        self: implicitly added.
228        paths: ([`collection`] of [`str`]) the paths that must exist. These are
229            compared to the `short_path` values of the files in the depset.
230            All the paths, and no more, must exist.
231    """
232    paths = [self.meta.format_str(p) for p in to_list(paths)]
233    check_contains_exactly(
234        expect_contains = paths,
235        actual_container = self.actual_paths,
236        format_actual = lambda: format_actual_collection(
237            self.actual_paths,
238            name = self.container_name,
239        ),
240        format_expected = lambda: format_problem_expected_exactly(
241            paths,
242            sort = True,
243        ),
244        format_missing = lambda missing: format_problem_missing_required_values(
245            missing,
246            sort = True,
247        ),
248        format_unexpected = lambda unexpected: format_problem_unexpected_values(
249            unexpected,
250            sort = True,
251        ),
252        format_out_of_order = lambda matches: fail("Should not be called"),
253        meta = self.meta,
254    )
255
256def _depset_file_subject_not_contains(self, short_path):
257    """Asserts that `short_path` is not in the depset.
258
259    Method: DepsetFileSubject.not_contains_predicate
260
261    Args:
262        self: implicitly added.
263        short_path: ([`str`]) the short path that should not be present.
264    """
265    short_path = self.meta.format_str(short_path)
266    matcher = matching.custom(short_path, lambda f: f.short_path == short_path)
267    check_not_contains_predicate(self.files, matcher, meta = self.meta)
268
269def _depset_file_subject_not_contains_predicate(self, matcher):
270    """Asserts that nothing in the depset matches `matcher`.
271
272    Method: DepsetFileSubject.not_contains_predicate
273
274    Args:
275        self: implicitly added.
276        matcher: ([`Matcher`]) that must match. It operates on [`File`] objects.
277    """
278    check_not_contains_predicate(self.files, matcher, meta = self.meta)
279
280# We use this name so it shows up nice in docs.
281# buildifier: disable=name-conventions
282DepsetFileSubject = struct(
283    new = _depset_file_subject_new,
284    contains = _depset_file_subject_contains,
285    contains_at_least = _depset_file_subject_contains_at_least,
286    contains_any_in = _depset_file_subject_contains_any_in,
287    contains_at_least_predicates = _depset_file_subject_contains_at_least_predicates,
288    contains_predicate = _depset_file_subject_contains_predicate,
289    contains_exactly = _depset_file_subject_contains_exactly,
290    not_contains = _depset_file_subject_not_contains,
291    not_contains_predicate = _depset_file_subject_not_contains_predicate,
292)
293