• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2022 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 package org.chromium.testing.local;
6 
7 import org.json.JSONArray;
8 import org.json.JSONException;
9 import org.json.JSONObject;
10 import org.junit.runner.Computer;
11 import org.junit.runner.Description;
12 import org.junit.runner.Runner;
13 import org.junit.runner.manipulation.Filter;
14 import org.junit.runner.manipulation.Filterable;
15 import org.junit.runner.manipulation.NoTestsRemainException;
16 import org.junit.runner.notification.RunNotifier;
17 import org.junit.runners.model.InitializationError;
18 import org.junit.runners.model.RunnerBuilder;
19 import org.robolectric.annotation.Config;
20 import org.robolectric.annotation.Implements;
21 import org.robolectric.annotation.LooperMode;
22 import org.robolectric.annotation.LooperMode.Mode;
23 import org.robolectric.internal.bytecode.SandboxConfig;
24 
25 import java.io.File;
26 import java.io.FileNotFoundException;
27 import java.io.FileOutputStream;
28 import java.io.PrintStream;
29 import java.util.ArrayList;
30 import java.util.Arrays;
31 import java.util.List;
32 import java.util.Set;
33 import java.util.TreeSet;
34 
35 class TestListComputer extends Computer {
36     private final List<Description> mDescriptions = new ArrayList<>();
37 
computeConfig( Description description, Set<String> instrumentedPackages, Set<String> instrumentedClasses)38     private static String computeConfig(
39             Description description,
40             Set<String> instrumentedPackages,
41             Set<String> instrumentedClasses) {
42         // Cache key for the ClassLoaders is in SandboxFactory.getSdkEnvironment:
43         // https://cs.android.com/android/platform/superproject/main/+/main:external/robolectric-shadows/robolectric/src/main/java/org/robolectric/internal/SandboxFactory.java?q=symbol%3A%5Cborg.robolectric.internal.SandboxFactory.getSdkEnvironment%5Cb%20case%3Ayes
44         List<Class<?>> shadows = new ArrayList<>();
45         LooperMode.Mode looperMode = Mode.PAUSED;
46         ArrayList allAnnotations = new ArrayList();
47         allAnnotations.addAll(Arrays.asList(description.getTestClass().getAnnotations()));
48         allAnnotations.addAll(description.getAnnotations());
49         for (var annotation : allAnnotations) {
50             if (annotation instanceof SandboxConfig) {
51                 shadows.addAll(Arrays.asList(((SandboxConfig) annotation).shadows()));
52             } else if (annotation instanceof Config) {
53                 shadows.addAll(Arrays.asList(((Config) annotation).shadows()));
54                 instrumentedPackages.addAll(
55                         Arrays.asList(((Config) annotation).instrumentedPackages()));
56             } else if (annotation instanceof LooperMode) {
57                 looperMode = ((LooperMode) annotation).value();
58             }
59         }
60         for (var clazz : shadows) {
61             Implements annotation = clazz.getAnnotation(Implements.class);
62             if (annotation != null) {
63                 String className = annotation.className();
64                 if (className.isEmpty()) {
65                     className = annotation.value().getName();
66                 }
67                 instrumentedClasses.add(className);
68             }
69         }
70         String methodName = description.getMethodName();
71         String sdkSuffix = "";
72         // Note: parameterized tests can look like: "FooTest.testFoo[28][6]", where the second [] is
73         // the parameterization.
74         int startIdx = methodName.indexOf('[');
75         if (startIdx != -1) {
76             int endIdx = methodName.indexOf(']', startIdx);
77             if (endIdx != -1) {
78                 sdkSuffix = methodName.substring(startIdx, endIdx + 1);
79             }
80         }
81         return looperMode + sdkSuffix;
82     }
83 
84     private class TestListRunner extends Runner implements Filterable {
85         private final Runner mRunner;
86 
TestListRunner(Runner contained)87         public TestListRunner(Runner contained) {
88             mRunner = contained;
89         }
90 
91         @Override
getDescription()92         public Description getDescription() {
93             return mRunner.getDescription();
94         }
95 
collectDescriptions(Description description)96         private void collectDescriptions(Description description) {
97             if (description.getMethodName() != null) {
98                 mDescriptions.add(description);
99             }
100             for (Description child : description.getChildren()) {
101                 collectDescriptions(child);
102             }
103         }
104 
105         @Override
run(RunNotifier notifier)106         public void run(RunNotifier notifier) {
107             collectDescriptions(mRunner.getDescription());
108         }
109 
110         @Override
filter(Filter filter)111         public void filter(Filter filter) throws NoTestsRemainException {
112             if (mRunner instanceof Filterable) {
113                 ((Filterable) mRunner).filter(filter);
114             }
115         }
116     }
117 
118     @Override
getSuite(final RunnerBuilder builder, Class<?>[] classes)119     public Runner getSuite(final RunnerBuilder builder, Class<?>[] classes)
120             throws InitializationError {
121         return super.getSuite(
122                 new RunnerBuilder() {
123                     @Override
124                     public Runner runnerForClass(Class<?> testClass) throws Throwable {
125                         return new TestListRunner(builder.runnerForClass(testClass));
126                     }
127                 },
128                 classes);
129     }
130 
131     private static JSONObject getOrNewObject(JSONObject parent, String key) throws JSONException {
132         JSONObject ret = parent.optJSONObject(key);
133         if (ret == null) {
134             ret = new JSONObject();
135             parent.put(key, ret);
136         }
137         return ret;
138     }
139 
140     private static JSONArray getOrNewArray(JSONObject parent, String key) throws JSONException {
141         JSONArray ret = parent.optJSONArray(key);
142         if (ret == null) {
143             ret = new JSONArray();
144             parent.put(key, ret);
145         }
146         return ret;
147     }
148 
149     public void writeJson(File outputFile) throws FileNotFoundException, JSONException {
150         var instrumentedPackages = new TreeSet<String>();
151         var instrumentedClasses = new TreeSet<String>();
152         JSONObject root = new JSONObject();
153 
154         JSONObject configsObj = new JSONObject();
155         root.put("configs", configsObj);
156         for (Description d : mDescriptions) {
157             String config = computeConfig(d, instrumentedPackages, instrumentedClasses);
158             JSONObject configObj = getOrNewObject(configsObj, config);
159             JSONArray methodsArr = getOrNewArray(configObj, d.getClassName());
160             methodsArr.put(d.getMethodName());
161         }
162 
163         JSONArray arr = new JSONArray();
164         root.put("instrumentedPackages", arr);
165         for (String s : instrumentedPackages) {
166             arr.put(s);
167         }
168 
169         arr = new JSONArray();
170         root.put("instrumentedClasses", arr);
171         for (String s : instrumentedClasses) {
172             arr.put(s);
173         }
174 
175         try (PrintStream stream = new PrintStream(new FileOutputStream(outputFile))) {
176             stream.print(root);
177         }
178     }
179 }
180