-
Notifications
You must be signed in to change notification settings - Fork 4.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
file_test, rule_test: now as sh_test rules #8352
Closed
Closed
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change | ||||||
---|---|---|---|---|---|---|---|---|
|
@@ -14,83 +14,153 @@ | |||||||
# See the License for the specific language governing permissions and | ||||||||
# limitations under the License. | ||||||||
|
||||||||
_INIT_BASH_RUNFILES = "\n".join([ | ||||||||
"#!/bin/bash", | ||||||||
"# --- begin runfiles.bash initialization ---", | ||||||||
"# Copy-pasted from Bazel Bash runfiles library (tools/bash/runfiles/runfiles.bash).", | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why copy-pasted? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. bazel/tools/bash/runfiles/runfiles.bash Lines 34 to 36 in 616481a
|
||||||||
"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 ---", | ||||||||
"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 = ["@bazel_tools//tools/bash/runfiles"], | ||||||||
**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. | ||||||||
|
||||||||
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 = _INIT_BASH_RUNFILES + 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 = _INIT_BASH_RUNFILES + 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 +255,8 @@ def analysis_results( | |||||||
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 +265,11 @@ def analysis_results( | |||||||
|
||||||||
### 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 +314,42 @@ def _rule_test_impl(ctx): | |||||||
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 = _INIT_BASH_RUNFILES + "\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 +364,29 @@ def _file_test_impl(ctx): | |||||||
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 = _INIT_BASH_RUNFILES + 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 = _INIT_BASH_RUNFILES + 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 +395,20 @@ file_test = rule( | |||||||
"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) |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you add a comment? It's not obvious to see what it does.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What should the comment say? The variable's name is descriptive and the first line of the code says what it does.