diff --git a/README.md b/README.md index 440f9b2..dfa0207 100644 --- a/README.md +++ b/README.md @@ -62,6 +62,8 @@ bats_test( ) ``` +If your test would like to make use of the bats-assert extension (`assert_success`, `assert_failure`, `assert_output`, etc), simply add `uses_bats_assert = True` to your `bats_test()` target. This still requires adding the appropriate `load` statements in your test file. + ## Examples This repository is an example of a repo with BATS tests. If you have `bazel` diff --git a/WORKSPACE b/WORKSPACE index 55f4a3e..2a5edc0 100644 --- a/WORKSPACE +++ b/WORKSPACE @@ -2,11 +2,20 @@ # https://stackoverflow.com/questions/47192668/idiomatic-retrieval-of-the-bazel-execution-path# workspace(name = "bazel_bats") +BATS_ASSERT_VERSION = "2.0.0" +BATS_ASSERT_SHA256 = "15dbf1abb98db785323b9327c86ee2b3114541fe5aa150c410a1632ec06d9903" BATS_CORE_VERSION = "1.7.0" BATS_CORE_SHA256 = "ac70c2a153f108b1ac549c2eaa4154dea4a7c1cc421e3352f0ce6ea49435454e" +BATS_SUPPORT_VERSION = "0.3.0" +BATS_SUPPORT_SHA256 = "7815237aafeb42ddcc1b8c698fc5808026d33317d8701d5ec2396e9634e2918f" load("@bazel_bats//:deps.bzl", "bazel_bats_dependencies") bazel_bats_dependencies( version = BATS_CORE_VERSION, - sha256 = BATS_CORE_SHA256) + sha256 = BATS_CORE_SHA256, + bats_assert_version = BATS_ASSERT_VERSION, + bats_assert_sha256 = BATS_ASSERT_SHA256, + bats_support_version = BATS_SUPPORT_VERSION, + bats_support_sha256 = BATS_SUPPORT_SHA256 +) diff --git a/rules.bzl b/rules.bzl index db4424e..e7189ff 100644 --- a/rules.bzl +++ b/rules.bzl @@ -11,6 +11,22 @@ def _test_files(bats, srcs): test_paths = " ".join(['"{}"'.format(s.short_path) for s in srcs]), ) +# Finds shortest path. +# Used to find commonpath for bats-assert or bats-support. +# This would not always necessarily return the correct path (in other contexts), +# but works for the filegroups used. +# A more robust method of finding the base directory is made more difficult by skylark's +# divergence from python. Bringing in skylab as a dependency was deemed overkill. +# So long as a file in the base dir exists (e.g. load.bash), this is fine. +def _base_dir(files): + result = files[0].dirname + min_len = len(files[0].dirname) + for file in files: + if len(file.dirname) < min_len: + min_len = len(file.dirname) + result = file.dirname + return result + def _bats_test_impl(ctx): path = ["$PWD/" + _dirname(b.short_path) for b in ctx.files.deps] sep = ctx.configuration.host_path_separator @@ -30,30 +46,84 @@ def _bats_test_impl(ctx): output = ctx.outputs.executable, content = content, ) + runfiles = ctx.runfiles( files = ctx.files.srcs, transitive_files = depset(ctx.files.data + ctx.files.deps), ).merge(ctx.attr._bats.default_runfiles) return [DefaultInfo(runfiles = runfiles)] -bats_test = rule( +def _bats_with_bats_assert_test_impl(ctx): + base_info = _bats_test_impl(ctx)[0] + + bats_assert_base_dir = _base_dir(ctx.attr._bats_assert.files.to_list()) + bats_support_base_dir = _base_dir(ctx.attr._bats_support.files.to_list()) + test_helper_outputs = [] + for src_file in ctx.files.srcs: + test_helper_dir = ctx.actions.declare_directory( + "test_helper", + sibling = src_file, + ) + test_helper_outputs.append(test_helper_dir) + ctx.actions.run_shell( + outputs=[test_helper_dir], + inputs=depset(ctx.attr._bats_assert.files.to_list() + ctx.attr._bats_support.files.to_list()), + arguments=[test_helper_dir.path, bats_assert_base_dir, bats_support_base_dir], + command=""" + mkdir -p $1/bats-support $1/bats-assert \\ + && cp -r $2/* $1/bats-assert \\ + && cp -r $3/* $1/bats-support + """) + + runfiles = ctx.runfiles( + files = test_helper_outputs, + ).merge(base_info.default_runfiles) + return [DefaultInfo(runfiles = runfiles)] + +_bats_test_attrs = { + "data": attr.label_list(allow_files = True), + "deps": attr.label_list(), + "env": attr.string_dict( + doc = "A list of key-value pairs of environment variables to define", + ), + "srcs": attr.label_list( + allow_files = [".bats"], + doc = "Source files to run a BATS test on", + ), + "_bats": attr.label( + default = Label("@bats_core//:bats"), + executable = True, + cfg = "exec", + ), +} + +_bats_with_bats_assert_test_attrs = { + "_bats_support": attr.label( + default = Label("@bats_support//:load_files"), + ), + "_bats_assert": attr.label( + default = Label("@bats_assert//:load_files"), + ), +} +_bats_with_bats_assert_test_attrs.update(_bats_test_attrs) + +_bats_test = rule( _bats_test_impl, - attrs = { - "data": attr.label_list(allow_files = True), - "deps": attr.label_list(), - "env": attr.string_dict( - doc = "A list of key-value pairs of environment variables to define", - ), - "srcs": attr.label_list( - allow_files = [".bats"], - doc = "Source files to run a BATS test on", - ), - "_bats": attr.label( - default = Label("@bats_core//:bats"), - executable = True, - cfg = "exec", - ), - }, + attrs = _bats_test_attrs, test = True, - doc = "Runs a BATS test on the supplied source files", + doc = "Runs a BATS test on the supplied source files.", ) + +_bats_with_bats_assert_test = rule( + _bats_with_bats_assert_test_impl, + attrs = _bats_with_bats_assert_test_attrs, + test = True, + doc = "Runs a BATS test on the supplied source files, allowing for usage of bats-support and bats-assert.", +) + + +def bats_test(uses_bats_assert = False, **kwargs): + if not uses_bats_assert: + _bats_test(**kwargs) + else: + _bats_with_bats_assert_test(**kwargs) diff --git a/tests/BUILD.bazel b/tests/BUILD.bazel index 4ca5232..165f7e5 100644 --- a/tests/BUILD.bazel +++ b/tests/BUILD.bazel @@ -13,6 +13,15 @@ bats_test( ], ) +bats_test( + name = "exit_with_input_bats_assert_test", + srcs = ["exit_with_input_bats_assert.bats"], + deps = [ + ":exit_with_input_bin", + ], + uses_bats_assert = True, +) + sh_library( name = "helper", srcs = ["helper.bash"], diff --git a/tests/exit_with_input b/tests/exit_with_input index afe8709..ec61314 100755 --- a/tests/exit_with_input +++ b/tests/exit_with_input @@ -1,2 +1,7 @@ #!/usr/bin/env bash + +if [ ! -z "$2" ]; then + echo "Given for output: $2" +fi + exit $1 diff --git a/tests/exit_with_input_bats_assert.bats b/tests/exit_with_input_bats_assert.bats new file mode 100644 index 0000000..9277122 --- /dev/null +++ b/tests/exit_with_input_bats_assert.bats @@ -0,0 +1,66 @@ +#!/usr/bin/env bats + +PATH_TO_EXIT_WITH_INPUT="tests/exit_with_input" + +setup() { + load "test_helper/bats-support/load" + load "test_helper/bats-assert/load" +} + +@test "can run succeeding executable" { + run "${PATH_TO_EXIT_WITH_INPUT}" 0 + + assert_success + refute_output +} + +@test "can run succeeding executable with output" { + run "${PATH_TO_EXIT_WITH_INPUT}" 0 'some message' + + assert_success + assert_output --partial 'Given for output:' + assert_output --partial 'some message' + assert_output --regexp '^Given for output: some message$' + assert_output 'Given for output: some message' + refute_output 'never printed' +} + +@test "can run failing executable" { + run "${PATH_TO_EXIT_WITH_INPUT}" 1 + + assert_failure + assert_failure 1 + refute_output +} + +@test "can run failing executable with output" { + run "${PATH_TO_EXIT_WITH_INPUT}" 1 'some message' + + assert_failure + assert_failure 1 + assert_output --partial 'Given for output:' + assert_output --partial 'some message' + assert_output --regexp '^Given for output: some message$' + assert_output 'Given for output: some message' + refute_output 'never printed' +} + +@test "can run failing executable with different return code" { + run "${PATH_TO_EXIT_WITH_INPUT}" 2 + + assert_failure + assert_failure 2 + refute_output +} + +@test "can run failing executable with different return code with output" { + run "${PATH_TO_EXIT_WITH_INPUT}" 2 'some message' + + assert_failure + assert_failure 2 + assert_output --partial 'Given for output:' + assert_output --partial 'some message' + assert_output --regexp '^Given for output: some message$' + assert_output 'Given for output: some message' + refute_output 'never printed' +}