| // Copyright 2010 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.junit4; |
| |
| import com.google.testing.junit.junit4.runner.RegExTestCaseFilter; |
| import com.google.testing.junit.junit4.runner.SuiteTrimmingFilter; |
| import com.google.testing.junit.runner.internal.Stdout; |
| import com.google.testing.junit.runner.internal.junit4.CancellableRequestFactory; |
| import com.google.testing.junit.runner.model.TestSuiteModel; |
| import com.google.testing.junit.runner.util.GoogleTestSecurityManager; |
| import com.google.testing.junit.runner.util.Supplier; |
| import java.io.File; |
| import java.io.FileOutputStream; |
| import java.io.IOException; |
| import java.io.PrintStream; |
| import java.util.Set; |
| import javax.annotation.Nullable; |
| import javax.inject.Inject; |
| import org.junit.internal.runners.ErrorReportingRunner; |
| import org.junit.runner.Description; |
| import org.junit.runner.JUnitCore; |
| import org.junit.runner.Request; |
| import org.junit.runner.Result; |
| import org.junit.runner.Runner; |
| import org.junit.runner.manipulation.Filter; |
| import org.junit.runner.manipulation.NoTestsRemainException; |
| import org.junit.runner.notification.RunListener; |
| import org.junit.runner.notification.RunNotifier; |
| |
| /** |
| * Main entry point for running JUnit4 tests.<p> |
| */ |
| public class JUnit4Runner { |
| private final Request request; |
| private final CancellableRequestFactory requestFactory; |
| private final Supplier<TestSuiteModel> modelSupplier; |
| private final PrintStream testRunnerOut; |
| private final JUnit4Config config; |
| private final Set<RunListener> runListeners; |
| private final Set<Initializer> initializers; |
| |
| private GoogleTestSecurityManager googleTestSecurityManager; |
| private SecurityManager previousSecurityManager; |
| |
| /** |
| * Creates a runner. |
| */ |
| @Inject |
| JUnit4Runner( |
| Request request, |
| CancellableRequestFactory requestFactory, |
| Supplier<TestSuiteModel> modelSupplier, |
| @Stdout PrintStream testRunnerOut, |
| JUnit4Config config, |
| Set<RunListener> runListeners, |
| Set<Initializer> initializers) { |
| this.request = request; |
| this.requestFactory = requestFactory; |
| this.modelSupplier = modelSupplier; |
| this.config = config; |
| this.testRunnerOut = testRunnerOut; |
| this.runListeners = runListeners; |
| this.initializers = initializers; |
| } |
| |
| /** |
| * Runs the JUnit4 test. |
| * |
| * @return Result of running the test |
| */ |
| public Result run() { |
| testRunnerOut.println("JUnit4 Test Runner"); |
| checkJUnitRunnerApiVersion(); |
| |
| for (Initializer init : initializers) { |
| init.initialize(); |
| } |
| |
| // Sharding |
| TestSuiteModel model = modelSupplier.get(); |
| Filter shardingFilter = model.getShardingFilter(); |
| |
| Request filteredRequest = applyFilters(request, shardingFilter, |
| config.getTestIncludeFilterRegexp(), |
| config.getTestExcludeFilterRegexp()); |
| |
| JUnitCore core = new JUnitCore(); |
| for (RunListener runListener : runListeners) { |
| core.addListener(runListener); |
| } |
| |
| File exitFile = getExitFile(); |
| exitFileActive(exitFile); |
| try { |
| try { |
| if (config.shouldInstallSecurityManager()) { |
| installSecurityManager(); |
| } |
| Request cancellableRequest = requestFactory.createRequest(filteredRequest); |
| return core.run(cancellableRequest); |
| } finally { |
| disableSecurityManager(); |
| } |
| } finally { |
| exitFileInactive(exitFile); |
| } |
| } |
| |
| // Support for "premature exit files": Tests may write this to communicate |
| // to the runner in case of premature exit. |
| private static File getExitFile() { |
| String exitFile = System.getenv("TEST_PREMATURE_EXIT_FILE"); |
| return exitFile == null ? null : new File(exitFile); |
| } |
| |
| private static void exitFileActive(@Nullable File file) { |
| if (file != null) { |
| try (FileOutputStream outputStream = new FileOutputStream(file, false)) { |
| // Overwrite file content. |
| outputStream.write(new byte[0]); |
| outputStream.close(); |
| } catch (IOException e) { |
| throw new RuntimeException("Could not write exit file at " + file, e); |
| } |
| } |
| } |
| |
| private void exitFileInactive(@Nullable File file) { |
| if (file != null) { |
| try { |
| file.delete(); |
| } catch (Throwable t) { |
| // Just print the stack trace, to avoid masking a real test failure. |
| t.printStackTrace(testRunnerOut); |
| } |
| } |
| } |
| |
| // VisibleForTesting |
| TestSuiteModel getModel() { |
| return modelSupplier.get(); |
| } |
| |
| private static Request applyFilter(Request request, Filter filter) |
| throws NoTestsRemainException { |
| Runner runner = request.getRunner(); |
| new SuiteTrimmingFilter(filter).apply(runner); |
| return Request.runner(runner); |
| } |
| |
| /** |
| * Apply command-line and sharding filters, if appropriate.<p> |
| * |
| * Note that this is carefully written to avoid running into potential |
| * problems with the way runners implement filtering. The JavaDoc for |
| * {@link org.junit.runner.manipulation.Filterable} states that tests that |
| * don't match the filter should be removed, which implies if you apply two |
| * filters, you will always get an intersection of the two. Unfortunately, the |
| * filtering implementation of {@link org.junit.runners.ParentRunner} does not |
| * do this, and instead uses a "last applied filter wins" strategy.<p> |
| * |
| * We work around potential problems by ensuring that if we apply a second |
| * filter, the filter is more restrictive than the first. We also assume that |
| * if filtering fails, the request will have a runner that is a |
| * {@link ErrorReportingRunner}. Luckily, we can cover this with tests to make |
| * sure we don't break if JUnit changes in the future. |
| * |
| * @param request Request to filter |
| * @param shardingFilter Sharding filter to use; {@link Filter#ALL} to not do sharding |
| * @param testIncludeFilterRegexp String denoting a regular expression with which |
| * to filter tests. Only test descriptions that match this regular |
| * expression will be run. If {@code null}, tests will not be filtered. |
| * @param testExcludeFilterRegexp String denoting a regular expression with which |
| * to filter tests. Only test descriptions that do not match this regular |
| * expression will be run. If {@code null}, tests will not be filtered. |
| * @return Filtered request (may be a request that delegates to |
| * {@link ErrorReportingRunner} |
| */ |
| private static Request applyFilters(Request request, Filter shardingFilter, |
| @Nullable String testIncludeFilterRegexp, @Nullable String testExcludeFilterRegexp) { |
| // Allow the user to specify a filter on the command line |
| boolean allowNoTests = false; |
| Filter filter = Filter.ALL; |
| if (testIncludeFilterRegexp != null) { |
| filter = RegExTestCaseFilter.include(testIncludeFilterRegexp); |
| } |
| |
| if (testExcludeFilterRegexp != null) { |
| Filter excludeFilter = RegExTestCaseFilter.exclude(testExcludeFilterRegexp); |
| filter = filter.intersect(excludeFilter); |
| } |
| |
| if (testIncludeFilterRegexp != null || testExcludeFilterRegexp != null) { |
| try { |
| request = applyFilter(request, filter); |
| } catch (NoTestsRemainException e) { |
| return createErrorReportingRequestForFilterError(filter); |
| } |
| |
| /* |
| * If you filter a sharded test to run one test, we don't want all the |
| * shards but one to fail. |
| */ |
| allowNoTests = (shardingFilter != Filter.ALL); |
| } |
| |
| // Sharding |
| if (shardingFilter != Filter.ALL) { |
| filter = filter.intersect(shardingFilter); |
| } |
| |
| if (filter != Filter.ALL) { |
| try { |
| request = applyFilter(request, filter); |
| } catch (NoTestsRemainException e) { |
| if (allowNoTests) { |
| return Request.runner(new NoOpRunner()); |
| } else { |
| return createErrorReportingRequestForFilterError(filter); |
| } |
| } |
| } |
| return request; |
| } |
| |
| @SuppressWarnings({"ThrowableInstanceNeverThrown"}) |
| private static Request createErrorReportingRequestForFilterError(Filter filter) { |
| ErrorReportingRunner runner = new ErrorReportingRunner(Filter.class, new Exception( |
| String.format("No tests found matching %s", filter.describe()))); |
| return Request.runner(runner); |
| } |
| |
| private void checkJUnitRunnerApiVersion() { |
| config.getJUnitRunnerApiVersion(); |
| } |
| |
| private void installSecurityManager() { |
| previousSecurityManager = System.getSecurityManager(); |
| GoogleTestSecurityManager newSecurityManager = new GoogleTestSecurityManager(); |
| System.setSecurityManager(newSecurityManager); |
| |
| // set field after call to setSecurityManager() in case that call fails |
| googleTestSecurityManager = newSecurityManager; |
| } |
| |
| private void disableSecurityManager() { |
| if (googleTestSecurityManager != null) { |
| GoogleTestSecurityManager.uninstallIfInstalled(); |
| System.setSecurityManager(previousSecurityManager); |
| } |
| } |
| |
| static class NoOpRunner extends Runner { |
| @Override |
| public Description getDescription() { |
| return Description.createTestDescription(getClass(), "nothingToDo"); |
| } |
| |
| @Override |
| public void run(RunNotifier notifier) { |
| } |
| } |
| |
| /** |
| * A simple initializer which can be used to provide additional initialization logic in custom |
| * runners. |
| * |
| * <p>Initializers will be run in unspecified order. If an exception is thrown it will not be |
| * deemed recoverable and will cause the runner to error-out. |
| */ |
| public interface Initializer { |
| void initialize(); |
| } |
| } |