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