| // Copyright 2015 The Bazel Authors. All Rights Reserved. |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| package com.google.testing.junit.runner; |
| |
| import com.google.common.collect.ImmutableList; |
| import com.google.common.util.concurrent.Uninterruptibles; |
| import com.google.inject.AbstractModule; |
| import com.google.inject.Guice; |
| import com.google.inject.Injector; |
| import com.google.inject.Provides; |
| import com.google.inject.Singleton; |
| import com.google.testing.junit.runner.internal.StackTraces; |
| import com.google.testing.junit.runner.internal.Stderr; |
| import com.google.testing.junit.runner.internal.Stdout; |
| import com.google.testing.junit.runner.junit4.JUnit4Runner; |
| import com.google.testing.junit.runner.junit4.JUnit4RunnerModule; |
| import com.google.testing.junit.runner.model.AntXmlResultWriter; |
| import com.google.testing.junit.runner.model.XmlResultWriter; |
| |
| import org.joda.time.DateTime; |
| import org.joda.time.format.DateTimeFormat; |
| import org.joda.time.format.DateTimeFormatter; |
| |
| import java.io.PrintStream; |
| import java.util.List; |
| import java.util.concurrent.TimeUnit; |
| |
| /** |
| * A class to run JUnit tests in a controlled environment. |
| * |
| * <p>Currently sets up a security manager to catch undesirable behaviour; |
| * System.exit. Also has nice command line options - run with "-help" for |
| * details. |
| * |
| * <p>This class traps writes to <code>System.err.println()</code> and |
| * <code>System.out.println()</code> including the output of failed tests in |
| * the error report. |
| * |
| * <p>It also traps SIGTERM signals to make sure that the test report is |
| * written when the signal is closed by the unit test framework for running |
| * over time. |
| */ |
| public class BazelTestRunner { |
| /** |
| * If no arguments are passed on the command line, use this System property to |
| * determine which test suite to run. |
| */ |
| static final String TEST_SUITE_PROPERTY_NAME = "bazel.test_suite"; |
| |
| private BazelTestRunner() { |
| // utility class; should not be instantiated |
| } |
| |
| /** |
| * Takes as arguments the classes or packages to test. |
| * |
| * <p>To help just run one test or method in a suite, the test suite |
| * may be passed in via system properties (-Dbazel.test_suite). |
| * An empty args parameter means to run all tests in the suite. |
| * A non-empty args parameter means to run only the specified tests/methods. |
| * |
| * <p>Return codes: |
| * <ul> |
| * <li>Test runner failure, bad arguments, etc.: exit code of 2</li> |
| * <li>Normal test failure: exit code of 1</li> |
| * <li>All tests pass: exit code of 0</li> |
| * </ul> |
| */ |
| public static void main(String args[]) { |
| PrintStream stderr = System.err; |
| |
| String suiteClassName = System.getProperty(TEST_SUITE_PROPERTY_NAME); |
| |
| if (!checkTestSuiteProperty(suiteClassName)) { |
| System.exit(2); |
| } |
| |
| int exitCode = runTestsInSuite(suiteClassName, args); |
| |
| System.err.printf("%nBazelTestRunner exiting with a return value of %d%n", exitCode); |
| System.err.println("JVM shutdown hooks (if any) will run now."); |
| System.err.println("The JVM will exit once they complete."); |
| System.err.println(); |
| |
| printStackTracesIfJvmExitHangs(stderr); |
| |
| DateTimeFormatter formatter = DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss"); |
| DateTime shutdownTime = new DateTime(); |
| String formattedShutdownTime = formatter.print(shutdownTime); |
| System.err.printf("-- JVM shutdown starting at %s --%n%n", formattedShutdownTime); |
| System.exit(exitCode); |
| } |
| |
| /** |
| * Ensures that the bazel.test_suite in argument is not {@code null} or print error and |
| * explanation. |
| * |
| * @param testSuiteProperty system property to check |
| */ |
| private static boolean checkTestSuiteProperty(String testSuiteProperty) { |
| if (testSuiteProperty == null) { |
| System.err.printf( |
| "Error: The test suite Java system property %s is required but missing.%n", |
| TEST_SUITE_PROPERTY_NAME); |
| System.err.println(); |
| System.err.println("This property is set automatically when running with Bazel like such:"); |
| System.err.printf(" java -D%s=[test-suite-class] %s%n", |
| TEST_SUITE_PROPERTY_NAME, BazelTestRunner.class.getName()); |
| System.err.printf(" java -D%s=[test-suite-class] -jar [deploy-jar]%n", |
| TEST_SUITE_PROPERTY_NAME); |
| System.err.println("E.g.:"); |
| System.err.printf(" java -D%s=org.example.testing.junit.runner.SmallTests %s%n", |
| TEST_SUITE_PROPERTY_NAME, BazelTestRunner.class.getName()); |
| System.err.printf(" java -D%s=org.example.testing.junit.runner.SmallTests " |
| + "-jar SmallTests_deploy.jar%n", |
| TEST_SUITE_PROPERTY_NAME); |
| return false; |
| } |
| return true; |
| } |
| |
| private static int runTestsInSuite(String suiteClassName, String[] args) { |
| Class<?> suite = getTestClass(suiteClassName); |
| |
| if (suite == null) { |
| // No class found corresponding to the system property passed in from Bazel |
| if (args.length == 0 && suiteClassName != null) { |
| System.err.printf("Class not found: [%s]%n", suiteClassName); |
| return 2; |
| } |
| } |
| |
| Injector injector = Guice.createInjector( |
| new BazelTestRunnerModule(suite, ImmutableList.copyOf(args))); |
| |
| JUnit4Runner runner = injector.getInstance(JUnit4Runner.class); |
| return runner.run().wasSuccessful() ? 0 : 1; |
| } |
| |
| private static Class<?> getTestClass(String name) { |
| if (name == null) { |
| return null; |
| } |
| |
| try { |
| return Class.forName(name); |
| } catch (ClassNotFoundException e) { |
| return null; |
| } |
| } |
| |
| /** |
| * Prints out stack traces if the JVM does not exit quickly. This can help detect shutdown hooks |
| * that are preventing the JVM from exiting quickly. |
| * |
| * @param out Print stream to use |
| */ |
| private static void printStackTracesIfJvmExitHangs(final PrintStream out) { |
| Thread thread = new Thread(new Runnable() { |
| @Override |
| public void run() { |
| Uninterruptibles.sleepUninterruptibly(5, TimeUnit.SECONDS); |
| out.println("JVM still up after five seconds. Dumping stack traces for all threads."); |
| StackTraces.printAll(out); |
| } |
| }, "BazelTestRunner: Print stack traces if JVM exit hangs"); |
| |
| thread.setDaemon(true); |
| thread.start(); |
| } |
| |
| static class BazelTestRunnerModule extends AbstractModule { |
| final Class<?> suite; |
| final List<String> args; |
| |
| BazelTestRunnerModule(Class<?> suite, List<String> args) { |
| this.suite = suite; |
| this.args = args; |
| } |
| |
| @Override |
| protected void configure() { |
| install(JUnit4RunnerModule.create(suite, args)); |
| bind(XmlResultWriter.class).to(AntXmlResultWriter.class); |
| } |
| |
| @Provides @Singleton @Stdout |
| PrintStream provideStdoutStream() { |
| return System.out; |
| } |
| |
| @Provides @Singleton @Stderr |
| PrintStream provideStderrStream() { |
| return System.err; |
| } |
| }; |
| } |