blob: 5652e1e6f4b01f73ff7e043aa38b282299ffe3a1 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more contributor license
* agreements. See the NOTICE file distributed with this work for additional information regarding
* copyright ownership. The ASF licenses this file to You 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 org.apache.geode.test.concurrency;
import java.util.Arrays;
import java.util.List;
import junit.framework.AssertionFailedError;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.Description;
import org.junit.runner.notification.Failure;
import org.junit.runner.notification.RunNotifier;
import org.junit.runners.ParentRunner;
import org.junit.runners.model.FrameworkMethod;
import org.junit.runners.model.InitializationError;
import org.apache.geode.test.concurrency.annotation.ConcurrentTestConfig;
import org.apache.geode.test.concurrency.loop.LoopRunner;
/**
* Test runner for tests that involve multiple threads.
*
* All methods annotated with @Test must take a {@link ParallelExecutor} as a parameter, which the
* test can use to invoke code in parallel.
*
* This test run will try to exercise the test method to flush out any concurrent bugs in the
* parallel execution.
*
* All test logic and state *must* be encapsulated in the individual test methods. This is because
* the concurrency testing logic may need to invoke the test body multiple times, possibly in
* parallel.
*
* No guarantees are currently made about logic in methods annotated with @Before, @BeforeClass or
* about the behavior of {@link Rule} for concurrent tests.
*
* Example
*
* <code>
* &#64;RunWith(ConcurrentTestRunner.class)
* public void MyTest {
* &#64;Test
* public void someTestMethod(ParallelExecutor executor) {
* AtomicInteger atomicInteger = new AtomicInteger();
* executor.inParallel(() -> atomicInteger.incrementAndGet());
* executor.inParallel(() -> atomicInteger.incrementAndGet());
* executor.execute();
* assertEquals(2, atomicInteger.get());
* }
* }
* </code>
*
* ConcurrentTestRunner currently executes tests using the {@link LoopRunner} which will run the
* test many times.
*/
public class ConcurrentTestRunner extends ParentRunner<FrameworkMethod> {
/**
* Delegate to actually run the test.
*/
private final Runner runner;
public ConcurrentTestRunner(Class testClass) throws InitializationError {
super(testClass);
ConcurrentTestConfig configuration = getTestClass().getAnnotation(ConcurrentTestConfig.class);
if (configuration == null) {
runner = new LoopRunner();
return;
}
try {
runner = configuration.runner().newInstance();
} catch (InstantiationException | IllegalAccessException e) {
throw new InitializationError(e);
}
}
@Override
protected List<FrameworkMethod> getChildren() {
return getTestClass().getAnnotatedMethods(Test.class);
}
@Override
protected void collectInitializationErrors(List<Throwable> errors) {
super.collectInitializationErrors(errors);
validateTestMethods(getChildren(), errors);
}
private void validateTestMethods(List<FrameworkMethod> methods, List<Throwable> errors) {
for (FrameworkMethod method : methods) {
if (!Arrays.equals(method.getMethod().getParameterTypes(),
new Class[] {ParallelExecutor.class})) {
errors.add(new AssertionFailedError("Incorrect signature on method: " + method
+ ". For a concurrent test, all test methods should take a ParallelExector parameter."));
}
}
}
@Override
protected Description describeChild(FrameworkMethod child) {
return Description.createTestDescription(child.getDeclaringClass(), child.getName());
}
@Override
protected void runChild(FrameworkMethod child, RunNotifier notifier) {
notifier.fireTestStarted(describeChild(child));
try {
List<Throwable> failures = runner.runTestMethod(child.getMethod());
failures.stream()
.forEach(failure -> notifier.fireTestFailure(new Failure(describeChild(child), failure)));
} finally {
notifier.fireTestFinished(describeChild(child));
}
}
}