file_test, rule_test: now as sh_test rules
All test rules in
@bazel_tools//tools/build_rule:test_rules.bzl are
now macros around sh_test.
This allows running them on Windows with the
Windows-native test wrapper.
Fixes https://github.com/bazelbuild/bazel/issues/8203
Unblocks https://github.com/bazelbuild/bazel/issues/6622
Closes #8352.
PiperOrigin-RevId: 248680587
diff --git a/tools/build_rules/BUILD b/tools/build_rules/BUILD
index 0b3f17a..7765a78 100644
--- a/tools/build_rules/BUILD
+++ b/tools/build_rules/BUILD
@@ -11,6 +11,7 @@
srcs = [
"BUILD.tools",
"test_rules.bzl",
+ "test_rules_private.bzl",
],
visibility = ["//visibility:public"],
)
@@ -18,6 +19,9 @@
py_test(
name = "test_rules_test",
srcs = ["test_rules_test.py"],
- data = ["test_rules.bzl"],
+ data = [
+ "test_rules.bzl",
+ "test_rules_private.bzl",
+ ],
deps = ["//src/test/py/bazel:test_base"],
)
diff --git a/tools/build_rules/test_rules.bzl b/tools/build_rules/test_rules.bzl
index 57526f9..cb23294 100644
--- a/tools/build_rules/test_rules.bzl
+++ b/tools/build_rules/test_rules.bzl
@@ -14,10 +14,31 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+load(":test_rules_private.bzl", "BASH_RUNFILES_DEP", "INIT_BASH_RUNFILES")
+
+_SH_STUB = "\n".join(["#!/bin/bash"] + INIT_BASH_RUNFILES + [
+ "function add_ws_name() {",
+ ' [[ "$1" =~ external/* ]] && echo "${1#external/}" || echo "$TEST_WORKSPACE/$1"',
+ "}",
+ "",
+])
+
+def _bash_rlocation(f):
+ return '"$(rlocation "$(add_ws_name "%s")")"' % f.short_path
+
+def _make_sh_test(name, **kwargs):
+ native.sh_test(
+ name = name,
+ srcs = [name + "_impl"],
+ data = [name + "_impl"],
+ deps = [BASH_RUNFILES_DEP],
+ **kwargs
+ )
+
### First, trivial tests that either always pass, always fail,
### or sometimes pass depending on a trivial computation.
-def success_target(ctx, msg):
+def success_target(ctx, msg, exe = None):
"""Return a success for an analysis test.
The test rule must have an executable output.
@@ -25,72 +46,100 @@
Args:
ctx: the Bazel rule context
msg: an informative message to display
+ exe: the output artifact (must have been created with
+ ctx.actions.declare_file or declared in ctx.output), or None meaning
+ ctx.outputs.executable
Returns:
- a suitable rule implementation struct(),
- with actions that always succeed at execution time.
+ DefaultInfo that can be added to a sh_test's srcs AND data. The test will
+ always pass.
"""
- exe = ctx.outputs.executable
+ exe = exe or ctx.outputs.executable
dat = ctx.actions.declare_file(exe.basename + ".dat")
ctx.actions.write(
output = dat,
content = msg,
)
+ script = "cat " + _bash_rlocation(dat) + " ; echo"
ctx.actions.write(
output = exe,
- content = "cat " + dat.path + " ; echo",
+ content = _SH_STUB + script,
is_executable = True,
)
- return [DefaultInfo(runfiles = ctx.runfiles([exe, dat]))]
+ return [DefaultInfo(files = depset([exe]), runfiles = ctx.runfiles([exe, dat]))]
def _successful_test_impl(ctx):
- return success_target(ctx, ctx.attr.msg)
+ return success_target(ctx, ctx.attr.msg, exe = ctx.outputs.out)
-successful_test = rule(
- attrs = {"msg": attr.string(mandatory = True)},
- executable = True,
- test = True,
+_successful_rule = rule(
+ attrs = {
+ "msg": attr.string(mandatory = True),
+ "out": attr.output(),
+ },
implementation = _successful_test_impl,
)
-def failure_target(ctx, msg):
- """Return a failure for an analysis test.
+def successful_test(name, msg, **kwargs):
+ _successful_rule(
+ name = name + "_impl",
+ msg = msg,
+ out = name + "_impl.sh",
+ visibility = ["//visibility:private"],
+ )
- The test rule must have an executable output.
+ _make_sh_test(name, **kwargs)
+
+def failure_target(ctx, msg, exe = None):
+ """Return a failure for an analysis test.
Args:
ctx: the Bazel rule context
msg: an informative message to display
+ exe: the output artifact (must have been created with
+ ctx.actions.declare_file or declared in ctx.output), or None meaning
+ ctx.outputs.executable
Returns:
- a suitable rule implementation struct(),
- with actions that always fail at execution time.
+ DefaultInfo that can be added to a sh_test's srcs AND data. The test will
+ always fail.
"""
### fail(msg) ### <--- This would fail at analysis time.
- exe = ctx.outputs.executable
+ exe = exe or ctx.outputs.executable
dat = ctx.actions.declare_file(exe.basename + ".dat")
ctx.actions.write(
output = dat,
content = msg,
)
+ script = "(cat " + _bash_rlocation(dat) + " ; echo ) >&2 ; exit 1"
ctx.actions.write(
output = exe,
- content = "(cat " + dat.short_path + " ; echo ) >&2 ; exit 1",
+ content = _SH_STUB + script,
is_executable = True,
)
- return [DefaultInfo(runfiles = ctx.runfiles([exe, dat]))]
+ return [DefaultInfo(files = depset([exe]), runfiles = ctx.runfiles([exe, dat]))]
def _failed_test_impl(ctx):
- return failure_target(ctx, ctx.attr.msg)
+ return failure_target(ctx, ctx.attr.msg, exe = ctx.outputs.out)
-failed_test = rule(
- attrs = {"msg": attr.string(mandatory = True)},
- executable = True,
- test = True,
+_failed_rule = rule(
+ attrs = {
+ "msg": attr.string(mandatory = True),
+ "out": attr.output(),
+ },
implementation = _failed_test_impl,
)
+def failed_test(name, msg, **kwargs):
+ _failed_rule(
+ name = name + "_impl",
+ msg = msg,
+ out = name + "_impl.sh",
+ visibility = ["//visibility:private"],
+ )
+
+ _make_sh_test(name, **kwargs)
+
### Second, general purpose utilities
def assert_(condition, string = "assertion failed", *args):
@@ -185,8 +234,8 @@
expect_failure: the expected failure message for the test, if any
Returns:
- a suitable rule implementation struct(),
- with actions that succeed at execution time if expectation were met,
+ DefaultInfo that can be added to a sh_test's srcs AND data. The test will
+ always succeed at execution time if expectation were met,
or fail at execution time if they didn't.
"""
(is_success, msg) = check_results(result, failure, expect, expect_failure)
@@ -195,11 +244,11 @@
### Simple tests
-def _rule_test_impl(ctx):
+def _rule_test_rule_impl(ctx):
"""check that a rule generates the desired outputs and providers."""
rule_ = ctx.attr.rule
rule_name = str(rule_.label)
- exe = ctx.outputs.executable
+ exe = ctx.outputs.out
if ctx.attr.generates:
# Generate the proper prefix to remove from generated files.
prefix_parts = []
@@ -244,30 +293,42 @@
files += [file_]
regexp = provides[k]
commands += [
- "if ! grep %s %s ; then echo 'bad %s:' ; cat %s ; echo ; exit 1 ; fi" %
- (repr(regexp), file_.short_path, k, file_.short_path),
+ "file_=%s" % _bash_rlocation(file_),
+ "if ! grep %s \"$file_\" ; then echo 'bad %s:' ; cat \"$file_\" ; echo ; exit 1 ; fi" %
+ (repr(regexp), k),
]
ctx.actions.write(output = file_, content = v)
- script = "\n".join(commands + ["true"])
+ script = _SH_STUB + "\n".join(commands)
ctx.actions.write(output = exe, content = script, is_executable = True)
- return [DefaultInfo(runfiles = ctx.runfiles([exe] + files))]
+ return [DefaultInfo(files = depset([exe]), runfiles = ctx.runfiles([exe] + files))]
else:
- return success_target(ctx, "success")
+ return success_target(ctx, "success", exe = exe)
-rule_test = rule(
+_rule_test_rule = rule(
attrs = {
"rule": attr.label(mandatory = True),
"generates": attr.string_list(),
"provides": attr.string_dict(),
+ "out": attr.output(),
},
- executable = True,
- test = True,
- implementation = _rule_test_impl,
+ implementation = _rule_test_rule_impl,
)
-def _file_test_impl(ctx):
+def rule_test(name, rule, generates = None, provides = None, **kwargs):
+ _rule_test_rule(
+ name = name + "_impl",
+ rule = rule,
+ generates = generates,
+ provides = provides,
+ out = name + ".sh",
+ visibility = ["//visibility:private"],
+ )
+
+ _make_sh_test(name, **kwargs)
+
+def _file_test_rule_impl(ctx):
"""check that a file has a given content."""
- exe = ctx.outputs.executable
+ exe = ctx.outputs.out
file_ = ctx.file.file
content = ctx.attr.content
regexp = ctx.attr.regexp
@@ -282,28 +343,29 @@
output = dat,
content = content,
)
+ script = "diff -u %s %s" % (_bash_rlocation(dat), _bash_rlocation(file_))
ctx.actions.write(
output = exe,
- content = "diff -u %s %s" % (dat.short_path, file_.short_path),
+ content = _SH_STUB + script,
is_executable = True,
)
- return [DefaultInfo(runfiles = ctx.runfiles([exe, dat, file_]))]
+ return [DefaultInfo(files = depset([exe]), runfiles = ctx.runfiles([exe, dat, file_]))]
if matches != -1:
script = "[ %s == $(grep -c %s %s) ]" % (
matches,
repr(regexp),
- file_.short_path,
+ _bash_rlocation(file_),
)
else:
- script = "grep %s %s" % (repr(regexp), file_.short_path)
+ script = "grep %s %s" % (repr(regexp), _bash_rlocation(file_))
ctx.actions.write(
output = exe,
- content = script,
+ content = _SH_STUB + script,
is_executable = True,
)
- return [DefaultInfo(runfiles = ctx.runfiles([exe, file_]))]
+ return [DefaultInfo(files = depset([exe]), runfiles = ctx.runfiles([exe, file_]))]
-file_test = rule(
+_file_test_rule = rule(
attrs = {
"file": attr.label(
mandatory = True,
@@ -312,8 +374,20 @@
"content": attr.string(default = ""),
"regexp": attr.string(default = ""),
"matches": attr.int(default = -1),
+ "out": attr.output(),
},
- executable = True,
- test = True,
- implementation = _file_test_impl,
+ implementation = _file_test_rule_impl,
)
+
+def file_test(name, file, content = None, regexp = None, matches = None, **kwargs):
+ _file_test_rule(
+ name = name + "_impl",
+ file = file,
+ content = content or "",
+ regexp = regexp or "",
+ matches = matches if (matches != None) else -1,
+ out = name + "_impl.sh",
+ visibility = ["//visibility:private"],
+ )
+
+ _make_sh_test(name, **kwargs)
diff --git a/tools/build_rules/test_rules_private.bzl b/tools/build_rules/test_rules_private.bzl
new file mode 100644
index 0000000..30b638d
--- /dev/null
+++ b/tools/build_rules/test_rules_private.bzl
@@ -0,0 +1,46 @@
+# Copyright 2019 The Bazel Authors. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""Bash runfiles library init code for test_rules.bzl."""
+
+# Init code to load the runfiles.bash file.
+# The runfiles library itself defines rlocation which you would need to look
+# up the library's runtime location, thus we have a chicken-and-egg problem.
+INIT_BASH_RUNFILES = [
+ "# --- begin runfiles.bash initialization ---",
+ "# Copy-pasted from Bazel Bash runfiles library (tools/bash/runfiles/runfiles.bash).",
+ "set -euo pipefail",
+ 'if [[ ! -d "${RUNFILES_DIR:-/dev/null}" && ! -f "${RUNFILES_MANIFEST_FILE:-/dev/null}" ]]; then',
+ ' if [[ -f "$0.runfiles_manifest" ]]; then',
+ ' export RUNFILES_MANIFEST_FILE="$0.runfiles_manifest"',
+ ' elif [[ -f "$0.runfiles/MANIFEST" ]]; then',
+ ' export RUNFILES_MANIFEST_FILE="$0.runfiles/MANIFEST"',
+ ' elif [[ -f "$0.runfiles/bazel_tools/tools/bash/runfiles/runfiles.bash" ]]; then',
+ ' export RUNFILES_DIR="$0.runfiles"',
+ " fi",
+ "fi",
+ 'if [[ -f "${RUNFILES_DIR:-/dev/null}/bazel_tools/tools/bash/runfiles/runfiles.bash" ]]; then',
+ ' source "${RUNFILES_DIR}/bazel_tools/tools/bash/runfiles/runfiles.bash"',
+ 'elif [[ -f "${RUNFILES_MANIFEST_FILE:-/dev/null}" ]]; then',
+ ' source "$(grep -m1 "^bazel_tools/tools/bash/runfiles/runfiles.bash " \\',
+ ' "$RUNFILES_MANIFEST_FILE" | cut -d " " -f 2-)"',
+ "else",
+ ' echo >&2 "ERROR: cannot find @bazel_tools//tools/bash/runfiles:runfiles.bash"',
+ " exit 1",
+ "fi",
+ "# --- end runfiles.bash initialization ---",
+]
+
+# Label of the runfiles library.
+BASH_RUNFILES_DEP = "@bazel_tools//tools/bash/runfiles"
diff --git a/tools/build_rules/test_rules_test.py b/tools/build_rules/test_rules_test.py
index 5299c57..eb3881b 100644
--- a/tools/build_rules/test_rules_test.py
+++ b/tools/build_rules/test_rules_test.py
@@ -38,6 +38,9 @@
self.CopyFile(
self.Rlocation('io_bazel/tools/build_rules/test_rules.bzl'),
'foo/test_rules.bzl')
+ self.CopyFile(
+ self.Rlocation('io_bazel/tools/build_rules/test_rules_private.bzl'),
+ 'foo/test_rules_private.bzl')
self.ScratchFile('foo/tested_file.txt',
['The quick brown', 'fox jumps over', 'the lazy dog.'])
self.ScratchFile('foo/BUILD', [