xref: /aosp_15_r20/build/soong/rust/rust_test.go (revision 333d2b3687b3a337dbcca9d65000bca186795e39)
1// Copyright 2019 The Android Open Source Project
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package rust
16
17import (
18	"os"
19	"runtime"
20	"strings"
21	"testing"
22
23	"github.com/google/blueprint/proptools"
24
25	"android/soong/android"
26	"android/soong/genrule"
27)
28
29func TestMain(m *testing.M) {
30	os.Exit(m.Run())
31}
32
33var prepareForRustTest = android.GroupFixturePreparers(
34	android.PrepareForTestWithArchMutator,
35	android.PrepareForTestWithDefaults,
36	android.PrepareForTestWithPrebuilts,
37
38	genrule.PrepareForTestWithGenRuleBuildComponents,
39
40	PrepareForIntegrationTestWithRust,
41)
42
43var rustMockedFiles = android.MockFS{
44	"foo.rs":                       nil,
45	"foo.c":                        nil,
46	"src/bar.rs":                   nil,
47	"src/any.h":                    nil,
48	"c_includes/c_header.h":        nil,
49	"rust_includes/rust_headers.h": nil,
50	"proto.proto":                  nil,
51	"proto/buf.proto":              nil,
52	"buf.proto":                    nil,
53	"foo.proto":                    nil,
54	"liby.so":                      nil,
55	"libz.so":                      nil,
56	"data.txt":                     nil,
57	"liblog.map.txt":               nil,
58}
59
60// testRust returns a TestContext in which a basic environment has been setup.
61// This environment contains a few mocked files. See rustMockedFiles for the list of these files.
62func testRust(t *testing.T, bp string) *android.TestContext {
63	t.Helper()
64	skipTestIfOsNotSupported(t)
65	result := android.GroupFixturePreparers(
66		prepareForRustTest,
67		rustMockedFiles.AddToFixture(),
68	).
69		RunTestWithBp(t, bp)
70	return result.TestContext
71}
72
73const (
74	sharedVendorVariant        = "android_vendor_arm64_armv8-a_shared"
75	rlibVendorVariant          = "android_vendor_arm64_armv8-a_rlib_rlib-std"
76	rlibDylibStdVendorVariant  = "android_vendor_arm64_armv8-a_rlib_rlib-std"
77	dylibVendorVariant         = "android_vendor_arm64_armv8-a_dylib"
78	sharedRecoveryVariant      = "android_recovery_arm64_armv8-a_shared"
79	rlibRecoveryVariant        = "android_recovery_arm64_armv8-a_rlib_dylib-std"
80	rlibRlibStdRecoveryVariant = "android_recovery_arm64_armv8-a_rlib_rlib-std"
81	dylibRecoveryVariant       = "android_recovery_arm64_armv8-a_dylib"
82	binaryCoreVariant          = "android_arm64_armv8-a"
83	binaryVendorVariant        = "android_vendor_arm64_armv8-a"
84	binaryProductVariant       = "android_product_arm64_armv8-a"
85	binaryRecoveryVariant      = "android_recovery_arm64_armv8-a"
86)
87
88// testRustCov returns a TestContext in which a basic environment has been
89// setup. This environment explicitly enables coverage.
90func testRustCov(t *testing.T, bp string) *android.TestContext {
91	skipTestIfOsNotSupported(t)
92	result := android.GroupFixturePreparers(
93		prepareForRustTest,
94		rustMockedFiles.AddToFixture(),
95		android.FixtureModifyProductVariables(
96			func(variables android.FixtureProductVariables) {
97				variables.ClangCoverage = proptools.BoolPtr(true)
98				variables.Native_coverage = proptools.BoolPtr(true)
99				variables.NativeCoveragePaths = []string{"*"}
100			},
101		),
102	).RunTestWithBp(t, bp)
103	return result.TestContext
104}
105
106// testRustError ensures that at least one error was raised and its value
107// matches the pattern provided. The error can be either in the parsing of the
108// Blueprint or when generating the build actions.
109func testRustError(t *testing.T, pattern string, bp string) {
110	skipTestIfOsNotSupported(t)
111	android.GroupFixturePreparers(
112		prepareForRustTest,
113		rustMockedFiles.AddToFixture(),
114	).
115		ExtendWithErrorHandler(android.FixtureExpectsAtLeastOneErrorMatchingPattern(pattern)).
116		RunTestWithBp(t, bp)
117}
118
119// testRustCtx is used to build a particular test environment. Unless your
120// tests requires a specific setup, prefer the wrapping functions: testRust,
121// testRustCov or testRustError.
122type testRustCtx struct {
123	bp     string
124	fs     map[string][]byte
125	env    map[string]string
126	config *android.Config
127}
128
129func skipTestIfOsNotSupported(t *testing.T) {
130	// TODO (b/140435149)
131	if runtime.GOOS != "linux" {
132		t.Skip("Rust Soong tests can only be run on Linux hosts currently")
133	}
134}
135
136// Test that we can extract the link path from a lib path.
137func TestLinkPathFromFilePath(t *testing.T) {
138	barPath := android.PathForTesting("out/soong/.intermediates/external/libbar/libbar/linux_glibc_x86_64_shared/libbar.so")
139	libName := linkPathFromFilePath(barPath)
140	expectedResult := "out/soong/.intermediates/external/libbar/libbar/linux_glibc_x86_64_shared/"
141
142	if libName != expectedResult {
143		t.Errorf("libNameFromFilePath returned the wrong name; expected '%#v', got '%#v'", expectedResult, libName)
144	}
145}
146
147// Test to make sure dependencies are being picked up correctly.
148func TestDepsTracking(t *testing.T) {
149	ctx := testRust(t, `
150		cc_library {
151			host_supported: true,
152			name: "cc_stubs_dep",
153		}
154		cc_library_host_static {
155			name: "libstatic",
156		}
157		cc_library_host_static {
158			name: "libwholestatic",
159		}
160		rust_ffi_host_shared {
161			name: "libshared",
162			srcs: ["foo.rs"],
163			crate_name: "shared",
164		}
165		rust_library_host_rlib {
166			name: "librlib",
167			srcs: ["foo.rs"],
168			crate_name: "rlib",
169			static_libs: ["libstatic"],
170			whole_static_libs: ["libwholestatic"],
171			shared_libs: ["cc_stubs_dep"],
172		}
173		rust_proc_macro {
174			name: "libpm",
175			srcs: ["foo.rs"],
176			crate_name: "pm",
177		}
178		rust_binary_host {
179			name: "fizz-buzz",
180			rlibs: ["librlib"],
181			proc_macros: ["libpm"],
182			static_libs: ["libstatic"],
183			shared_libs: ["libshared"],
184			srcs: ["foo.rs"],
185		}
186	`)
187	module := ctx.ModuleForTests("fizz-buzz", "linux_glibc_x86_64").Module().(*Module)
188	rustc := ctx.ModuleForTests("librlib", "linux_glibc_x86_64_rlib_rlib-std").Rule("rustc")
189
190	// Since dependencies are added to AndroidMk* properties, we can check these to see if they've been picked up.
191	if !android.InList("librlib.rlib-std", module.Properties.AndroidMkRlibs) {
192		t.Errorf("Rlib dependency not detected (dependency missing from AndroidMkRlibs)")
193	}
194
195	if !android.InList("libpm", module.Properties.AndroidMkProcMacroLibs) {
196		t.Errorf("Proc_macro dependency not detected (dependency missing from AndroidMkProcMacroLibs)")
197	}
198
199	if !android.InList("libshared", module.transitiveAndroidMkSharedLibs.ToList()) {
200		t.Errorf("Shared library dependency not detected (dependency missing from AndroidMkSharedLibs)")
201	}
202
203	if !android.InList("libstatic", module.Properties.AndroidMkStaticLibs) {
204		t.Errorf("Static library dependency not detected (dependency missing from AndroidMkStaticLibs)")
205	}
206
207	if !strings.Contains(rustc.Args["rustcFlags"], "-lstatic=wholestatic") {
208		t.Errorf("-lstatic flag not being passed to rustc for static library %#v", rustc.Args["rustcFlags"])
209	}
210
211	if !strings.Contains(rustc.Args["linkFlags"], "cc_stubs_dep.so") {
212		t.Errorf("shared cc_library not being passed to rustc linkFlags %#v", rustc.Args["linkFlags"])
213	}
214
215	if !android.SuffixInList(rustc.OrderOnly.Strings(), "cc_stubs_dep.so") {
216		t.Errorf("shared cc dep not being passed as order-only to rustc %#v", rustc.OrderOnly.Strings())
217	}
218
219	if !android.SuffixInList(rustc.Implicits.Strings(), "cc_stubs_dep.so.toc") {
220		t.Errorf("shared cc dep TOC not being passed as implicit to rustc %#v", rustc.Implicits.Strings())
221	}
222}
223
224func TestSourceProviderDeps(t *testing.T) {
225	ctx := testRust(t, `
226		rust_binary {
227			name: "fizz-buzz-dep",
228			srcs: [
229				"foo.rs",
230				":my_generator",
231				":libbindings",
232			],
233			rlibs: ["libbindings"],
234		}
235		rust_proc_macro {
236			name: "libprocmacro",
237			srcs: [
238				"foo.rs",
239				":my_generator",
240				":libbindings",
241			],
242			rlibs: ["libbindings"],
243			crate_name: "procmacro",
244		}
245		rust_library {
246			name: "libfoo",
247			srcs: [
248				"foo.rs",
249				":my_generator",
250				":libbindings",
251			],
252			rlibs: ["libbindings"],
253			crate_name: "foo",
254		}
255		genrule {
256			name: "my_generator",
257			tools: ["any_rust_binary"],
258			cmd: "$(location) -o $(out) $(in)",
259			srcs: ["src/any.h"],
260			out: ["src/any.rs"],
261		}
262		rust_binary_host {
263			name: "any_rust_binary",
264			srcs: [
265				"foo.rs",
266			],
267		}
268		rust_bindgen {
269			name: "libbindings",
270			crate_name: "bindings",
271			source_stem: "bindings",
272			host_supported: true,
273			wrapper_src: "src/any.h",
274		}
275	`)
276
277	libfoo := ctx.ModuleForTests("libfoo", "android_arm64_armv8-a_rlib_dylib-std").Rule("rustc")
278	if !android.SuffixInList(libfoo.Implicits.Strings(), "/out/bindings.rs") {
279		t.Errorf("rust_bindgen generated source not included as implicit input for libfoo; Implicits %#v", libfoo.Implicits.Strings())
280	}
281	if !android.SuffixInList(libfoo.Implicits.Strings(), "/out/any.rs") {
282		t.Errorf("genrule generated source not included as implicit input for libfoo; Implicits %#v", libfoo.Implicits.Strings())
283	}
284
285	fizzBuzz := ctx.ModuleForTests("fizz-buzz-dep", "android_arm64_armv8-a").Rule("rustc")
286	if !android.SuffixInList(fizzBuzz.Implicits.Strings(), "/out/bindings.rs") {
287		t.Errorf("rust_bindgen generated source not included as implicit input for fizz-buzz-dep; Implicits %#v", libfoo.Implicits.Strings())
288	}
289	if !android.SuffixInList(fizzBuzz.Implicits.Strings(), "/out/any.rs") {
290		t.Errorf("genrule generated source not included as implicit input for fizz-buzz-dep; Implicits %#v", libfoo.Implicits.Strings())
291	}
292
293	libprocmacro := ctx.ModuleForTests("libprocmacro", "linux_glibc_x86_64").Rule("rustc")
294	if !android.SuffixInList(libprocmacro.Implicits.Strings(), "/out/bindings.rs") {
295		t.Errorf("rust_bindgen generated source not included as implicit input for libprocmacro; Implicits %#v", libfoo.Implicits.Strings())
296	}
297	if !android.SuffixInList(libprocmacro.Implicits.Strings(), "/out/any.rs") {
298		t.Errorf("genrule generated source not included as implicit input for libprocmacro; Implicits %#v", libfoo.Implicits.Strings())
299	}
300
301	// Check that our bindings are picked up as crate dependencies as well
302	libfooMod := ctx.ModuleForTests("libfoo", "android_arm64_armv8-a_dylib").Module().(*Module)
303	if !android.InList("libbindings", libfooMod.Properties.AndroidMkRlibs) {
304		t.Errorf("bindgen dependency not detected as a rlib dependency (dependency missing from AndroidMkRlibs)")
305	}
306	fizzBuzzMod := ctx.ModuleForTests("fizz-buzz-dep", "android_arm64_armv8-a").Module().(*Module)
307	if !android.InList("libbindings", fizzBuzzMod.Properties.AndroidMkRlibs) {
308		t.Errorf("bindgen dependency not detected as a rlib dependency (dependency missing from AndroidMkRlibs)")
309	}
310	libprocmacroMod := ctx.ModuleForTests("libprocmacro", "linux_glibc_x86_64").Module().(*Module)
311	if !android.InList("libbindings.rlib-std", libprocmacroMod.Properties.AndroidMkRlibs) {
312		t.Errorf("bindgen dependency not detected as a rlib dependency (dependency missing from AndroidMkRlibs)")
313	}
314}
315
316func TestSourceProviderTargetMismatch(t *testing.T) {
317	// This might error while building the dependency tree or when calling depsToPaths() depending on the lunched
318	// target, which results in two different errors. So don't check the error, just confirm there is one.
319	testRustError(t, ".*", `
320		rust_proc_macro {
321			name: "libprocmacro",
322			srcs: [
323				"foo.rs",
324				":libbindings",
325			],
326			crate_name: "procmacro",
327		}
328		rust_bindgen {
329			name: "libbindings",
330			crate_name: "bindings",
331			source_stem: "bindings",
332			wrapper_src: "src/any.h",
333		}
334	`)
335}
336
337// Test to make sure proc_macros use host variants when building device modules.
338func TestProcMacroDeviceDeps(t *testing.T) {
339	ctx := testRust(t, `
340		rust_library_host_rlib {
341			name: "libbar",
342			srcs: ["foo.rs"],
343			crate_name: "bar",
344		}
345		rust_proc_macro {
346			name: "libpm",
347			rlibs: ["libbar"],
348			srcs: ["foo.rs"],
349			crate_name: "pm",
350		}
351		rust_binary {
352			name: "fizz-buzz",
353			proc_macros: ["libpm"],
354			srcs: ["foo.rs"],
355		}
356	`)
357	rustc := ctx.ModuleForTests("libpm", "linux_glibc_x86_64").Rule("rustc")
358
359	if !strings.Contains(rustc.Args["libFlags"], "libbar/linux_glibc_x86_64") {
360		t.Errorf("Proc_macro is not using host variant of dependent modules.")
361	}
362}
363
364// Test that no_stdlibs suppresses dependencies on rust standard libraries
365func TestNoStdlibs(t *testing.T) {
366	ctx := testRust(t, `
367		rust_binary {
368			name: "fizz-buzz",
369			srcs: ["foo.rs"],
370			no_stdlibs: true,
371		}`)
372	module := ctx.ModuleForTests("fizz-buzz", "android_arm64_armv8-a").Module().(*Module)
373
374	if android.InList("libstd", module.Properties.AndroidMkDylibs) {
375		t.Errorf("no_stdlibs did not suppress dependency on libstd")
376	}
377}
378
379// Test that libraries provide both 32-bit and 64-bit variants.
380func TestMultilib(t *testing.T) {
381	ctx := testRust(t, `
382		rust_library_rlib {
383			name: "libfoo",
384			srcs: ["foo.rs"],
385			crate_name: "foo",
386		}`)
387
388	_ = ctx.ModuleForTests("libfoo", "android_arm64_armv8-a_rlib_dylib-std")
389	_ = ctx.ModuleForTests("libfoo", "android_arm_armv7-a-neon_rlib_dylib-std")
390}
391
392// Test that library size measurements are generated.
393func TestLibrarySizes(t *testing.T) {
394	ctx := testRust(t, `
395		rust_library_dylib {
396			name: "libwaldo",
397			srcs: ["foo.rs"],
398			crate_name: "waldo",
399		}`)
400
401	m := ctx.SingletonForTests("file_metrics")
402	m.Output("unstripped/libwaldo.dylib.so.bloaty.csv")
403	m.Output("libwaldo.dylib.so.bloaty.csv")
404}
405
406// Test that aliases are respected.
407func TestRustAliases(t *testing.T) {
408	ctx := testRust(t, `
409		rust_library {
410			name: "libbar",
411			crate_name: "bar",
412			srcs: ["src/lib.rs"],
413		}
414		rust_library {
415			name: "libbaz",
416			crate_name: "baz",
417			srcs: ["src/lib.rs"],
418		}
419		rust_binary {
420			name: "foo",
421			srcs: ["src/main.rs"],
422			rustlibs: ["libbar", "libbaz"],
423			aliases: ["bar:bar_renamed"],
424		}`)
425
426	fooRustc := ctx.ModuleForTests("foo", "android_arm64_armv8-a").Rule("rustc")
427	if !strings.Contains(fooRustc.Args["libFlags"], "--extern bar_renamed=out/soong/.intermediates/libbar/android_arm64_armv8-a_dylib/unstripped/libbar.dylib.so") {
428		t.Errorf("--extern bar_renamed=out/soong/.intermediates/libbar/android_arm64_armv8-a_dylib/unstripped/libbar.dylib.so flag not being passed to rustc for rust_binary with aliases. libFlags: %#v", fooRustc.Args["libFlags"])
429	}
430	if !strings.Contains(fooRustc.Args["libFlags"], "--extern baz=out/soong/.intermediates/libbaz/android_arm64_armv8-a_dylib/unstripped/libbaz.dylib.so") {
431		t.Errorf("--extern baz=out/soong/.intermediates/libbaz/android_arm64_armv8-a_dylib/unstripped/libbaz.dylib.so flag not being passed to rustc for rust_binary with aliases. libFlags: %#v", fooRustc.Args["libFlags"])
432	}
433}
434
435func TestRustRlibs(t *testing.T) {
436	ctx := testRust(t, `
437		rust_ffi_rlib {
438			name: "libbar",
439			crate_name: "bar",
440			srcs: ["src/lib.rs"],
441			export_include_dirs: ["bar_includes"]
442		}
443
444		rust_ffi_rlib {
445			name: "libfoo",
446			crate_name: "foo",
447			srcs: ["src/lib.rs"],
448			export_include_dirs: ["foo_includes"]
449		}
450
451		rust_ffi_rlib {
452			name: "libbuzz",
453			crate_name: "buzz",
454			srcs: ["src/lib.rs"],
455			export_include_dirs: ["buzz_includes"]
456		}
457
458		cc_library_shared {
459			name: "libcc_shared",
460			srcs:["foo.c"],
461			static_libs: ["libbar"],
462		}
463
464		cc_library_static {
465			name: "libcc_static",
466			srcs:["foo.c"],
467			static_libs: ["libbuzz"],
468			whole_static_libs: ["libfoo"],
469		}
470
471		cc_binary {
472			name: "ccBin",
473			srcs:["foo.c"],
474			static_libs: ["libcc_static", "libbar"],
475		}
476		`)
477
478	libbar := ctx.ModuleForTests("libbar", "android_arm64_armv8-a_rlib_rlib-std").Rule("rustc")
479	libcc_shared_rustc := ctx.ModuleForTests("libcc_shared", "android_arm64_armv8-a_shared").Rule("rustc")
480	libcc_shared_ld := ctx.ModuleForTests("libcc_shared", "android_arm64_armv8-a_shared").Rule("ld")
481	libcc_shared_cc := ctx.ModuleForTests("libcc_shared", "android_arm64_armv8-a_shared").Rule("cc")
482	ccbin_rustc := ctx.ModuleForTests("ccBin", "android_arm64_armv8-a").Rule("rustc")
483	ccbin_ld := ctx.ModuleForTests("ccBin", "android_arm64_armv8-a").Rule("ld")
484	ccbin_cc := ctx.ModuleForTests("ccBin", "android_arm64_armv8-a").Rule("cc")
485
486	if !strings.Contains(libbar.Args["rustcFlags"], "crate-type=rlib") {
487		t.Errorf("missing crate-type for static variant, expecting %#v, rustcFlags: %#v", "rlib", libbar.Args["rustcFlags"])
488	}
489
490	// Make sure there's a rustc command, and it's producing a staticlib
491	if !strings.Contains(libcc_shared_rustc.Args["rustcFlags"], "crate-type=staticlib") {
492		t.Errorf("missing crate-type for static variant, expecting %#v, rustcFlags: %#v",
493			"staticlib", libcc_shared_rustc.Args["rustcFlags"])
494	}
495
496	// Make sure the static lib is included in the ld command
497	if !strings.Contains(libcc_shared_ld.Args["libFlags"], "generated_rust_staticlib/librustlibs.a") {
498		t.Errorf("missing generated static library in linker step libFlags %#v, libFlags: %#v",
499			"libcc_shared.generated_rust_staticlib.a", libcc_shared_ld.Args["libFlags"])
500	}
501
502	// Make sure the static lib includes are in the cc command
503	if !strings.Contains(libcc_shared_cc.Args["cFlags"], "-Ibar_includes") {
504		t.Errorf("missing rlibs includes, expecting %#v, cFlags: %#v",
505			"-Ibar_includes", libcc_shared_cc.Args["cFlags"])
506	}
507
508	// Make sure there's a rustc command, and it's producing a staticlib
509	if !strings.Contains(ccbin_rustc.Args["rustcFlags"], "crate-type=staticlib") {
510		t.Errorf("missing crate-type for static variant, expecting %#v, rustcFlags: %#v", "staticlib", ccbin_rustc.Args["rustcFlags"])
511	}
512
513	// Make sure the static lib is included in the cc command
514	if !strings.Contains(ccbin_ld.Args["libFlags"], "generated_rust_staticlib/librustlibs.a") {
515		t.Errorf("missing generated static library in linker step libFlags, expecting %#v, libFlags: %#v",
516			"ccBin.generated_rust_staticlib.a", ccbin_ld.Args["libFlags"])
517	}
518
519	// Make sure the static lib includes are in the ld command
520	if !strings.Contains(ccbin_cc.Args["cFlags"], "-Ibar_includes") {
521		t.Errorf("missing rlibs includes, expecting %#v, cFlags: %#v",
522			"-Ibar_includes", ccbin_cc.Args)
523	}
524
525	// Make sure that direct dependencies and indirect whole static dependencies are
526	// propagating correctly to the generated rlib.
527	if !strings.Contains(ccbin_rustc.Args["libFlags"], "--extern foo=") {
528		t.Errorf("Missing indirect whole_static_lib dependency libfoo when writing generated Rust staticlib: %#v", ccbin_rustc.Args["libFlags"])
529	}
530	if strings.Contains(ccbin_rustc.Args["libFlags"], "--extern buzz=") {
531		t.Errorf("Indirect static_lib dependency libbuzz found when writing generated Rust staticlib: %#v", ccbin_rustc.Args["libFlags"])
532	}
533	if !strings.Contains(ccbin_rustc.Args["libFlags"], "--extern bar=") {
534		t.Errorf("Missing direct dependency libbar when writing generated Rust staticlib: %#v", ccbin_rustc.Args["libFlags"])
535	}
536
537	// Test indirect includes propagation
538	if !strings.Contains(ccbin_cc.Args["cFlags"], "-Ifoo_includes") {
539		t.Errorf("missing rlibs includes, expecting %#v, cFlags: %#v",
540			"-Ifoo_includes", ccbin_cc.Args)
541	}
542}
543
544func assertString(t *testing.T, got, expected string) {
545	t.Helper()
546	if got != expected {
547		t.Errorf("expected %q got %q", expected, got)
548	}
549}
550