xref: /aosp_15_r20/external/bazelbuild-rules_testing/lib/private/matching.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"""Implementation of matchers."""
16
17def _match_custom(desc, func):
18    """Wrap an arbitrary function up as a Matcher.
19
20    Method: Matcher.new
21
22    `Matcher` struct attributes:
23
24    * `desc`: ([`str`]) a human-friendly description
25    * `match`: (callable) accepts 1 positional arg (the value to match) and
26        returns [`bool`] (`True` if it matched, `False` if not).
27
28    Args:
29        desc: ([`str`]) a human-friendly string describing what is matched.
30        func: (callable) accepts 1 positional arg (the value to match) and
31            returns [`bool`] (`True` if it matched, `False` if not).
32
33    Returns:
34        [`Matcher`] (see above).
35    """
36    return struct(desc = desc, match = func)
37
38def _match_equals_wrapper(value):
39    """Match that a value equals `value`, but use `value` as the `desc`.
40
41    This is a helper so that simple equality comparisons can re-use predicate
42    based APIs.
43
44    Args:
45        value: object, the value that must be equal to.
46
47    Returns:
48        [`Matcher`] (see `_match_custom()`), whose description is `value`.
49    """
50    return _match_custom(value, lambda other: other == value)
51
52def _match_file_basename_contains(substr):
53    """Match that a a `File.basename` string contains a substring.
54
55    Args:
56        substr: ([`str`]) the substring to match.
57
58    Returns:
59        [`Matcher`] (see `_match_custom()`).
60    """
61    return struct(
62        desc = "<basename contains '{}'>".format(substr),
63        match = lambda f: substr in f.basename,
64    )
65
66def _match_file_path_matches(pattern):
67    """Match that a `File.path` string matches a glob-style pattern.
68
69    Args:
70        pattern: ([`str`]) the pattern to match. "*" can be used to denote
71            "match anything".
72
73    Returns:
74        [`Matcher`] (see `_match_custom`).
75    """
76    parts = pattern.split("*")
77    return struct(
78        desc = "<path matches '{}'>".format(pattern),
79        match = lambda f: _match_parts_in_order(f.path, parts),
80    )
81
82def _match_file_basename_equals(value):
83    """Match that a `File.basename` string equals `value`.
84
85    Args:
86        value: ([`str`]) the basename to match.
87
88    Returns:
89        [`Matcher`] instance
90    """
91    return struct(
92        desc = "<file basename equals '{}'>".format(value),
93        match = lambda f: f.basename == value,
94    )
95
96def _match_file_extension_in(values):
97    """Match that a `File.extension` string is any of `values`.
98
99    See also: `file_path_matches` for matching extensions that
100    have multiple parts, e.g. `*.tar.gz` or `*.so.*`.
101
102    Args:
103        values: ([`list`] of [`str`]) the extensions to match.
104
105    Returns:
106        [`Matcher`] instance
107    """
108    return struct(
109        desc = "<file extension is any of {}>".format(repr(values)),
110        match = lambda f: f.extension in values,
111    )
112
113def _match_is_in(values):
114    """Match that the to-be-matched value is in a collection of other values.
115
116    This is equivalent to: `to_be_matched in values`. See `_match_contains`
117    for the reversed operation.
118
119    Args:
120        values: The collection that the value must be within.
121
122    Returns:
123        [`Matcher`] (see `_match_custom()`).
124    """
125    return struct(
126        desc = "<is any of {}>".format(repr(values)),
127        match = lambda v: v in values,
128    )
129
130def _match_never(desc):
131    """A matcher that never matches.
132
133    This is mostly useful for testing, as it allows preventing any match
134    while providing a custom description.
135
136    Args:
137        desc: ([`str`]) human-friendly string.
138
139    Returns:
140        [`Matcher`] (see `_match_custom`).
141    """
142    return struct(
143        desc = desc,
144        match = lambda value: False,
145    )
146
147def _match_contains(contained):
148    """Match that `contained` is within the to-be-matched value.
149
150    This is equivalent to: `contained in to_be_matched`. See `_match_is_in`
151    for the reversed operation.
152
153    Args:
154        contained: the value that to-be-matched value must contain.
155
156    Returns:
157        [`Matcher`] (see `_match_custom`).
158    """
159    return struct(
160        desc = "<contains {}>".format(contained),
161        match = lambda value: contained in value,
162    )
163
164def _match_str_endswith(suffix):
165    """Match that a string contains another string.
166
167    Args:
168        suffix: ([`str`]) the suffix that must be present
169
170    Returns:
171        [`Matcher`] (see `_match_custom`).
172    """
173    return struct(
174        desc = "<endswith '{}'>".format(suffix),
175        match = lambda value: value.endswith(suffix),
176    )
177
178def _match_str_matches(pattern):
179    """Match that a string matches a glob-style pattern.
180
181    Args:
182        pattern: ([`str`]) the pattern to match. `*` can be used to denote
183            "match anything". There is an implicit `*` at the start and
184            end of the pattern.
185
186    Returns:
187        [`Matcher`] object.
188    """
189    parts = pattern.split("*")
190    return struct(
191        desc = "<matches '{}'>".format(pattern),
192        match = lambda value: _match_parts_in_order(value, parts),
193    )
194
195def _match_str_startswith(prefix):
196    """Match that a string contains another string.
197
198    Args:
199        prefix: ([`str`]) the prefix that must be present
200
201    Returns:
202        [`Matcher`] (see `_match_custom`).
203    """
204    return struct(
205        desc = "<startswith '{}'>".format(prefix),
206        match = lambda value: value.startswith(prefix),
207    )
208
209def _match_parts_in_order(string, parts):
210    start = 0
211    for part in parts:
212        start = string.find(part, start)
213        if start == -1:
214            return False
215    return True
216
217def _is_matcher(obj):
218    return hasattr(obj, "desc") and hasattr(obj, "match")
219
220# For the definition of a `Matcher` object, see `_match_custom`.
221matching = struct(
222    # keep sorted start
223    contains = _match_contains,
224    custom = _match_custom,
225    equals_wrapper = _match_equals_wrapper,
226    file_basename_contains = _match_file_basename_contains,
227    file_basename_equals = _match_file_basename_equals,
228    file_path_matches = _match_file_path_matches,
229    file_extension_in = _match_file_extension_in,
230    is_in = _match_is_in,
231    never = _match_never,
232    str_endswith = _match_str_endswith,
233    str_matches = _match_str_matches,
234    str_startswith = _match_str_startswith,
235    is_matcher = _is_matcher,
236    # keep sorted end
237)
238