feat: implement bats test runner (#699)

This commit is contained in:
Sahin Yort 2023-12-20 13:08:47 -08:00 committed by GitHub
parent 8d805e16bf
commit 4f6b4bd5cb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 502 additions and 3 deletions

View File

@ -22,7 +22,8 @@ bazel_lib_toolchains.yq()
bazel_lib_toolchains.coreutils()
bazel_lib_toolchains.tar()
bazel_lib_toolchains.expand_template()
use_repo(bazel_lib_toolchains, "bsd_tar_toolchains", "copy_directory_toolchains", "copy_to_directory_toolchains", "coreutils_toolchains", "expand_template_toolchains", "jq_toolchains", "yq_toolchains")
bazel_lib_toolchains.bats()
use_repo(bazel_lib_toolchains, "bats_toolchains", "bsd_tar_toolchains", "copy_directory_toolchains", "copy_to_directory_toolchains", "coreutils_toolchains", "expand_template_toolchains", "jq_toolchains", "yq_toolchains")
register_toolchains(
"@copy_directory_toolchains//:all",
@ -31,6 +32,7 @@ register_toolchains(
"@yq_toolchains//:all",
"@coreutils_toolchains//:all",
"@expand_template_toolchains//:all",
"@bats_toolchains//:all",
# Expand bsd_tar_toolchains
"@bsd_tar_toolchains//:linux_amd64_toolchain",
"@bsd_tar_toolchains//:linux_arm64_toolchain",

View File

@ -151,4 +151,9 @@ stardoc_with_diff_test(
bzl_library_target = "//lib:strings",
)
stardoc_with_diff_test(
name = "bats",
bzl_library_target = "//lib:bats",
)
update_docs()

25
docs/bats.md generated Normal file
View File

@ -0,0 +1,25 @@
<!-- Generated with Stardoc: http://skydoc.bazel.build -->
Bats test runner
<a id="bats_test"></a>
## bats_test
<pre>
bats_test(<a href="#bats_test-name">name</a>, <a href="#bats_test-data">data</a>, <a href="#bats_test-env">env</a>, <a href="#bats_test-srcs">srcs</a>)
</pre>
**ATTRIBUTES**
| Name | Description | Type | Mandatory | Default |
| :------------- | :------------- | :------------- | :------------- | :------------- |
| <a id="bats_test-name"></a>name | A unique name for this target. | <a href="https://bazel.build/concepts/labels#target-names">Name</a> | required | |
| <a id="bats_test-data"></a>data | Runtime dependencies of the test. | <a href="https://bazel.build/concepts/labels">List of labels</a> | optional | <code>[]</code> |
| <a id="bats_test-env"></a>env | Environment variables of the action.<br><br> Subject to [$(location)](https://bazel.build/reference/be/make-variables#predefined_label_variables) and ["Make variable"](https://bazel.build/reference/be/make-variables) substitution. | <a href="https://bazel.build/rules/lib/dict">Dictionary: String -> String</a> | optional | <code>{}</code> |
| <a id="bats_test-srcs"></a>srcs | Test files | <a href="https://bazel.build/concepts/labels">List of labels</a> | optional | <code>[]</code> |

25
docs/repositories.md generated
View File

@ -29,6 +29,31 @@ call the individual toolchain registration macros.
<a id="register_bats_toolchains"></a>
## register_bats_toolchains
<pre>
register_bats_toolchains(<a href="#register_bats_toolchains-name">name</a>, <a href="#register_bats_toolchains-core_version">core_version</a>, <a href="#register_bats_toolchains-support_version">support_version</a>, <a href="#register_bats_toolchains-assert_version">assert_version</a>, <a href="#register_bats_toolchains-file_version">file_version</a>,
<a href="#register_bats_toolchains-libraries">libraries</a>, <a href="#register_bats_toolchains-register">register</a>)
</pre>
Registers bats toolchain and repositories
**PARAMETERS**
| Name | Description | Default Value |
| :------------- | :------------- | :------------- |
| <a id="register_bats_toolchains-name"></a>name | override the prefix for the generated toolchain repositories | <code>"bats"</code> |
| <a id="register_bats_toolchains-core_version"></a>core_version | bats-core version to use | <code>"v1.10.0"</code> |
| <a id="register_bats_toolchains-support_version"></a>support_version | bats-support version to use | <code>"v0.3.0"</code> |
| <a id="register_bats_toolchains-assert_version"></a>assert_version | bats-assert version to use | <code>"v2.1.0"</code> |
| <a id="register_bats_toolchains-file_version"></a>file_version | bats-file version to use | <code>"v0.4.0"</code> |
| <a id="register_bats_toolchains-libraries"></a>libraries | additional labels for libraries | <code>[]</code> |
| <a id="register_bats_toolchains-register"></a>register | whether to call through to native.register_toolchains. Should be True for WORKSPACE users, but false when used under bzlmod extension | <code>True</code> |
<a id="register_copy_directory_toolchains"></a>
## register_copy_directory_toolchains

View File

@ -1,3 +1,4 @@
load("@aspect_bazel_lib//lib:bats.bzl", "bats_test")
load("@aspect_bazel_lib//lib:copy_directory.bzl", "copy_directory")
load("@aspect_bazel_lib//lib:copy_to_directory.bzl", "copy_to_directory")
load("@aspect_bazel_lib//lib:diff_test.bzl", "diff_test")
@ -91,3 +92,10 @@ tar(
srcs = [],
mtree = [],
)
bats_test(
name = "bats",
srcs = [
"basic.bats",
],
)

8
e2e/smoke/basic.bats Normal file
View File

@ -0,0 +1,8 @@
bats_load_library "bats-support"
bats_load_library "bats-assert"
@test 'basic' {
run echo 'have'
assert_output 'have'
}

View File

@ -4,7 +4,7 @@ Users should *not* need to install these. If users see a load()
statement from these, that's a bug in our distribution.
"""
load("//lib:repositories.bzl", "register_coreutils_toolchains", "register_jq_toolchains", "register_tar_toolchains", "register_yq_toolchains")
load("//lib:repositories.bzl", "register_bats_toolchains", "register_coreutils_toolchains", "register_jq_toolchains", "register_tar_toolchains", "register_yq_toolchains")
load("//lib:utils.bzl", http_archive = "maybe_http_archive")
# buildifier: disable=unnamed-macro
@ -72,3 +72,6 @@ def bazel_lib_internal_deps():
register_yq_toolchains()
register_coreutils_toolchains()
register_tar_toolchains()
register_bats_toolchains(
libraries = ["@aspect_bazel_lib//lib/tests/bats/bats-custom:custom"],
)

View File

@ -59,11 +59,16 @@ toolchain_type(
name = "tar_toolchain_type",
)
toolchain_type(
name = "bats_toolchain_type",
)
bzl_library(
name = "expand_make_vars",
srcs = ["expand_make_vars.bzl"],
deps = [
":expand_template",
"//lib/private:expand_locations",
"//lib/private:expand_variables",
],
)
@ -235,6 +240,7 @@ bzl_library(
srcs = ["repositories.bzl"],
deps = [
":utils",
"//lib/private:bats_toolchain",
"//lib/private:copy_directory_toolchain",
"//lib/private:copy_to_directory_toolchain",
"//lib/private:coreutils_toolchain",
@ -295,3 +301,9 @@ bzl_library(
srcs = ["windows_utils.bzl"],
deps = ["//lib/private:paths"],
)
bzl_library(
name = "bats",
srcs = ["bats.bzl"],
deps = ["//lib/private:bats"],
)

5
lib/bats.bzl Normal file
View File

@ -0,0 +1,5 @@
"Bats test runner"
load("//lib/private:bats.bzl", _bats_test = "bats_test")
bats_test = _bats_test

View File

@ -2,6 +2,8 @@
load(
"@aspect_bazel_lib//lib:repositories.bzl",
"DEFAULT_BATS_CORE_VERSION",
"DEFAULT_BATS_REPOSITORY",
"DEFAULT_COPY_DIRECTORY_REPOSITORY",
"DEFAULT_COPY_TO_DIRECTORY_REPOSITORY",
"DEFAULT_COREUTILS_REPOSITORY",
@ -12,6 +14,7 @@ load(
"DEFAULT_TAR_REPOSITORY",
"DEFAULT_YQ_REPOSITORY",
"DEFAULT_YQ_VERSION",
"register_bats_toolchains",
"register_copy_directory_toolchains",
"register_copy_to_directory_toolchains",
"register_coreutils_toolchains",
@ -94,6 +97,15 @@ def _toolchains_extension_impl(mctx):
get_version_fn = lambda attr: None,
)
extension_utils.toolchain_repos_bfs(
mctx = mctx,
get_tag_fn = lambda tags: tags.bats,
toolchain_name = "bats",
default_repository = DEFAULT_BATS_REPOSITORY,
toolchain_repos_fn = lambda name, version: register_bats_toolchains(name = name, core_version = version, register = False),
get_version_fn = lambda attr: attr.core_version,
)
toolchains = module_extension(
implementation = _toolchains_extension_impl,
tag_classes = {
@ -104,5 +116,9 @@ toolchains = module_extension(
"coreutils": tag_class(attrs = {"name": attr.string(default = DEFAULT_COREUTILS_REPOSITORY), "version": attr.string(default = DEFAULT_COREUTILS_VERSION)}),
"tar": tag_class(attrs = {"name": attr.string(default = DEFAULT_TAR_REPOSITORY)}),
"expand_template": tag_class(attrs = {"name": attr.string(default = DEFAULT_EXPAND_TEMPLATE_REPOSITORY)}),
"bats": tag_class(attrs = {
"name": attr.string(default = DEFAULT_BATS_REPOSITORY),
"core_version": attr.string(default = DEFAULT_BATS_CORE_VERSION),
}),
},
)

View File

@ -274,6 +274,23 @@ bzl_library(
deps = ["@aspect_bazel_lib//lib:paths"],
)
bzl_library(
name = "bats",
srcs = ["bats.bzl"],
visibility = ["//lib:__subpackages__"],
deps = [
":expand_locations",
":expand_variables",
"@aspect_bazel_lib//lib:paths",
],
)
bzl_library(
name = "bats_toolchain",
srcs = ["bats_toolchain.bzl"],
visibility = ["//lib:__subpackages__"],
)
bzl_library(
name = "copy_common",
srcs = ["copy_common.bzl"],

90
lib/private/bats.bzl Normal file
View File

@ -0,0 +1,90 @@
"bats_test"
load("//lib:paths.bzl", "BASH_RLOCATION_FUNCTION", "to_rlocation_path")
load(":expand_locations.bzl", "expand_locations")
load(":expand_variables.bzl", "expand_variables")
_RUNNER_TMPL = """#!/usr/bin/env bash
set -o errexit -o nounset -o pipefail
{BASH_RLOCATION_FUNCTION}
readonly core_path="$(rlocation {core})"
readonly bats="$core_path/bin/bats"
readonly libs=( {libraries} )
{envs}
NEW_LIBS=()
for lib in "${{libs[@]}}"; do
NEW_LIBS+=( $(cd "$(rlocation $lib)/.." && pwd) )
done
export BATS_LIB_PATH=$(
IFS=:
echo "${{NEW_LIBS[*]}}"
)
export BATS_TEST_TIMEOUT="$TEST_TIMEOUT"
export BATS_TMPDIR="$TEST_TMPDIR"
exec $bats {tests} $@
"""
_ENV_SET = """export {var}=\"{value}\""""
def _bats_test_impl(ctx):
toolchain = ctx.toolchains["@aspect_bazel_lib//lib:bats_toolchain_type"]
batsinfo = toolchain.batsinfo
envs = []
for (key, value) in ctx.attr.env.items():
envs.append(_ENV_SET.format(
var = key,
value = " ".join([expand_variables(ctx, exp, attribute_name = "env") for exp in expand_locations(ctx, value, ctx.attr.data).split(" ")]),
))
runner = ctx.actions.declare_file("%s_bats.sh" % ctx.label.name)
ctx.actions.write(
output = runner,
content = _RUNNER_TMPL.format(
core = to_rlocation_path(ctx, batsinfo.core),
libraries = " ".join([to_rlocation_path(ctx, lib) for lib in batsinfo.libraries]),
tests = " ".join([test.short_path for test in ctx.files.srcs]),
envs = "\n".join(envs),
BASH_RLOCATION_FUNCTION = BASH_RLOCATION_FUNCTION,
),
is_executable = True,
)
runfiles = ctx.runfiles(ctx.files.srcs + ctx.files.data)
runfiles = runfiles.merge(toolchain.default.default_runfiles)
runfiles = runfiles.merge(ctx.attr._runfiles.default_runfiles)
return DefaultInfo(
executable = runner,
runfiles = runfiles,
)
bats_test = rule(
implementation = _bats_test_impl,
attrs = {
"srcs": attr.label_list(
allow_files = [".bats"],
doc = "Test files",
),
"data": attr.label_list(
allow_files = True,
doc = "Runtime dependencies of the test.",
),
"env": attr.string_dict(
doc = """Environment variables of the action.
Subject to [$(location)](https://bazel.build/reference/be/make-variables#predefined_label_variables)
and ["Make variable"](https://bazel.build/reference/be/make-variables) substitution.
""",
),
"_runfiles": attr.label(default = "@bazel_tools//tools/bash/runfiles"),
},
toolchains = ["@aspect_bazel_lib//lib:bats_toolchain_type"],
test = True,
)

View File

@ -0,0 +1,103 @@
"Provide access to a bats executable"
BATS_CORE_VERSIONS = {
"v1.10.0": "a1a9f7875aa4b6a9480ca384d5865f1ccf1b0b1faead6b47aa47d79709a5c5fd",
}
BATS_SUPPORT_VERSIONS = {
"v0.3.0": "7815237aafeb42ddcc1b8c698fc5808026d33317d8701d5ec2396e9634e2918f",
}
BATS_ASSERT_VERSIONS = {
"v2.1.0": "98ca3b685f8b8993e48ec057565e6e2abcc541034ed5b0e81f191505682037fd",
}
BATS_FILE_VERSIONS = {
"v0.4.0": "9b69043241f3af1c2d251f89b4fcafa5df3f05e97b89db18d7c9bdf5731bb27a",
}
BATS_CORE_TEMPLATE = """\
load("@local_config_platform//:constraints.bzl", "HOST_CONSTRAINTS")
load("@aspect_bazel_lib//lib/private:bats_toolchain.bzl", "bats_toolchain")
load("@aspect_bazel_lib//lib:copy_to_directory.bzl", "copy_to_directory")
copy_to_directory(
name = "core",
hardlink = "on",
srcs = glob([
"lib/**",
"libexec/**"
]) + ["bin/bats"],
out = "bats-core",
)
bats_toolchain(
name = "toolchain",
core = ":core",
libraries = {libraries}
)
toolchain(
name = "bats_toolchain",
exec_compatible_with = HOST_CONSTRAINTS,
toolchain = ":toolchain",
toolchain_type = "@aspect_bazel_lib//lib:bats_toolchain_type",
)
"""
BATS_LIBRARY_TEMPLATE = """\
load("@aspect_bazel_lib//lib:copy_to_directory.bzl", "copy_to_directory")
copy_to_directory(
name = "{name}",
hardlink = "on",
srcs = glob([
"src/**",
"load.bash",
]),
out = "bats-{name}",
visibility = ["//visibility:public"]
)
"""
BatsInfo = provider(
doc = "Provide info for executing bats",
fields = {
"core": "bats executable",
"libraries": "bats helper libraries",
},
)
def _bats_toolchain_impl(ctx):
core = ctx.file.core
default_info = DefaultInfo(
files = depset(ctx.files.core + ctx.files.libraries),
runfiles = ctx.runfiles(ctx.files.core + ctx.files.libraries),
)
batsinfo = BatsInfo(
core = core,
libraries = ctx.files.libraries,
)
# Export all the providers inside our ToolchainInfo
# so the resolved_toolchain rule can grab and re-export them.
toolchain_info = platform_common.ToolchainInfo(
batsinfo = batsinfo,
default = default_info,
)
return [toolchain_info, default_info]
bats_toolchain = rule(
implementation = _bats_toolchain_impl,
attrs = {
"core": attr.label(
doc = "Label to the bats executable",
allow_single_file = True,
mandatory = True,
),
"libraries": attr.label_list(),
},
)

View File

@ -1,6 +1,7 @@
"Macros for loading dependencies and registering toolchains"
load("//lib:utils.bzl", http_archive = "maybe_http_archive")
load("//lib/private:bats_toolchain.bzl", "BATS_ASSERT_VERSIONS", "BATS_CORE_TEMPLATE", "BATS_CORE_VERSIONS", "BATS_FILE_VERSIONS", "BATS_LIBRARY_TEMPLATE", "BATS_SUPPORT_VERSIONS")
load("//lib/private:copy_directory_toolchain.bzl", "COPY_DIRECTORY_PLATFORMS", "copy_directory_platform_repo", "copy_directory_toolchains_repo")
load("//lib/private:copy_to_directory_toolchain.bzl", "COPY_TO_DIRECTORY_PLATFORMS", "copy_to_directory_platform_repo", "copy_to_directory_toolchains_repo")
load("//lib/private:coreutils_toolchain.bzl", "COREUTILS_PLATFORMS", "coreutils_platform_repo", "coreutils_toolchains_repo", _DEFAULT_COREUTILS_VERSION = "DEFAULT_COREUTILS_VERSION")
@ -103,6 +104,81 @@ def register_tar_toolchains(name = DEFAULT_TAR_REPOSITORY, register = True):
user_repository_name = name,
)
DEFAULT_BATS_REPOSITORY = "bats"
DEFAULT_BATS_CORE_VERSION = "v1.10.0"
DEFAULT_BATS_SUPPORT_VERSION = "v0.3.0"
DEFAULT_BATS_ASSERT_VERSION = "v2.1.0"
DEFAULT_BATS_FILE_VERSION = "v0.4.0"
def register_bats_toolchains(
name = DEFAULT_BATS_REPOSITORY,
core_version = DEFAULT_BATS_CORE_VERSION,
support_version = DEFAULT_BATS_SUPPORT_VERSION,
assert_version = DEFAULT_BATS_ASSERT_VERSION,
file_version = DEFAULT_BATS_FILE_VERSION,
libraries = [],
register = True):
"""Registers bats toolchain and repositories
Args:
name: override the prefix for the generated toolchain repositories
core_version: bats-core version to use
support_version: bats-support version to use
assert_version: bats-assert version to use
file_version: bats-file version to use
libraries: additional labels for libraries
register: whether to call through to native.register_toolchains.
Should be True for WORKSPACE users, but false when used under bzlmod extension
"""
http_archive(
name = "%s_support" % name,
sha256 = BATS_SUPPORT_VERSIONS[support_version],
urls = [
"https://github.com/bats-core/bats-support/archive/{}.tar.gz".format(support_version),
],
strip_prefix = "bats-support-{}".format(support_version.removeprefix("v")),
build_file_content = BATS_LIBRARY_TEMPLATE.format(name = "support"),
)
http_archive(
name = "%s_assert" % name,
sha256 = BATS_ASSERT_VERSIONS[assert_version],
urls = [
"https://github.com/bats-core/bats-assert/archive/{}.tar.gz".format(assert_version),
],
strip_prefix = "bats-assert-{}".format(assert_version.removeprefix("v")),
build_file_content = BATS_LIBRARY_TEMPLATE.format(name = "assert"),
)
http_archive(
name = "%s_file" % name,
sha256 = BATS_FILE_VERSIONS[file_version],
urls = [
"https://github.com/bats-core/bats-file/archive/{}.tar.gz".format(file_version),
],
strip_prefix = "bats-file-{}".format(file_version.removeprefix("v")),
build_file_content = BATS_LIBRARY_TEMPLATE.format(name = "file"),
)
http_archive(
name = "%s_toolchains" % name,
sha256 = BATS_CORE_VERSIONS[core_version],
urls = [
"https://github.com/bats-core/bats-core/archive/{}.tar.gz".format(core_version),
],
strip_prefix = "bats-core-{}".format(core_version.removeprefix("v")),
build_file_content = BATS_CORE_TEMPLATE.format(libraries = [
"@%s_support//:support" % name,
"@%s_assert//:assert" % name,
"@%s_file//:file" % name,
] + libraries),
)
if register:
native.register_toolchains("@%s_toolchains//:bats_toolchain" % name)
DEFAULT_COREUTILS_REPOSITORY = "coreutils"
DEFAULT_COREUTILS_VERSION = _DEFAULT_COREUTILS_VERSION
@ -248,3 +324,4 @@ def aspect_bazel_lib_register_toolchains():
register_jq_toolchains()
register_yq_toolchains()
register_tar_toolchains()
register_bats_toolchains()

View File

@ -0,0 +1,56 @@
load("//lib:bats.bzl", "bats_test")
bats_test(
name = "basic",
size = "small",
srcs = [
"basic.bats",
],
)
bats_test(
name = "env",
size = "small",
srcs = [
"env.bats",
],
env = {
"USE_BAZEL_VERSION": "latest",
},
)
bats_test(
name = "args",
size = "small",
srcs = [
"basic.bats",
],
args = ["--timing"],
)
bats_test(
name = "env_expansion",
size = "small",
srcs = [
"env_expansion.bats",
],
data = [
"data.bin",
],
env = {
"DATA_PATH": "$(location :data.bin)",
},
)
bats_test(
name = "additional_lib",
size = "small",
srcs = [
"additional_lib.bats",
],
target_compatible_with = select({
# TODO(thesayyn): incompatible with bzlmod
"@aspect_bazel_lib//lib:bzlmod": ["@platforms//:incompatible"],
"//conditions:default": [],
}),
)

View File

@ -0,0 +1,8 @@
bats_load_library 'bats-support'
bats_load_library 'bats-assert'
bats_load_library 'bats-custom'
@test 'env' {
custom_test_fn
}

View File

@ -0,0 +1,8 @@
bats_load_library 'bats-support'
bats_load_library 'bats-assert'
@test 'assert_output() check for existence' {
run echo 'have'
assert_output 'have'
}

View File

@ -0,0 +1,10 @@
load("@aspect_bazel_lib//lib:copy_to_directory.bzl", "copy_to_directory")
copy_to_directory(
name = "custom",
srcs = [
"load.bash",
],
out = "bats-custom",
visibility = ["//visibility:public"],
)

View File

@ -0,0 +1,3 @@
custom_test_fn() {
:
}

0
lib/tests/bats/data.bin Normal file
View File

8
lib/tests/bats/env.bats Normal file
View File

@ -0,0 +1,8 @@
bats_load_library 'bats-support'
bats_load_library 'bats-assert'
@test 'env' {
run echo $USE_BAZEL_VERSION
assert_output 'latest'
}

View File

@ -0,0 +1,10 @@
bats_load_library 'bats-support'
bats_load_library 'bats-assert'
bats_load_library 'bats-file'
@test 'env expansion' {
run echo $DATA_PATH
assert_output 'lib/tests/bats/data.bin'
assert_file_exists 'lib/tests/bats/data.bin'
}