aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/java_tools/junitrunner/java/com/google/testing/junit/runner/junit4/JUnit4Runner.java
blob: 10447a7b70dee8b031625b47bd30e8aed036a699 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
// 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.common.annotations.VisibleForTesting;
import com.google.common.base.Supplier;
import com.google.common.io.Files;
import com.google.testing.junit.junit4.runner.SuiteTrimmingFilter;
import com.google.testing.junit.runner.internal.Stdout;
import com.google.testing.junit.runner.model.TestSuiteModel;
import com.google.testing.junit.runner.util.GoogleTestSecurityManager;

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;

import java.io.File;
import java.io.IOException;
import java.io.PrintStream;
import java.util.Set;

import javax.annotation.Nullable;
import javax.inject.Inject;

/**
 * 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 {
        Files.write(new byte[0], file);
      } 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();
  }
}