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