xref: /aosp_15_r20/external/bazelbuild-rules_testing/lib/private/truth_common.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"""Common code used by truth."""
16*d6050574SRomain Jobredeaux
17*d6050574SRomain Jobredeauxload("@bazel_skylib//lib:types.bzl", "types")
18*d6050574SRomain Jobredeaux
19*d6050574SRomain Jobredeauxdef mkmethod(self, method):
20*d6050574SRomain Jobredeaux    """Bind a struct as the first arg to a function.
21*d6050574SRomain Jobredeaux
22*d6050574SRomain Jobredeaux    This is loosely equivalent to creating a bound method of a class.
23*d6050574SRomain Jobredeaux    """
24*d6050574SRomain Jobredeaux    return lambda *args, **kwargs: method(self, *args, **kwargs)
25*d6050574SRomain Jobredeaux
26*d6050574SRomain Jobredeauxdef repr_with_type(value):
27*d6050574SRomain Jobredeaux    return "<{} {}>".format(type(value), repr(value))
28*d6050574SRomain Jobredeaux
29*d6050574SRomain Jobredeauxdef _informative_str(value):
30*d6050574SRomain Jobredeaux    value_str = str(value)
31*d6050574SRomain Jobredeaux    if not value_str:
32*d6050574SRomain Jobredeaux        return "<empty string ∅>"
33*d6050574SRomain Jobredeaux    elif "\n" in value_str:
34*d6050574SRomain Jobredeaux        return '"""{}""" <sans triple-quotes; note newlines and whitespace>'.format(value_str)
35*d6050574SRomain Jobredeaux    elif value_str != value_str.strip():
36*d6050574SRomain Jobredeaux        return '"{}" <sans quotes; note whitespace within>'.format(value_str)
37*d6050574SRomain Jobredeaux    else:
38*d6050574SRomain Jobredeaux        return value_str
39*d6050574SRomain Jobredeaux
40*d6050574SRomain Jobredeauxdef enumerate_list_as_lines(values, prefix = "", format_value = None):
41*d6050574SRomain Jobredeaux    """Format a list of values in a human-friendly list.
42*d6050574SRomain Jobredeaux
43*d6050574SRomain Jobredeaux    Args:
44*d6050574SRomain Jobredeaux        values: ([`list`]) the values to display, one per line.
45*d6050574SRomain Jobredeaux        prefix: ([`str`]) prefix to add before each line item.
46*d6050574SRomain Jobredeaux        format_value: optional callable to convert each value to a string.
47*d6050574SRomain Jobredeaux            If not specified, then an appropriate converter will be inferred
48*d6050574SRomain Jobredeaux            based on the values. If specified, then the callable must accept
49*d6050574SRomain Jobredeaux            1 positional arg and return a string.
50*d6050574SRomain Jobredeaux
51*d6050574SRomain Jobredeaux    Returns:
52*d6050574SRomain Jobredeaux        [`str`]; the values formatted as a human-friendly list.
53*d6050574SRomain Jobredeaux    """
54*d6050574SRomain Jobredeaux    if not values:
55*d6050574SRomain Jobredeaux        return "{}<empty>".format(prefix)
56*d6050574SRomain Jobredeaux
57*d6050574SRomain Jobredeaux    if format_value == None:
58*d6050574SRomain Jobredeaux        format_value = guess_format_value(values)
59*d6050574SRomain Jobredeaux
60*d6050574SRomain Jobredeaux    # Subtract 1 because we start at 0; i.e. length 10 prints 0 to 9
61*d6050574SRomain Jobredeaux    max_i_width = len(str(len(values) - 1))
62*d6050574SRomain Jobredeaux
63*d6050574SRomain Jobredeaux    return "\n".join([
64*d6050574SRomain Jobredeaux        "{prefix}{ipad}{i}: {value}".format(
65*d6050574SRomain Jobredeaux            prefix = prefix,
66*d6050574SRomain Jobredeaux            ipad = " " * (max_i_width - len(str(i))),
67*d6050574SRomain Jobredeaux            i = i,
68*d6050574SRomain Jobredeaux            value = format_value(v),
69*d6050574SRomain Jobredeaux        )
70*d6050574SRomain Jobredeaux        for i, v in enumerate(values)
71*d6050574SRomain Jobredeaux    ])
72*d6050574SRomain Jobredeaux
73*d6050574SRomain Jobredeauxdef guess_format_value(values):
74*d6050574SRomain Jobredeaux    """Guess an appropriate human-friendly formatter to use with the value.
75*d6050574SRomain Jobredeaux
76*d6050574SRomain Jobredeaux    Args:
77*d6050574SRomain Jobredeaux        values: The object to pick a formatter for.
78*d6050574SRomain Jobredeaux
79*d6050574SRomain Jobredeaux    Returns:
80*d6050574SRomain Jobredeaux        callable that accepts the value.
81*d6050574SRomain Jobredeaux    """
82*d6050574SRomain Jobredeaux    found_types = {}
83*d6050574SRomain Jobredeaux    for value in values:
84*d6050574SRomain Jobredeaux        found_types[type(value)] = None
85*d6050574SRomain Jobredeaux        if len(found_types) > 1:
86*d6050574SRomain Jobredeaux            return repr_with_type
87*d6050574SRomain Jobredeaux    found_types = found_types.keys()
88*d6050574SRomain Jobredeaux    if len(found_types) != 1:
89*d6050574SRomain Jobredeaux        return repr_with_type
90*d6050574SRomain Jobredeaux    elif found_types[0] in ("string", "File"):
91*d6050574SRomain Jobredeaux        # For strings: omit the extra quotes and escaping. Just noise.
92*d6050574SRomain Jobredeaux        # For Files: they include <TYPE path> already
93*d6050574SRomain Jobredeaux        return _informative_str
94*d6050574SRomain Jobredeaux    else:
95*d6050574SRomain Jobredeaux        return repr_with_type
96*d6050574SRomain Jobredeaux
97*d6050574SRomain Jobredeauxdef maybe_sorted(container, allow_sorting = True):
98*d6050574SRomain Jobredeaux    """Attempts to return the values of `container` in sorted order, if possible.
99*d6050574SRomain Jobredeaux
100*d6050574SRomain Jobredeaux    Args:
101*d6050574SRomain Jobredeaux        container: ([`list`] | (or other object convertible to list))
102*d6050574SRomain Jobredeaux        allow_sorting: ([`bool`]) whether to sort even if it can be sorted. This
103*d6050574SRomain Jobredeaux            is primarily so that callers can avoid boilerplate when they have
104*d6050574SRomain Jobredeaux            a "should it be sorted" arg, but also always convert to a list.
105*d6050574SRomain Jobredeaux
106*d6050574SRomain Jobredeaux    Returns:
107*d6050574SRomain Jobredeaux        A list, in sorted order if possible, otherwise in the original order.
108*d6050574SRomain Jobredeaux        This *may* be the same object as given as input.
109*d6050574SRomain Jobredeaux    """
110*d6050574SRomain Jobredeaux    container = to_list(container)
111*d6050574SRomain Jobredeaux    if not allow_sorting:
112*d6050574SRomain Jobredeaux        return container
113*d6050574SRomain Jobredeaux
114*d6050574SRomain Jobredeaux    if all([_is_sortable(v) for v in container]):
115*d6050574SRomain Jobredeaux        return sorted(container)
116*d6050574SRomain Jobredeaux    else:
117*d6050574SRomain Jobredeaux        return container
118*d6050574SRomain Jobredeaux
119*d6050574SRomain Jobredeauxdef _is_sortable(obj):
120*d6050574SRomain Jobredeaux    return (
121*d6050574SRomain Jobredeaux        types.is_string(obj) or types.is_int(obj) or types.is_none(obj) or
122*d6050574SRomain Jobredeaux        types.is_bool(obj)
123*d6050574SRomain Jobredeaux    )
124*d6050574SRomain Jobredeaux
125*d6050574SRomain Jobredeauxdef to_list(obj):
126*d6050574SRomain Jobredeaux    """Attempt to convert the object to a list, else error.
127*d6050574SRomain Jobredeaux
128*d6050574SRomain Jobredeaux    NOTE: This only supports objects that are typically understood as
129*d6050574SRomain Jobredeaux    lists, not any iterable. Types like `dict` and `str` are iterable,
130*d6050574SRomain Jobredeaux    but will be rejected.
131*d6050574SRomain Jobredeaux
132*d6050574SRomain Jobredeaux    Args:
133*d6050574SRomain Jobredeaux        obj: ([`list`] | [`depset`]) The object to convert to a list.
134*d6050574SRomain Jobredeaux
135*d6050574SRomain Jobredeaux    Returns:
136*d6050574SRomain Jobredeaux        [`list`] of the object
137*d6050574SRomain Jobredeaux    """
138*d6050574SRomain Jobredeaux    if types.is_string(obj):
139*d6050574SRomain Jobredeaux        fail("Cannot pass string to to_list(): {}".format(obj))
140*d6050574SRomain Jobredeaux    elif types.is_list(obj):
141*d6050574SRomain Jobredeaux        return obj
142*d6050574SRomain Jobredeaux    elif types.is_depset(obj):
143*d6050574SRomain Jobredeaux        return obj.to_list()
144*d6050574SRomain Jobredeaux    else:
145*d6050574SRomain Jobredeaux        fail("Unable to convert to list: {}".format(repr_with_type(obj)))
146