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.
diff --git a/rust/private/rust.bzl b/rust/private/rust.bzl
index b189525..9af3a32 100644
--- a/rust/private/rust.bzl
+++ b/rust/private/rust.bzl
@@ -384,13 +384,11 @@
),
)
-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 @@
_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 @@
# 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 @@
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 @@
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("""\
diff --git a/test/rust_test_suite/BUILD.bazel b/test/rust_test_suite/BUILD.bazel
index 8490f8b..df8ed74 100644
--- a/test/rust_test_suite/BUILD.bazel
+++ b/test/rust_test_suite/BUILD.bazel
@@ -7,7 +7,7 @@
)
rust_test_suite(
- name = "integrated_tests_suite",
+ name = "tests_suite",
srcs = glob(["tests/**"]),
edition = "2018",
deps = [":math_lib"],
diff --git a/test/unit/linkstamps/linkstamps_test.bzl b/test/unit/linkstamps/linkstamps_test.bzl
index 39fb048..a0ae6fe 100644
--- a/test/unit/linkstamps/linkstamps_test.bzl
+++ b/test/unit/linkstamps/linkstamps_test.bzl
@@ -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 @@
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,
diff --git a/test/unit/rust_test_outputs_are_in_subdirectory/BUILD.bazel b/test/unit/rust_test_outputs_are_in_subdirectory/BUILD.bazel
new file mode 100644
index 0000000..bf00a3a
--- /dev/null
+++ b/test/unit/rust_test_outputs_are_in_subdirectory/BUILD.bazel
@@ -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")
diff --git a/test/unit/rust_test_outputs_are_in_subdirectory/foo.rs b/test/unit/rust_test_outputs_are_in_subdirectory/foo.rs
new file mode 100644
index 0000000..da0f5d9
--- /dev/null
+++ b/test/unit/rust_test_outputs_are_in_subdirectory/foo.rs
@@ -0,0 +1 @@
+pub fn main() {}
diff --git a/test/unit/rust_test_outputs_are_in_subdirectory/rust_test_outputs.bzl b/test/unit/rust_test_outputs_are_in_subdirectory/rust_test_outputs.bzl
new file mode 100644
index 0000000..8c0f480
--- /dev/null
+++ b/test/unit/rust_test_outputs_are_in_subdirectory/rust_test_outputs.bzl
@@ -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",
+ ],
+ )