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