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