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