1"""Unittests for rust rules."""
2
3load("@bazel_skylib//lib:unittest.bzl", "analysistest", "asserts")
4load("//rust:defs.bzl", "rust_binary", "rust_library", "rust_proc_macro")
5load("//test/unit:common.bzl", "assert_argv_contains", "assert_list_contains_adjacent_elements", "assert_list_contains_adjacent_elements_not")
6load(":wrap.bzl", "wrap")
7
8NOT_WINDOWS = select({
9    "@platforms//os:linux": [],
10    "@platforms//os:macos": [],
11    "//conditions:default": ["@platforms//:incompatible"],
12})
13
14ENABLE_PIPELINING = {
15    str(Label("//rust/settings:pipelined_compilation")): True,
16}
17
18def _second_lib_test_impl(ctx):
19    env = analysistest.begin(ctx)
20    tut = analysistest.target_under_test(env)
21    rlib_action = [act for act in tut.actions if act.mnemonic == "Rustc"][0]
22    metadata_action = [act for act in tut.actions if act.mnemonic == "RustcMetadata"][0]
23
24    # Both actions should use the same --emit=
25    assert_argv_contains(env, rlib_action, "--emit=dep-info,link,metadata")
26    assert_argv_contains(env, metadata_action, "--emit=dep-info,link,metadata")
27
28    # The metadata action should have a .rmeta as output and the rlib action a .rlib
29    path = rlib_action.outputs.to_list()[0].path
30    asserts.true(
31        env,
32        path.endswith(".rlib"),
33        "expected Rustc to output .rlib, got " + path,
34    )
35    path = metadata_action.outputs.to_list()[0].path
36    asserts.true(
37        env,
38        path.endswith(".rmeta"),
39        "expected RustcMetadata to output .rmeta, got " + path,
40    )
41
42    # Only the action building metadata should contain --rustc-quit-on-rmeta
43    assert_list_contains_adjacent_elements_not(env, rlib_action.argv, ["--rustc-quit-on-rmeta", "true"])
44    assert_list_contains_adjacent_elements(env, metadata_action.argv, ["--rustc-quit-on-rmeta", "true"])
45
46    # Check that both actions refer to the metadata of :first, not the rlib
47    extern_metadata = [arg for arg in metadata_action.argv if arg.startswith("--extern=first=") and "libfirst" in arg and arg.endswith(".rmeta")]
48    asserts.true(
49        env,
50        len(extern_metadata) == 1,
51        "did not find a --extern=first=*.rmeta but expected one",
52    )
53    extern_rlib = [arg for arg in rlib_action.argv if arg.startswith("--extern=first=") and "libfirst" in arg and arg.endswith(".rmeta")]
54    asserts.true(
55        env,
56        len(extern_rlib) == 1,
57        "did not find a --extern=first=*.rlib but expected one",
58    )
59
60    # Check that the input to both actions is the metadata of :first
61    input_metadata = [i for i in metadata_action.inputs.to_list() if i.basename.startswith("libfirst")]
62    asserts.true(env, len(input_metadata) == 1, "expected only one libfirst input, found " + str([i.path for i in input_metadata]))
63    asserts.true(env, input_metadata[0].extension == "rmeta", "expected libfirst dependency to be rmeta, found " + input_metadata[0].path)
64    input_rlib = [i for i in rlib_action.inputs.to_list() if i.basename.startswith("libfirst")]
65    asserts.true(env, len(input_rlib) == 1, "expected only one libfirst input, found " + str([i.path for i in input_rlib]))
66    asserts.true(env, input_rlib[0].extension == "rmeta", "expected libfirst dependency to be rmeta, found " + input_rlib[0].path)
67
68    return analysistest.end(env)
69
70def _bin_test_impl(ctx):
71    env = analysistest.begin(ctx)
72    tut = analysistest.target_under_test(env)
73    bin_action = [act for act in tut.actions if act.mnemonic == "Rustc"][0]
74
75    # Check that no inputs to this binary are .rmeta files.
76    metadata_inputs = [i.path for i in bin_action.inputs.to_list() if i.path.endswith(".rmeta")]
77    asserts.false(env, metadata_inputs, "expected no metadata inputs, found " + str(metadata_inputs))
78
79    return analysistest.end(env)
80
81bin_test = analysistest.make(_bin_test_impl, config_settings = ENABLE_PIPELINING)
82second_lib_test = analysistest.make(_second_lib_test_impl, config_settings = ENABLE_PIPELINING)
83
84def _pipelined_compilation_test():
85    rust_proc_macro(
86        name = "my_macro",
87        edition = "2021",
88        srcs = ["my_macro.rs"],
89    )
90
91    rust_library(
92        name = "first",
93        edition = "2021",
94        srcs = ["first.rs"],
95    )
96
97    rust_library(
98        name = "second",
99        edition = "2021",
100        srcs = ["second.rs"],
101        deps = [":first"],
102        proc_macro_deps = [":my_macro"],
103    )
104
105    rust_binary(
106        name = "bin",
107        edition = "2021",
108        srcs = ["bin.rs"],
109        deps = [":second"],
110    )
111
112    second_lib_test(name = "second_lib_test", target_under_test = ":second", target_compatible_with = NOT_WINDOWS)
113    bin_test(name = "bin_test", target_under_test = ":bin", target_compatible_with = NOT_WINDOWS)
114
115def _rmeta_is_propagated_through_custom_rule_test_impl(ctx):
116    env = analysistest.begin(ctx)
117    tut = analysistest.target_under_test(env)
118
119    # This is the metadata-generating action. It should depend on metadata for the library and, if generate_metadata is set
120    # also depend on metadata for 'wrapper'.
121    rust_action = [act for act in tut.actions if act.mnemonic == "RustcMetadata"][0]
122
123    metadata_inputs = [i for i in rust_action.inputs.to_list() if i.path.endswith(".rmeta")]
124    rlib_inputs = [i for i in rust_action.inputs.to_list() if i.path.endswith(".rlib")]
125
126    seen_wrapper_metadata = False
127    seen_to_wrap_metadata = False
128    for mi in metadata_inputs:
129        if "libwrapper" in mi.path:
130            seen_wrapper_metadata = True
131        if "libto_wrap" in mi.path:
132            seen_to_wrap_metadata = True
133
134    seen_wrapper_rlib = False
135    seen_to_wrap_rlib = False
136    for ri in rlib_inputs:
137        if "libwrapper" in ri.path:
138            seen_wrapper_rlib = True
139        if "libto_wrap" in ri.path:
140            seen_to_wrap_rlib = True
141
142    if ctx.attr.generate_metadata:
143        asserts.true(env, seen_wrapper_metadata, "expected dependency on metadata for 'wrapper' but not found")
144        asserts.false(env, seen_wrapper_rlib, "expected no dependency on object for 'wrapper' but it was found")
145    else:
146        asserts.true(env, seen_wrapper_rlib, "expected dependency on object for 'wrapper' but not found")
147        asserts.false(env, seen_wrapper_metadata, "expected no dependency on metadata for 'wrapper' but it was found")
148
149    asserts.true(env, seen_to_wrap_metadata, "expected dependency on metadata for 'to_wrap' but not found")
150    asserts.false(env, seen_to_wrap_rlib, "expected no dependency on object for 'to_wrap' but it was found")
151
152    return analysistest.end(env)
153
154def _rmeta_is_used_when_building_custom_rule_test_impl(ctx):
155    env = analysistest.begin(ctx)
156    tut = analysistest.target_under_test(env)
157
158    # This is the custom rule invocation of rustc.
159    rust_action = [act for act in tut.actions if act.mnemonic == "Rustc"][0]
160
161    # We want to check that the action depends on metadata, regardless of ctx.attr.generate_metadata
162    seen_to_wrap_rlib = False
163    seen_to_wrap_rmeta = False
164    for act in rust_action.inputs.to_list():
165        if "libto_wrap" in act.path and act.path.endswith(".rlib"):
166            seen_to_wrap_rlib = True
167        elif "libto_wrap" in act.path and act.path.endswith(".rmeta"):
168            seen_to_wrap_rmeta = True
169
170    asserts.true(env, seen_to_wrap_rmeta, "expected dependency on metadata for 'to_wrap' but not found")
171    asserts.false(env, seen_to_wrap_rlib, "expected no dependency on object for 'to_wrap' but it was found")
172
173    return analysistest.end(env)
174
175rmeta_is_propagated_through_custom_rule_test = analysistest.make(_rmeta_is_propagated_through_custom_rule_test_impl, attrs = {"generate_metadata": attr.bool()}, config_settings = ENABLE_PIPELINING)
176rmeta_is_used_when_building_custom_rule_test = analysistest.make(_rmeta_is_used_when_building_custom_rule_test_impl, config_settings = ENABLE_PIPELINING)
177
178def _rmeta_not_produced_if_pipelining_disabled_test_impl(ctx):
179    env = analysistest.begin(ctx)
180    tut = analysistest.target_under_test(env)
181
182    rust_action = [act for act in tut.actions if act.mnemonic == "RustcMetadata"]
183    asserts.true(env, len(rust_action) == 0, "expected no metadata to be produced, but found a metadata action")
184
185    return analysistest.end(env)
186
187rmeta_not_produced_if_pipelining_disabled_test = analysistest.make(_rmeta_not_produced_if_pipelining_disabled_test_impl, config_settings = ENABLE_PIPELINING)
188
189def _disable_pipelining_test():
190    rust_library(
191        name = "lib",
192        srcs = ["custom_rule_test/to_wrap.rs"],
193        edition = "2021",
194        disable_pipelining = True,
195    )
196    rmeta_not_produced_if_pipelining_disabled_test(
197        name = "rmeta_not_produced_if_pipelining_disabled_test",
198        target_compatible_with = NOT_WINDOWS,
199        target_under_test = ":lib",
200    )
201
202def _custom_rule_test(generate_metadata, suffix):
203    rust_library(
204        name = "to_wrap" + suffix,
205        crate_name = "to_wrap",
206        srcs = ["custom_rule_test/to_wrap.rs"],
207        edition = "2021",
208    )
209    wrap(
210        name = "wrapper" + suffix,
211        crate_name = "wrapper",
212        target = ":to_wrap" + suffix,
213        generate_metadata = generate_metadata,
214    )
215    rust_library(
216        name = "uses_wrapper" + suffix,
217        srcs = ["custom_rule_test/uses_wrapper.rs"],
218        deps = [":wrapper" + suffix],
219        edition = "2021",
220    )
221
222    rmeta_is_propagated_through_custom_rule_test(
223        name = "rmeta_is_propagated_through_custom_rule_test" + suffix,
224        generate_metadata = generate_metadata,
225        target_compatible_with = NOT_WINDOWS,
226        target_under_test = ":uses_wrapper" + suffix,
227    )
228
229    rmeta_is_used_when_building_custom_rule_test(
230        name = "rmeta_is_used_when_building_custom_rule_test" + suffix,
231        target_compatible_with = NOT_WINDOWS,
232        target_under_test = ":wrapper" + suffix,
233    )
234
235def pipelined_compilation_test_suite(name):
236    """Entry-point macro called from the BUILD file.
237
238    Args:
239        name: Name of the macro.
240    """
241    _pipelined_compilation_test()
242    _disable_pipelining_test()
243    _custom_rule_test(generate_metadata = True, suffix = "_with_metadata")
244    _custom_rule_test(generate_metadata = False, suffix = "_without_metadata")
245
246    native.test_suite(
247        name = name,
248        tests = [
249            ":bin_test",
250            ":second_lib_test",
251            ":rmeta_is_propagated_through_custom_rule_test_with_metadata",
252            ":rmeta_is_propagated_through_custom_rule_test_without_metadata",
253            ":rmeta_is_used_when_building_custom_rule_test_with_metadata",
254            ":rmeta_is_used_when_building_custom_rule_test_without_metadata",
255            ":rmeta_not_produced_if_pipelining_disabled_test",
256        ],
257    )
258