Skip to content
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

Improve getting actions from the target under test #468

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
44 changes: 43 additions & 1 deletion docs/unittest_doc.md
Expand Up @@ -175,23 +175,65 @@ Unconditionally causes the current test to fail.
## analysistest.target_actions

<pre>
analysistest.target_actions(<a href="#analysistest.target_actions-env">env</a>)
analysistest.target_actions(<a href="#analysistest.target_actions-env">env</a>, <a href="#analysistest.target_actions-mnemonic">mnemonic</a>)
</pre>

Returns a list of actions registered by the target under test.

If mnemonic is provided, the list of actions will be filtered by
the given mnemonic. The list will be empty if no actions match the
given mnemonic.


**PARAMETERS**


| Name | Description | Default Value |
| :------------- | :------------- | :------------- |
| <a id="analysistest.target_actions-env"></a>env | The test environment returned by <code>analysistest.begin</code>. | none |
| <a id="analysistest.target_actions-mnemonic"></a>mnemonic | Filter the list of actions by this mnemonic. | <code>None</code> |

**RETURNS**

A list of actions registered by the target under test


<a id="analysistest.target_action"></a>

## analysistest.target_action

<pre>
analysistest.target_action(<a href="#analysistest.target_action-env">env</a>, <a href="#analysistest.target_action-mnemonic">mnemonic</a>, <a href="#analysistest.target_action-output_ending_with">output_ending_with</a>)
</pre>

Returns an action registered by the target under test.

If mnemonic is provided, returns the action with the specified
mnemonic. If no action with the given mnemonic is found, or if
multiple actions with the given mnemonic are found, fail() will
be called. Use target_actions(env, mnemonic = ...) instead.

If output_ending_with is provided, returns the action with an output
whose path ends with output_ending_with. If no such action is found,
fail() will be called.

One of mnemonic or output_ending_with must be provided.


**PARAMETERS**


| Name | Description | Default Value |
| :------------- | :------------- | :------------- |
| <a id="analysistest.target_action-env"></a>env | The test environment returned by <code>analysistest.begin</code>. | none |
| <a id="analysistest.target_action-mnemonic"></a>mnemonic | Finds the action with the given mnemonic. | <code>None</code> |
| <a id="analysistest.target_action-output_ending_with"></a>output_ending_with | Finds the action with an output that ends with the given suffix. | <code>None</code> |

**RETURNS**

An action registered by the target under test


<a id="analysistest.target_bin_dir_path"></a>

## analysistest.target_bin_dir_path
Expand Down
65 changes: 63 additions & 2 deletions lib/unittest.bzl
Expand Up @@ -554,18 +554,78 @@ def _expect_failure(env, expected_failure_msg = ""):
else:
_fail(env, "Expected failure of target_under_test, but found success")

def _target_actions(env):
def _target_actions(env, mnemonic = None):
"""Returns a list of actions registered by the target under test.

If mnemonic is provided, the list of actions will be filtered by
the given mnemonic. The list will be empty if no actions match the
given mnemonic.

Args:
env: The test environment returned by `analysistest.begin`.
mnemonic: Filter the list of actions by this mnemonic.

Returns:
A list of actions registered by the target under test
"""

actions = _target_under_test(env)[_ActionInfo].actions

if mnemonic != None:
actions = [a for a in actions if a.mnemonic == mnemonic]

# Validate?
return _target_under_test(env)[_ActionInfo].actions
return actions

def _target_action(env, mnemonic = None, output_ending_with = None):
"""Returns an action registered by the target under test.

If mnemonic is provided, returns the action with the specified
mnemonic. If no action with the given mnemonic is found, or if
multiple actions with the given mnemonic are found, fail() will
be called. Use target_actions(env, mnemonic = ...) instead.

If output_ending_with is provided, returns the action with an output
whose path ends with output_ending_with. If no such action is found,
fail() will be called.

One of mnemonic or output_ending_with must be provided.

Args:
env: The test environment returned by `analysistest.begin`.
mnemonic: Finds the action with the given mnemonic.
output_ending_with: Finds the action with an output that ends with
the given suffix.

Returns:
An action registered by the target under test
"""

if mnemonic == None and output_ending_with == None:
fail("One of mnemonic or output_ending_with must be provided.")

if mnemonic != None and output_ending_with != None:
fail("Only one of mnemonic or output_ending_with may be provided.")

actions = _target_under_test(env)[_ActionInfo].actions

if mnemonic != None:
matching_actions = []
for action in actions:
if action.mnemonic == mnemonic:
matching_actions.append(action)
if not matching_actions:
fail("No action with mnemonic '%s' found." % mnemonic)
if len(matching_actions) > 1:
fail("Multiple actions with mnemonic '%s' found." % mnemonic)
return matching_actions[0]

if output_ending_with != None:
for action in actions:
for output in action.outputs.to_list():
if output.path.endswith(output_ending_with):
return action
fail("No action with an output ending with '%s' found." % output_ending_with)

def _target_bin_dir_path(env):
"""Returns ctx.bin_dir.path for the target under test.
Expand Down Expand Up @@ -679,6 +739,7 @@ analysistest = struct(
end = _end_analysis_test,
fail = _fail,
target_actions = _target_actions,
target_action = _target_action,
target_bin_dir_path = _target_bin_dir_path,
target_under_test = _target_under_test,
)
Expand Down
2 changes: 2 additions & 0 deletions tests/BUILD
Expand Up @@ -78,7 +78,9 @@ sh_test(
srcs = ["analysis_test_test.sh"],
data = [
":unittest.bash",
"//lib:unittest",
"//rules:analysis_test.bzl",
"//toolchains/unittest:test_deps",
"@bazel_tools//tools/bash/runfiles",
],
tags = ["local"],
Expand Down
145 changes: 145 additions & 0 deletions tests/analysis_test_test.sh
Expand Up @@ -51,6 +51,10 @@ function create_pkg() {

cat > WORKSPACE <<EOF
workspace(name = 'bazel_skylib')

load("//lib:unittest.bzl", "register_unittest_toolchains")

register_unittest_toolchains()
EOF

mkdir -p rules
Expand All @@ -74,10 +78,19 @@ types = struct(
EOF

ln -sf "$(rlocation $TEST_WORKSPACE/rules/analysis_test.bzl)" rules/analysis_test.bzl
ln -sf "$(rlocation $TEST_WORKSPACE/lib/unittest.bzl)" lib/unittest.bzl
ln -sf "$(rlocation $TEST_WORKSPACE/lib/new_sets.bzl)" lib/new_sets.bzl
ln -sf "$(rlocation $TEST_WORKSPACE/lib/partial.bzl)" lib/partial.bzl
ln -sf "$(rlocation $TEST_WORKSPACE/lib/types.bzl)" lib/types.bzl
ln -sf "$(rlocation $TEST_WORKSPACE/lib/dicts.bzl)" lib/dicts.bzl

mkdir -p toolchains/unittest
ln -sf "$(rlocation $TEST_WORKSPACE/toolchains/unittest/BUILD)" toolchains/unittest/BUILD

mkdir -p fakerules
cat > fakerules/rules.bzl <<EOF
load("//rules:analysis_test.bzl", "analysis_test")
load("//lib:unittest.bzl", "analysistest")

def _fake_rule_impl(ctx):
fail("This rule should never work")
Expand All @@ -93,6 +106,110 @@ fake_depending_rule = rule(
implementation = _fake_depending_rule_impl,
attrs = {"deps" : attr.label_list()},
)

############################################
####### inspect actions failure tests ######
############################################

def _inspect_actions_fake_rule(ctx):
out1_file = ctx.actions.declare_file("out1.txt")
ctx.actions.run_shell(
command = "echo 'hello 1' > %s" % out1_file.basename,
outputs = [out1_file],
mnemonic = "MnemonicA",
)
out2_file = ctx.actions.declare_file("out2.txt")
ctx.actions.run_shell(
command = "echo 'hello 1' > %s" % out2_file.basename,
outputs = [out2_file],
mnemonic = "MnemonicA",
)
return [
DefaultInfo(files = depset([out1_file, out2_file]))
]

inspect_actions_fake_rule = rule(
implementation = _inspect_actions_fake_rule,
)

def _inspect_actions_no_mnemonic_test(ctx):
env = analysistest.begin(ctx)
# Should fail, no actions with MnemonicC
mnemonic_c_actions = analysistest.target_action(
env, mnemonic = "MnemonicC")
return analysistest.end(env)

inspect_actions_no_mnemonic_test = analysistest.make(
_inspect_actions_no_mnemonic_test,
)

def _inspect_actions_multiple_actions_with_mnemonic_test(ctx):
env = analysistest.begin(ctx)
# Should fail, multiple actions with same mnemonic
output_foo_actions = analysistest.target_action(
env, mnemonic = "MnemonicA")
return analysistest.end(env)

inspect_actions_multiple_actions_with_mnemonic_test = analysistest.make(
_inspect_actions_multiple_actions_with_mnemonic_test,
)

def _inspect_actions_no_output_test(ctx):
env = analysistest.begin(ctx)
# Should fail, no actions with output foo
output_foo_actions = analysistest.target_action(
env, output_ending_with = "foo")
return analysistest.end(env)

inspect_actions_no_output_test = analysistest.make(
_inspect_actions_no_output_test,
)

def _inspect_actions_no_filter_test(ctx):
env = analysistest.begin(ctx)
# Should fail, need filter
output_foo_actions = analysistest.target_action(env)
return analysistest.end(env)

inspect_actions_no_filter_test = analysistest.make(
_inspect_actions_no_filter_test,
)

def _inspect_actions_too_many_filters_test(ctx):
env = analysistest.begin(ctx)
# Should fail, can't specify both mnemonic and output_ending_with
output_foo_actions = analysistest.target_action(
env, mnemonic = "MnemonicC", output_ending_with = "out1.txt")
return analysistest.end(env)

inspect_actions_too_many_filters_test = analysistest.make(
_inspect_actions_too_many_filters_test,
)

def action_inspection_failure_tests():
inspect_actions_fake_rule(name = "inspect_actions_fake_rule")

inspect_actions_no_mnemonic_test(
name = "inspect_actions_no_mnemonic_test",
target_under_test = "inspect_actions_fake_rule"
)
inspect_actions_no_output_test(
name = "inspect_actions_no_output_test",
target_under_test = "inspect_actions_fake_rule"
)
inspect_actions_multiple_actions_with_mnemonic_test(
name = "inspect_actions_multiple_actions_with_mnemonic_test",
target_under_test = "inspect_actions_fake_rule"
)
inspect_actions_no_filter_test(
name = "inspect_actions_no_filter_test",
target_under_test = "inspect_actions_fake_rule"
)
inspect_actions_too_many_filters_test(
name = "inspect_actions_too_many_filters_test",
target_under_test = "inspect_actions_fake_rule"
)

EOF

cat > fakerules/BUILD <<EOF
Expand All @@ -107,6 +224,7 @@ EOF
cat > testdir/BUILD <<EOF
load("//rules:analysis_test.bzl", "analysis_test")
load("//fakerules:rules.bzl", "fake_rule", "fake_depending_rule")
load("//fakerules:rules.bzl", "action_inspection_failure_tests")

fake_rule(name = "target_fails")

Expand All @@ -133,6 +251,8 @@ analysis_test(
name = "target_succeeds",
targets = [":dummy_cc_library"],
)

action_inspection_failure_tests()
EOF
}

Expand Down Expand Up @@ -165,5 +285,30 @@ function test_transitive_target_fails() {
expect_log "This rule should never work"
}

function test_inspect_actions_fails() {
local -r pkg="${FUNCNAME[0]}"
create_pkg "$pkg"

bazel test testdir:inspect_actions_no_mnemonic_test --test_output=all --verbose_failures \
>"$TEST_log" 2>&1 && fail "Expected test to fail" || true
expect_log "No action with mnemonic 'MnemonicC' found."

bazel test testdir:inspect_actions_multiple_actions_with_mnemonic_test --test_output=all --verbose_failures \
>"$TEST_log" 2>&1 && fail "Expected test to fail" || true
expect_log "Multiple actions with mnemonic 'MnemonicA' found."

bazel test testdir:inspect_actions_no_output_test --test_output=all --verbose_failures \
>"$TEST_log" 2>&1 && fail "Expected test to fail" || true
expect_log "No action with an output ending with 'foo' found."

bazel test testdir:inspect_actions_no_filter_test --test_output=all --verbose_failures \
>"$TEST_log" 2>&1 && fail "Expected test to fail" || true
expect_log "One of mnemonic or output_ending_with must be provided."

bazel test testdir:inspect_actions_too_many_filters_test --test_output=all --verbose_failures \
>"$TEST_log" 2>&1 && fail "Expected test to fail" || true
expect_log "Only one of mnemonic or output_ending_with may be provided."
}

cd "$TEST_TMPDIR"
run_suite "analysis_test test suite"