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