1// Copyright 2019 Google Inc. All rights reserved. 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 java 16 17import ( 18 "fmt" 19 20 "android/soong/android" 21 "android/soong/java/config" 22 "android/soong/tradefed" 23 24 "github.com/google/blueprint" 25 "github.com/google/blueprint/proptools" 26) 27 28func init() { 29 RegisterRobolectricBuildComponents(android.InitRegistrationContext) 30} 31 32type RobolectricRuntimesInfo struct { 33 Runtimes []android.InstallPath 34} 35 36var RobolectricRuntimesInfoProvider = blueprint.NewProvider[RobolectricRuntimesInfo]() 37 38type roboRuntimeOnlyDependencyTag struct { 39 blueprint.BaseDependencyTag 40} 41 42var roboRuntimeOnlyDepTag roboRuntimeOnlyDependencyTag 43 44// Mark this tag so dependencies that use it are excluded from visibility enforcement. 45func (t roboRuntimeOnlyDependencyTag) ExcludeFromVisibilityEnforcement() {} 46 47var _ android.ExcludeFromVisibilityEnforcementTag = roboRuntimeOnlyDepTag 48 49func RegisterRobolectricBuildComponents(ctx android.RegistrationContext) { 50 ctx.RegisterModuleType("android_robolectric_test", RobolectricTestFactory) 51 ctx.RegisterModuleType("android_robolectric_runtimes", robolectricRuntimesFactory) 52} 53 54var robolectricDefaultLibs = []string{ 55 "mockito-robolectric-prebuilt", 56 "truth", 57 // TODO(ccross): this is not needed at link time 58 "junitxml", 59} 60 61const robolectricCurrentLib = "Robolectric_all-target" 62const clearcutJunitLib = "ClearcutJunitListenerAar" 63const robolectricPrebuiltLibPattern = "platform-robolectric-%s-prebuilt" 64 65var ( 66 roboCoverageLibsTag = dependencyTag{name: "roboCoverageLibs"} 67 roboRuntimesTag = dependencyTag{name: "roboRuntimes"} 68) 69 70type robolectricProperties struct { 71 // The name of the android_app module that the tests will run against. 72 Instrumentation_for *string 73 74 // Additional libraries for which coverage data should be generated 75 Coverage_libs []string 76 77 Test_options struct { 78 // Timeout in seconds when running the tests. 79 Timeout *int64 80 81 // Number of shards to use when running the tests. 82 Shards *int64 83 } 84 85 // Use /external/robolectric rather than /external/robolectric-shadows as the version of robolectric 86 // to use. /external/robolectric closely tracks github's master, and will fully replace /external/robolectric-shadows 87 Upstream *bool 88 89 // Use strict mode to limit access of Robolectric API directly. See go/roboStrictMode 90 Strict_mode *bool 91 92 Jni_libs proptools.Configurable[[]string] 93} 94 95type robolectricTest struct { 96 Library 97 98 robolectricProperties robolectricProperties 99 testProperties testProperties 100 101 testConfig android.Path 102 data android.Paths 103 104 forceOSType android.OsType 105 forceArchType android.ArchType 106} 107 108func (r *robolectricTest) TestSuites() []string { 109 return r.testProperties.Test_suites 110} 111 112var _ android.TestSuiteModule = (*robolectricTest)(nil) 113 114func (r *robolectricTest) DepsMutator(ctx android.BottomUpMutatorContext) { 115 r.Library.DepsMutator(ctx) 116 117 if r.robolectricProperties.Instrumentation_for != nil { 118 ctx.AddVariationDependencies(nil, instrumentationForTag, String(r.robolectricProperties.Instrumentation_for)) 119 } else { 120 ctx.PropertyErrorf("instrumentation_for", "missing required instrumented module") 121 } 122 123 ctx.AddVariationDependencies(nil, roboRuntimeOnlyDepTag, clearcutJunitLib) 124 125 if proptools.BoolDefault(r.robolectricProperties.Strict_mode, true) { 126 ctx.AddVariationDependencies(nil, roboRuntimeOnlyDepTag, robolectricCurrentLib) 127 } else { 128 ctx.AddVariationDependencies(nil, staticLibTag, robolectricCurrentLib) 129 } 130 131 ctx.AddVariationDependencies(nil, staticLibTag, robolectricDefaultLibs...) 132 133 ctx.AddVariationDependencies(nil, roboCoverageLibsTag, r.robolectricProperties.Coverage_libs...) 134 135 ctx.AddFarVariationDependencies(ctx.Config().BuildOSCommonTarget.Variations(), 136 roboRuntimesTag, "robolectric-android-all-prebuilts") 137 138 for _, lib := range r.robolectricProperties.Jni_libs.GetOrDefault(ctx, nil) { 139 ctx.AddVariationDependencies(ctx.Config().BuildOSTarget.Variations(), jniLibTag, lib) 140 } 141} 142 143func (r *robolectricTest) GenerateAndroidBuildActions(ctx android.ModuleContext) { 144 r.forceOSType = ctx.Config().BuildOS 145 r.forceArchType = ctx.Config().BuildArch 146 147 var extraTestRunnerOptions []tradefed.Option 148 extraTestRunnerOptions = append(extraTestRunnerOptions, tradefed.Option{Name: "java-flags", Value: "-Drobolectric=true"}) 149 if proptools.BoolDefault(r.robolectricProperties.Strict_mode, true) { 150 extraTestRunnerOptions = append(extraTestRunnerOptions, tradefed.Option{Name: "java-flags", Value: "-Drobolectric.strict.mode=true"}) 151 } 152 153 var extraOptions []tradefed.Option 154 var javaHome = ctx.Config().Getenv("ANDROID_JAVA_HOME") 155 extraOptions = append(extraOptions, tradefed.Option{Name: "java-folder", Value: javaHome}) 156 157 r.testConfig = tradefed.AutoGenTestConfig(ctx, tradefed.AutoGenTestConfigOptions{ 158 TestConfigProp: r.testProperties.Test_config, 159 TestConfigTemplateProp: r.testProperties.Test_config_template, 160 TestSuites: r.testProperties.Test_suites, 161 OptionsForAutogenerated: extraOptions, 162 TestRunnerOptions: extraTestRunnerOptions, 163 AutoGenConfig: r.testProperties.Auto_gen_config, 164 DeviceTemplate: "${RobolectricTestConfigTemplate}", 165 HostTemplate: "${RobolectricTestConfigTemplate}", 166 }) 167 r.data = android.PathsForModuleSrc(ctx, r.testProperties.Data) 168 r.data = append(r.data, android.PathsForModuleSrc(ctx, r.testProperties.Device_common_data)...) 169 r.data = append(r.data, android.PathsForModuleSrc(ctx, r.testProperties.Device_first_data)...) 170 r.data = append(r.data, android.PathsForModuleSrc(ctx, r.testProperties.Device_first_prefer32_data)...) 171 r.data = append(r.data, android.PathsForModuleSrc(ctx, r.testProperties.Host_common_data)...) 172 173 var ok bool 174 var instrumentedApp *JavaInfo 175 var appInfo *AppInfo 176 177 // TODO: this inserts paths to built files into the test, it should really be inserting the contents. 178 instrumented := ctx.GetDirectDepsProxyWithTag(instrumentationForTag) 179 180 if len(instrumented) == 1 { 181 appInfo, ok = android.OtherModuleProvider(ctx, instrumented[0], AppInfoProvider) 182 if !ok { 183 ctx.PropertyErrorf("instrumentation_for", "dependency must be an android_app") 184 } 185 instrumentedApp = android.OtherModuleProviderOrDefault(ctx, instrumented[0], JavaInfoProvider) 186 } else if !ctx.Config().AllowMissingDependencies() { 187 panic(fmt.Errorf("expected exactly 1 instrumented dependency, got %d", len(instrumented))) 188 } 189 190 var resourceApk android.Path 191 var manifest android.Path 192 if appInfo != nil { 193 manifest = appInfo.MergedManifestFile 194 resourceApk = instrumentedApp.OutputFile 195 } 196 197 roboTestConfigJar := android.PathForModuleOut(ctx, "robolectric_samedir", "samedir_config.jar") 198 generateSameDirRoboTestConfigJar(ctx, roboTestConfigJar) 199 200 extraCombinedJars := android.Paths{roboTestConfigJar} 201 202 handleLibDeps := func(dep android.ModuleProxy) { 203 if !android.InList(ctx.OtherModuleName(dep), config.FrameworkLibraries) { 204 if m, ok := android.OtherModuleProvider(ctx, dep, JavaInfoProvider); ok { 205 extraCombinedJars = append(extraCombinedJars, m.ImplementationAndResourcesJars...) 206 } 207 } 208 } 209 210 for _, dep := range ctx.GetDirectDepsProxyWithTag(libTag) { 211 handleLibDeps(dep) 212 } 213 for _, dep := range ctx.GetDirectDepsProxyWithTag(sdkLibTag) { 214 handleLibDeps(dep) 215 } 216 // handle the runtimeOnly tag for strict_mode 217 for _, dep := range ctx.GetDirectDepsProxyWithTag(roboRuntimeOnlyDepTag) { 218 handleLibDeps(dep) 219 } 220 221 if appInfo != nil { 222 extraCombinedJars = append(extraCombinedJars, instrumentedApp.ImplementationAndResourcesJars...) 223 } 224 225 r.stem = proptools.StringDefault(r.overridableProperties.Stem, ctx.ModuleName()) 226 r.classLoaderContexts = r.usesLibrary.classLoaderContextForUsesLibDeps(ctx) 227 r.dexpreopter.disableDexpreopt() 228 javaInfo := r.compile(ctx, nil, nil, nil, extraCombinedJars) 229 230 installPath := android.PathForModuleInstall(ctx, r.BaseModuleName()) 231 var installDeps android.InstallPaths 232 233 for _, data := range r.data { 234 installedData := ctx.InstallFile(installPath, data.Rel(), data) 235 installDeps = append(installDeps, installedData) 236 } 237 238 if manifest != nil { 239 r.data = append(r.data, manifest) 240 installedManifest := ctx.InstallFile(installPath, ctx.ModuleName()+"-AndroidManifest.xml", manifest) 241 installDeps = append(installDeps, installedManifest) 242 } 243 244 if resourceApk != nil { 245 r.data = append(r.data, resourceApk) 246 installedResourceApk := ctx.InstallFile(installPath, ctx.ModuleName()+".apk", resourceApk) 247 installDeps = append(installDeps, installedResourceApk) 248 } 249 250 runtimes := ctx.GetDirectDepProxyWithTag("robolectric-android-all-prebuilts", roboRuntimesTag) 251 for _, runtime := range android.OtherModuleProviderOrDefault(ctx, runtimes, RobolectricRuntimesInfoProvider).Runtimes { 252 installDeps = append(installDeps, runtime) 253 } 254 255 installedConfig := ctx.InstallFile(installPath, ctx.ModuleName()+".config", r.testConfig) 256 installDeps = append(installDeps, installedConfig) 257 258 soInstallPath := installPath.Join(ctx, getLibPath(r.forceArchType)) 259 for _, jniLib := range collectTransitiveJniDeps(ctx) { 260 installJni := ctx.InstallFile(soInstallPath, jniLib.path.Base(), jniLib.path) 261 installDeps = append(installDeps, installJni) 262 } 263 264 r.installFile = ctx.InstallFile(installPath, ctx.ModuleName()+".jar", r.outputFile, installDeps...) 265 266 if javaInfo != nil { 267 setExtraJavaInfo(ctx, r, javaInfo) 268 android.SetProvider(ctx, JavaInfoProvider, javaInfo) 269 } 270 271 moduleInfoJSON := r.javaLibraryModuleInfoJSON(ctx) 272 if _, ok := r.testConfig.(android.WritablePath); ok { 273 moduleInfoJSON.AutoTestConfig = []string{"true"} 274 } 275 if r.testConfig != nil { 276 moduleInfoJSON.TestConfig = append(moduleInfoJSON.TestConfig, r.testConfig.String()) 277 } 278 if len(r.testProperties.Test_suites) > 0 { 279 moduleInfoJSON.CompatibilitySuites = append(moduleInfoJSON.CompatibilitySuites, r.testProperties.Test_suites...) 280 } else { 281 moduleInfoJSON.CompatibilitySuites = append(moduleInfoJSON.CompatibilitySuites, "null-suite") 282 } 283 284 android.SetProvider(ctx, android.TestSuiteInfoProvider, android.TestSuiteInfo{ 285 TestSuites: r.TestSuites(), 286 }) 287 288 android.SetProvider(ctx, android.TestOnlyProviderKey, android.TestModuleInformation{ 289 TestOnly: Bool(r.sourceProperties.Test_only), 290 TopLevelTarget: r.sourceProperties.Top_level_test_target, 291 }) 292} 293 294func generateSameDirRoboTestConfigJar(ctx android.ModuleContext, outputFile android.ModuleOutPath) { 295 rule := android.NewRuleBuilder(pctx, ctx) 296 297 outputDir := outputFile.InSameDir(ctx) 298 configFile := outputDir.Join(ctx, "com/android/tools/test_config.properties") 299 rule.Temporary(configFile) 300 rule.Command().Text("rm -f").Output(outputFile).Output(configFile) 301 rule.Command().Textf("mkdir -p $(dirname %s)", configFile.String()) 302 rule.Command(). 303 Text("("). 304 Textf(`echo "android_merged_manifest=%s-AndroidManifest.xml" &&`, ctx.ModuleName()). 305 Textf(`echo "android_resource_apk=%s.apk"`, ctx.ModuleName()). 306 Text(") >>").Output(configFile) 307 rule.Command(). 308 BuiltTool("soong_zip"). 309 FlagWithArg("-C ", outputDir.String()). 310 FlagWithInput("-f ", configFile). 311 FlagWithOutput("-o ", outputFile) 312 313 rule.Build("generate_test_config_samedir", "generate test_config.properties") 314} 315 316func (r *robolectricTest) AndroidMkEntries() []android.AndroidMkEntries { 317 entriesList := r.Library.AndroidMkEntries() 318 entries := &entriesList[0] 319 entries.ExtraEntries = append(entries.ExtraEntries, 320 func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) { 321 entries.SetBool("LOCAL_UNINSTALLABLE_MODULE", true) 322 entries.AddStrings("LOCAL_COMPATIBILITY_SUITE", "robolectric-tests") 323 if r.testConfig != nil { 324 entries.SetPath("LOCAL_FULL_TEST_CONFIG", r.testConfig) 325 } 326 }) 327 return entriesList 328} 329 330// An android_robolectric_test module compiles tests against the Robolectric framework that can run on the local host 331// instead of on a device. 332func RobolectricTestFactory() android.Module { 333 module := &robolectricTest{} 334 335 module.addHostProperties() 336 module.AddProperties( 337 &module.Module.deviceProperties, 338 &module.robolectricProperties, 339 &module.testProperties) 340 341 module.Module.dexpreopter.isTest = true 342 module.Module.linter.properties.Lint.Test_module_type = proptools.BoolPtr(true) 343 module.Module.sourceProperties.Test_only = proptools.BoolPtr(true) 344 module.Module.sourceProperties.Top_level_test_target = true 345 module.testProperties.Test_suites = []string{"robolectric-tests"} 346 347 InitJavaModule(module, android.DeviceSupported) 348 return module 349} 350 351func (r *robolectricTest) InstallInTestcases() bool { return true } 352func (r *robolectricTest) InstallForceOS() (*android.OsType, *android.ArchType) { 353 return &r.forceOSType, &r.forceArchType 354} 355 356func robolectricRuntimesFactory() android.Module { 357 module := &robolectricRuntimes{} 358 module.AddProperties(&module.props) 359 android.InitAndroidArchModule(module, android.HostSupportedNoCross, android.MultilibCommon) 360 return module 361} 362 363type robolectricRuntimesProperties struct { 364 Jars []string `android:"path"` 365 Lib *string 366} 367 368type robolectricRuntimes struct { 369 android.ModuleBase 370 371 props robolectricRuntimesProperties 372 373 runtimes []android.InstallPath 374 375 forceOSType android.OsType 376 forceArchType android.ArchType 377} 378 379func (r *robolectricRuntimes) TestSuites() []string { 380 return []string{"robolectric-tests"} 381} 382 383var _ android.TestSuiteModule = (*robolectricRuntimes)(nil) 384 385func (r *robolectricRuntimes) DepsMutator(ctx android.BottomUpMutatorContext) { 386 if !ctx.Config().AlwaysUsePrebuiltSdks() && r.props.Lib != nil { 387 ctx.AddVariationDependencies(nil, libTag, String(r.props.Lib)) 388 } 389} 390 391func (r *robolectricRuntimes) GenerateAndroidBuildActions(ctx android.ModuleContext) { 392 if ctx.Target().Os != ctx.Config().BuildOSCommonTarget.Os { 393 return 394 } 395 396 r.forceOSType = ctx.Config().BuildOS 397 r.forceArchType = ctx.Config().BuildArch 398 399 files := android.PathsForModuleSrc(ctx, r.props.Jars) 400 401 androidAllDir := android.PathForModuleInstall(ctx, "android-all") 402 for _, from := range files { 403 installedRuntime := ctx.InstallFile(androidAllDir, from.Base(), from) 404 r.runtimes = append(r.runtimes, installedRuntime) 405 } 406 407 if !ctx.Config().AlwaysUsePrebuiltSdks() && r.props.Lib != nil { 408 runtimeFromSourceModule := ctx.GetDirectDepProxyWithTag(String(r.props.Lib), libTag) 409 if runtimeFromSourceModule == nil { 410 if ctx.Config().AllowMissingDependencies() { 411 ctx.AddMissingDependencies([]string{String(r.props.Lib)}) 412 } else { 413 ctx.PropertyErrorf("lib", "missing dependency %q", String(r.props.Lib)) 414 } 415 return 416 } 417 runtimeFromSourceJar := android.OutputFileForModule(ctx, runtimeFromSourceModule, "") 418 419 // "TREE" name is essential here because it hooks into the "TREE" name in 420 // Robolectric's SdkConfig.java that will always correspond to the NEWEST_SDK 421 // in Robolectric configs. 422 runtimeName := "android-all-current-robolectric-r0.jar" 423 installedRuntime := ctx.InstallFile(androidAllDir, runtimeName, runtimeFromSourceJar) 424 r.runtimes = append(r.runtimes, installedRuntime) 425 } 426 427 android.SetProvider(ctx, RobolectricRuntimesInfoProvider, RobolectricRuntimesInfo{ 428 Runtimes: r.runtimes, 429 }) 430 431 android.SetProvider(ctx, android.TestSuiteInfoProvider, android.TestSuiteInfo{ 432 TestSuites: r.TestSuites(), 433 }) 434} 435 436func (r *robolectricRuntimes) InstallInTestcases() bool { return true } 437func (r *robolectricRuntimes) InstallForceOS() (*android.OsType, *android.ArchType) { 438 return &r.forceOSType, &r.forceArchType 439} 440