Have rust_test put its compilation outputs in a subdirectory (#1434)

This is supposed to fix the long standing Windows flakiness as described in https://github.com/bazelbuild/rules_rust/pull/1427#issuecomment-1171940333

Initially I thought it's an issue with `rustdoc_test`, however the actual issue is that `rust_binary` and its `rust_test` have the same crate name and generate the same intermediary `.o` files. For sandboxed builds this is not an issue, as the actions are isolated, however, we have a race condition in non-sandboxed builds (Windows):

Let's consider the following build:
```
bazel build --spawn_strategy=standalone \
    //test/unit/rustdoc:bin_with_transitive_proc_macro \
    //test/unit/rustdoc:bin_with_transitive_proc_macro_test
```

The `bin_with_transitive_proc_macro` compile action will create the following intermediate file: `bazel-out/k8-fastbuild/bin/test/unit/rustdoc/bin_with_transitive_proc_macro.bin_with_transitive_proc_macro.573369dc-cgu.2.rcgu.o`, as will the `bin_with_transitive_proc_macro_test` action. These two files differ slightly (as the second action is for a test), namely the `bin_with_transitive_proc_macro` output has the following symbols:

```
0000000000000000 T main
0000000000000000 t _ZN30bin_with_transitive_proc_macro4main17hfc292cc42314e131E
                 U _ZN3std2rt10lang_start17h1dbc829c47cd61d9E
```

while the `bin_with_transitive_proc_macro_test` `.o` output looks like this:
```
0000000000000000 T main
0000000000000000 t _ZN30bin_with_transitive_proc_macro4main17h28726504dc060f8dE
                 U _ZN3std2rt10lang_start17h1dbc829c47cd61d9E
                 U _ZN4test16test_main_static17h37e3d88407f5b40fE
```

Now, when the two actions run in parallel, it can happen that `bin_with_transitive_proc_macro` creates the output, and `bin_with_transitive_proc_macro_test` overwrites it. Then, at linking time for `bin_with_transitive_proc_macro`, `rustc` will complain:
```
note: ld.lld: error: undefined symbol: test::test_main_static::h37e3d88407f5b40f
```
This is because `bin_with_transitive_proc_macro` is not a test and as such is not linked against `libtest`.

This PR fixes the issue by directing the compilation outputs of `rust_test` to be under a `test-{hash}` directory.
This commit is contained in:
scentini 2022-07-07 11:57:20 +00:00 committed by GitHub
parent 8b0f644122
commit 26344d4cd7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 99 additions and 24 deletions

View File

@ -384,13 +384,11 @@ def _rust_binary_impl(ctx):
),
)
def _rust_test_common(ctx, toolchain, output):
"""Builds a Rust test binary.
def _rust_test_impl(ctx):
"""The implementation of the `rust_test` rule.
Args:
ctx (ctx): The ctx object for the current target.
toolchain (rust_toolchain): The current `rust_toolchain`
output (File): The output File that will be produced, depends on crate type.
Returns:
list: The list of providers. See `rustc_compile_action`
@ -398,6 +396,8 @@ def _rust_test_common(ctx, toolchain, output):
_assert_no_deprecated_attributes(ctx)
_assert_correct_dep_mapping(ctx)
toolchain = find_toolchain(ctx)
srcs, crate_root = _transform_sources(ctx, ctx.files.srcs, getattr(ctx.file, "crate_root", None))
crate_type = "bin"
@ -408,6 +408,15 @@ def _rust_test_common(ctx, toolchain, output):
# Target is building the crate in `test` config
crate = ctx.attr.crate[rust_common.crate_info] if rust_common.crate_info in ctx.attr.crate else ctx.attr.crate[rust_common.test_crate_info].crate
output_hash = determine_output_hash(crate.root, ctx.label)
output = ctx.actions.declare_file(
"test-%s/%s%s" % (
output_hash,
ctx.label.name,
toolchain.binary_ext,
),
)
# Optionally join compile data
if crate.compile_data:
compile_data = depset(ctx.files.compile_data, transitive = [crate.compile_data])
@ -435,6 +444,15 @@ def _rust_test_common(ctx, toolchain, output):
if not crate_root:
crate_root = crate_root_src(ctx.attr.name, ctx.files.srcs, "lib")
output_hash = determine_output_hash(crate_root, ctx.label)
output = ctx.actions.declare_file(
"test-%s/%s%s" % (
output_hash,
ctx.label.name,
toolchain.binary_ext,
),
)
# Target is a standalone crate. Build the test binary as its own crate.
crate_info = rust_common.create_crate_info(
name = compute_crate_name(ctx.workspace_name, ctx.label, toolchain, ctx.attr.crate_name),
@ -476,23 +494,6 @@ def _rust_test_common(ctx, toolchain, output):
return providers
def _rust_test_impl(ctx):
"""The implementation of the `rust_test` rule
Args:
ctx (ctx): The rule's context object
Returns:
list: A list of providers. See `_rust_test_common`
"""
toolchain = find_toolchain(ctx)
output = ctx.actions.declare_file(
ctx.label.name + toolchain.binary_ext,
)
return _rust_test_common(ctx, toolchain, output)
_common_attrs = {
"aliases": attr.label_keyed_string_dict(
doc = dedent("""\

View File

@ -7,7 +7,7 @@ rust_library(
)
rust_test_suite(
name = "integrated_tests_suite",
name = "tests_suite",
srcs = glob(["tests/**"]),
edition = "2018",
deps = [":math_lib"],

View File

@ -2,7 +2,7 @@
load("@bazel_skylib//lib:unittest.bzl", "analysistest", "asserts")
load("@rules_cc//cc:defs.bzl", "cc_library")
load("//rust:defs.bzl", "rust_binary", "rust_library", "rust_test")
load("//rust:defs.bzl", "rust_binary", "rust_common", "rust_library", "rust_test")
load("//test/unit:common.bzl", "assert_action_mnemonic")
def _is_running_on_linux(ctx):
@ -20,8 +20,13 @@ def _supports_linkstamps_test(ctx):
linkstamp_out = linkstamp_action.outputs.to_list()[0]
asserts.equals(env, linkstamp_out.basename, "linkstamp.o")
tut_out = tut.files.to_list()[0]
is_test = tut[rust_common.crate_info].is_test
workspace_prefix = "" if ctx.workspace_name == "rules_rust" else "/external/rules_rust"
expected_linkstamp_path = tut_out.dirname + "/_objs/" + tut_out.basename + workspace_prefix + "/test/unit/linkstamps/linkstamp.o"
# Rust compilation outputs coming from a test are put in test-{hash} directory
# which we need to remove in order to obtain the linkstamp file path.
dirname = "/".join(tut_out.dirname.split("/")[:-1]) if is_test else tut_out.dirname
expected_linkstamp_path = dirname + "/_objs/" + tut_out.basename + workspace_prefix + "/test/unit/linkstamps/linkstamp.o"
asserts.equals(
env,
linkstamp_out.path,

View File

@ -0,0 +1,4 @@
load(":rust_test_outputs.bzl", "rust_test_outputs_test_suite")
############################ UNIT TESTS #############################
rust_test_outputs_test_suite(name = "rust_test_outputs_test_suite")

View File

@ -0,0 +1 @@
pub fn main() {}

View File

@ -0,0 +1,64 @@
"""Tests for rust_test outputs directory."""
load("@bazel_skylib//lib:unittest.bzl", "analysistest", "asserts")
load("//rust:defs.bzl", "rust_binary", "rust_common", "rust_test")
def _rust_test_outputs_test(ctx):
env = analysistest.begin(ctx)
tut = analysistest.target_under_test(env)
output = tut[rust_common.crate_info].output
asserts.true(env, output.dirname.split("/")[-1].startswith("test-"))
return analysistest.end(env)
rust_test_outputs_test = analysistest.make(
_rust_test_outputs_test,
)
def _rust_test_outputs_targets():
rust_binary(
name = "bin_outputs",
srcs = ["foo.rs"],
edition = "2018",
)
rust_test(
name = "test_outputs_with_srcs",
srcs = ["foo.rs"],
edition = "2018",
)
rust_test_outputs_test(
name = "rust_test_outputs_using_srcs_attr",
target_under_test = ":test_outputs_with_srcs",
)
rust_test(
name = "test_outputs_with_crate",
crate = "bin_outputs",
edition = "2018",
)
rust_test_outputs_test(
name = "rust_test_outputs_using_crate_attr",
target_under_test = ":test_outputs_with_crate",
)
def rust_test_outputs_test_suite(name):
"""Entry-point macro called from the BUILD file.
Args:
name: Name of the macro.
"""
_rust_test_outputs_targets()
native.test_suite(
name = name,
tests = [
":rust_test_outputs_using_srcs_attr",
":rust_test_outputs_using_crate_attr",
],
)