blob: 3195615ffc52f6b86f1598d95a6443959d16419a [file] [log] [blame]
Colin Cross0ef08162019-05-01 15:50:51 -07001// 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 "io"
Colin Crossd2d11772019-05-30 11:17:23 -070020 "strconv"
Colin Cross0ef08162019-05-01 15:50:51 -070021 "strings"
22
23 "android/soong/android"
24)
25
26func init() {
27 android.RegisterModuleType("android_robolectric_test", RobolectricTestFactory)
28}
29
30var robolectricDefaultLibs = []string{
31 "robolectric_android-all-stub",
32 "Robolectric_all-target",
33 "mockito-robolectric-prebuilt",
34 "truth-prebuilt",
35}
36
Colin Cross3ec27ec2019-05-01 15:54:05 -070037var (
38 roboCoverageLibsTag = dependencyTag{name: "roboSrcs"}
39)
40
Colin Cross0ef08162019-05-01 15:50:51 -070041type robolectricProperties struct {
42 // The name of the android_app module that the tests will run against.
43 Instrumentation_for *string
44
Colin Cross3ec27ec2019-05-01 15:54:05 -070045 // Additional libraries for which coverage data should be generated
46 Coverage_libs []string
47
Colin Cross0ef08162019-05-01 15:50:51 -070048 Test_options struct {
49 // Timeout in seconds when running the tests.
Colin Cross2f9a7c82019-05-30 11:16:26 -070050 Timeout *int64
Colin Crossd2d11772019-05-30 11:17:23 -070051
52 // Number of shards to use when running the tests.
53 Shards *int64
Colin Cross0ef08162019-05-01 15:50:51 -070054 }
55}
56
57type robolectricTest struct {
58 Library
59
60 robolectricProperties robolectricProperties
61
Colin Crossd2d11772019-05-30 11:17:23 -070062 libs []string
63 tests []string
Colin Cross3ec27ec2019-05-01 15:54:05 -070064
65 roboSrcJar android.Path
Colin Cross0ef08162019-05-01 15:50:51 -070066}
67
68func (r *robolectricTest) DepsMutator(ctx android.BottomUpMutatorContext) {
69 r.Library.DepsMutator(ctx)
70
71 if r.robolectricProperties.Instrumentation_for != nil {
72 ctx.AddVariationDependencies(nil, instrumentationForTag, String(r.robolectricProperties.Instrumentation_for))
73 } else {
74 ctx.PropertyErrorf("instrumentation_for", "missing required instrumented module")
75 }
76
77 ctx.AddVariationDependencies(nil, libTag, robolectricDefaultLibs...)
Colin Cross3ec27ec2019-05-01 15:54:05 -070078
79 ctx.AddVariationDependencies(nil, roboCoverageLibsTag, r.robolectricProperties.Coverage_libs...)
Colin Cross0ef08162019-05-01 15:50:51 -070080}
81
82func (r *robolectricTest) GenerateAndroidBuildActions(ctx android.ModuleContext) {
Colin Cross3ec27ec2019-05-01 15:54:05 -070083 roboTestConfig := android.PathForModuleGen(ctx, "robolectric").
84 Join(ctx, "com/android/tools/test_config.properties")
85
86 // TODO: this inserts paths to built files into the test, it should really be inserting the contents.
87 instrumented := ctx.GetDirectDepsWithTag(instrumentationForTag)
88
89 if len(instrumented) != 1 {
90 panic(fmt.Errorf("expected exactly 1 instrumented dependency, got %d", len(instrumented)))
91 }
92
93 instrumentedApp, ok := instrumented[0].(*AndroidApp)
94 if !ok {
95 ctx.PropertyErrorf("instrumentation_for", "dependency must be an android_app")
96 }
97
98 generateRoboTestConfig(ctx, roboTestConfig, instrumentedApp)
99 r.extraResources = android.Paths{roboTestConfig}
100
Colin Cross0ef08162019-05-01 15:50:51 -0700101 r.Library.GenerateAndroidBuildActions(ctx)
102
Colin Cross3ec27ec2019-05-01 15:54:05 -0700103 roboSrcJar := android.PathForModuleGen(ctx, "robolectric", ctx.ModuleName()+".srcjar")
104 r.generateRoboSrcJar(ctx, roboSrcJar, instrumentedApp)
105 r.roboSrcJar = roboSrcJar
106
Colin Cross0ef08162019-05-01 15:50:51 -0700107 for _, dep := range ctx.GetDirectDepsWithTag(libTag) {
Colin Crosse323f3c2019-09-17 15:34:09 -0700108 r.libs = append(r.libs, dep.(Dependency).BaseModuleName())
Colin Cross0ef08162019-05-01 15:50:51 -0700109 }
Colin Crossd2d11772019-05-30 11:17:23 -0700110
111 // TODO: this could all be removed if tradefed was used as the test runner, it will find everything
112 // annotated as a test and run it.
113 for _, src := range r.compiledJavaSrcs {
114 s := src.Rel()
115 if !strings.HasSuffix(s, "Test.java") {
116 continue
117 } else if strings.HasSuffix(s, "/BaseRobolectricTest.java") {
118 continue
119 } else if strings.HasPrefix(s, "src/") {
120 s = strings.TrimPrefix(s, "src/")
121 }
122 r.tests = append(r.tests, s)
123 }
124}
125
Colin Cross3ec27ec2019-05-01 15:54:05 -0700126func generateRoboTestConfig(ctx android.ModuleContext, outputFile android.WritablePath, instrumentedApp *AndroidApp) {
127 manifest := instrumentedApp.mergedManifestFile
128 resourceApk := instrumentedApp.outputFile
129
130 rule := android.NewRuleBuilder()
131
132 rule.Command().Text("rm -f").Output(outputFile)
133 rule.Command().
134 Textf(`echo "android_merged_manifest=%s" >>`, manifest.String()).Output(outputFile).Text("&&").
135 Textf(`echo "android_resource_apk=%s" >>`, resourceApk.String()).Output(outputFile).
136 // Make it depend on the files to which it points so the test file's timestamp is updated whenever the
137 // contents change
138 Implicit(manifest).
139 Implicit(resourceApk)
140
141 rule.Build(pctx, ctx, "generate_test_config", "generate test_config.properties")
142}
143
144func (r *robolectricTest) generateRoboSrcJar(ctx android.ModuleContext, outputFile android.WritablePath,
145 instrumentedApp *AndroidApp) {
146
147 srcJarArgs := copyOf(instrumentedApp.srcJarArgs)
148 srcJarDeps := append(android.Paths(nil), instrumentedApp.srcJarDeps...)
149
150 for _, m := range ctx.GetDirectDepsWithTag(roboCoverageLibsTag) {
151 if dep, ok := m.(Dependency); ok {
152 depSrcJarArgs, depSrcJarDeps := dep.SrcJarArgs()
153 srcJarArgs = append(srcJarArgs, depSrcJarArgs...)
154 srcJarDeps = append(srcJarDeps, depSrcJarDeps...)
155 }
156 }
157
158 TransformResourcesToJar(ctx, outputFile, srcJarArgs, srcJarDeps)
159}
160
Jiyong Park0b0e1b92019-12-03 13:24:29 +0900161func (r *robolectricTest) AndroidMkEntries() []android.AndroidMkEntries {
162 entriesList := r.Library.AndroidMkEntries()
163 entries := &entriesList[0]
Colin Cross0ef08162019-05-01 15:50:51 -0700164
Jaewoong Jungb0c127c2019-08-29 14:56:03 -0700165 entries.ExtraFooters = []android.AndroidMkExtraFootersFunc{
166 func(w io.Writer, name, prefix, moduleDir string, entries *android.AndroidMkEntries) {
167 if s := r.robolectricProperties.Test_options.Shards; s != nil && *s > 1 {
Colin Cross0a2f7192019-09-23 14:33:09 -0700168 numShards := int(*s)
169 shardSize := (len(r.tests) + numShards - 1) / numShards
170 shards := android.ShardStrings(r.tests, shardSize)
Jaewoong Jungb0c127c2019-08-29 14:56:03 -0700171 for i, shard := range shards {
172 r.writeTestRunner(w, name, "Run"+name+strconv.Itoa(i), shard)
173 }
Colin Cross0ef08162019-05-01 15:50:51 -0700174
Jaewoong Jungb0c127c2019-08-29 14:56:03 -0700175 // TODO: add rules to dist the outputs of the individual tests, or combine them together?
176 fmt.Fprintln(w, "")
177 fmt.Fprintln(w, ".PHONY:", "Run"+name)
178 fmt.Fprintln(w, "Run"+name, ": \\")
179 for i := range shards {
180 fmt.Fprintln(w, " ", "Run"+name+strconv.Itoa(i), "\\")
181 }
182 fmt.Fprintln(w, "")
183 } else {
184 r.writeTestRunner(w, name, "Run"+name, r.tests)
Colin Crossd2d11772019-05-30 11:17:23 -0700185 }
Jaewoong Jungb0c127c2019-08-29 14:56:03 -0700186 },
Colin Cross0ef08162019-05-01 15:50:51 -0700187 }
188
Jiyong Park0b0e1b92019-12-03 13:24:29 +0900189 return entriesList
Colin Cross0ef08162019-05-01 15:50:51 -0700190}
191
Colin Crossd2d11772019-05-30 11:17:23 -0700192func (r *robolectricTest) writeTestRunner(w io.Writer, module, name string, tests []string) {
193 fmt.Fprintln(w, "")
194 fmt.Fprintln(w, "include $(CLEAR_VARS)")
195 fmt.Fprintln(w, "LOCAL_MODULE :=", name)
196 fmt.Fprintln(w, "LOCAL_JAVA_LIBRARIES :=", module)
197 fmt.Fprintln(w, "LOCAL_JAVA_LIBRARIES += ", strings.Join(r.libs, " "))
198 fmt.Fprintln(w, "LOCAL_TEST_PACKAGE :=", String(r.robolectricProperties.Instrumentation_for))
199 fmt.Fprintln(w, "LOCAL_INSTRUMENT_SRCJARS :=", r.roboSrcJar.String())
200 fmt.Fprintln(w, "LOCAL_ROBOTEST_FILES :=", strings.Join(tests, " "))
201 if t := r.robolectricProperties.Test_options.Timeout; t != nil {
202 fmt.Fprintln(w, "LOCAL_ROBOTEST_TIMEOUT :=", *t)
203 }
204 fmt.Fprintln(w, "-include external/robolectric-shadows/run_robotests.mk")
205
206}
207
Colin Cross0ef08162019-05-01 15:50:51 -0700208// An android_robolectric_test module compiles tests against the Robolectric framework that can run on the local host
209// instead of on a device. It also generates a rule with the name of the module prefixed with "Run" that can be
210// used to run the tests. Running the tests with build rule will eventually be deprecated and replaced with atest.
Colin Crossd2d11772019-05-30 11:17:23 -0700211//
212// The test runner considers any file listed in srcs whose name ends with Test.java to be a test class, unless
213// it is named BaseRobolectricTest.java. The path to the each source file must exactly match the package
214// name, or match the package name when the prefix "src/" is removed.
Colin Cross0ef08162019-05-01 15:50:51 -0700215func RobolectricTestFactory() android.Module {
216 module := &robolectricTest{}
217
218 module.AddProperties(
219 &module.Module.properties,
Colin Crosse323f3c2019-09-17 15:34:09 -0700220 &module.Module.deviceProperties,
Colin Cross0ef08162019-05-01 15:50:51 -0700221 &module.Module.protoProperties,
222 &module.robolectricProperties)
223
224 module.Module.dexpreopter.isTest = true
225
226 InitJavaModule(module, android.DeviceSupported)
227 return module
228}