• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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	PrepareForTestWithRustIncludeVndk,
41	android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
42		variables.DeviceVndkVersion = StringPtr("current")
43		variables.ProductVndkVersion = StringPtr("current")
44		variables.Platform_vndk_version = StringPtr("29")
45	}),
46)
47
48var rustMockedFiles = android.MockFS{
49	"foo.rs":                       nil,
50	"foo.c":                        nil,
51	"src/bar.rs":                   nil,
52	"src/any.h":                    nil,
53	"c_includes/c_header.h":        nil,
54	"rust_includes/rust_headers.h": nil,
55	"proto.proto":                  nil,
56	"proto/buf.proto":              nil,
57	"buf.proto":                    nil,
58	"foo.proto":                    nil,
59	"liby.so":                      nil,
60	"libz.so":                      nil,
61	"data.txt":                     nil,
62	"liblog.map.txt":               nil,
63}
64
65// testRust returns a TestContext in which a basic environment has been setup.
66// This environment contains a few mocked files. See rustMockedFiles for the list of these files.
67func testRust(t *testing.T, bp string) *android.TestContext {
68	skipTestIfOsNotSupported(t)
69	result := android.GroupFixturePreparers(
70		prepareForRustTest,
71		rustMockedFiles.AddToFixture(),
72	).
73		RunTestWithBp(t, bp)
74	return result.TestContext
75}
76
77func testRustVndk(t *testing.T, bp string) *android.TestContext {
78	return testRustVndkFs(t, bp, rustMockedFiles)
79}
80
81const (
82	sharedVendorVariant   = "android_vendor.29_arm64_armv8-a_shared"
83	rlibVendorVariant     = "android_vendor.29_arm64_armv8-a_rlib_rlib-std"
84	sharedRecoveryVariant = "android_recovery_arm64_armv8-a_shared"
85	rlibRecoveryVariant   = "android_recovery_arm64_armv8-a_rlib_rlib-std"
86)
87
88func testRustVndkFs(t *testing.T, bp string, fs android.MockFS) *android.TestContext {
89	return testRustVndkFsVersions(t, bp, fs, "current", "current", "29")
90}
91
92func testRustVndkFsVersions(t *testing.T, bp string, fs android.MockFS, device_version, product_version, vndk_version string) *android.TestContext {
93	skipTestIfOsNotSupported(t)
94	result := android.GroupFixturePreparers(
95		prepareForRustTest,
96		fs.AddToFixture(),
97		android.FixtureModifyProductVariables(
98			func(variables android.FixtureProductVariables) {
99				variables.DeviceVndkVersion = StringPtr(device_version)
100				variables.ProductVndkVersion = StringPtr(product_version)
101				variables.Platform_vndk_version = StringPtr(vndk_version)
102			},
103		),
104	).RunTestWithBp(t, bp)
105	return result.TestContext
106}
107
108func testRustRecoveryFsVersions(t *testing.T, bp string, fs android.MockFS, device_version, vndk_version, recovery_version string) *android.TestContext {
109	skipTestIfOsNotSupported(t)
110	result := android.GroupFixturePreparers(
111		prepareForRustTest,
112		fs.AddToFixture(),
113		android.FixtureModifyProductVariables(
114			func(variables android.FixtureProductVariables) {
115				variables.DeviceVndkVersion = StringPtr(device_version)
116				variables.RecoverySnapshotVersion = StringPtr(recovery_version)
117				variables.Platform_vndk_version = StringPtr(vndk_version)
118			},
119		),
120	).RunTestWithBp(t, bp)
121	return result.TestContext
122}
123
124// testRustCov returns a TestContext in which a basic environment has been
125// setup. This environment explicitly enables coverage.
126func testRustCov(t *testing.T, bp string) *android.TestContext {
127	skipTestIfOsNotSupported(t)
128	result := android.GroupFixturePreparers(
129		prepareForRustTest,
130		rustMockedFiles.AddToFixture(),
131		android.FixtureModifyProductVariables(
132			func(variables android.FixtureProductVariables) {
133				variables.ClangCoverage = proptools.BoolPtr(true)
134				variables.Native_coverage = proptools.BoolPtr(true)
135				variables.NativeCoveragePaths = []string{"*"}
136			},
137		),
138	).RunTestWithBp(t, bp)
139	return result.TestContext
140}
141
142// testRustError ensures that at least one error was raised and its value
143// matches the pattern provided. The error can be either in the parsing of the
144// Blueprint or when generating the build actions.
145func testRustError(t *testing.T, pattern string, bp string) {
146	skipTestIfOsNotSupported(t)
147	android.GroupFixturePreparers(
148		prepareForRustTest,
149		rustMockedFiles.AddToFixture(),
150	).
151		ExtendWithErrorHandler(android.FixtureExpectsAtLeastOneErrorMatchingPattern(pattern)).
152		RunTestWithBp(t, bp)
153}
154
155// testRustVndkError is similar to testRustError, but can be used to test VNDK-related errors.
156func testRustVndkError(t *testing.T, pattern string, bp string) {
157	testRustVndkFsError(t, pattern, bp, rustMockedFiles)
158}
159
160func testRustVndkFsError(t *testing.T, pattern string, bp string, fs android.MockFS) {
161	skipTestIfOsNotSupported(t)
162	android.GroupFixturePreparers(
163		prepareForRustTest,
164		fs.AddToFixture(),
165		android.FixtureModifyProductVariables(
166			func(variables android.FixtureProductVariables) {
167				variables.DeviceVndkVersion = StringPtr("current")
168				variables.ProductVndkVersion = StringPtr("current")
169				variables.Platform_vndk_version = StringPtr("VER")
170			},
171		),
172	).
173		ExtendWithErrorHandler(android.FixtureExpectsAtLeastOneErrorMatchingPattern(pattern)).
174		RunTestWithBp(t, bp)
175}
176
177// testRustCtx is used to build a particular test environment. Unless your
178// tests requires a specific setup, prefer the wrapping functions: testRust,
179// testRustCov or testRustError.
180type testRustCtx struct {
181	bp     string
182	fs     map[string][]byte
183	env    map[string]string
184	config *android.Config
185}
186
187func skipTestIfOsNotSupported(t *testing.T) {
188	// TODO (b/140435149)
189	if runtime.GOOS != "linux" {
190		t.Skip("Rust Soong tests can only be run on Linux hosts currently")
191	}
192}
193
194// Test that we can extract the link path from a lib path.
195func TestLinkPathFromFilePath(t *testing.T) {
196	barPath := android.PathForTesting("out/soong/.intermediates/external/libbar/libbar/linux_glibc_x86_64_shared/libbar.so")
197	libName := linkPathFromFilePath(barPath)
198	expectedResult := "out/soong/.intermediates/external/libbar/libbar/linux_glibc_x86_64_shared/"
199
200	if libName != expectedResult {
201		t.Errorf("libNameFromFilePath returned the wrong name; expected '%#v', got '%#v'", expectedResult, libName)
202	}
203}
204
205// Test to make sure dependencies are being picked up correctly.
206func TestDepsTracking(t *testing.T) {
207	ctx := testRust(t, `
208		rust_ffi_host_static {
209			name: "libstatic",
210			srcs: ["foo.rs"],
211			crate_name: "static",
212		}
213		rust_ffi_host_static {
214			name: "libwholestatic",
215			srcs: ["foo.rs"],
216			crate_name: "wholestatic",
217		}
218		rust_ffi_host_shared {
219			name: "libshared",
220			srcs: ["foo.rs"],
221			crate_name: "shared",
222		}
223		rust_library_host_dylib {
224			name: "libdylib",
225			srcs: ["foo.rs"],
226			crate_name: "dylib",
227		}
228		rust_library_host_rlib {
229			name: "librlib",
230			srcs: ["foo.rs"],
231			crate_name: "rlib",
232			static_libs: ["libstatic"],
233			whole_static_libs: ["libwholestatic"],
234		}
235		rust_proc_macro {
236			name: "libpm",
237			srcs: ["foo.rs"],
238			crate_name: "pm",
239		}
240		rust_binary_host {
241			name: "fizz-buzz",
242			dylibs: ["libdylib"],
243			rlibs: ["librlib"],
244			proc_macros: ["libpm"],
245			static_libs: ["libstatic"],
246			shared_libs: ["libshared"],
247			srcs: ["foo.rs"],
248		}
249	`)
250	module := ctx.ModuleForTests("fizz-buzz", "linux_glibc_x86_64").Module().(*Module)
251	rustc := ctx.ModuleForTests("librlib", "linux_glibc_x86_64_rlib_rlib-std").Rule("rustc")
252
253	// Since dependencies are added to AndroidMk* properties, we can check these to see if they've been picked up.
254	if !android.InList("libdylib", module.Properties.AndroidMkDylibs) {
255		t.Errorf("Dylib dependency not detected (dependency missing from AndroidMkDylibs)")
256	}
257
258	if !android.InList("librlib.rlib-std", module.Properties.AndroidMkRlibs) {
259		t.Errorf("Rlib dependency not detected (dependency missing from AndroidMkRlibs)")
260	}
261
262	if !android.InList("libpm", module.Properties.AndroidMkProcMacroLibs) {
263		t.Errorf("Proc_macro dependency not detected (dependency missing from AndroidMkProcMacroLibs)")
264	}
265
266	if !android.InList("libshared", module.Properties.AndroidMkSharedLibs) {
267		t.Errorf("Shared library dependency not detected (dependency missing from AndroidMkSharedLibs)")
268	}
269
270	if !android.InList("libstatic", module.Properties.AndroidMkStaticLibs) {
271		t.Errorf("Static library dependency not detected (dependency missing from AndroidMkStaticLibs)")
272	}
273
274	if !strings.Contains(rustc.Args["rustcFlags"], "-lstatic=wholestatic") {
275		t.Errorf("-lstatic flag not being passed to rustc for static library %#v", rustc.Args["rustcFlags"])
276	}
277
278}
279
280func TestSourceProviderDeps(t *testing.T) {
281	ctx := testRust(t, `
282		rust_binary {
283			name: "fizz-buzz-dep",
284			srcs: [
285				"foo.rs",
286				":my_generator",
287				":libbindings",
288			],
289			rlibs: ["libbindings"],
290		}
291		rust_proc_macro {
292			name: "libprocmacro",
293			srcs: [
294				"foo.rs",
295				":my_generator",
296				":libbindings",
297			],
298			rlibs: ["libbindings"],
299			crate_name: "procmacro",
300		}
301		rust_library {
302			name: "libfoo",
303			srcs: [
304				"foo.rs",
305				":my_generator",
306				":libbindings",
307			],
308			rlibs: ["libbindings"],
309			crate_name: "foo",
310		}
311		genrule {
312			name: "my_generator",
313			tools: ["any_rust_binary"],
314			cmd: "$(location) -o $(out) $(in)",
315			srcs: ["src/any.h"],
316			out: ["src/any.rs"],
317		}
318		rust_binary_host {
319			name: "any_rust_binary",
320			srcs: [
321				"foo.rs",
322			],
323		}
324		rust_bindgen {
325			name: "libbindings",
326			crate_name: "bindings",
327			source_stem: "bindings",
328			host_supported: true,
329			wrapper_src: "src/any.h",
330        }
331	`)
332
333	libfoo := ctx.ModuleForTests("libfoo", "android_arm64_armv8-a_rlib_dylib-std").Rule("rustc")
334	if !android.SuffixInList(libfoo.Implicits.Strings(), "/out/bindings.rs") {
335		t.Errorf("rust_bindgen generated source not included as implicit input for libfoo; Implicits %#v", libfoo.Implicits.Strings())
336	}
337	if !android.SuffixInList(libfoo.Implicits.Strings(), "/out/any.rs") {
338		t.Errorf("genrule generated source not included as implicit input for libfoo; Implicits %#v", libfoo.Implicits.Strings())
339	}
340
341	fizzBuzz := ctx.ModuleForTests("fizz-buzz-dep", "android_arm64_armv8-a").Rule("rustc")
342	if !android.SuffixInList(fizzBuzz.Implicits.Strings(), "/out/bindings.rs") {
343		t.Errorf("rust_bindgen generated source not included as implicit input for fizz-buzz-dep; Implicits %#v", libfoo.Implicits.Strings())
344	}
345	if !android.SuffixInList(fizzBuzz.Implicits.Strings(), "/out/any.rs") {
346		t.Errorf("genrule generated source not included as implicit input for fizz-buzz-dep; Implicits %#v", libfoo.Implicits.Strings())
347	}
348
349	libprocmacro := ctx.ModuleForTests("libprocmacro", "linux_glibc_x86_64").Rule("rustc")
350	if !android.SuffixInList(libprocmacro.Implicits.Strings(), "/out/bindings.rs") {
351		t.Errorf("rust_bindgen generated source not included as implicit input for libprocmacro; Implicits %#v", libfoo.Implicits.Strings())
352	}
353	if !android.SuffixInList(libprocmacro.Implicits.Strings(), "/out/any.rs") {
354		t.Errorf("genrule generated source not included as implicit input for libprocmacro; Implicits %#v", libfoo.Implicits.Strings())
355	}
356
357	// Check that our bindings are picked up as crate dependencies as well
358	libfooMod := ctx.ModuleForTests("libfoo", "android_arm64_armv8-a_dylib").Module().(*Module)
359	if !android.InList("libbindings.dylib-std", libfooMod.Properties.AndroidMkRlibs) {
360		t.Errorf("bindgen dependency not detected as a rlib dependency (dependency missing from AndroidMkRlibs)")
361	}
362	fizzBuzzMod := ctx.ModuleForTests("fizz-buzz-dep", "android_arm64_armv8-a").Module().(*Module)
363	if !android.InList("libbindings.dylib-std", fizzBuzzMod.Properties.AndroidMkRlibs) {
364		t.Errorf("bindgen dependency not detected as a rlib dependency (dependency missing from AndroidMkRlibs)")
365	}
366	libprocmacroMod := ctx.ModuleForTests("libprocmacro", "linux_glibc_x86_64").Module().(*Module)
367	if !android.InList("libbindings.rlib-std", libprocmacroMod.Properties.AndroidMkRlibs) {
368		t.Errorf("bindgen dependency not detected as a rlib dependency (dependency missing from AndroidMkRlibs)")
369	}
370
371}
372
373func TestSourceProviderTargetMismatch(t *testing.T) {
374	// This might error while building the dependency tree or when calling depsToPaths() depending on the lunched
375	// target, which results in two different errors. So don't check the error, just confirm there is one.
376	testRustError(t, ".*", `
377		rust_proc_macro {
378			name: "libprocmacro",
379			srcs: [
380				"foo.rs",
381				":libbindings",
382			],
383			crate_name: "procmacro",
384		}
385		rust_bindgen {
386			name: "libbindings",
387			crate_name: "bindings",
388			source_stem: "bindings",
389			wrapper_src: "src/any.h",
390		}
391	`)
392}
393
394// Test to make sure proc_macros use host variants when building device modules.
395func TestProcMacroDeviceDeps(t *testing.T) {
396	ctx := testRust(t, `
397		rust_library_host_rlib {
398			name: "libbar",
399			srcs: ["foo.rs"],
400			crate_name: "bar",
401		}
402		rust_proc_macro {
403			name: "libpm",
404			rlibs: ["libbar"],
405			srcs: ["foo.rs"],
406			crate_name: "pm",
407		}
408		rust_binary {
409			name: "fizz-buzz",
410			proc_macros: ["libpm"],
411			srcs: ["foo.rs"],
412		}
413	`)
414	rustc := ctx.ModuleForTests("libpm", "linux_glibc_x86_64").Rule("rustc")
415
416	if !strings.Contains(rustc.Args["libFlags"], "libbar/linux_glibc_x86_64") {
417		t.Errorf("Proc_macro is not using host variant of dependent modules.")
418	}
419}
420
421// Test that no_stdlibs suppresses dependencies on rust standard libraries
422func TestNoStdlibs(t *testing.T) {
423	ctx := testRust(t, `
424		rust_binary {
425			name: "fizz-buzz",
426			srcs: ["foo.rs"],
427			no_stdlibs: true,
428		}`)
429	module := ctx.ModuleForTests("fizz-buzz", "android_arm64_armv8-a").Module().(*Module)
430
431	if android.InList("libstd", module.Properties.AndroidMkDylibs) {
432		t.Errorf("no_stdlibs did not suppress dependency on libstd")
433	}
434}
435
436// Test that libraries provide both 32-bit and 64-bit variants.
437func TestMultilib(t *testing.T) {
438	ctx := testRust(t, `
439		rust_library_rlib {
440			name: "libfoo",
441			srcs: ["foo.rs"],
442			crate_name: "foo",
443		}`)
444
445	_ = ctx.ModuleForTests("libfoo", "android_arm64_armv8-a_rlib_dylib-std")
446	_ = ctx.ModuleForTests("libfoo", "android_arm_armv7-a-neon_rlib_dylib-std")
447}
448
449// Test that library size measurements are generated.
450func TestLibrarySizes(t *testing.T) {
451	ctx := testRust(t, `
452		rust_library_dylib {
453			name: "libwaldo",
454			srcs: ["foo.rs"],
455			crate_name: "waldo",
456		}`)
457
458	m := ctx.SingletonForTests("file_metrics")
459	m.Output("unstripped/libwaldo.dylib.so.bloaty.csv")
460	m.Output("libwaldo.dylib.so.bloaty.csv")
461}
462
463func assertString(t *testing.T, got, expected string) {
464	t.Helper()
465	if got != expected {
466		t.Errorf("expected %q got %q", expected, got)
467	}
468}
469