com.google.testing.threadtester.InterleavedRunner Maven / Gradle / Ivy
Show all versions of threadweaver Show documentation
/*
* Copyright 2009 Weaver authors
*
* 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.threadtester;
import java.util.ArrayList;
import java.util.List;
/**
* Utility class that tests two interleaved threads. The majority of methods in
* this class are designed to be used with {@link ClassInstrumentation instrumented
* classes}, although there is one version of the {@link
* #interleave(MainRunnable, SecondaryRunnable, List)} method that can be used
* with non-instrumented classes.
*
* @author [email protected] (Alasdair Mackintosh)
*/
/*
* A note on exception handling. There are several places here where we catch
* Throwable. This is normally frowned upon, but in each case we record the
* Throwable, and return it in the RunResult, so that the caller can determine
* what went wrong during the test. We catch Throwable rather than Exception
* becasue assertion failures are subclasses of Error, not of Exception, and it
* is quite likely that a test failure in one of the interleaved threads will
* result in an assertion error.
*/
public class InterleavedRunner {
private InterleavedRunner() {
// All methods are static, so no public constructor
}
/**
* Invokes two runnable instances, interleaving the execution. The main
* runnable instance specifes an instrumented method that will be called. (See
* {@link MainRunnable#getMethod}. If N is the number of executable lines in
* this method, then the main runnable will be executed N times, stopping at a
* different line each time. When the main runnable is stopped, the secondary
* runnable will be executed until it completes, and then the main runnable
* will continue. This allows a test to verify that the main method behaves
* correctly even if another method is called part way through its
* execution. Note that the framework will correctly handle the case where the
* second thread is blocked because of synchronization and/or locks in the
* first method.
*
* Due to the structure of the method being tested, not every executable line
* may be reached. (E.g. conditional or error-handling blocks may not be
* entered.) The InterleavedRunner will attempt to break execution at every
* line, but if a given line is not reached, the runner will continue until
* the end of the test method.
*
* Note that the method being tested must be instrumented. See {@link
* ClassInstrumentation}.
*
* @param main the main runnable
* @param secondary the secondary runnable
*
* @return a RunResult indicating any exceptions thrown by the two runnables.
*
* @throws IllegalArgumentException if the main runnable does not specify a
* valid instrumented class/method.
*/
public static , T> RunResult interleave(
M main, SecondaryRunnable secondary) {
return doInterleave(main, secondary, null, 0);
}
/**
* Invokes two runnable instances, interleaving the execution. This is
* identical to {@link #interleave}, except that the main method will not stop
* until the given code position is reached the given number of times. This
* allows a test to simulate cases when a second thread is invoked after a
* certain condition. (E.g. an event is received after the main method
* registers a listener, but not before.)
*
* Note that this method can only be invoked when the given runnables are
* calling a method in an instrumented class. See {@link ClassInstrumentation}.
*
* @param main the main runnable
* @param secondary the secondary runnable
* @param position the secondary runnable will not start until the first
* runnable has at least reached this position .
* @param posCount the number of time the first runnable must reach the given
* position
*
* @return a RunResult indicating any exceptions thrown by the two runnables.
*
* @throws IllegalArgumentException if the main runnable does not specify a
* valid instrumented class/method, or if the code position does not specify a
* position within the main method.
*/
public static , T> RunResult interleaveAfter(
M main, SecondaryRunnable secondary, CodePosition position, int posCount) {
return doInterleave(main, secondary, position, posCount);
}
private static MethodInstrumentation getMainMethod(ClassInstrumentation clss,
MainRunnable> main) {
try {
if (main.getMethod() != null) {
return clss.getMethod(main.getMethod());
} else if (main.getMethodName() != null) {
return clss.getMethod(main.getMethodName());
} else {
throw new IllegalArgumentException("No main method defined");
}
} catch (NoSuchMethodException e) {
throw new IllegalArgumentException("Invalid main method defined", e);
}
}
private static , T> RunResult doInterleave(
M main, SecondaryRunnable secondary, CodePosition startPosition, int posCount) {
CallLoggerFactory logger = CallLoggerFactory.getFactory();
ClassInstrumentation instrClss = logger.getClassInstrumentation(main.getClassUnderTest());
MethodInstrumentation method = getMainMethod(instrClss, main);
int numLines = method.getNumLines();
for (int i = 0; i < numLines; i++) {
Throwable mainException = null;
Throwable secondaryException = null;
try {
main.initialize();
} catch (Throwable e) {
return new RunResult(e, null);
}
try {
secondary.initialize(main);
} catch (Throwable e) {
return new RunResult(null, e);
}
ObjectInstrumentationImpl instr =
ObjectInstrumentationImpl.getObject(main.getMainObject());
SteppedRunResult result = instr.interleave(main, method, i, secondary, secondary.canBlock(),
startPosition, posCount);
if (result.hadException()) {
return result;
}
try {
main.terminate();
} catch (Throwable e) {
mainException = e;
}
try {
secondary.terminate();
} catch (Throwable e) {
secondaryException = e;
}
if (mainException != null || secondaryException != null) {
return new RunResult(mainException, secondaryException);
}
}
return new RunResult();
}
/**
* Invokes two runnable instances, interleaving the execution. The main
* runnable will be executed once for each CodePosition in the list, stopping
* at each position in turn. When the main runnable is stopped, the secondary
* runnable will be executed until it completes, and then the main runnable
* will continue. This allows a test to verify that the main method behaves
* correctly even if another method is called part way through its
* execution. Note that the framework will correctly handle the case where the
* second thread is blocked because of synchronization and/or locks in the
* first method.
*
* It is the caller's responsibility to ensure that the main runnable will
* reach the given positions when executing.
*
* Note that the method being tested must be instrumented. See {@link
* ClassInstrumentation}.
*
* @param main the main runnable
* @param secondary the secondary runnable
* @param positions a list of code positions where the main runnable will stop
*
* @return a RunResult indicating any exceptions thrown by the two runnables.
*
* @throws IllegalArgumentException if the main runnable does not specify a
* valid instrumented class/method.
*/
public static , T> RunResult interleave(
M main, SecondaryRunnable secondary, List positions) {
List wrappers = new ArrayList(positions.size());
for (CodePosition position : positions) {
wrappers.add(new PositionWrapper(position));
}
return interleaveAtWrappedBreakpoints(main, secondary, wrappers);
}
/**
* Version of {@link #interleave(MainRunnable, SecondaryRunnable, List)} that
* uses ordinary Runnables, and takes a single ReusableBreakpoint. Because there is
* only a single ReusableBreakpoint, the main and secondary Runnables will only be
* executed once, and hence do not need to go through the full lifecycle
* defined by the {@link MainRunnable} class.
*/
public static RunResult interleaveAtBreakpoint(final Runnable mainRunnable,
final Runnable secondaryRunnable, ReusableBreakpoint breakpoint) {
MainRunnableImpl