xref: /aosp_15_r20/external/bazelbuild-kotlin-rules/kotlin/common/testing/unittest_suites.bzl (revision 3a22c0a33dd99bcca39a024d43e6fbcc55c2806e)
1*3a22c0a3SAlix# Copyright 2022 Google LLC. All rights reserved.
2*3a22c0a3SAlix#
3*3a22c0a3SAlix# Licensed under the Apache License, Version 2.0 (the License);
4*3a22c0a3SAlix# you may not use this file except in compliance with the License.
5*3a22c0a3SAlix# You may obtain a copy of the License at
6*3a22c0a3SAlix#
7*3a22c0a3SAlix#     http://www.apache.org/licenses/LICENSE-2.0
8*3a22c0a3SAlix#
9*3a22c0a3SAlix# Unless required by applicable law or agreed to in writing, software
10*3a22c0a3SAlix# distributed under the License is distributed on an "AS IS" BASIS,
11*3a22c0a3SAlix# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12*3a22c0a3SAlix# See the License for the specific language governing permissions and
13*3a22c0a3SAlix# limitations under the License.
14*3a22c0a3SAlix
15*3a22c0a3SAlix"""A framework for writing tests of Starlark code with minimal overhead.
16*3a22c0a3SAlix
17*3a22c0a3SAlixTest cases are written as Starlark functions that will eventually be executed as
18*3a22c0a3SAlixindividual test targets. Two types of tests are supported: those including their
19*3a22c0a3SAlixown assertions, and those expected to call 'fail()'.
20*3a22c0a3SAlix
21*3a22c0a3SAlixBasic usage looks like:
22*3a22c0a3SAlix```
23*3a22c0a3SAlix# unittests.bzl
24*3a22c0a3SAlixload("//kotlin/common/testing:unittest_suites.bzl", "kt_unittest_suites")
25*3a22c0a3SAlixload("@bazel_skylib//lib:unittest.bzl", "asserts")
26*3a22c0a3SAlix
27*3a22c0a3SAlixunittests = kt_unittest_suite.create() # Create a new suite in this file.
28*3a22c0a3SAlix
29*3a22c0a3SAlixdef _some_test_case(ctx, env):
30*3a22c0a3SAlix    # Test logic here
31*3a22c0a3SAlix    asserts.true(env, 1 == 1)
32*3a22c0a3SAlix    return [] # Return any declared files
33*3a22c0a3SAlix
34*3a22c0a3SAlixunittests.expect_finish(_some_test_case) # Include the test case in the suite
35*3a22c0a3SAlix
36*3a22c0a3SAlixdef _some_fail_case(ctx):
37*3a22c0a3SAlix    # No assertions are allowed in fail cases
38*3a22c0a3SAlix    _some_logic_that_should_call_fail(ctx)
39*3a22c0a3SAlix
40*3a22c0a3SAlixunittests.expect_fail(_some_fail_case, "fail message substring") # Expect this case to call fail
41*3a22c0a3SAlix
42*3a22c0a3SAlix# Generate a pair of rules that will be used for test targets
43*3a22c0a3SAlix_test, _fail = unittests.close()  # @unused
44*3a22c0a3SAlix```
45*3a22c0a3SAlix
46*3a22c0a3SAlix```
47*3a22c0a3SAlix// BUILD
48*3a22c0a3SAlixload(":unittests.bzl", "unittests")
49*3a22c0a3SAlix
50*3a22c0a3SAlix# Render each test case as a target in this package
51*3a22c0a3SAlixunittests.render(
52*3a22c0a3SAlix    name = "unittests"
53*3a22c0a3SAlix)
54*3a22c0a3SAlix```
55*3a22c0a3SAlix"""
56*3a22c0a3SAlix
57*3a22c0a3SAlixload("//:visibility.bzl", "RULES_KOTLIN")
58*3a22c0a3SAlixload("@bazel_skylib//lib:unittest.bzl", "unittest")
59*3a22c0a3SAlixload(":testing_rules.bzl", "kt_testing_rules")
60*3a22c0a3SAlix
61*3a22c0a3SAlixvisibility(RULES_KOTLIN)
62*3a22c0a3SAlix
63*3a22c0a3SAlixdef _create():
64*3a22c0a3SAlix    """Create a new test suite.
65*3a22c0a3SAlix
66*3a22c0a3SAlix    Returns:
67*3a22c0a3SAlix        [kt_unittest_suite] An object representing the suite under construction
68*3a22c0a3SAlix    """
69*3a22c0a3SAlix
70*3a22c0a3SAlix    test_cases = dict()
71*3a22c0a3SAlix    rule_holder = []  # Use a list rather than separate vars becase captured vars are final
72*3a22c0a3SAlix
73*3a22c0a3SAlix    def expect_fail(test_case, msg_contains):
74*3a22c0a3SAlix        """Add a test case to the suite which is expected to call fail.
75*3a22c0a3SAlix
76*3a22c0a3SAlix        Args:
77*3a22c0a3SAlix            test_case: [function(ctx)]
78*3a22c0a3SAlix            msg_contains: [string] A substring expected in the failure message
79*3a22c0a3SAlix        """
80*3a22c0a3SAlix
81*3a22c0a3SAlix        if rule_holder:
82*3a22c0a3SAlix            fail("Test suite is closed")
83*3a22c0a3SAlix
84*3a22c0a3SAlix        test_case_name = _fn_name(test_case)
85*3a22c0a3SAlix        if not test_case_name.startswith("_"):
86*3a22c0a3SAlix            fail("Test cases must be private '%s'" % test_case_name)
87*3a22c0a3SAlix        if test_case_name in test_cases:
88*3a22c0a3SAlix            fail("Existing test case named '%s'" % test_case_name)
89*3a22c0a3SAlix
90*3a22c0a3SAlix        test_cases[test_case_name] = struct(
91*3a22c0a3SAlix            impl = test_case,
92*3a22c0a3SAlix            msg_contains = msg_contains,
93*3a22c0a3SAlix        )
94*3a22c0a3SAlix
95*3a22c0a3SAlix    def expect_finish(test_case):
96*3a22c0a3SAlix        """Add a test case to the suite.
97*3a22c0a3SAlix
98*3a22c0a3SAlix        Args:
99*3a22c0a3SAlix            test_case: [function(ctx,unittest.env):None|list[File]]
100*3a22c0a3SAlix        """
101*3a22c0a3SAlix
102*3a22c0a3SAlix        expect_fail(test_case, None)
103*3a22c0a3SAlix
104*3a22c0a3SAlix    def close():
105*3a22c0a3SAlix        """Close the suite from expect_finishing new tests.
106*3a22c0a3SAlix
107*3a22c0a3SAlix        The return value must be assigned to '_test, _fail' with an '# @unused' suppression.
108*3a22c0a3SAlix
109*3a22c0a3SAlix        Returns:
110*3a22c0a3SAlix            [(rule, rule)]
111*3a22c0a3SAlix        """
112*3a22c0a3SAlix
113*3a22c0a3SAlix        if rule_holder:
114*3a22c0a3SAlix            fail("Test suite is closed")
115*3a22c0a3SAlix
116*3a22c0a3SAlix        def test_impl(ctx):
117*3a22c0a3SAlix            env = unittest.begin(ctx)
118*3a22c0a3SAlix
119*3a22c0a3SAlix            output_files = test_cases[ctx.attr.case_name].impl(ctx, env) or []
120*3a22c0a3SAlix            if output_files:
121*3a22c0a3SAlix                ctx.actions.run_shell(
122*3a22c0a3SAlix                    outputs = output_files,
123*3a22c0a3SAlix                    command = "exit 1",
124*3a22c0a3SAlix                )
125*3a22c0a3SAlix
126*3a22c0a3SAlix            return unittest.end(env) + [OutputGroupInfo(_file_sink = depset(output_files))]
127*3a22c0a3SAlix
128*3a22c0a3SAlix        test_rule = unittest.make(
129*3a22c0a3SAlix            impl = test_impl,
130*3a22c0a3SAlix            attrs = dict(case_name = attr.string()),
131*3a22c0a3SAlix        )
132*3a22c0a3SAlix        rule_holder.append(test_rule)
133*3a22c0a3SAlix
134*3a22c0a3SAlix        def fail_impl(ctx):
135*3a22c0a3SAlix            test_cases[ctx.attr.case_name].impl(ctx)
136*3a22c0a3SAlix            return []
137*3a22c0a3SAlix
138*3a22c0a3SAlix        fail_rule = rule(
139*3a22c0a3SAlix            implementation = fail_impl,
140*3a22c0a3SAlix            attrs = dict(case_name = attr.string()),
141*3a22c0a3SAlix        )
142*3a22c0a3SAlix        rule_holder.append(fail_rule)
143*3a22c0a3SAlix
144*3a22c0a3SAlix        # Rules must be assigned to top-level Starlark vars before being called
145*3a22c0a3SAlix        return test_rule, fail_rule
146*3a22c0a3SAlix
147*3a22c0a3SAlix    def render(name, tags = [], **kwargs):
148*3a22c0a3SAlix        """Render the test suite into targets.
149*3a22c0a3SAlix
150*3a22c0a3SAlix        Args:
151*3a22c0a3SAlix            name: [string]
152*3a22c0a3SAlix            tags: [list[string]]
153*3a22c0a3SAlix            **kwargs: Generic rule kwargs
154*3a22c0a3SAlix        """
155*3a22c0a3SAlix
156*3a22c0a3SAlix        if not rule_holder:
157*3a22c0a3SAlix            fail("Test suite is not closed")
158*3a22c0a3SAlix        test_rule = rule_holder[0]
159*3a22c0a3SAlix        fail_rule = rule_holder[1]
160*3a22c0a3SAlix
161*3a22c0a3SAlix        test_targets = []
162*3a22c0a3SAlix        for test_case_name, test_case_data in test_cases.items():
163*3a22c0a3SAlix            target_name = test_case_name.removeprefix("_") + "_test"
164*3a22c0a3SAlix            test_targets.append(target_name)
165*3a22c0a3SAlix
166*3a22c0a3SAlix            if test_case_data.msg_contains == None:
167*3a22c0a3SAlix                test_rule(
168*3a22c0a3SAlix                    name = target_name,
169*3a22c0a3SAlix                    tags = tags,
170*3a22c0a3SAlix                    case_name = test_case_name,
171*3a22c0a3SAlix                    **kwargs
172*3a22c0a3SAlix                )
173*3a22c0a3SAlix            else:
174*3a22c0a3SAlix                fail_rule(
175*3a22c0a3SAlix                    name = test_case_name,
176*3a22c0a3SAlix                    tags = tags + kt_testing_rules.ONLY_FOR_ANALYSIS_TAGS,
177*3a22c0a3SAlix                    case_name = test_case_name,
178*3a22c0a3SAlix                    **kwargs
179*3a22c0a3SAlix                )
180*3a22c0a3SAlix                kt_testing_rules.assert_failure_test(
181*3a22c0a3SAlix                    name = target_name,
182*3a22c0a3SAlix                    target_under_test = test_case_name,
183*3a22c0a3SAlix                    msg_contains = test_case_data.msg_contains,
184*3a22c0a3SAlix                )
185*3a22c0a3SAlix
186*3a22c0a3SAlix        native.test_suite(
187*3a22c0a3SAlix            name = name,
188*3a22c0a3SAlix            tests = test_targets,
189*3a22c0a3SAlix            **kwargs
190*3a22c0a3SAlix        )
191*3a22c0a3SAlix
192*3a22c0a3SAlix    return struct(
193*3a22c0a3SAlix        expect_finish = expect_finish,
194*3a22c0a3SAlix        expect_fail = expect_fail,
195*3a22c0a3SAlix        close = close,
196*3a22c0a3SAlix        render = render,
197*3a22c0a3SAlix    )
198*3a22c0a3SAlix
199*3a22c0a3SAlixdef _fn_name(rule_or_fn):
200*3a22c0a3SAlix    parts = str(rule_or_fn).removeprefix("<").removesuffix(">").split(" ")
201*3a22c0a3SAlix    return parts[0] if (len(parts) == 1) else parts[1]
202*3a22c0a3SAlix
203*3a22c0a3SAlixkt_unittest_suites = struct(
204*3a22c0a3SAlix    create = _create,
205*3a22c0a3SAlix)
206