github.phpd.cn/thought-machine/please@v12.2.0+incompatible/tools/junit_runner/src/build/please/test/TestMain.java (about) 1 package build.please.test; 2 3 import org.junit.Ignore; 4 import org.junit.Test; 5 import org.junit.runner.Description; 6 import org.junit.runner.JUnitCore; 7 import org.junit.runner.Request; 8 import org.junit.runner.manipulation.Filter; 9 import org.w3c.dom.Document; 10 import org.w3c.dom.Element; 11 12 import java.io.BufferedOutputStream; 13 import java.io.File; 14 import java.io.FileOutputStream; 15 import java.io.IOException; 16 import java.io.OutputStream; 17 import java.io.PrintWriter; 18 import java.io.StringWriter; 19 import java.lang.management.ManagementFactory; 20 import java.lang.reflect.Method; 21 import java.net.MalformedURLException; 22 import java.net.URL; 23 import java.net.URLClassLoader; 24 import java.util.ArrayList; 25 import java.util.HashSet; 26 import java.util.List; 27 import java.util.Set; 28 29 import javax.xml.parsers.DocumentBuilder; 30 import javax.xml.parsers.DocumentBuilderFactory; 31 import javax.xml.transform.OutputKeys; 32 import javax.xml.transform.Transformer; 33 import javax.xml.transform.TransformerFactory; 34 import javax.xml.transform.dom.DOMSource; 35 import javax.xml.transform.stream.StreamResult; 36 37 38 public class TestMain { 39 // Main class for JUnit tests which writes output to a directory. 40 private static int exitCode = 0; 41 private static final String RESULTS_DIR = "test.results"; 42 private static String[] program_args; 43 private static int numTests = 0; 44 45 public static void main(String[] args) throws Exception { 46 String testPackage = System.getProperty("build.please.testpackage"); 47 program_args = args; 48 49 // Ensure this guy matches 50 String tmpDir = System.getenv("TMP_DIR"); 51 if (tmpDir != null) { 52 System.setProperty("java.io.tmpdir", tmpDir); 53 } 54 55 Set<Class> classes = new HashSet<>(); 56 Set<Class> allClasses = findClasses(testPackage); 57 if (allClasses.isEmpty()) { 58 throw new RuntimeException("No test classes found"); 59 } 60 for (Class testClass : allClasses) { 61 if (testClass.getAnnotation(Ignore.class) == null) { 62 for (Method method : testClass.getMethods()) { 63 if (method.getAnnotation(Test.class) != null) { 64 classes.add(testClass); 65 break; 66 } 67 } 68 } 69 } 70 if (System.getenv("COVERAGE") != null) { 71 String prefix = System.getProperty("build.please.instrumentationPrefix", ""); 72 if (!prefix.isEmpty()) { 73 // User indicates that they want additional classes instrumented, which can be 74 // needed in some cases so classloaders match. 75 ClassLoader loader = Thread.currentThread().getContextClassLoader(); 76 ClassFinder finder = new ClassFinder(loader, prefix); 77 allClasses.addAll(finder.getClasses()); 78 } 79 TestCoverage.RunTestClasses(classes, allClasses); 80 } else { 81 for (Class testClass : classes) { 82 runClass(testClass); 83 } 84 } 85 // Note that it isn't a fatal failure if there aren't any tests, unless the user specified 86 // test selectors. 87 if (args.length > 0 && numTests == 0) { 88 throw new RuntimeException("No tests were run."); 89 } 90 System.exit(exitCode); 91 } 92 93 /** 94 * Constructs a URLClassLoader from the current classpath. We can't just get the classloader of the current thread 95 * as its implementation is not guaranteed to be one that allows us to enumerate all the tests available to us. 96 */ 97 private static URLClassLoader getClassLoader() throws MalformedURLException { 98 String classpath = ManagementFactory.getRuntimeMXBean().getClassPath(); 99 String[] classpathEntries = classpath.split(":"); 100 // Convert from String[] to URL[] 101 URL[] classpathUrls = new URL[classpathEntries.length]; 102 for (int i = 0; i < classpathEntries.length; i++) { 103 classpathUrls[i] = new File(classpathEntries[i]).toURI().toURL(); 104 } 105 return new URLClassLoader(classpathUrls); 106 } 107 108 /** 109 * Loads all the available test classes. 110 * This is a little complex because we want to try to avoid scanning every single class on our classpath. 111 * @param testPackage the test package to load from. If empty we'll look for them by filename. 112 */ 113 private static Set<Class> findClasses(String testPackage) throws Exception { 114 ClassLoader loader = getClassLoader(); 115 if (testPackage != null && !testPackage.isEmpty()) { 116 return new ClassFinder(loader, testPackage).getClasses(); 117 } 118 // Need to load by filename. Fortunately we have a list of the files we compiled in please_sourcemap. 119 ClassFinder finder = new ClassFinder(loader); 120 for (String key : TestCoverage.readSourceMap().keySet()) { 121 finder.loadClass(key.replace(".java", ".class")); 122 } 123 return finder.getClasses(); 124 } 125 126 public static void runClass(Class testClass) throws Exception { 127 List<TestResult> results = new ArrayList<>(); 128 JUnitCore core = new JUnitCore(); 129 core.addListener(new TestListener(results)); 130 Request request = Request.aClass(testClass); 131 for (int i = 0; i < program_args.length; ++i) { 132 request = request.filterWith(Filter.matchMethodDescription(testDescription(testClass, program_args[i]))); 133 } 134 core.run(request); 135 writeResults(testClass.getName(), results); 136 numTests += results.size(); 137 } 138 139 // This is again fairly directly lifted from Buck's writing code, because I am in no way 140 // interested in reinventing XML writing code like this if I can possibly avoid it. 141 static void writeResults(String testClassName, List<TestResult> results) throws Exception { 142 DocumentBuilder docBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder(); 143 Document doc = docBuilder.newDocument(); 144 doc.setXmlVersion("1.0"); 145 146 Element root = doc.createElement("testcase"); 147 root.setAttribute("name", testClassName); 148 doc.appendChild(root); 149 150 for (TestResult result : results) { 151 Element test = doc.createElement("test"); 152 153 // name attribute 154 test.setAttribute("name", result.testMethodName); 155 test.setAttribute("classname", result.testClassName); 156 157 // success attribute 158 boolean isSuccess = result.isSuccess(); 159 test.setAttribute("success", Boolean.toString(isSuccess)); 160 if (!isSuccess) { 161 exitCode = 1; 162 } 163 164 // type attribute 165 test.setAttribute("type", result.type.toString()); 166 167 // time attribute 168 long runTime = result.runTime; 169 test.setAttribute("time", String.valueOf(runTime)); 170 171 // Include failure details, if appropriate. 172 Throwable failure = result.failure; 173 if (failure != null) { 174 String message = failure.getMessage(); 175 test.setAttribute("message", message); 176 177 String stacktrace = stackTraceToString(failure); 178 test.setAttribute("stacktrace", stacktrace); 179 } 180 181 // stdout, if non-empty. 182 if (result.stdOut != null) { 183 Element stdOutEl = doc.createElement("stdout"); 184 stdOutEl.appendChild(doc.createTextNode(result.stdOut)); 185 test.appendChild(stdOutEl); 186 } 187 188 // stderr, if non-empty. 189 if (result.stdErr != null) { 190 Element stdErrEl = doc.createElement("stderr"); 191 stdErrEl.appendChild(doc.createTextNode(result.stdErr)); 192 test.appendChild(stdErrEl); 193 } 194 195 root.appendChild(test); 196 } 197 198 File dir = new File(RESULTS_DIR); 199 if (!dir.exists() && !dir.mkdir()) { 200 throw new IOException("Failed to create output directory: " + RESULTS_DIR); 201 } 202 writeXMLDocumentToFile(RESULTS_DIR + "/" + testClassName + ".xml", doc); 203 } 204 205 public static void writeXMLDocumentToFile(String filename, Document doc) throws Exception { 206 // Create an XML transformer that pretty-prints with a 2-space indent. 207 TransformerFactory transformerFactory = TransformerFactory.newInstance(); 208 Transformer trans = transformerFactory.newTransformer(); 209 trans.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "no"); 210 trans.setOutputProperty(OutputKeys.INDENT, "yes"); 211 trans.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2"); 212 213 File outputFile = new File(filename); 214 OutputStream output = new BufferedOutputStream(new FileOutputStream(outputFile)); 215 StreamResult streamResult = new StreamResult(output); 216 DOMSource source = new DOMSource(doc); 217 trans.transform(source, streamResult); 218 output.close(); 219 } 220 221 static String stackTraceToString(Throwable exc) { 222 StringWriter writer = new StringWriter(); 223 exc.printStackTrace(new PrintWriter(writer, true)); 224 return writer.toString(); 225 } 226 227 /** 228 * Returns a JUnit Description matching the given argument string. 229 */ 230 static Description testDescription(Class testClass, String s) { 231 int index = s.lastIndexOf('.'); 232 if (index == -1) { 233 return Description.createTestDescription(testClass, s); 234 } else { 235 return Description.createTestDescription(s.substring(0, index), s.substring(index + 1)); 236 } 237 } 238 }