Add support for 'loading' unit tests, which evaluate a LOADING phase. (#347)

This is a relatively simple addition to unittest that statically creates rules
that either explicitly fail or not depending on if the test case is valid during
LOADING phase of bazel.  The test conditions are evaluated entirely in loading
phase, but if we want an actual test to fail rather than just `fail()` killing
the build, we can use this to assert state and report test failures.
This commit is contained in:
Kevin Kress 2022-02-10 13:03:48 -08:00 committed by GitHub
parent 1e1c324391
commit 7270e3b345
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 124 additions and 1 deletions

View File

@ -352,6 +352,53 @@ Asserts that the given `condition` is true.
| <a id="asserts.true-msg"></a>msg | An optional message that will be printed that describes the failure. If omitted, a default will be used. | <code>"Expected condition to be true, but was false."</code> | | <a id="asserts.true-msg"></a>msg | An optional message that will be printed that describes the failure. If omitted, a default will be used. | <code>"Expected condition to be true, but was false."</code> |
<a id="#loadingtest.make"></a>
## loadingtest.make
<pre>
loadingtest.make(<a href="#loadingtest.make-name">name</a>)
</pre>
Creates a loading phase test environment and test_suite.
**PARAMETERS**
| Name | Description | Default Value |
| :------------- | :------------- | :------------- |
| <a id="loadingtest.make-name"></a>name | name of the suite of tests to create | none |
**RETURNS**
loading phase environment passed to other loadingtest functions
<a id="#loadingtest.equals"></a>
## loadingtest.equals
<pre>
loadingtest.equals(<a href="#loadingtest.equals-env">env</a>, <a href="#loadingtest.equals-test_case">test_case</a>, <a href="#loadingtest.equals-expected">expected</a>, <a href="#loadingtest.equals-actual">actual</a>)
</pre>
Creates a test case for asserting state at LOADING phase.
**PARAMETERS**
| Name | Description | Default Value |
| :------------- | :------------- | :------------- |
| <a id="loadingtest.equals-env"></a>env | Loading test env created from loadingtest.make | none |
| <a id="loadingtest.equals-test_case"></a>test_case | Name of the test case | none |
| <a id="loadingtest.equals-expected"></a>expected | Expected value to test | none |
| <a id="loadingtest.equals-actual"></a>actual | Actual value received. | none |
**RETURNS**
None, creates test case
<a id="#register_unittest_toolchains"></a> <a id="#register_unittest_toolchains"></a>
## register_unittest_toolchains ## register_unittest_toolchains

View File

@ -564,6 +564,67 @@ def _target_under_test(env):
fail("test rule does not have a target_under_test") fail("test rule does not have a target_under_test")
return result return result
def _loading_test_impl(ctx):
tc = ctx.toolchains[TOOLCHAIN_TYPE].unittest_toolchain_info
content = tc.success_templ
if ctx.attr.failure_message:
content = tc.failure_templ % ctx.attr.failure_message
testbin = ctx.actions.declare_file("loading_test_" + ctx.label.name + tc.file_ext)
ctx.actions.write(
output = testbin,
content = content,
is_executable = True,
)
return [DefaultInfo(executable = testbin)]
_loading_test = rule(
implementation = _loading_test_impl,
attrs = {
"failure_message": attr.string(),
},
toolchains = [TOOLCHAIN_TYPE],
test = True,
)
def _loading_make(name):
"""Creates a loading phase test environment and test_suite.
Args:
name: name of the suite of tests to create
Returns:
loading phase environment passed to other loadingtest functions
"""
native.test_suite(
name = name + "_tests",
tags = [name + "_test_case"],
)
return struct(name = name)
def _loading_assert_equals(env, test_case, expected, actual):
"""Creates a test case for asserting state at LOADING phase.
Args:
env: Loading test env created from loadingtest.make
test_case: Name of the test case
expected: Expected value to test
actual: Actual value received.
Returns:
None, creates test case
"""
msg = None
if expected != actual:
msg = 'Expected "%s", but got "%s"' % (expected, actual)
_loading_test(
name = "%s_%s" % (env.name, test_case),
failure_message = msg,
tags = [env.name + "_test_case"],
)
asserts = struct( asserts = struct(
expect_failure = _expect_failure, expect_failure = _expect_failure,
equals = _assert_equals, equals = _assert_equals,
@ -590,3 +651,8 @@ analysistest = struct(
target_bin_dir_path = _target_bin_dir_path, target_bin_dir_path = _target_bin_dir_path,
target_under_test = _target_under_test, target_under_test = _target_under_test,
) )
loadingtest = struct(
make = _loading_make,
equals = _loading_assert_equals,
)

View File

@ -15,7 +15,7 @@
"""Unit tests for unittest.bzl.""" """Unit tests for unittest.bzl."""
load("//lib:partial.bzl", "partial") load("//lib:partial.bzl", "partial")
load("//lib:unittest.bzl", "analysistest", "asserts", "unittest") load("//lib:unittest.bzl", "analysistest", "asserts", "loadingtest", "unittest")
################################### ###################################
####### basic_failing_test ######## ####### basic_failing_test ########
@ -308,6 +308,13 @@ inspect_output_dirs_test = analysistest.make(
}, },
) )
def _loading_phase_test(env):
loadingtest.equals(env, "self_glob", ["unittest_tests.bzl"], native.glob(["unittest_tests.bzl"]))
# now use our own calls to assert we created a test case rule and test_suite for it.
loadingtest.equals(env, "test_exists", True, native.existing_rule(env.name + "_self_glob") != None)
loadingtest.equals(env, "suite_exists", True, native.existing_rule(env.name + "_tests") != None)
######################################### #########################################
# buildifier: disable=unnamed-macro # buildifier: disable=unnamed-macro
@ -377,3 +384,6 @@ def unittest_passing_tests_suite():
name = "inspect_output_dirs_fake_target", name = "inspect_output_dirs_fake_target",
tags = ["manual"], tags = ["manual"],
) )
loading_env = loadingtest.make("selftest")
_loading_phase_test(loading_env)