1*bcb5dc79SHONG Yifan# Copyright 2017 The Bazel Authors. All rights reserved. 2*bcb5dc79SHONG Yifan# 3*bcb5dc79SHONG Yifan# Licensed under the Apache License, Version 2.0 (the "License"); 4*bcb5dc79SHONG Yifan# you may not use this file except in compliance with the License. 5*bcb5dc79SHONG Yifan# You may obtain a copy of the License at 6*bcb5dc79SHONG Yifan# 7*bcb5dc79SHONG Yifan# http://www.apache.org/licenses/LICENSE-2.0 8*bcb5dc79SHONG Yifan# 9*bcb5dc79SHONG Yifan# Unless required by applicable law or agreed to in writing, software 10*bcb5dc79SHONG Yifan# distributed under the License is distributed on an "AS IS" BASIS, 11*bcb5dc79SHONG Yifan# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12*bcb5dc79SHONG Yifan# See the License for the specific language governing permissions and 13*bcb5dc79SHONG Yifan# limitations under the License. 14*bcb5dc79SHONG Yifan 15*bcb5dc79SHONG Yifan"""Unit testing support. 16*bcb5dc79SHONG Yifan 17*bcb5dc79SHONG YifanUnlike most Skylib files, this exports four modules: 18*bcb5dc79SHONG Yifan* `unittest` contains functions to declare and define unit tests for ordinary 19*bcb5dc79SHONG Yifan Starlark functions; 20*bcb5dc79SHONG Yifan* `analysistest` contains functions to declare and define tests for analysis 21*bcb5dc79SHONG Yifan phase behavior of a rule, such as a given target's providers or registered 22*bcb5dc79SHONG Yifan actions; 23*bcb5dc79SHONG Yifan* `loadingtest` contains functions to declare and define tests for loading 24*bcb5dc79SHONG Yifan phase behavior, such as macros and `native.*`; 25*bcb5dc79SHONG Yifan* `asserts` contains the assertions used within tests. 26*bcb5dc79SHONG Yifan 27*bcb5dc79SHONG YifanSee https://bazel.build/extending/concepts for background about macros, rules, 28*bcb5dc79SHONG Yifanand the different phases of a build. 29*bcb5dc79SHONG Yifan""" 30*bcb5dc79SHONG Yifan 31*bcb5dc79SHONG Yifanload(":new_sets.bzl", new_sets = "sets") 32*bcb5dc79SHONG Yifanload(":partial.bzl", "partial") 33*bcb5dc79SHONG Yifanload(":types.bzl", "types") 34*bcb5dc79SHONG Yifan 35*bcb5dc79SHONG Yifan# The following function should only be called from WORKSPACE files and workspace macros. 36*bcb5dc79SHONG Yifan# buildifier: disable=unnamed-macro 37*bcb5dc79SHONG Yifandef register_unittest_toolchains(): 38*bcb5dc79SHONG Yifan """Registers the toolchains for unittest users.""" 39*bcb5dc79SHONG Yifan native.register_toolchains( 40*bcb5dc79SHONG Yifan "@bazel_skylib//toolchains/unittest:cmd_toolchain", 41*bcb5dc79SHONG Yifan "@bazel_skylib//toolchains/unittest:bash_toolchain", 42*bcb5dc79SHONG Yifan ) 43*bcb5dc79SHONG Yifan 44*bcb5dc79SHONG YifanTOOLCHAIN_TYPE = "@bazel_skylib//toolchains/unittest:toolchain_type" 45*bcb5dc79SHONG Yifan 46*bcb5dc79SHONG Yifan_UnittestToolchainInfo = provider( 47*bcb5dc79SHONG Yifan doc = "Execution platform information for rules in the bazel_skylib repository.", 48*bcb5dc79SHONG Yifan fields = [ 49*bcb5dc79SHONG Yifan "file_ext", 50*bcb5dc79SHONG Yifan "success_templ", 51*bcb5dc79SHONG Yifan "failure_templ", 52*bcb5dc79SHONG Yifan "join_on", 53*bcb5dc79SHONG Yifan "escape_chars_with", 54*bcb5dc79SHONG Yifan "escape_other_chars_with", 55*bcb5dc79SHONG Yifan ], 56*bcb5dc79SHONG Yifan) 57*bcb5dc79SHONG Yifan 58*bcb5dc79SHONG Yifandef _unittest_toolchain_impl(ctx): 59*bcb5dc79SHONG Yifan return [ 60*bcb5dc79SHONG Yifan platform_common.ToolchainInfo( 61*bcb5dc79SHONG Yifan unittest_toolchain_info = _UnittestToolchainInfo( 62*bcb5dc79SHONG Yifan file_ext = ctx.attr.file_ext, 63*bcb5dc79SHONG Yifan success_templ = ctx.attr.success_templ, 64*bcb5dc79SHONG Yifan failure_templ = ctx.attr.failure_templ, 65*bcb5dc79SHONG Yifan join_on = ctx.attr.join_on, 66*bcb5dc79SHONG Yifan escape_chars_with = ctx.attr.escape_chars_with, 67*bcb5dc79SHONG Yifan escape_other_chars_with = ctx.attr.escape_other_chars_with, 68*bcb5dc79SHONG Yifan ), 69*bcb5dc79SHONG Yifan ), 70*bcb5dc79SHONG Yifan ] 71*bcb5dc79SHONG Yifan 72*bcb5dc79SHONG Yifanunittest_toolchain = rule( 73*bcb5dc79SHONG Yifan implementation = _unittest_toolchain_impl, 74*bcb5dc79SHONG Yifan attrs = { 75*bcb5dc79SHONG Yifan "failure_templ": attr.string( 76*bcb5dc79SHONG Yifan mandatory = True, 77*bcb5dc79SHONG Yifan doc = ( 78*bcb5dc79SHONG Yifan "Test script template with a single `%s`. That " + 79*bcb5dc79SHONG Yifan "placeholder is replaced with the lines in the " + 80*bcb5dc79SHONG Yifan "failure message joined with the string " + 81*bcb5dc79SHONG Yifan "specified in `join_with`. The resulting script " + 82*bcb5dc79SHONG Yifan "should print the failure message and exit with " + 83*bcb5dc79SHONG Yifan "non-zero status." 84*bcb5dc79SHONG Yifan ), 85*bcb5dc79SHONG Yifan ), 86*bcb5dc79SHONG Yifan "file_ext": attr.string( 87*bcb5dc79SHONG Yifan mandatory = True, 88*bcb5dc79SHONG Yifan doc = ( 89*bcb5dc79SHONG Yifan "File extension for test script, including leading dot." 90*bcb5dc79SHONG Yifan ), 91*bcb5dc79SHONG Yifan ), 92*bcb5dc79SHONG Yifan "join_on": attr.string( 93*bcb5dc79SHONG Yifan mandatory = True, 94*bcb5dc79SHONG Yifan doc = ( 95*bcb5dc79SHONG Yifan "String used to join the lines in the failure " + 96*bcb5dc79SHONG Yifan "message before including the resulting string " + 97*bcb5dc79SHONG Yifan "in the script specified in `failure_templ`." 98*bcb5dc79SHONG Yifan ), 99*bcb5dc79SHONG Yifan ), 100*bcb5dc79SHONG Yifan "success_templ": attr.string( 101*bcb5dc79SHONG Yifan mandatory = True, 102*bcb5dc79SHONG Yifan doc = ( 103*bcb5dc79SHONG Yifan "Test script generated when the test passes. " + 104*bcb5dc79SHONG Yifan "Should exit with status 0." 105*bcb5dc79SHONG Yifan ), 106*bcb5dc79SHONG Yifan ), 107*bcb5dc79SHONG Yifan "escape_chars_with": attr.string_dict( 108*bcb5dc79SHONG Yifan doc = ( 109*bcb5dc79SHONG Yifan "Dictionary of characters that need escaping in " + 110*bcb5dc79SHONG Yifan "test failure message to prefix appended to escape " + 111*bcb5dc79SHONG Yifan "those characters. For example, " + 112*bcb5dc79SHONG Yifan '`{"%": "%", ">": "^"}` would replace `%` with ' + 113*bcb5dc79SHONG Yifan "`%%` and `>` with `^>` in the failure message " + 114*bcb5dc79SHONG Yifan "before that is included in `success_templ`." 115*bcb5dc79SHONG Yifan ), 116*bcb5dc79SHONG Yifan ), 117*bcb5dc79SHONG Yifan "escape_other_chars_with": attr.string( 118*bcb5dc79SHONG Yifan default = "", 119*bcb5dc79SHONG Yifan doc = ( 120*bcb5dc79SHONG Yifan "String to prefix every character in test failure " + 121*bcb5dc79SHONG Yifan "message which is not a key in `escape_chars_with` " + 122*bcb5dc79SHONG Yifan "before including that in `success_templ`. For " + 123*bcb5dc79SHONG Yifan 'example, `"\"` would prefix every character in ' + 124*bcb5dc79SHONG Yifan "the failure message (except those in the keys of " + 125*bcb5dc79SHONG Yifan "`escape_chars_with`) with `\\`." 126*bcb5dc79SHONG Yifan ), 127*bcb5dc79SHONG Yifan ), 128*bcb5dc79SHONG Yifan }, 129*bcb5dc79SHONG Yifan) 130*bcb5dc79SHONG Yifan 131*bcb5dc79SHONG Yifandef _impl_function_name(impl): 132*bcb5dc79SHONG Yifan """Derives the name of the given rule implementation function. 133*bcb5dc79SHONG Yifan 134*bcb5dc79SHONG Yifan This can be used for better test feedback. 135*bcb5dc79SHONG Yifan 136*bcb5dc79SHONG Yifan Args: 137*bcb5dc79SHONG Yifan impl: the rule implementation function 138*bcb5dc79SHONG Yifan 139*bcb5dc79SHONG Yifan Returns: 140*bcb5dc79SHONG Yifan The name of the given function 141*bcb5dc79SHONG Yifan """ 142*bcb5dc79SHONG Yifan 143*bcb5dc79SHONG Yifan # Starlark currently stringifies a function as "<function NAME>", so we use 144*bcb5dc79SHONG Yifan # that knowledge to parse the "NAME" portion out. If this behavior ever 145*bcb5dc79SHONG Yifan # changes, we'll need to update this. 146*bcb5dc79SHONG Yifan # TODO(bazel-team): Expose a ._name field on functions to avoid this. 147*bcb5dc79SHONG Yifan impl_name = str(impl) 148*bcb5dc79SHONG Yifan impl_name = impl_name.partition("<function ")[-1] 149*bcb5dc79SHONG Yifan return impl_name.rpartition(">")[0] 150*bcb5dc79SHONG Yifan 151*bcb5dc79SHONG Yifandef _make(impl, attrs = {}, doc = "", toolchains = []): 152*bcb5dc79SHONG Yifan """Creates a unit test rule from its implementation function. 153*bcb5dc79SHONG Yifan 154*bcb5dc79SHONG Yifan Each unit test is defined in an implementation function that must then be 155*bcb5dc79SHONG Yifan associated with a rule so that a target can be built. This function handles 156*bcb5dc79SHONG Yifan the boilerplate to create and return a test rule and captures the 157*bcb5dc79SHONG Yifan implementation function's name so that it can be printed in test feedback. 158*bcb5dc79SHONG Yifan 159*bcb5dc79SHONG Yifan The optional `attrs` argument can be used to define dependencies for this 160*bcb5dc79SHONG Yifan test, in order to form unit tests of rules. 161*bcb5dc79SHONG Yifan 162*bcb5dc79SHONG Yifan The optional `toolchains` argument can be used to define toolchain 163*bcb5dc79SHONG Yifan dependencies for this test. 164*bcb5dc79SHONG Yifan 165*bcb5dc79SHONG Yifan An example of a unit test: 166*bcb5dc79SHONG Yifan 167*bcb5dc79SHONG Yifan ``` 168*bcb5dc79SHONG Yifan def _your_test(ctx): 169*bcb5dc79SHONG Yifan env = unittest.begin(ctx) 170*bcb5dc79SHONG Yifan 171*bcb5dc79SHONG Yifan # Assert statements go here 172*bcb5dc79SHONG Yifan 173*bcb5dc79SHONG Yifan return unittest.end(env) 174*bcb5dc79SHONG Yifan 175*bcb5dc79SHONG Yifan your_test = unittest.make(_your_test) 176*bcb5dc79SHONG Yifan ``` 177*bcb5dc79SHONG Yifan 178*bcb5dc79SHONG Yifan Recall that names of test rules must end in `_test`. 179*bcb5dc79SHONG Yifan 180*bcb5dc79SHONG Yifan Args: 181*bcb5dc79SHONG Yifan impl: The implementation function of the unit test. 182*bcb5dc79SHONG Yifan attrs: An optional dictionary to supplement the attrs passed to the 183*bcb5dc79SHONG Yifan unit test's `rule()` constructor. 184*bcb5dc79SHONG Yifan doc: A description of the rule that can be extracted by documentation generating tools. 185*bcb5dc79SHONG Yifan toolchains: An optional list to supplement the toolchains passed to 186*bcb5dc79SHONG Yifan the unit test's `rule()` constructor. 187*bcb5dc79SHONG Yifan 188*bcb5dc79SHONG Yifan Returns: 189*bcb5dc79SHONG Yifan A rule definition that should be stored in a global whose name ends in 190*bcb5dc79SHONG Yifan `_test`. 191*bcb5dc79SHONG Yifan """ 192*bcb5dc79SHONG Yifan attrs = dict(attrs) 193*bcb5dc79SHONG Yifan attrs["_impl_name"] = attr.string(default = _impl_function_name(impl)) 194*bcb5dc79SHONG Yifan 195*bcb5dc79SHONG Yifan return rule( 196*bcb5dc79SHONG Yifan impl, 197*bcb5dc79SHONG Yifan doc = doc, 198*bcb5dc79SHONG Yifan attrs = attrs, 199*bcb5dc79SHONG Yifan _skylark_testable = True, 200*bcb5dc79SHONG Yifan test = True, 201*bcb5dc79SHONG Yifan toolchains = toolchains + [TOOLCHAIN_TYPE], 202*bcb5dc79SHONG Yifan ) 203*bcb5dc79SHONG Yifan 204*bcb5dc79SHONG Yifan_ActionInfo = provider( 205*bcb5dc79SHONG Yifan doc = "Information relating to the target under test.", 206*bcb5dc79SHONG Yifan fields = ["actions", "bin_path"], 207*bcb5dc79SHONG Yifan) 208*bcb5dc79SHONG Yifan 209*bcb5dc79SHONG Yifandef _action_retrieving_aspect_impl(target, ctx): 210*bcb5dc79SHONG Yifan return [ 211*bcb5dc79SHONG Yifan _ActionInfo( 212*bcb5dc79SHONG Yifan actions = target.actions, 213*bcb5dc79SHONG Yifan bin_path = ctx.bin_dir.path, 214*bcb5dc79SHONG Yifan ), 215*bcb5dc79SHONG Yifan ] 216*bcb5dc79SHONG Yifan 217*bcb5dc79SHONG Yifan_action_retrieving_aspect = aspect( 218*bcb5dc79SHONG Yifan attr_aspects = [], 219*bcb5dc79SHONG Yifan implementation = _action_retrieving_aspect_impl, 220*bcb5dc79SHONG Yifan) 221*bcb5dc79SHONG Yifan 222*bcb5dc79SHONG Yifan# TODO(cparsons): Provide more full documentation on analysis testing in README. 223*bcb5dc79SHONG Yifandef _make_analysis_test( 224*bcb5dc79SHONG Yifan impl, 225*bcb5dc79SHONG Yifan expect_failure = False, 226*bcb5dc79SHONG Yifan attrs = {}, 227*bcb5dc79SHONG Yifan fragments = [], 228*bcb5dc79SHONG Yifan config_settings = {}, 229*bcb5dc79SHONG Yifan extra_target_under_test_aspects = [], 230*bcb5dc79SHONG Yifan doc = ""): 231*bcb5dc79SHONG Yifan """Creates an analysis test rule from its implementation function. 232*bcb5dc79SHONG Yifan 233*bcb5dc79SHONG Yifan An analysis test verifies the behavior of a "real" rule target by examining 234*bcb5dc79SHONG Yifan and asserting on the providers given by the real target. 235*bcb5dc79SHONG Yifan 236*bcb5dc79SHONG Yifan Each analysis test is defined in an implementation function that must then be 237*bcb5dc79SHONG Yifan associated with a rule so that a target can be built. This function handles 238*bcb5dc79SHONG Yifan the boilerplate to create and return a test rule and captures the 239*bcb5dc79SHONG Yifan implementation function's name so that it can be printed in test feedback. 240*bcb5dc79SHONG Yifan 241*bcb5dc79SHONG Yifan An example of an analysis test: 242*bcb5dc79SHONG Yifan 243*bcb5dc79SHONG Yifan ``` 244*bcb5dc79SHONG Yifan def _your_test(ctx): 245*bcb5dc79SHONG Yifan env = analysistest.begin(ctx) 246*bcb5dc79SHONG Yifan 247*bcb5dc79SHONG Yifan # Assert statements go here 248*bcb5dc79SHONG Yifan 249*bcb5dc79SHONG Yifan return analysistest.end(env) 250*bcb5dc79SHONG Yifan 251*bcb5dc79SHONG Yifan your_test = analysistest.make(_your_test) 252*bcb5dc79SHONG Yifan ``` 253*bcb5dc79SHONG Yifan 254*bcb5dc79SHONG Yifan Recall that names of test rules must end in `_test`. 255*bcb5dc79SHONG Yifan 256*bcb5dc79SHONG Yifan Args: 257*bcb5dc79SHONG Yifan impl: The implementation function of the unit test. 258*bcb5dc79SHONG Yifan expect_failure: If true, the analysis test will expect the target_under_test 259*bcb5dc79SHONG Yifan to fail. Assertions can be made on the underlying failure using asserts.expect_failure 260*bcb5dc79SHONG Yifan attrs: An optional dictionary to supplement the attrs passed to the 261*bcb5dc79SHONG Yifan unit test's `rule()` constructor. 262*bcb5dc79SHONG Yifan fragments: An optional list of fragment names that can be used to give rules access to 263*bcb5dc79SHONG Yifan language-specific parts of configuration. 264*bcb5dc79SHONG Yifan config_settings: A dictionary of configuration settings to change for the target under 265*bcb5dc79SHONG Yifan test and its dependencies. This may be used to essentially change 'build flags' for 266*bcb5dc79SHONG Yifan the target under test, and may thus be utilized to test multiple targets with different 267*bcb5dc79SHONG Yifan flags in a single build 268*bcb5dc79SHONG Yifan extra_target_under_test_aspects: An optional list of aspects to apply to the target_under_test 269*bcb5dc79SHONG Yifan in addition to those set up by default for the test harness itself. 270*bcb5dc79SHONG Yifan doc: A description of the rule that can be extracted by documentation generating tools. 271*bcb5dc79SHONG Yifan 272*bcb5dc79SHONG Yifan Returns: 273*bcb5dc79SHONG Yifan A rule definition that should be stored in a global whose name ends in 274*bcb5dc79SHONG Yifan `_test`. 275*bcb5dc79SHONG Yifan """ 276*bcb5dc79SHONG Yifan attrs = dict(attrs) 277*bcb5dc79SHONG Yifan attrs["_impl_name"] = attr.string(default = _impl_function_name(impl)) 278*bcb5dc79SHONG Yifan 279*bcb5dc79SHONG Yifan changed_settings = dict(config_settings) 280*bcb5dc79SHONG Yifan if expect_failure: 281*bcb5dc79SHONG Yifan changed_settings["//command_line_option:allow_analysis_failures"] = "True" 282*bcb5dc79SHONG Yifan 283*bcb5dc79SHONG Yifan target_attr_kwargs = {} 284*bcb5dc79SHONG Yifan if changed_settings: 285*bcb5dc79SHONG Yifan test_transition = analysis_test_transition( 286*bcb5dc79SHONG Yifan settings = changed_settings, 287*bcb5dc79SHONG Yifan ) 288*bcb5dc79SHONG Yifan target_attr_kwargs["cfg"] = test_transition 289*bcb5dc79SHONG Yifan 290*bcb5dc79SHONG Yifan attrs["target_under_test"] = attr.label( 291*bcb5dc79SHONG Yifan aspects = [_action_retrieving_aspect] + extra_target_under_test_aspects, 292*bcb5dc79SHONG Yifan mandatory = True, 293*bcb5dc79SHONG Yifan **target_attr_kwargs 294*bcb5dc79SHONG Yifan ) 295*bcb5dc79SHONG Yifan 296*bcb5dc79SHONG Yifan return rule( 297*bcb5dc79SHONG Yifan impl, 298*bcb5dc79SHONG Yifan doc = doc, 299*bcb5dc79SHONG Yifan attrs = attrs, 300*bcb5dc79SHONG Yifan fragments = fragments, 301*bcb5dc79SHONG Yifan test = True, 302*bcb5dc79SHONG Yifan toolchains = [TOOLCHAIN_TYPE], 303*bcb5dc79SHONG Yifan analysis_test = True, 304*bcb5dc79SHONG Yifan ) 305*bcb5dc79SHONG Yifan 306*bcb5dc79SHONG Yifandef _suite(name, *test_rules): 307*bcb5dc79SHONG Yifan """Defines a `test_suite` target that contains multiple tests. 308*bcb5dc79SHONG Yifan 309*bcb5dc79SHONG Yifan After defining your test rules in a `.bzl` file, you need to create targets 310*bcb5dc79SHONG Yifan from those rules so that `blaze test` can execute them. Doing this manually 311*bcb5dc79SHONG Yifan in a BUILD file would consist of listing each test in your `load` statement 312*bcb5dc79SHONG Yifan and then creating each target one by one. To reduce duplication, we recommend 313*bcb5dc79SHONG Yifan writing a macro in your `.bzl` file to instantiate all targets, and calling 314*bcb5dc79SHONG Yifan that macro from your BUILD file so you only have to load one symbol. 315*bcb5dc79SHONG Yifan 316*bcb5dc79SHONG Yifan You can use this function to create the targets and wrap them in a single 317*bcb5dc79SHONG Yifan test_suite target. If a test rule requires no arguments, you can simply list 318*bcb5dc79SHONG Yifan it as an argument. If you wish to supply attributes explicitly, you can do so 319*bcb5dc79SHONG Yifan using `partial.make()`. For instance, in your `.bzl` file, you could write: 320*bcb5dc79SHONG Yifan 321*bcb5dc79SHONG Yifan ``` 322*bcb5dc79SHONG Yifan def your_test_suite(): 323*bcb5dc79SHONG Yifan unittest.suite( 324*bcb5dc79SHONG Yifan "your_test_suite", 325*bcb5dc79SHONG Yifan your_test, 326*bcb5dc79SHONG Yifan your_other_test, 327*bcb5dc79SHONG Yifan partial.make(yet_another_test, timeout = "short"), 328*bcb5dc79SHONG Yifan ) 329*bcb5dc79SHONG Yifan ``` 330*bcb5dc79SHONG Yifan 331*bcb5dc79SHONG Yifan Then, in your `BUILD` file, simply load the macro and invoke it to have all 332*bcb5dc79SHONG Yifan of the targets created: 333*bcb5dc79SHONG Yifan 334*bcb5dc79SHONG Yifan ``` 335*bcb5dc79SHONG Yifan load("//path/to/your/package:tests.bzl", "your_test_suite") 336*bcb5dc79SHONG Yifan your_test_suite() 337*bcb5dc79SHONG Yifan ``` 338*bcb5dc79SHONG Yifan 339*bcb5dc79SHONG Yifan If you pass _N_ unit test rules to `unittest.suite`, _N_ + 1 targets will be 340*bcb5dc79SHONG Yifan created: a `test_suite` target named `${name}` (where `${name}` is the name 341*bcb5dc79SHONG Yifan argument passed in here) and targets named `${name}_test_${i}`, where `${i}` 342*bcb5dc79SHONG Yifan is the index of the test in the `test_rules` list, which is used to uniquely 343*bcb5dc79SHONG Yifan name each target. 344*bcb5dc79SHONG Yifan 345*bcb5dc79SHONG Yifan Args: 346*bcb5dc79SHONG Yifan name: The name of the `test_suite` target, and the prefix of all the test 347*bcb5dc79SHONG Yifan target names. 348*bcb5dc79SHONG Yifan *test_rules: A list of test rules defines by `unittest.test`. 349*bcb5dc79SHONG Yifan """ 350*bcb5dc79SHONG Yifan test_names = [] 351*bcb5dc79SHONG Yifan for index, test_rule in enumerate(test_rules): 352*bcb5dc79SHONG Yifan test_name = "%s_test_%d" % (name, index) 353*bcb5dc79SHONG Yifan if partial.is_instance(test_rule): 354*bcb5dc79SHONG Yifan partial.call(test_rule, name = test_name) 355*bcb5dc79SHONG Yifan else: 356*bcb5dc79SHONG Yifan test_rule(name = test_name) 357*bcb5dc79SHONG Yifan test_names.append(test_name) 358*bcb5dc79SHONG Yifan 359*bcb5dc79SHONG Yifan native.test_suite( 360*bcb5dc79SHONG Yifan name = name, 361*bcb5dc79SHONG Yifan tests = [":%s" % t for t in test_names], 362*bcb5dc79SHONG Yifan ) 363*bcb5dc79SHONG Yifan 364*bcb5dc79SHONG Yifandef _begin(ctx): 365*bcb5dc79SHONG Yifan """Begins a unit test. 366*bcb5dc79SHONG Yifan 367*bcb5dc79SHONG Yifan This should be the first function called in a unit test implementation 368*bcb5dc79SHONG Yifan function. It initializes a "test environment" that is used to collect 369*bcb5dc79SHONG Yifan assertion failures so that they can be reported and logged at the end of the 370*bcb5dc79SHONG Yifan test. 371*bcb5dc79SHONG Yifan 372*bcb5dc79SHONG Yifan Args: 373*bcb5dc79SHONG Yifan ctx: The Starlark context. Pass the implementation function's `ctx` argument 374*bcb5dc79SHONG Yifan in verbatim. 375*bcb5dc79SHONG Yifan 376*bcb5dc79SHONG Yifan Returns: 377*bcb5dc79SHONG Yifan A test environment struct that must be passed to assertions and finally to 378*bcb5dc79SHONG Yifan `unittest.end`. Do not rely on internal details about the fields in this 379*bcb5dc79SHONG Yifan struct as it may change. 380*bcb5dc79SHONG Yifan """ 381*bcb5dc79SHONG Yifan return struct(ctx = ctx, failures = []) 382*bcb5dc79SHONG Yifan 383*bcb5dc79SHONG Yifandef _begin_analysis_test(ctx): 384*bcb5dc79SHONG Yifan """Begins an analysis test. 385*bcb5dc79SHONG Yifan 386*bcb5dc79SHONG Yifan This should be the first function called in an analysis test implementation 387*bcb5dc79SHONG Yifan function. It initializes a "test environment" that is used to collect 388*bcb5dc79SHONG Yifan assertion failures so that they can be reported and logged at the end of the 389*bcb5dc79SHONG Yifan test. 390*bcb5dc79SHONG Yifan 391*bcb5dc79SHONG Yifan Args: 392*bcb5dc79SHONG Yifan ctx: The Starlark context. Pass the implementation function's `ctx` argument 393*bcb5dc79SHONG Yifan in verbatim. 394*bcb5dc79SHONG Yifan 395*bcb5dc79SHONG Yifan Returns: 396*bcb5dc79SHONG Yifan A test environment struct that must be passed to assertions and finally to 397*bcb5dc79SHONG Yifan `analysistest.end`. Do not rely on internal details about the fields in this 398*bcb5dc79SHONG Yifan struct as it may change. 399*bcb5dc79SHONG Yifan """ 400*bcb5dc79SHONG Yifan return struct(ctx = ctx, failures = []) 401*bcb5dc79SHONG Yifan 402*bcb5dc79SHONG Yifandef _end_analysis_test(env): 403*bcb5dc79SHONG Yifan """Ends an analysis test and logs the results. 404*bcb5dc79SHONG Yifan 405*bcb5dc79SHONG Yifan This must be called and returned at the end of an analysis test implementation function so 406*bcb5dc79SHONG Yifan that the results are reported. 407*bcb5dc79SHONG Yifan 408*bcb5dc79SHONG Yifan Args: 409*bcb5dc79SHONG Yifan env: The test environment returned by `analysistest.begin`. 410*bcb5dc79SHONG Yifan 411*bcb5dc79SHONG Yifan Returns: 412*bcb5dc79SHONG Yifan A list of providers needed to automatically register the analysis test result. 413*bcb5dc79SHONG Yifan """ 414*bcb5dc79SHONG Yifan return [AnalysisTestResultInfo( 415*bcb5dc79SHONG Yifan success = (len(env.failures) == 0), 416*bcb5dc79SHONG Yifan message = "\n".join(env.failures), 417*bcb5dc79SHONG Yifan )] 418*bcb5dc79SHONG Yifan 419*bcb5dc79SHONG Yifandef _end(env): 420*bcb5dc79SHONG Yifan """Ends a unit test and logs the results. 421*bcb5dc79SHONG Yifan 422*bcb5dc79SHONG Yifan This must be called and returned at the end of a unit test implementation function so 423*bcb5dc79SHONG Yifan that the results are reported. 424*bcb5dc79SHONG Yifan 425*bcb5dc79SHONG Yifan Args: 426*bcb5dc79SHONG Yifan env: The test environment returned by `unittest.begin`. 427*bcb5dc79SHONG Yifan 428*bcb5dc79SHONG Yifan Returns: 429*bcb5dc79SHONG Yifan A list of providers needed to automatically register the test result. 430*bcb5dc79SHONG Yifan """ 431*bcb5dc79SHONG Yifan 432*bcb5dc79SHONG Yifan tc = env.ctx.toolchains[TOOLCHAIN_TYPE].unittest_toolchain_info 433*bcb5dc79SHONG Yifan testbin = env.ctx.actions.declare_file(env.ctx.label.name + tc.file_ext) 434*bcb5dc79SHONG Yifan if env.failures: 435*bcb5dc79SHONG Yifan failure_message_lines = "\n".join(env.failures).split("\n") 436*bcb5dc79SHONG Yifan escaped_failure_message_lines = [ 437*bcb5dc79SHONG Yifan "".join([ 438*bcb5dc79SHONG Yifan tc.escape_chars_with.get(c, tc.escape_other_chars_with) + c 439*bcb5dc79SHONG Yifan for c in line.elems() 440*bcb5dc79SHONG Yifan ]) 441*bcb5dc79SHONG Yifan for line in failure_message_lines 442*bcb5dc79SHONG Yifan ] 443*bcb5dc79SHONG Yifan cmd = tc.failure_templ % tc.join_on.join(escaped_failure_message_lines) 444*bcb5dc79SHONG Yifan else: 445*bcb5dc79SHONG Yifan cmd = tc.success_templ 446*bcb5dc79SHONG Yifan 447*bcb5dc79SHONG Yifan env.ctx.actions.write( 448*bcb5dc79SHONG Yifan output = testbin, 449*bcb5dc79SHONG Yifan content = cmd, 450*bcb5dc79SHONG Yifan is_executable = True, 451*bcb5dc79SHONG Yifan ) 452*bcb5dc79SHONG Yifan return [DefaultInfo(executable = testbin)] 453*bcb5dc79SHONG Yifan 454*bcb5dc79SHONG Yifandef _fail(env, msg): 455*bcb5dc79SHONG Yifan """Unconditionally causes the current test to fail. 456*bcb5dc79SHONG Yifan 457*bcb5dc79SHONG Yifan Args: 458*bcb5dc79SHONG Yifan env: The test environment returned by `unittest.begin`. 459*bcb5dc79SHONG Yifan msg: The message to log describing the failure. 460*bcb5dc79SHONG Yifan """ 461*bcb5dc79SHONG Yifan full_msg = "In test %s: %s" % (env.ctx.attr._impl_name, msg) 462*bcb5dc79SHONG Yifan 463*bcb5dc79SHONG Yifan # There isn't a better way to output the message in Starlark, so use print. 464*bcb5dc79SHONG Yifan # buildifier: disable=print 465*bcb5dc79SHONG Yifan print(full_msg) 466*bcb5dc79SHONG Yifan env.failures.append(full_msg) 467*bcb5dc79SHONG Yifan 468*bcb5dc79SHONG Yifandef _assert_true( 469*bcb5dc79SHONG Yifan env, 470*bcb5dc79SHONG Yifan condition, 471*bcb5dc79SHONG Yifan msg = "Expected condition to be true, but was false."): 472*bcb5dc79SHONG Yifan """Asserts that the given `condition` is true. 473*bcb5dc79SHONG Yifan 474*bcb5dc79SHONG Yifan Args: 475*bcb5dc79SHONG Yifan env: The test environment returned by `unittest.begin`. 476*bcb5dc79SHONG Yifan condition: A value that will be evaluated in a Boolean context. 477*bcb5dc79SHONG Yifan msg: An optional message that will be printed that describes the failure. 478*bcb5dc79SHONG Yifan If omitted, a default will be used. 479*bcb5dc79SHONG Yifan """ 480*bcb5dc79SHONG Yifan if not condition: 481*bcb5dc79SHONG Yifan _fail(env, msg) 482*bcb5dc79SHONG Yifan 483*bcb5dc79SHONG Yifandef _assert_false( 484*bcb5dc79SHONG Yifan env, 485*bcb5dc79SHONG Yifan condition, 486*bcb5dc79SHONG Yifan msg = "Expected condition to be false, but was true."): 487*bcb5dc79SHONG Yifan """Asserts that the given `condition` is false. 488*bcb5dc79SHONG Yifan 489*bcb5dc79SHONG Yifan Args: 490*bcb5dc79SHONG Yifan env: The test environment returned by `unittest.begin`. 491*bcb5dc79SHONG Yifan condition: A value that will be evaluated in a Boolean context. 492*bcb5dc79SHONG Yifan msg: An optional message that will be printed that describes the failure. 493*bcb5dc79SHONG Yifan If omitted, a default will be used. 494*bcb5dc79SHONG Yifan """ 495*bcb5dc79SHONG Yifan if condition: 496*bcb5dc79SHONG Yifan _fail(env, msg) 497*bcb5dc79SHONG Yifan 498*bcb5dc79SHONG Yifandef _assert_equals(env, expected, actual, msg = None): 499*bcb5dc79SHONG Yifan """Asserts that the given `expected` and `actual` values are equal. 500*bcb5dc79SHONG Yifan 501*bcb5dc79SHONG Yifan Args: 502*bcb5dc79SHONG Yifan env: The test environment returned by `unittest.begin`. 503*bcb5dc79SHONG Yifan expected: The expected value of some computation. 504*bcb5dc79SHONG Yifan actual: The actual value returned by some computation. 505*bcb5dc79SHONG Yifan msg: An optional message that will be printed that describes the failure. 506*bcb5dc79SHONG Yifan If omitted, a default will be used. 507*bcb5dc79SHONG Yifan """ 508*bcb5dc79SHONG Yifan if expected != actual: 509*bcb5dc79SHONG Yifan expectation_msg = 'Expected "%s", but got "%s"' % (expected, actual) 510*bcb5dc79SHONG Yifan if msg: 511*bcb5dc79SHONG Yifan full_msg = "%s (%s)" % (msg, expectation_msg) 512*bcb5dc79SHONG Yifan else: 513*bcb5dc79SHONG Yifan full_msg = expectation_msg 514*bcb5dc79SHONG Yifan _fail(env, full_msg) 515*bcb5dc79SHONG Yifan 516*bcb5dc79SHONG Yifandef _assert_set_equals(env, expected, actual, msg = None): 517*bcb5dc79SHONG Yifan """Asserts that the given `expected` and `actual` sets are equal. 518*bcb5dc79SHONG Yifan 519*bcb5dc79SHONG Yifan Args: 520*bcb5dc79SHONG Yifan env: The test environment returned by `unittest.begin`. 521*bcb5dc79SHONG Yifan expected: The expected set resulting from some computation. 522*bcb5dc79SHONG Yifan actual: The actual set returned by some computation. 523*bcb5dc79SHONG Yifan msg: An optional message that will be printed that describes the failure. 524*bcb5dc79SHONG Yifan If omitted, a default will be used. 525*bcb5dc79SHONG Yifan """ 526*bcb5dc79SHONG Yifan if not new_sets.is_equal(expected, actual): 527*bcb5dc79SHONG Yifan missing = new_sets.difference(expected, actual) 528*bcb5dc79SHONG Yifan unexpected = new_sets.difference(actual, expected) 529*bcb5dc79SHONG Yifan expectation_msg = "Expected %s, but got %s" % (new_sets.str(expected), new_sets.str(actual)) 530*bcb5dc79SHONG Yifan if new_sets.length(missing) > 0: 531*bcb5dc79SHONG Yifan expectation_msg += ", missing are %s" % (new_sets.str(missing)) 532*bcb5dc79SHONG Yifan if new_sets.length(unexpected) > 0: 533*bcb5dc79SHONG Yifan expectation_msg += ", unexpected are %s" % (new_sets.str(unexpected)) 534*bcb5dc79SHONG Yifan if msg: 535*bcb5dc79SHONG Yifan full_msg = "%s (%s)" % (msg, expectation_msg) 536*bcb5dc79SHONG Yifan else: 537*bcb5dc79SHONG Yifan full_msg = expectation_msg 538*bcb5dc79SHONG Yifan _fail(env, full_msg) 539*bcb5dc79SHONG Yifan 540*bcb5dc79SHONG Yifan_assert_new_set_equals = _assert_set_equals 541*bcb5dc79SHONG Yifan 542*bcb5dc79SHONG Yifandef _expect_failure(env, expected_failure_msg = ""): 543*bcb5dc79SHONG Yifan """Asserts that the target under test has failed with a given error message. 544*bcb5dc79SHONG Yifan 545*bcb5dc79SHONG Yifan This requires that the analysis test is created with `analysistest.make()` and 546*bcb5dc79SHONG Yifan `expect_failures = True` is specified. 547*bcb5dc79SHONG Yifan 548*bcb5dc79SHONG Yifan Args: 549*bcb5dc79SHONG Yifan env: The test environment returned by `analysistest.begin`. 550*bcb5dc79SHONG Yifan expected_failure_msg: The error message to expect as a result of analysis failures. 551*bcb5dc79SHONG Yifan """ 552*bcb5dc79SHONG Yifan dep = _target_under_test(env) 553*bcb5dc79SHONG Yifan if AnalysisFailureInfo in dep: 554*bcb5dc79SHONG Yifan actual_errors = "" 555*bcb5dc79SHONG Yifan for cause in dep[AnalysisFailureInfo].causes.to_list(): 556*bcb5dc79SHONG Yifan actual_errors += cause.message + "\n" 557*bcb5dc79SHONG Yifan if actual_errors.find(expected_failure_msg) < 0: 558*bcb5dc79SHONG Yifan expectation_msg = "Expected errors to contain '%s' but did not. " % expected_failure_msg 559*bcb5dc79SHONG Yifan expectation_msg += "Actual errors:%s" % actual_errors 560*bcb5dc79SHONG Yifan _fail(env, expectation_msg) 561*bcb5dc79SHONG Yifan else: 562*bcb5dc79SHONG Yifan _fail(env, "Expected failure of target_under_test, but found success") 563*bcb5dc79SHONG Yifan 564*bcb5dc79SHONG Yifandef _target_actions(env): 565*bcb5dc79SHONG Yifan """Returns a list of actions registered by the target under test. 566*bcb5dc79SHONG Yifan 567*bcb5dc79SHONG Yifan Args: 568*bcb5dc79SHONG Yifan env: The test environment returned by `analysistest.begin`. 569*bcb5dc79SHONG Yifan 570*bcb5dc79SHONG Yifan Returns: 571*bcb5dc79SHONG Yifan A list of actions registered by the target under test 572*bcb5dc79SHONG Yifan """ 573*bcb5dc79SHONG Yifan 574*bcb5dc79SHONG Yifan # Validate? 575*bcb5dc79SHONG Yifan return _target_under_test(env)[_ActionInfo].actions 576*bcb5dc79SHONG Yifan 577*bcb5dc79SHONG Yifandef _target_bin_dir_path(env): 578*bcb5dc79SHONG Yifan """Returns ctx.bin_dir.path for the target under test. 579*bcb5dc79SHONG Yifan 580*bcb5dc79SHONG Yifan Args: 581*bcb5dc79SHONG Yifan env: The test environment returned by `analysistest.begin`. 582*bcb5dc79SHONG Yifan 583*bcb5dc79SHONG Yifan Returns: 584*bcb5dc79SHONG Yifan Output bin dir path string. 585*bcb5dc79SHONG Yifan """ 586*bcb5dc79SHONG Yifan return _target_under_test(env)[_ActionInfo].bin_path 587*bcb5dc79SHONG Yifan 588*bcb5dc79SHONG Yifandef _target_under_test(env): 589*bcb5dc79SHONG Yifan """Returns the target under test. 590*bcb5dc79SHONG Yifan 591*bcb5dc79SHONG Yifan Args: 592*bcb5dc79SHONG Yifan env: The test environment returned by `analysistest.begin`. 593*bcb5dc79SHONG Yifan 594*bcb5dc79SHONG Yifan Returns: 595*bcb5dc79SHONG Yifan The target under test. 596*bcb5dc79SHONG Yifan """ 597*bcb5dc79SHONG Yifan result = getattr(env.ctx.attr, "target_under_test") 598*bcb5dc79SHONG Yifan if types.is_list(result): 599*bcb5dc79SHONG Yifan if result: 600*bcb5dc79SHONG Yifan return result[0] 601*bcb5dc79SHONG Yifan else: 602*bcb5dc79SHONG Yifan fail("test rule does not have a target_under_test") 603*bcb5dc79SHONG Yifan return result 604*bcb5dc79SHONG Yifan 605*bcb5dc79SHONG Yifandef _loading_test_impl(ctx): 606*bcb5dc79SHONG Yifan tc = ctx.toolchains[TOOLCHAIN_TYPE].unittest_toolchain_info 607*bcb5dc79SHONG Yifan content = tc.success_templ 608*bcb5dc79SHONG Yifan if ctx.attr.failure_message: 609*bcb5dc79SHONG Yifan content = tc.failure_templ % ctx.attr.failure_message 610*bcb5dc79SHONG Yifan 611*bcb5dc79SHONG Yifan testbin = ctx.actions.declare_file("loading_test_" + ctx.label.name + tc.file_ext) 612*bcb5dc79SHONG Yifan ctx.actions.write( 613*bcb5dc79SHONG Yifan output = testbin, 614*bcb5dc79SHONG Yifan content = content, 615*bcb5dc79SHONG Yifan is_executable = True, 616*bcb5dc79SHONG Yifan ) 617*bcb5dc79SHONG Yifan return [DefaultInfo(executable = testbin)] 618*bcb5dc79SHONG Yifan 619*bcb5dc79SHONG Yifan_loading_test = rule( 620*bcb5dc79SHONG Yifan implementation = _loading_test_impl, 621*bcb5dc79SHONG Yifan attrs = { 622*bcb5dc79SHONG Yifan "failure_message": attr.string(), 623*bcb5dc79SHONG Yifan }, 624*bcb5dc79SHONG Yifan toolchains = [TOOLCHAIN_TYPE], 625*bcb5dc79SHONG Yifan test = True, 626*bcb5dc79SHONG Yifan) 627*bcb5dc79SHONG Yifan 628*bcb5dc79SHONG Yifandef _loading_make(name): 629*bcb5dc79SHONG Yifan """Creates a loading phase test environment and test_suite. 630*bcb5dc79SHONG Yifan 631*bcb5dc79SHONG Yifan Args: 632*bcb5dc79SHONG Yifan name: name of the suite of tests to create 633*bcb5dc79SHONG Yifan 634*bcb5dc79SHONG Yifan Returns: 635*bcb5dc79SHONG Yifan loading phase environment passed to other loadingtest functions 636*bcb5dc79SHONG Yifan """ 637*bcb5dc79SHONG Yifan native.test_suite( 638*bcb5dc79SHONG Yifan name = name + "_tests", 639*bcb5dc79SHONG Yifan tags = [name + "_test_case"], 640*bcb5dc79SHONG Yifan ) 641*bcb5dc79SHONG Yifan return struct(name = name) 642*bcb5dc79SHONG Yifan 643*bcb5dc79SHONG Yifandef _loading_assert_equals(env, test_case, expected, actual): 644*bcb5dc79SHONG Yifan """Creates a test case for asserting state at LOADING phase. 645*bcb5dc79SHONG Yifan 646*bcb5dc79SHONG Yifan Args: 647*bcb5dc79SHONG Yifan env: Loading test env created from loadingtest.make 648*bcb5dc79SHONG Yifan test_case: Name of the test case 649*bcb5dc79SHONG Yifan expected: Expected value to test 650*bcb5dc79SHONG Yifan actual: Actual value received. 651*bcb5dc79SHONG Yifan 652*bcb5dc79SHONG Yifan Returns: 653*bcb5dc79SHONG Yifan None, creates test case 654*bcb5dc79SHONG Yifan """ 655*bcb5dc79SHONG Yifan 656*bcb5dc79SHONG Yifan msg = None 657*bcb5dc79SHONG Yifan if expected != actual: 658*bcb5dc79SHONG Yifan msg = 'Expected "%s", but got "%s"' % (expected, actual) 659*bcb5dc79SHONG Yifan 660*bcb5dc79SHONG Yifan _loading_test( 661*bcb5dc79SHONG Yifan name = "%s_%s" % (env.name, test_case), 662*bcb5dc79SHONG Yifan failure_message = msg, 663*bcb5dc79SHONG Yifan tags = [env.name + "_test_case"], 664*bcb5dc79SHONG Yifan ) 665*bcb5dc79SHONG Yifan 666*bcb5dc79SHONG Yifanasserts = struct( 667*bcb5dc79SHONG Yifan expect_failure = _expect_failure, 668*bcb5dc79SHONG Yifan equals = _assert_equals, 669*bcb5dc79SHONG Yifan false = _assert_false, 670*bcb5dc79SHONG Yifan set_equals = _assert_set_equals, 671*bcb5dc79SHONG Yifan new_set_equals = _assert_new_set_equals, 672*bcb5dc79SHONG Yifan true = _assert_true, 673*bcb5dc79SHONG Yifan) 674*bcb5dc79SHONG Yifan 675*bcb5dc79SHONG Yifanunittest = struct( 676*bcb5dc79SHONG Yifan make = _make, 677*bcb5dc79SHONG Yifan suite = _suite, 678*bcb5dc79SHONG Yifan begin = _begin, 679*bcb5dc79SHONG Yifan end = _end, 680*bcb5dc79SHONG Yifan fail = _fail, 681*bcb5dc79SHONG Yifan) 682*bcb5dc79SHONG Yifan 683*bcb5dc79SHONG Yifananalysistest = struct( 684*bcb5dc79SHONG Yifan make = _make_analysis_test, 685*bcb5dc79SHONG Yifan begin = _begin_analysis_test, 686*bcb5dc79SHONG Yifan end = _end_analysis_test, 687*bcb5dc79SHONG Yifan fail = _fail, 688*bcb5dc79SHONG Yifan target_actions = _target_actions, 689*bcb5dc79SHONG Yifan target_bin_dir_path = _target_bin_dir_path, 690*bcb5dc79SHONG Yifan target_under_test = _target_under_test, 691*bcb5dc79SHONG Yifan) 692*bcb5dc79SHONG Yifan 693*bcb5dc79SHONG Yifanloadingtest = struct( 694*bcb5dc79SHONG Yifan make = _loading_make, 695*bcb5dc79SHONG Yifan equals = _loading_assert_equals, 696*bcb5dc79SHONG Yifan) 697