1 // Copyright 2014 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.JSONException; 8 import org.json.JSONObject; 9 import org.junit.runner.Computer; 10 import org.junit.runner.JUnitCore; 11 import org.junit.runner.Request; 12 import org.junit.runner.Result; 13 import org.junit.runner.RunWith; 14 15 import java.io.File; 16 import java.io.FileNotFoundException; 17 import java.io.IOException; 18 import java.nio.file.Files; 19 import java.nio.file.Paths; 20 import java.util.ArrayList; 21 import java.util.Enumeration; 22 import java.util.List; 23 import java.util.jar.JarEntry; 24 import java.util.jar.JarFile; 25 import java.util.regex.Pattern; 26 27 /** 28 * Runs tests based on JUnit from the classpath on the host JVM based on the 29 * provided filter configurations. 30 */ 31 public final class JunitTestMain { 32 private static final int CLASS_SUFFIX_LEN = ".class".length(); 33 private static final Pattern COLON = Pattern.compile(":"); 34 private static final Pattern FORWARD_SLASH = Pattern.compile("/"); 35 JunitTestMain()36 private JunitTestMain() {} 37 38 /** Finds all test classes on the class path annotated with RunWith. */ findClassesFromClasspath()39 public static Class[] findClassesFromClasspath() { 40 String[] jarPaths = COLON.split(System.getProperty("java.class.path")); 41 List<Class> classes = new ArrayList<Class>(); 42 for (String jp : jarPaths) { 43 // Do not look at android.jar. 44 if (jp.contains("third_party/android_sdk")) { 45 continue; 46 } 47 try { 48 JarFile jf = new JarFile(jp); 49 for (Enumeration<JarEntry> eje = jf.entries(); eje.hasMoreElements(); ) { 50 JarEntry je = eje.nextElement(); 51 String cn = je.getName(); 52 // Skip classes in common libraries. 53 if (cn.startsWith("androidx.") || cn.startsWith("junit")) { 54 continue; 55 } 56 // Skip nested classes and classes that do not end with "Test". 57 // That tests end with "Test" is enforced by TestClassNameCheck ErrorProne 58 // check. 59 if (cn.contains("$") || !cn.endsWith("Test.class")) { 60 continue; 61 } 62 cn = cn.substring(0, cn.length() - CLASS_SUFFIX_LEN); 63 cn = FORWARD_SLASH.matcher(cn).replaceAll("."); 64 Class<?> c = classOrNull(cn); 65 if (c != null && c.isAnnotationPresent(RunWith.class)) { 66 classes.add(c); 67 } 68 } 69 jf.close(); 70 } catch (IOException e) { 71 System.err.println("Error while reading classes from " + jp); 72 } 73 } 74 return classes.toArray(new Class[0]); 75 } 76 classOrNull(String className)77 private static Class<?> classOrNull(String className) { 78 try { 79 // Do not initialize classes (clinit) yet, Android methods are all 80 // stubs until robolectric loads the real implementations. 81 return Class.forName( 82 className, /* initialize= */ false, JunitTestMain.class.getClassLoader()); 83 } catch (ClassNotFoundException e) { 84 System.err.println("Class not found: " + className); 85 } catch (NoClassDefFoundError e) { 86 System.err.println("Class definition not found: " + className); 87 } catch (Exception e) { 88 System.err.println("Other exception while reading class: " + className); 89 } 90 return null; 91 } 92 listTestMain(JunitTestArgParser parser)93 private static Result listTestMain(JunitTestArgParser parser) 94 throws FileNotFoundException, JSONException { 95 JUnitCore core = new JUnitCore(); 96 TestListComputer computer = new TestListComputer(); 97 Class[] classes = findClassesFromClasspath(); 98 Request testRequest = Request.classes(computer, classes); 99 for (String packageFilter : parser.mPackageFilters) { 100 testRequest = testRequest.filterWith(new PackageFilter(packageFilter)); 101 } 102 for (Class<?> runnerFilter : parser.mRunnerFilters) { 103 testRequest = testRequest.filterWith(new RunnerFilter(runnerFilter)); 104 } 105 for (String gtestFilter : parser.mGtestFilters) { 106 testRequest = testRequest.filterWith(new GtestFilter(gtestFilter)); 107 } 108 Result ret = core.run(testRequest); 109 computer.writeJson(new File(parser.mJsonConfig)); 110 return ret; 111 } 112 runTestsMain(JunitTestArgParser parser)113 private static Result runTestsMain(JunitTestArgParser parser) throws Exception { 114 String data = new String(Files.readAllBytes(Paths.get(parser.mJsonConfig))); 115 JSONObject jsonConfig = new JSONObject(data); 116 ChromiumAndroidConfigurer.setJsonConfig(jsonConfig); 117 Class[] classes = ConfigFilter.classesFromConfig(jsonConfig); 118 119 JUnitCore core = new JUnitCore(); 120 GtestLogger gtestLogger = new GtestLogger(System.out); 121 core.addListener(new GtestListener(gtestLogger)); 122 JsonLogger jsonLogger = new JsonLogger(new File(parser.mJsonOutput)); 123 core.addListener(new JsonListener(jsonLogger)); 124 Computer computer = new GtestComputer(gtestLogger); 125 126 Request testRequest = 127 Request.classes(computer, classes).filterWith(new ConfigFilter(jsonConfig)); 128 return core.run(testRequest); 129 } 130 main(String[] args)131 public static void main(String[] args) throws Exception { 132 // Causes test names to have the sdk version as a [suffix]. 133 // This enables sharding by SDK version. 134 System.setProperty("robolectric.alwaysIncludeVariantMarkersInTestName", "true"); 135 136 JunitTestArgParser parser = JunitTestArgParser.parse(args); 137 Result r = parser.mListTests ? listTestMain(parser) : runTestsMain(parser); 138 System.exit(r.wasSuccessful() ? 0 : 1); 139 } 140 } 141