/* * Copyright 2012-present Facebook, Inc. * * 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.facebook.buck.testrunner; import com.facebook.buck.util.concurrent.MostExecutors; import java.util.List; import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; import java.util.concurrent.Future; import org.junit.Rule; import org.junit.Test; import org.junit.rules.Timeout; import org.junit.runners.BlockJUnit4ClassRunner; import org.junit.runners.model.FrameworkField; import org.junit.runners.model.FrameworkMethod; import org.junit.runners.model.InitializationError; import org.junit.runners.model.Statement; import org.junit.runners.model.TestClass; /** * JUnit-4-compatible test class runner that supports the concept of a "default timeout." If the * value of {@code defaultTestTimeoutMillis} passed to the constructor is non-zero, then it will be * used in place of {@link Test#timeout()} if {@link Test#timeout()} returns zero. * * <p>The superclass, {@link BlockJUnit4ClassRunner}, was introduced in JUnit 4.5 as a published API * that was designed to be extended. * * <p>This runner also creates Descriptions that allow JUnitRunner to filter which test-methods to * run and should be forced into the test code path whenever test-selectors are in use. */ public class BuckBlockJUnit4ClassRunner extends BlockJUnit4ClassRunner { // We create an ExecutorService based on the implementation of // Executors.newSingleThreadExecutor(). The problem with Executors.newSingleThreadExecutor() is // that it does not let us specify a RejectedExecutionHandler, which we need to ensure that // garbage is not spewed to the user's console if the build fails. private final ThreadLocal<ExecutorService> executor = new ThreadLocal<ExecutorService>() { @Override protected ExecutorService initialValue() { return MostExecutors.newSingleThreadExecutor(getClass().getSimpleName()); } }; private final long defaultTestTimeoutMillis; public BuckBlockJUnit4ClassRunner(Class<?> klass, long defaultTestTimeoutMillis) throws InitializationError { super(klass); this.defaultTestTimeoutMillis = defaultTestTimeoutMillis; } @Override protected Object createTest() throws Exception { // Pushing tests onto threads because the test timeout has been set is Unexpected Behaviour. It // causes things like the SingleThreadGuard in jmock2 to get most upset because the test is // being run on a thread different from the one it was created on. Work around this by creating // the test on the same thread we will be timing it out on. // See https://github.com/junit-team/junit/issues/686 for more context. Callable<Object> maker = () -> BuckBlockJUnit4ClassRunner.super.createTest(); Future<Object> createdTest = executor.get().submit(maker); return createdTest.get(); } private boolean isNeedingCustomTimeout() { return defaultTestTimeoutMillis <= 0 || hasTimeoutRule(getTestClass()); } private long getTimeout(FrameworkMethod method) { // Check to see if the method has declared a timeout on the @Test annotation. If that's present // and set, then let JUnit handle the timeout for us, which (counter-intuitively) means letting // the test run indefinitely. Test annotation = method.getMethod().getAnnotation(Test.class); if (annotation != null) { long timeout = annotation.timeout(); if (timeout != 0) { // 0 represents the default timeout return Long.MAX_VALUE; } } if (!isNeedingCustomTimeout()) { return defaultTestTimeoutMillis; } return Long.MAX_VALUE; } /** * Override the default timeout behavior so that when no timeout is specified in the {@link Test} * annotation, the timeout specified by the constructor will be used (if it has been set). */ @Override protected Statement methodBlock(FrameworkMethod method) { Statement statement = super.methodBlock(method); // If the test class has a Timeout @Rule, then that should supersede the default timeout. The // same applies if it has a timeout value for the @Test annotation. long timeout = getTimeout(method); return new SameThreadFailOnTimeout(executor.get(), timeout, testName(method), statement); } /** * @return {@code true} if the test class has any fields annotated with {@code Rule} whose type is * {@link Timeout}. */ static boolean hasTimeoutRule(TestClass testClass) { // Many protected convenience methods in BlockJUnit4ClassRunner that are available in JUnit 4.11 // such as getTestRules(Object) were not public until // https://github.com/junit-team/junit/commit/8782efa08abf5d47afdc16740678661443706740, // which appears to be JUnit 4.9. Because we allow users to use JUnit 4.7, we need to include a // custom implementation that is backwards compatible to JUnit 4.7. List<FrameworkField> fields = testClass.getAnnotatedFields(Rule.class); for (FrameworkField field : fields) { if (field.getField().getType().equals(Timeout.class)) { return true; } } return false; } /** Override default init error collector so that class without any test methods will pass */ @Override protected void collectInitializationErrors(List<Throwable> errors) { if (!computeTestMethods().isEmpty()) { super.collectInitializationErrors(errors); } } }