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