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  }