/******************************************************************************* * Copyright (c) 2013 Rene Schneider, GEBIT Solutions GmbH and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html *******************************************************************************/ package de.gebit.integrity.runner; import java.io.IOException; import java.io.Serializable; import java.lang.annotation.Annotation; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.net.BindException; import java.util.ArrayList; import java.util.Collections; import java.util.Deque; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.concurrent.Semaphore; import org.eclipse.emf.common.util.BasicDiagnostic; import org.eclipse.emf.common.util.Diagnostic; import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.ecore.util.Diagnostician; import org.eclipse.xtext.common.types.JvmType; import org.eclipse.xtext.nodemodel.ICompositeNode; import org.eclipse.xtext.nodemodel.util.NodeModelUtils; import com.google.inject.Inject; import com.google.inject.Injector; import com.google.inject.Provider; import com.google.inject.Singleton; import de.gebit.integrity.classloading.IntegrityClassLoader; import de.gebit.integrity.comparator.ComparisonResult; import de.gebit.integrity.dsl.Call; import de.gebit.integrity.dsl.Constant; import de.gebit.integrity.dsl.ConstantDefinition; import de.gebit.integrity.dsl.ConstantEntity; import de.gebit.integrity.dsl.ConstantValue; import de.gebit.integrity.dsl.DslFactory; import de.gebit.integrity.dsl.ForkDefinition; import de.gebit.integrity.dsl.ForkParameter; import de.gebit.integrity.dsl.NamedCallResult; import de.gebit.integrity.dsl.NamedResult; import de.gebit.integrity.dsl.ResultTableHeader; import de.gebit.integrity.dsl.StaticValue; import de.gebit.integrity.dsl.Suite; import de.gebit.integrity.dsl.SuiteDefinition; import de.gebit.integrity.dsl.SuiteParameter; import de.gebit.integrity.dsl.SuiteParameterDefinition; import de.gebit.integrity.dsl.SuiteReturn; import de.gebit.integrity.dsl.SuiteReturnDefinition; import de.gebit.integrity.dsl.SuiteStatement; import de.gebit.integrity.dsl.SuiteStatementWithResult; import de.gebit.integrity.dsl.TableTest; import de.gebit.integrity.dsl.TableTestRow; import de.gebit.integrity.dsl.Test; import de.gebit.integrity.dsl.ValueOrEnumValueOrOperation; import de.gebit.integrity.dsl.ValueOrEnumValueOrOperationCollection; import de.gebit.integrity.dsl.Variable; import de.gebit.integrity.dsl.VariableAssignment; import de.gebit.integrity.dsl.VariableDefinition; import de.gebit.integrity.dsl.VariableEntity; import de.gebit.integrity.dsl.VariableOrConstantEntity; import de.gebit.integrity.dsl.VariantDefinition; import de.gebit.integrity.dsl.VisibleComment; import de.gebit.integrity.dsl.VisibleDivider; import de.gebit.integrity.dsl.VisibleMultiLineComment; import de.gebit.integrity.dsl.VisibleMultiLineTitleComment; import de.gebit.integrity.dsl.VisibleSingleLineComment; import de.gebit.integrity.dsl.VisibleSingleLineTitleComment; import de.gebit.integrity.exceptions.AbortExecutionException; import de.gebit.integrity.exceptions.MethodNotFoundException; import de.gebit.integrity.exceptions.ThisShouldNeverHappenException; import de.gebit.integrity.fixtures.ExtendedResultFixture.ExtendedResult; import de.gebit.integrity.fixtures.ExtendedResultFixture.FixtureInvocationResult; import de.gebit.integrity.fixtures.FixtureWrapper; import de.gebit.integrity.forker.ForkerParameter; import de.gebit.integrity.modelsource.ModelSourceExplorer; import de.gebit.integrity.modelsource.ModelSourceInformationElement; import de.gebit.integrity.operations.UnexecutableException; import de.gebit.integrity.parameter.conversion.ConversionContext; import de.gebit.integrity.parameter.conversion.ConversionException; import de.gebit.integrity.parameter.conversion.UnresolvableVariableHandling; import de.gebit.integrity.parameter.conversion.ValueConverter; import de.gebit.integrity.parameter.resolving.ParameterResolver; import de.gebit.integrity.parameter.resolving.TableTestParameterResolveMethod; import de.gebit.integrity.parameter.variables.VariableManager; import de.gebit.integrity.remoting.IntegrityRemotingConstants; import de.gebit.integrity.remoting.entities.setlist.SetList; import de.gebit.integrity.remoting.entities.setlist.SetListEntry; import de.gebit.integrity.remoting.entities.setlist.SetListEntryTypes; import de.gebit.integrity.remoting.server.IntegrityRemotingServer; import de.gebit.integrity.remoting.server.IntegrityRemotingServerListener; import de.gebit.integrity.remoting.transport.Endpoint; import de.gebit.integrity.remoting.transport.enums.BreakpointActions; import de.gebit.integrity.remoting.transport.enums.ExecutionCommands; import de.gebit.integrity.remoting.transport.enums.ExecutionStates; import de.gebit.integrity.remoting.transport.messages.BreakpointUpdateMessage; import de.gebit.integrity.remoting.transport.messages.IntegrityRemotingVersionMessage; import de.gebit.integrity.remoting.transport.messages.SetListBaselineMessage; import de.gebit.integrity.runner.callbacks.CompoundTestRunnerCallback; import de.gebit.integrity.runner.callbacks.TestFormatter; import de.gebit.integrity.runner.callbacks.TestRunnerCallback; import de.gebit.integrity.runner.callbacks.remoting.SetListCallback; import de.gebit.integrity.runner.comparator.ResultComparator; import de.gebit.integrity.runner.exceptions.ValidationException; import de.gebit.integrity.runner.forking.DefaultForker; import de.gebit.integrity.runner.forking.Fork; import de.gebit.integrity.runner.forking.ForkCallback; import de.gebit.integrity.runner.forking.ForkException; import de.gebit.integrity.runner.forking.ForkResultSummary; import de.gebit.integrity.runner.forking.Forker; import de.gebit.integrity.runner.forking.processes.ProcessTerminator; import de.gebit.integrity.runner.modelcheck.ModelChecker; import de.gebit.integrity.runner.operations.RandomNumberOperation; import de.gebit.integrity.runner.results.Result; import de.gebit.integrity.runner.results.SuiteResult; import de.gebit.integrity.runner.results.SuiteSummaryResult; import de.gebit.integrity.runner.results.call.CallResult; import de.gebit.integrity.runner.results.call.CallResult.UpdatedVariable; import de.gebit.integrity.runner.results.test.TestComparisonFailureResult; import de.gebit.integrity.runner.results.test.TestComparisonResult; import de.gebit.integrity.runner.results.test.TestComparisonUndeterminedResult; import de.gebit.integrity.runner.results.test.TestExceptionSubResult; import de.gebit.integrity.runner.results.test.TestExecutedSubResult; import de.gebit.integrity.runner.results.test.TestResult; import de.gebit.integrity.runner.results.test.TestSubResult; import de.gebit.integrity.runner.wrapper.AbortExecutionCauseWrapper; import de.gebit.integrity.utils.IntegrityDSLUtil; import de.gebit.integrity.utils.ParameterUtil; import de.gebit.integrity.utils.ParameterUtil.UnresolvableVariableException; import de.gebit.integrity.wrapper.WrapperFactory; /** * The test runner executes tests. This class is the core of the Integrity runtime system. * * * @author Rene Schneider - initial API and implementation * */ @Singleton public class DefaultTestRunner implements TestRunner { /** * The test model being executed. */ protected TestModel model; /** * The current setlist. */ protected SetList setList; /** * A waiter object used by remoting while waiting for the setlist to be created. */ protected Object setListWaiter = new Object(); /** * A semaphore used for single-stepping tests. */ protected Semaphore executionWaiter = new Semaphore(0); /** * Whether the test runner shall pause before executing the next step. */ protected boolean shallWaitBeforeNextStep; /** * All enabled breakpoints. */ protected Set<Integer> breakpoints = Collections.synchronizedSet(new HashSet<Integer>()); /** * The callback provided by the creator of the {@link TestRunner}. */ protected TestRunnerCallback callback; /** * The setlist callback (used to create/update the setlist). */ protected SetListCallback setListCallback; /** * The currently used callback, that is, the callback that gets directly called during execution. */ protected TestRunnerCallback currentCallback; /** * The current execution phase. */ protected Phase currentPhase; /** * The variable manager, which keeps track of the variable values (local and global). */ @Inject protected VariableManager variableManager; /** * The value converter. */ @Inject protected ValueConverter valueConverter; /** * The parameter resolver. */ @Inject protected ParameterResolver parameterResolver; /** * The wrapper factory. */ @Inject protected WrapperFactory wrapperFactory; /** * The result comparator to use. */ @Inject protected ResultComparator resultComparator; /** * The process watchdog, used to govern other processes started by the test runner. */ @Inject protected ProcessTerminator processTerminator; /** * The Guice injector. */ @Inject protected Injector injector; /** * The model source explorer. */ @Inject protected ModelSourceExplorer modelSourceExplorer; /** * The conversion context provider. */ @Inject protected Provider<ConversionContext> conversionContextProvider; /** * The test formatter. */ @Inject protected TestFormatter testFormatter; /** * The remoting server. */ protected IntegrityRemotingServer remotingServer; /** * The remoting listener, which allows the remoting server to influence test execution. */ protected RemotingListener remotingListener; /** * Maps fork definitions to actual fork instances. */ protected Map<ForkDefinition, Fork> forkMap = new LinkedHashMap<ForkDefinition, Fork>(); /** * Collects all forks that have died. If forks die after they have executed their last statement, this is perfectly * fine, but if forks die earlier, this set is used to detect that erroneous situation. */ protected Set<ForkDefinition> diedForks = new HashSet<ForkDefinition>(); /** * The original command line arguments, as given to the test runner by the test runner creator. */ protected String[] commandLineArguments; /** * This maps fully qualified constant names to parameter values. Defined during initialization. This allows to * define the values for "parameterized" constants. */ protected Map<String, String> parameterizedConstantValues; /** * The model checker is used to validate the test model prior to execution. */ @Inject protected ModelChecker modelChecker; /** * The classloader. */ @Inject protected IntegrityClassLoader classLoader; /** * The classloader used to load model-related classes. */ @Inject protected ClassLoader javaClassLoader; /** * If this JVM instance is executing a fork, the name is stored here. */ protected static final String MY_FORK_NAME = System.getProperty(Forker.SYSPARAM_FORK_NAME); /** * The system property that allows to override the timeout used when connecting to forks. */ protected static final String FORK_CONNECTION_TIMEOUT_PROPERTY = "integrity.fork.timeout"; /** * The default connection timeout, in seconds. */ protected static final int FORK_CONNECTION_TIMEOUT_DEFAULT = 180; protected static int getForkConnectionTimeoutDefault() { return FORK_CONNECTION_TIMEOUT_DEFAULT; } /** * The timeout in milliseconds used for a single connection attempt to a fork. If this timeout is hit, but the total * timeout for connecting is not yet over, another attempt is being started. */ protected static final int FORK_SINGLE_CONNECT_TIMEOUT = 10000; protected static int getForkSingleConnectTimeout() { return FORK_SINGLE_CONNECT_TIMEOUT; } /** * The delay until connection attempts are made to a newly started fork. */ protected static final int FORK_CONNECT_DELAY = 5000; protected static int getForkConnectDelay() { return FORK_CONNECT_DELAY; } /** * The interval in which the forks' execution state is checked on first connect. */ protected static final int FORK_PAUSE_WAIT_INTERVAL = 200; protected static int getForkPauseWaitInterval() { return FORK_PAUSE_WAIT_INTERVAL; } /** * The time to wait for child processes to be killed. */ protected static final int CHILD_PROCESS_KILL_TIMEOUT = 60000; protected int getChildProcessKillTimeout() { return CHILD_PROCESS_KILL_TIMEOUT; } /** * The fork that is currently being executed. */ protected ForkDefinition forkInExecution; /** * The currently executed test variant. */ protected VariantDefinition variantInExecution; /** * The setup suites that have been executed. */ protected Map<ForkDefinition, Set<SuiteDefinition>> setupSuitesExecuted = new HashMap<ForkDefinition, Set<SuiteDefinition>>(); /** * In case of an {@link AbortExecutionException} aborting the test execution, this exception is stored here * (actually, only the message and stack trace string are stored - this is because the data could just as well come * from a fork, in which case an exception can not be transported over the remoting connection). */ protected AbortExecutionCauseWrapper abortExecutionCause; /** * Maps each {@link ForkDefinition} to the suite call and a number that counts any suite invocations. The fork * should die after this number of suites has executed. Since suite invocation numbers are predeterminable in the * dry run and deterministic, this can be used to find out whether a suite during the test run has been the last one * to run on a particular fork. During this real test run, the counter is decremented on each suite invocation on a * fork, so in the end, we know when the fork has finished execution (= counter reaches zero). */ protected Map<ForkDefinition, Integer> lastSuiteForFork = new HashMap<ForkDefinition, Integer>(); @Override public void initialize(TestModel aModel, Map<String, String> someParameterizedConstants, TestRunnerCallback aCallback, Integer aRemotingPort, String aRemotingBindHost, Long aRandomSeed, String[] someCommandLineArguments) throws IOException { model = aModel; callback = aCallback; if (callback instanceof CompoundTestRunnerCallback) { ((CompoundTestRunnerCallback) callback).injectDependencies(injector); } else { injector.injectMembers(callback); } if (aRandomSeed != null) { RandomNumberOperation.seed(aRandomSeed); } else { String tempRandomSeed = System.getProperty(Forker.SYSPARAM_FORK_SEED); if (tempRandomSeed != null) { RandomNumberOperation.seed(Long.parseLong(tempRandomSeed)); } else { RandomNumberOperation.seed(null); } } parameterizedConstantValues = someParameterizedConstants; commandLineArguments = someCommandLineArguments; Integer tempRemotingPort = aRemotingPort; String tempRemotingBindHost = aRemotingBindHost; if (isFork()) { tempRemotingPort = Integer.parseInt(System.getProperty(Forker.SYSPARAM_FORK_REMOTING_PORT)); tempRemotingBindHost = System.getProperty(Forker.SYSPARAM_FORK_REMOTING_HOST, aRemotingBindHost); } if (tempRemotingPort != null) { remotingListener = new RemotingListener(); try { remotingServer = new IntegrityRemotingServer(tempRemotingBindHost, tempRemotingPort, remotingListener, javaClassLoader, isFork()); } catch (BindException exc) { System.err.println("FAILED TO BIND REMOTING SERVER TO " + aRemotingBindHost + ":" + aRemotingPort); throw exc; } } } /** * Shuts down this test runner instance. * * @param anEmptyRemotingOutputQueueFlag * true if the remoting server shall be given time to send all remaining messages to clients while * closing connections */ @Override public void shutdown(boolean anEmptyRemotingOutputQueueFlag) { if (remotingServer != null) { remotingServer.closeAll(anEmptyRemotingOutputQueueFlag); } } @Override public SuiteSummaryResult run(SuiteDefinition aRootSuite, VariantDefinition aVariant, boolean aBlockForRemotingFlag) { Suite tempRootSuiteCall = DslFactory.eINSTANCE.createSuite(); tempRootSuiteCall.setDefinition(aRootSuite); return run(tempRootSuiteCall, aVariant, aBlockForRemotingFlag); } /** * Executes a specific suite call. Internal starting point for test execution. * * @param aRootSuiteCall * the suite call to execute * @param aBlockForRemotingFlag * whether execution should pause before actually starting until execution is resumed via remoting * @return the suite execution result */ protected SuiteSummaryResult run(Suite aRootSuiteCall, VariantDefinition aVariant, boolean aBlockForRemotingFlag) { variantInExecution = aVariant; boolean tempBlockForRemoting = isFork() ? false : aBlockForRemotingFlag; Runtime.getRuntime().addShutdownHook(new Thread("Integrity - Process Terminator Shutdown Hook") { @Override public void run() { processTerminator.killAndWait(getChildProcessKillTimeout()); } }); try { currentPhase = Phase.DRY_RUN; SetList tempSetList = new SetList(); reset(false); setListCallback = new SetListCallback(tempSetList, remotingServer); injector.injectMembers(setListCallback); currentCallback = setListCallback; currentCallback.setDryRun(true); runInternal(aRootSuiteCall); currentCallback.setDryRun(false); synchronized (setListWaiter) { setList = tempSetList; setListWaiter.notify(); } if (remotingServer != null && tempBlockForRemoting) { try { waitForContinue(false); } catch (InterruptedException exc) { if (remotingServer != null) { remotingServer.closeAll(false); } return null; } } tempSetList.rewind(); currentCallback = new CompoundTestRunnerCallback(setListCallback, callback); currentPhase = Phase.TEST_RUN; reset(true); if (isFork()) { // the callback will require the remoting server to be able to push stuff to the master currentCallback.setRemotingServer(remotingServer); // we start out in "dry run" mode if we're a fork currentCallback.setDryRun(true); } return runInternal(aRootSuiteCall); } finally { if (remotingServer != null) { remotingServer.closeAll(true); } processTerminator.killAndWait(getChildProcessKillTimeout()); } } /** * If the exception provided is an {@link AbortExecutionException}, this method performs the necessary steps to * enter "abort" mode locally. * * @param anException */ protected void handlePossibleAbortException(Throwable anException) { if (anException instanceof AbortExecutionException && abortExecutionCause == null) { abortExecutionCause = new AbortExecutionCauseWrapper((AbortExecutionException) anException); currentCallback.onAbortExecution(abortExecutionCause.getMessage(), abortExecutionCause.getStackTrace()); } } /** * Checks whether an {@link AbortExecutionException} has been thrown (either locally or by a fork). This is * determined by looking at {@link #abortExecutionCause}. This method does NOT only check something, but also * ensures that the {@link #setListCallback} is also removed from the current callback hierarchy - a very important * step, since after this method has returned true for the first time, a different execution path than normally is * expected, which would cause the set list callback to throw an inconsistency exception. * * @return */ protected boolean checkForAbortion() { if (abortExecutionCause != null) { // Remove the setlist callback from the callback chain to prevent inconsistencies due to the expected change // in the execution path if (setListCallback != null && (currentCallback instanceof CompoundTestRunnerCallback)) { ((CompoundTestRunnerCallback) currentCallback).removeCallback(setListCallback); setListCallback = null; } return true; } else { return false; } } /** * Initializes the parameterized constants from the {@link #parameterizedConstantValues} map. */ protected void initializeParameterizedConstants() { for (ConstantDefinition tempConstant : model.getConstantDefinitionsInPackages()) { String tempName = IntegrityDSLUtil.getQualifiedVariableEntityName(tempConstant.getName(), false); String tempValue = (parameterizedConstantValues != null) ? parameterizedConstantValues.get(tempName) : null; if (tempConstant.getParameterized() != null) { defineConstant(tempConstant, tempValue, (tempConstant.eContainer() instanceof SuiteDefinition) ? ((SuiteDefinition) tempConstant.eContainer()) : null); } else { if (tempValue != null) { throw new IllegalArgumentException("Constant '" + tempName + "' is not defined as 'parameterized' in test scripts, but parameter data was specified."); } } } } /** * Initializes all constants with the values given in the scripts. */ protected void initializeConstants() { for (ConstantDefinition tempConstantDef : model.getConstantDefinitionsInPackages()) { // Parameterized constants have already been initialized separately, so we don't initialize them here again if (tempConstantDef.getParameterized() == null) { defineConstant(tempConstantDef, null); } } } /** * Initializes all variables with the initial values given in the scripts. */ protected void initializeVariables() { for (VariableDefinition tempVariableDef : model.getVariableDefinitionsInPackages()) { defineVariable(tempVariableDef, null); } } /** * Resets the internal variable state. * * @param aSoftResetFlag * Whether to perform a "soft" reset only. Soft resets are supposed to be performed between the dry run * and the actual test run. */ protected void reset(boolean aSoftResetFlag) { // Soft reset doesn't clear constants variableManager.clear(!aSoftResetFlag); setupSuitesExecuted.clear(); } /** * Actually executes a root suite call. * * @param aRootSuiteCall * the suite call to execute * @return the result */ protected SuiteSummaryResult runInternal(Suite aRootSuiteCall) { if (remotingServer != null && currentPhase == Phase.TEST_RUN) { remotingServer.updateExecutionState(ExecutionStates.RUNNING); } if (currentCallback != null) { currentCallback.onCallbackProcessingStart(); currentCallback.onExecutionStart(model, variantInExecution); currentCallback.onCallbackProcessingEnd(); } initializeParameterizedConstants(); initializeConstants(); initializeVariables(); SuiteSummaryResult tempResult = callSuiteSingle(aRootSuiteCall); if (remotingServer != null && currentPhase == Phase.TEST_RUN) { if (abortExecutionCause != null) { remotingServer.sendAbortMessage(abortExecutionCause.getMessage(), abortExecutionCause.getStackTrace()); } remotingServer.updateExecutionState(ExecutionStates.FINALIZING); } if (currentCallback != null) { currentCallback.onCallbackProcessingStart(); currentCallback.onExecutionFinish(model, tempResult); currentCallback.onCallbackProcessingEnd(); } if (remotingServer != null && currentPhase == Phase.TEST_RUN) { remotingServer.updateExecutionState(ExecutionStates.ENDED); } return tempResult; } /** * Performs a specified suite call. * * @param aSuiteCall * the suite call to execute * @return the suite results (multiple if the suite has an execution multiplier) */ protected List<SuiteSummaryResult> callSuite(Suite aSuiteCall) { int tempCount = 1; if (aSuiteCall.getMultiplier() != null && aSuiteCall.getMultiplier().getCount() != null) { try { tempCount = (Integer) valueConverter.convertValue(Integer.class, aSuiteCall.getMultiplier().getCount(), conversionContextProvider.get() .withUnresolvableVariableHandlingPolicy(UnresolvableVariableHandling.EXCEPTION)); } catch (UnresolvableVariableException exc) { // should never happen, since constant values are not allowed to be variables which still need resolving throw new ThisShouldNeverHappenException(exc); } catch (UnexecutableException exc) { // should never happen, since constant values are not allowed to be unexecuted operations throw new ThisShouldNeverHappenException(exc); } } List<SuiteSummaryResult> tempResults = new ArrayList<SuiteSummaryResult>(); for (int i = 0; i < tempCount; i++) { tempResults.add(callSuiteSingle(aSuiteCall)); if (checkForAbortion()) { break; } } return tempResults; } /** * Performs a specified suite call (doesn't honor the multiplier!). * * @param aSuiteCall * the suite call to execute * @return the suite result */ protected SuiteSummaryResult callSuiteSingle(Suite aSuiteCall) { modelChecker.check(aSuiteCall); boolean tempForkInExecutionOnEntry = forkInExecution != null; if (aSuiteCall.getFork() != null && !tempForkInExecutionOnEntry) { if (!isFork() && forkInExecution != null && aSuiteCall.getFork() != forkInExecution) { throw new UnsupportedOperationException( "It is not supported to execute another fork while inside a fork (" + aSuiteCall.getFork().getName() + " inside " + forkInExecution.getName() + ")."); } forkInExecution = aSuiteCall.getFork(); currentCallback.setForkInExecution(forkInExecution); } if (currentPhase == Phase.TEST_RUN && !tempForkInExecutionOnEntry) { // all of this only has to be done in case of a real test run if (!isFork()) { // we're the master if (forkInExecution != null) { // set the currently executed entry to the suite call entry that will be created next // this signifies that a fork is about to be started executing the highlighted suite if (remotingServer != null) { remotingServer.updateSetList(setList.getEntryListPosition(), new SetListEntry[0]); } // we may need to start a new fork if (!forkMap.containsKey(aSuiteCall.getFork())) { // but first see if this fork has already died once. if true, then the fork has died // prematurely, which means we cannot continue execution at all if (diedForks.contains(aSuiteCall.getFork())) { throw new RuntimeException( "Fork " + aSuiteCall.getFork().getName() + " has died prematurely!"); } try { forkMap.put(aSuiteCall.getFork(), createFork(aSuiteCall)); } catch (ForkException exc) { // forking failed -> cannot continue at all :( kill all other still-living forks and // then exit with a runtime exception throw new RuntimeException(exc); } } // the master will perform all of this in dry run mode currentCallback.setDryRun(true); } } else { if (forkInExecution != null) { // now see if this is a job for us if (IntegrityDSLUtil.getQualifiedForkName(forkInExecution).equals(MY_FORK_NAME)) { // we're a fork, and we are at a point where we're gonna execute some stuff // but we have to wait until our master gives us the 'go'! scheduleWaitBeforeNextStep(); pauseIfRequiredByRemoteClient(true); // and now we leave dry run mode currentCallback.setDryRun(false); } } } } Map<SuiteDefinition, Result> tempSetupResults = new HashMap<SuiteDefinition, Result>(); Map<SuiteDefinition, Result> tempTearDownResults = new HashMap<SuiteDefinition, Result>(); if (currentCallback != null) { currentCallback.onCallbackProcessingStart(); currentCallback.onSuiteStart(aSuiteCall); currentCallback.onCallbackProcessingEnd(); } List<SuiteDefinition> tempSetupSuitesExecuted = executeSetupSuites(aSuiteCall.getDefinition(), tempSetupResults); // Define variables for all the parameters provided to the suite call List<VariableOrConstantEntity> tempVariablesSet = new ArrayList<>(); for (SuiteParameter tempParam : aSuiteCall.getParameters()) { if (tempParam.getValue() instanceof Variable) { Variable tempVariable = (Variable) tempParam.getValue(); defineVariable(tempParam.getName(), variableManager.get(tempVariable), aSuiteCall.getDefinition()); } else { defineVariable(tempParam.getName(), tempParam.getValue(), aSuiteCall.getDefinition()); } tempVariablesSet.add(tempParam.getName()); } // And for all variables supported by the suite that were missing in the call, their respective default is set for (SuiteParameterDefinition tempParamDefinition : aSuiteCall.getDefinition().getParameters()) { if (tempParamDefinition.getDefault() != null && !tempVariablesSet.contains(tempParamDefinition.getName())) { if (tempParamDefinition.getDefault().getValue() instanceof Variable) { Variable tempVariable = (Variable) tempParamDefinition.getDefault().getValue(); defineVariable(tempParamDefinition.getName(), variableManager.get(tempVariable), aSuiteCall.getDefinition()); } else { defineVariable(tempParamDefinition.getName(), tempParamDefinition.getDefault(), aSuiteCall.getDefinition()); } tempVariablesSet.add(tempParamDefinition.getName()); } } // Also define the output variables for (SuiteReturnDefinition tempReturnDefinition : aSuiteCall.getDefinition().getReturn()) { defineVariable(tempReturnDefinition.getName(), null, aSuiteCall.getDefinition()); tempVariablesSet.add(tempReturnDefinition.getName()); } long tempSuiteDuration = System.nanoTime(); Map<SuiteStatementWithResult, List<? extends Result>> tempResults; // It is possible that a setup suite caused an abortion. If that's the case, use an empty suite result. // Fixes issue #112. if (!checkForAbortion()) { tempResults = executeSuite(aSuiteCall.getDefinition()); } else { tempResults = new HashMap<>(); } tempSuiteDuration = System.nanoTime() - tempSuiteDuration; // Fetch all output values into their respective local target variables for (SuiteReturn tempReturn : aSuiteCall.getReturn()) { VariableEntity tempSource = tempReturn.getName().getName(); VariableEntity tempTarget = tempReturn.getTarget().getName(); Object tempValue = variableManager.get(tempSource); // If we are in the dry run phase on a fork, do not send anything to the clients (which in this case is the // master process)! This would otherwise possibly cause ConcurrentModificationExceptions on the master. // See issue #111, which is fixed by this. boolean tempSendToClients = (!isFork() || currentPhase != Phase.DRY_RUN); setVariableValue(tempTarget, tempValue, tempSendToClients); if (currentCallback != null) { currentCallback.onCallbackProcessingStart(); currentCallback.onReturnVariableAssignment(tempReturn, tempSource, tempTarget, aSuiteCall, tempValue); currentCallback.onCallbackProcessingEnd(); } } // Now unset all the suite-scoped variables again (fixes issue #44) for (VariableOrConstantEntity tempVariableSet : tempVariablesSet) { variableManager.unset(tempVariableSet); } if (!checkForAbortion()) { executeTearDownSuites(tempSetupSuitesExecuted, tempTearDownResults); } SuiteSummaryResult tempResult = (!shouldExecuteFixtures()) ? null : new SuiteResult(tempResults, tempSetupResults, tempTearDownResults, tempSuiteDuration); if (currentCallback != null) { currentCallback.onCallbackProcessingStart(); currentCallback.onSuiteFinish(aSuiteCall, tempResult); currentCallback.onCallbackProcessingEnd(); } if (forkInExecution != null && forkInExecution.equals(aSuiteCall.getFork())) { if (currentPhase == Phase.TEST_RUN) { // all of this only has to be done in case of a real test run if (!isFork()) { // we're the master and need to kick off the fork, which then actually executes the stuff we've just // jumped over Fork tempFork = forkMap.get(forkInExecution); // Since we might have been requested to wait before the next step, we need to push that request // forward to the fork, which will actually execute the next step. if (shallWaitBeforeNextStep) { tempFork.getClient().createBreakpoint(null); } ForkResultSummary tempForkResultSummary = null; tempSuiteDuration = System.nanoTime(); if (tempFork != null) { // Count backwards during actual execution. When this counter reaches zero, the fork is // executing its last suite and shall terminate afterwards. Integer tempCounter = lastSuiteForFork.get(aSuiteCall.getFork()) - 1; lastSuiteForFork.put(aSuiteCall.getFork(), tempCounter); tempForkResultSummary = tempFork.executeNextSegment(tempCounter <= 0); } tempSuiteDuration = System.nanoTime() - tempSuiteDuration; if (tempForkResultSummary != null) { tempResult = new SuiteSummaryResult(tempForkResultSummary.getSuccessCount(), tempForkResultSummary.getFailureCount(), tempForkResultSummary.getTestExceptionCount(), tempForkResultSummary.getCallExceptionCount(), tempSuiteDuration); } if (tempFork != null && tempFork.hasAborted()) { // If this happens, an abortion has happened on the fork due to an AbortExecutionException. // TODO make this nicer, it's kind of ugly to create a fake object with null values abortExecutionCause = new AbortExecutionCauseWrapper(null, null); } // and afterwards we'll switch back to real test mode currentCallback.setDryRun(false); } else { // we're a fork and will return to dry run mode currentCallback.setDryRun(true); } } forkInExecution = null; currentCallback.setForkInExecution(null); } if (currentPhase == Phase.DRY_RUN && aSuiteCall.getFork() != null) { // Determining the last suite for all forks is simple: just count suites invoked for each fork in a map // during dry run. At the end of dry run, we know the "number" of the last suite per fork. Integer tempCounter = lastSuiteForFork.get(aSuiteCall.getFork()); if (tempCounter == null) { tempCounter = 0; } lastSuiteForFork.put(aSuiteCall.getFork(), tempCounter + 1); } return tempResult; } /** * Executes the provided suite as a setup suite. This includes executing further nested setup suites (but not * teardown suites, as those will intentionally be executed when the original suite which caused this setup suite to * be executed has finished). * * @param aSuite * the suite to be executed as setup suite * @param aSetupResultMap * the map of setup results to add the result to * @return a list of executed setup suites */ protected List<SuiteDefinition> executeSetupSuites(SuiteDefinition aSuite, Map<SuiteDefinition, Result> aSetupResultMap) { List<SuiteDefinition> tempSetupSuitesExecuted = new ArrayList<SuiteDefinition>(); Set<SuiteDefinition> tempSetupsAlreadyRun = setupSuitesExecuted.get(forkInExecution); if (tempSetupsAlreadyRun == null) { tempSetupsAlreadyRun = new HashSet<SuiteDefinition>(); setupSuitesExecuted.put(forkInExecution, tempSetupsAlreadyRun); } for (SuiteDefinition tempSetupSuite : aSuite.getDependencies()) { if (!tempSetupsAlreadyRun.contains(tempSetupSuite)) { if (currentCallback != null) { currentCallback.onCallbackProcessingStart(); currentCallback.onSetupStart(tempSetupSuite); currentCallback.onCallbackProcessingEnd(); } // This setup suite might have setup suites itself (issue #11) tempSetupSuitesExecuted.addAll(executeSetupSuites(tempSetupSuite, aSetupResultMap)); if (tempSetupsAlreadyRun.contains(tempSetupSuite)) { // A circle has been created. This is a hard error -> abort before the stack inevitably explodes. throw new IllegalStateException( "A setup suite circle has been detected (" + tempSetupSuite.getName() + " called from " + aSuite.getName() + "). Please break the circle!"); } long tempStart = System.nanoTime(); Map<SuiteStatementWithResult, List<? extends Result>> tempSuiteResults = executeSuite(tempSetupSuite); SuiteResult tempSetupResult = (!shouldExecuteFixtures()) ? null : new SuiteResult(tempSuiteResults, null, null, System.nanoTime() - tempStart); aSetupResultMap.put(tempSetupSuite, tempSetupResult); if (!checkForAbortion()) { tempSetupsAlreadyRun.add(tempSetupSuite); tempSetupSuitesExecuted.add(tempSetupSuite); } if (currentCallback != null) { currentCallback.onCallbackProcessingStart(); currentCallback.onSetupFinish(tempSetupSuite, tempSetupResult); currentCallback.onCallbackProcessingEnd(); } } if (checkForAbortion()) { break; } } return tempSetupSuitesExecuted; } /** * Executes the teardown suites required by the provided setup suites. * * @param aSetupSuitesList * the list of setup suites * @param aTearDownResultMap * a map into which teardown suite execution results will be stored */ protected void executeTearDownSuites(List<SuiteDefinition> aSetupSuitesList, Map<SuiteDefinition, Result> aTearDownResultMap) { Set<SuiteDefinition> tempSetupsAlreadyRun = setupSuitesExecuted.get(forkInExecution); for (int i = aSetupSuitesList.size() - 1; i >= 0; i--) { SuiteDefinition tempSetupSuite = aSetupSuitesList.get(i); for (SuiteDefinition tempTearDownSuite : tempSetupSuite.getFinalizers()) { if (currentCallback != null) { currentCallback.onCallbackProcessingStart(); currentCallback.onTearDownStart(tempTearDownSuite); currentCallback.onCallbackProcessingEnd(); } long tempStart = System.nanoTime(); Map<SuiteStatementWithResult, List<? extends Result>> tempSuiteResults = executeSuite( tempTearDownSuite); SuiteResult tempTearDownResult = (!shouldExecuteFixtures()) ? null : new SuiteResult(tempSuiteResults, null, null, System.nanoTime() - tempStart); aTearDownResultMap.put(tempTearDownSuite, tempTearDownResult); if (currentCallback != null) { currentCallback.onCallbackProcessingStart(); currentCallback.onTearDownFinish(tempTearDownSuite, tempTearDownResult); currentCallback.onCallbackProcessingEnd(); } } tempSetupsAlreadyRun.remove(tempSetupSuite); } } /** * Executes a suite. * * @param aSuite * the suite to execute * @return a map that maps statements to results */ protected Map<SuiteStatementWithResult, List<? extends Result>> executeSuite(SuiteDefinition aSuite) { Map<SuiteStatementWithResult, List<? extends Result>> tempResults = new LinkedHashMap<SuiteStatementWithResult, List<? extends Result>>(); List<VariableOrConstantEntity> tempDefinedVariables = new ArrayList<VariableOrConstantEntity>(); for (SuiteStatement tempStatement : aSuite.getStatements()) { checkForValidationError(tempStatement); if (tempStatement instanceof Suite) { Suite tempSuite = (Suite) tempStatement; boolean tempExecute = false; if (tempSuite.getVariants().size() > 0) { for (VariantDefinition tempVariant : tempSuite.getVariants()) { if (tempVariant == variantInExecution) { tempExecute = true; break; } } } else { tempExecute = true; } if (tempExecute) { tempResults.put((Suite) tempStatement, callSuite((Suite) tempStatement)); } } else if (tempStatement instanceof Test) { List<Result> tempInnerList = new ArrayList<Result>(); tempInnerList.add(executeTest((Test) tempStatement)); tempResults.put((Test) tempStatement, tempInnerList); } else if (tempStatement instanceof TableTest) { List<Result> tempInnerList = new ArrayList<Result>(); tempInnerList.add(executeTableTest((TableTest) tempStatement)); tempResults.put((TableTest) tempStatement, tempInnerList); } else if (tempStatement instanceof Call) { List<Result> tempInnerList = new ArrayList<Result>(); tempInnerList.addAll(executeCall((Call) tempStatement)); tempResults.put((Call) tempStatement, tempInnerList); } else if (tempStatement instanceof VariableDefinition) { tempDefinedVariables.add(((VariableDefinition) tempStatement).getName()); defineVariable((VariableDefinition) tempStatement, aSuite); } else if (tempStatement instanceof ConstantDefinition) { tempDefinedVariables.add(((ConstantDefinition) tempStatement).getName()); defineConstant((ConstantDefinition) tempStatement, aSuite); } else if (tempStatement instanceof VariableAssignment) { executeVariableAssignment((VariableAssignment) tempStatement, aSuite); } else if (tempStatement instanceof VisibleSingleLineComment) { if (currentCallback != null) { boolean tempIsTitle = (tempStatement instanceof VisibleSingleLineTitleComment); currentCallback.onCallbackProcessingStart(); currentCallback.onVisibleComment( IntegrityDSLUtil.cleanSingleLineComment((VisibleSingleLineComment) tempStatement), tempIsTitle, (VisibleComment) tempStatement); currentCallback.onCallbackProcessingEnd(); } } else if (tempStatement instanceof VisibleMultiLineComment) { if (currentCallback != null) { boolean tempIsTitle = (tempStatement instanceof VisibleMultiLineTitleComment); currentCallback.onCallbackProcessingStart(); currentCallback.onVisibleComment( IntegrityDSLUtil.cleanMultiLineComment((VisibleMultiLineComment) tempStatement), tempIsTitle, (VisibleComment) tempStatement); currentCallback.onCallbackProcessingEnd(); } } else if (tempStatement instanceof VisibleDivider) { if (currentCallback != null) { currentCallback.onCallbackProcessingStart(); currentCallback.onVisibleDivider(((VisibleDivider) tempStatement).getContent(), (VisibleDivider) tempStatement); currentCallback.onCallbackProcessingEnd(); } } if (checkForAbortion()) { break; } } for (VariableOrConstantEntity tempEntity : tempDefinedVariables) { variableManager.unset(tempEntity); } return tempResults; } /** * Defines a variable. * * @param aDefinition * the variable definition * @param aSuite * the suite in which the variable is defined */ protected void defineVariable(VariableDefinition aDefinition, SuiteDefinition aSuite) { defineVariable(aDefinition.getName(), aDefinition.getInitialValue(), aSuite); } /** * Defines a constant. * * @param aDefinition * the constant definition * @param aSuite * the suite in which the constant is defined * @throws InstantiationException * @throws ClassNotFoundException * @throws UnexecutableException */ protected void defineConstant(ConstantDefinition aDefinition, SuiteDefinition aSuite) { defineConstant(aDefinition, null, aSuite); } /** * Defines a constant. * * @param aDefinition * the constant definition * @param aValue * the value to define (if null, the value in the constant definition is used; this should only be set in * case of parameterizable constants!) * @param aSuite * the suite in which the constant is defined * @throws InstantiationException * @throws ClassNotFoundException * @throws UnexecutableException */ protected void defineConstant(ConstantDefinition aDefinition, Object aValue, SuiteDefinition aSuite) { // Constants can only be defined once, thus we'll define them in the first (dry) run and leave them defined for // the actual test run. if (currentPhase == Phase.DRY_RUN || !IntegrityDSLUtil.isGlobalVariableOrConstant(aDefinition.getName())) { Object tempValue; if (aValue == null) { try { tempValue = parameterResolver.resolveStatically(aDefinition, variantInExecution); } catch (UnexecutableException exc) { throw new RuntimeException("Failed to define constant value: " + exc.getMessage(), exc); } catch (ClassNotFoundException exc) { throw new RuntimeException("Failed to define constant value: " + exc.getMessage(), exc); } catch (InstantiationException exc) { throw new RuntimeException("Failed to define constant value: " + exc.getMessage(), exc); } } else { tempValue = aValue; } if (tempValue != null) { defineVariable(aDefinition.getName(), tempValue, aSuite); } } else { // The constant must be already defined, but in order for the calls to the callbacks to be consistent, we // need to perform that call, basically just as if we had determined the value just now. if (currentCallback != null) { Object tempConstantValue = variableManager.get(aDefinition.getName()); if (tempConstantValue != null) { currentCallback.onCallbackProcessingStart(); currentCallback.onConstantDefinition(aDefinition.getName(), aSuite, tempConstantValue, (aDefinition.getParameterized() != null)); currentCallback.onCallbackProcessingEnd(); } } } } /** * Defines a variable. * * @param anEntity * the variable entity * @param anInitialValue * the initial variable value, or null if the variable is not initialized * @param aSuite * the suite in which the variable is defined */ protected void defineVariable(VariableOrConstantEntity anEntity, Object anInitialValue, SuiteDefinition aSuite) { Object tempInitialValue = null; if (anInitialValue instanceof Variable) { tempInitialValue = variableManager.get(((Variable) anInitialValue).getName()); } else { tempInitialValue = anInitialValue; } setVariableValue(anEntity, tempInitialValue, false); if (currentCallback != null) { currentCallback.onCallbackProcessingStart(); if (anEntity instanceof VariableEntity) { currentCallback.onVariableDefinition((VariableEntity) anEntity, aSuite, tempInitialValue); } else if (anEntity instanceof ConstantEntity) { currentCallback.onConstantDefinition((ConstantEntity) anEntity, aSuite, tempInitialValue, (((ConstantDefinition) anEntity.eContainer()).getParameterized() != null)); } currentCallback.onCallbackProcessingEnd(); } } /** * Sets the value of a variable. * * @param anEntity * the variable entity to update * @param aValue * the new value (which will be converted to its default type before assigning it!) * @param aDoSendUpdateFlag * whether this update should be sent to connected master/slaves * @throws InstantiationException * @throws ClassNotFoundException * @throws UnexecutableException */ protected void setVariableValueConverted(VariableOrConstantEntity anEntity, ValueOrEnumValueOrOperationCollection aValue, boolean aDoSendUpdateFlag) throws InstantiationException, ClassNotFoundException, UnexecutableException { Object tempConvertedValue = valueConverter.convertValue(null, aValue, null); setVariableValue(anEntity, tempConvertedValue, aDoSendUpdateFlag); } /** * Sets the value of a variable. * * @param anEntity * the variable entity to update * @param aValue * the new value * @param aDoSendUpdateFlag * whether this update should be sent to connected master/slaves */ protected void setVariableValue(VariableOrConstantEntity anEntity, Object aValue, boolean aDoSendUpdateFlag) { variableManager.set(anEntity, aValue); if (aDoSendUpdateFlag) { if (isFork()) { // A fork will have to send updates to its master, but not for constants, as the master has those anyway if (remotingServer != null && !(anEntity instanceof ConstantEntity)) { String tempName = IntegrityDSLUtil.getQualifiedVariableEntityName(anEntity, true); if (aValue == null || (aValue instanceof Serializable)) { remotingServer.sendVariableUpdate(tempName, (Serializable) aValue); } else { System.err.println("SKIPPED SYNCING OF VARIABLE '" + tempName + "' TO MASTER - VALUE '" + aValue + "' OF TYPE '" + aValue.getClass().getName() + "' IS NOT SERIALIZABLE!"); } } } else { // The master will have to update all active forks. for (Entry<ForkDefinition, Fork> tempEntry : forkMap.entrySet()) { tempEntry.getValue().updateVariableValue(anEntity, aValue); } } } } /** * Updates a variables' value. * * @param aQualifiedVariableName * the name of the variable to update * @param aValue * the new value * @param aDoSendUpdateFlag * whether this update should be sent to connected master/slaves */ protected void setVariableValue(String aQualifiedVariableName, Object aValue, boolean aDoSendUpdateFlag) { VariableOrConstantEntity tempEntity = model.getVariableOrConstantByName(aQualifiedVariableName); if (tempEntity != null) { setVariableValue(tempEntity, aValue, aDoSendUpdateFlag); } } /** * Resolves a constant value (either it's a static value anyway, or it's a constant which needs to be resolved). * * @param aConstantValue * the constant value * @return the value */ protected Object resolveConstantValue(ConstantValue aConstantValue) { if (aConstantValue instanceof StaticValue) { return aConstantValue; } else if (aConstantValue instanceof Constant) { return variableManager.get(((Constant) aConstantValue).getName()); } throw new ThisShouldNeverHappenException(); } /** * Executes variable assignments. * * @param anAssignment * the assignment to execute * @param aSuite * the suite that the assignment is in */ protected void executeVariableAssignment(VariableAssignment anAssignment, SuiteDefinition aSuite) { if (currentCallback != null) { currentCallback.onCallbackProcessingStart(); currentCallback.onVariableAssignment(anAssignment, anAssignment.getTarget().getName(), aSuite, anAssignment.getValue()); currentCallback.onCallbackProcessingEnd(); } if (shouldExecuteFixtures()) { // Only perform variable assignments if we are not in dry run mode try { setVariableValueConverted(anAssignment.getTarget().getName(), anAssignment.getValue(), true); } catch (InstantiationException | ClassNotFoundException | UnexecutableException exc) { exc.printStackTrace(); } } } /** * Executes a test (doesn't pay attention to the multiplier). * * @param aTest * the test to execute * @return the result */ protected TestResult executeTest(Test aTest) { modelChecker.check(aTest); if (currentCallback != null) { currentCallback.onCallbackProcessingStart(); currentCallback.onTestStart(aTest); currentCallback.onCallbackProcessingEnd(); } TestResult tempReturn = null; TestComparisonResult tempComparisonResult; Throwable tempException = null; long tempDuration = 0; FixtureWrapper<?> tempFixtureInstance = null; String tempFixtureMethodName = aTest.getDefinition().getFixtureMethod().getMethod().getSimpleName(); Map<String, TestComparisonResult> tempComparisonMap = new LinkedHashMap<String, TestComparisonResult>(); boolean tempUndeterminedComparisonResultsRequired = false; if (!shouldExecuteFixtures()) { tempUndeterminedComparisonResultsRequired = true; } else { pauseIfRequiredByRemoteClient(false); try { Map<String, Object> tempParameters = parameterResolver.createParameterMap(aTest, true, UnresolvableVariableHandling.RESOLVE_TO_NULL_VALUE); tempFixtureInstance = wrapperFactory.newFixtureWrapper(aTest.getDefinition().getFixtureMethod()); tempFixtureInstance.announceTestResults(aTest.getResult(), aTest.getResults()); Object tempFixtureResult; tempDuration = System.nanoTime(); try { tempFixtureResult = tempFixtureInstance.execute(tempParameters); } finally { tempDuration = System.nanoTime() - tempDuration; } if (aTest.getResults() != null && aTest.getResults().size() > 0) { Map<String, Object> tempFixtureResultMap = ParameterUtil .getValuesFromNamedResultContainer(tempFixtureResult); for (NamedResult tempNamedResult : aTest.getResults()) { String tempResultName = IntegrityDSLUtil .getExpectedResultNameStringFromTestResultName(tempNamedResult.getName()); Object tempSingleFixtureResult = tempFixtureResultMap.get(tempResultName); ComparisonResult tempResult = resultComparator.compareResult(tempSingleFixtureResult, tempNamedResult.getValue(), tempFixtureInstance, aTest.getDefinition().getFixtureMethod(), tempResultName); tempComparisonResult = TestComparisonResult.wrap(tempResult, tempResultName, tempSingleFixtureResult, tempNamedResult.getValue()); tempComparisonMap.put(tempResultName, tempComparisonResult); } } else { ComparisonResult tempResult = resultComparator.compareResult(tempFixtureResult, aTest.getResult(), tempFixtureInstance, aTest.getDefinition().getFixtureMethod(), null); tempComparisonResult = TestComparisonResult.wrap(tempResult, ParameterUtil.DEFAULT_PARAMETER_NAME, tempFixtureResult, aTest.getResult()); tempComparisonMap.put(ParameterUtil.DEFAULT_PARAMETER_NAME, tempComparisonResult); } } catch (Throwable exc) { tempException = exc; tempUndeterminedComparisonResultsRequired = true; } } if (tempUndeterminedComparisonResultsRequired) { // We always need to provide the comparison results, even if no comparison was done due to dry mode or // exception, in which case the "undetermined result" is used. tempComparisonMap.clear(); if (aTest.getResults() != null && aTest.getResults().size() > 0) { for (NamedResult tempNamedResult : aTest.getResults()) { String tempParameter = IntegrityDSLUtil .getExpectedResultNameStringFromTestResultName(tempNamedResult.getName()); tempComparisonResult = new TestComparisonUndeterminedResult(tempParameter, tempNamedResult.getValue()); tempComparisonMap.put(tempParameter, tempComparisonResult); } } else { tempComparisonResult = new TestComparisonUndeterminedResult(ParameterUtil.DEFAULT_PARAMETER_NAME, aTest.getResult()); tempComparisonMap.put(ParameterUtil.DEFAULT_PARAMETER_NAME, tempComparisonResult); } } if (tempException == null && aTest.getCheckpoint() != null) { // In case of checkpoint tests, execution has to be aborted if they fail. This is done by "throwing" an // abort exception if there is any failed test result. for (TestComparisonResult tempResult : tempComparisonMap.values()) { if (tempResult instanceof TestComparisonFailureResult) { String tempTestDescription = "<unknown>"; try { tempTestDescription = testFormatter.testToHumanReadableString(aTest, null); } catch (ClassNotFoundException | InstantiationException | UnexecutableException | MethodNotFoundException exc) { // ignored } tempException = new AbortExecutionException( "Checkpoint Test '" + tempTestDescription + "' has failed!"); break; } } } List<TestSubResult> tempSubResults = new LinkedList<TestSubResult>(); if (tempException != null) { tempSubResults.add(new TestExceptionSubResult(tempException, tempComparisonMap, tempFixtureInstance, tempFixtureMethodName, tempDuration)); handlePossibleAbortException(tempException); } else { tempSubResults.add(new TestExecutedSubResult(tempComparisonMap, tempFixtureInstance, tempFixtureMethodName, tempDuration)); } List<ExtendedResult> tempExtendedResults = null; if (shouldExecuteFixtures()) { try { tempExtendedResults = tempFixtureInstance .retrieveExtendedResults(evaluateTestSubResultsToFixtureInvocationResult(tempSubResults)); } catch (Throwable exc) { exc.printStackTrace(); } } tempReturn = new TestResult(tempSubResults, tempFixtureInstance, tempFixtureMethodName, tempDuration, tempExtendedResults); if (currentCallback != null) { currentCallback.onCallbackProcessingStart(); currentCallback.onTestFinish(aTest, tempReturn); currentCallback.onCallbackProcessingEnd(); } if (tempFixtureInstance != null) { tempFixtureInstance.release(); } return tempReturn; } /** * Executes a table test. * * @param aTest * the test to execute * @return the result */ protected TestResult executeTableTest(TableTest aTest) { modelChecker.check(aTest); if (currentCallback != null) { currentCallback.onCallbackProcessingStart(); currentCallback.onTableTestStart(aTest); currentCallback.onCallbackProcessingEnd(); } if (currentPhase == Phase.TEST_RUN) { pauseIfRequiredByRemoteClient(false); } List<TestSubResult> tempSubResults = new LinkedList<TestSubResult>(); String tempFixtureMethodName = aTest.getDefinition().getFixtureMethod().getMethod().getSimpleName(); long tempOuterStart = System.nanoTime(); FixtureWrapper<?> tempFixtureInstance = null; for (TableTestRow tempRow : aTest.getRows()) { if (currentCallback != null) { currentCallback.onCallbackProcessingStart(); currentCallback.onTableTestRowStart(aTest, tempRow); currentCallback.onCallbackProcessingEnd(); } Map<String, TestComparisonResult> tempComparisonMap = new LinkedHashMap<String, TestComparisonResult>(); TestComparisonResult tempComparisonResult = null; Throwable tempException = null; Long tempDuration = null; boolean tempUndeterminedComparisonResultsRequired = false; if (!shouldExecuteFixtures()) { tempUndeterminedComparisonResultsRequired = true; } else { long tempStart = System.nanoTime(); try { Map<String, Object> tempParameters = parameterResolver.createParameterMap(aTest, tempRow, TableTestParameterResolveMethod.COMBINED, true, UnresolvableVariableHandling.RESOLVE_TO_NULL_VALUE); if (tempFixtureInstance == null) { // only instantiate on first pass tempFixtureInstance = wrapperFactory .newFixtureWrapper(aTest.getDefinition().getFixtureMethod()); } // We need the default result value before the actual result comparison takes place ValueOrEnumValueOrOperationCollection tempExpectedDefaultResultValue = null; if ((aTest.getResultHeaders() == null || aTest.getResultHeaders().isEmpty()) && aTest.getDefaultResultColumn() != null) { // the last column MUST be the result column tempExpectedDefaultResultValue = tempRow.getValues().get(tempRow.getValues().size() - 1) .getValue(); } tempFixtureInstance.announceTableTestResults(tempExpectedDefaultResultValue, aTest.getResultHeaders()); tempStart = System.nanoTime(); Object tempFixtureResult = tempFixtureInstance.execute(tempParameters); tempDuration = System.nanoTime() - tempStart; if (aTest.getResultHeaders() != null && aTest.getResultHeaders().size() > 0) { // Use named results Map<String, Object> tempFixtureResultMap = ParameterUtil .getValuesFromNamedResultContainer(tempFixtureResult); int tempColumn = aTest.getParameterHeaders().size(); for (ResultTableHeader tempNamedResultHeader : aTest.getResultHeaders()) { String tempResultName = IntegrityDSLUtil .getExpectedResultNameStringFromTestResultName(tempNamedResultHeader.getName()); ValueOrEnumValueOrOperationCollection tempExpectedNamedResultValue = (tempColumn < tempRow .getValues().size()) ? tempRow.getValues().get(tempColumn).getValue() : null; Object tempSingleFixtureResult = tempFixtureResultMap.get(tempResultName); ComparisonResult tempResult = resultComparator.compareResult(tempSingleFixtureResult, tempExpectedNamedResultValue, tempFixtureInstance, aTest.getDefinition().getFixtureMethod(), tempResultName); tempComparisonResult = TestComparisonResult.wrap(tempResult, tempResultName, tempSingleFixtureResult, tempExpectedNamedResultValue); tempComparisonMap.put(tempResultName, tempComparisonResult); tempColumn++; } } else { // Use the default result ComparisonResult tempResult = resultComparator.compareResult(tempFixtureResult, tempExpectedDefaultResultValue, tempFixtureInstance, aTest.getDefinition().getFixtureMethod(), null); tempComparisonResult = TestComparisonResult.wrap(tempResult, ParameterUtil.DEFAULT_PARAMETER_NAME, tempFixtureResult, tempExpectedDefaultResultValue); tempComparisonMap.put(ParameterUtil.DEFAULT_PARAMETER_NAME, tempComparisonResult); } // SUPPRESS CHECKSTYLE IllegalCatch } catch (Throwable exc) { tempDuration = System.nanoTime() - tempStart; tempException = exc; tempUndeterminedComparisonResultsRequired = true; } } if (tempUndeterminedComparisonResultsRequired) { tempComparisonMap.clear(); if (aTest.getResultHeaders() != null && aTest.getResultHeaders().size() > 0) { int tempColumn = aTest.getParameterHeaders().size(); for (ResultTableHeader tempNamedResultHeader : aTest.getResultHeaders()) { String tempParameter = IntegrityDSLUtil .getExpectedResultNameStringFromTestResultName(tempNamedResultHeader.getName()); ValueOrEnumValueOrOperationCollection tempExpectedValue = (tempColumn < tempRow.getValues() .size()) ? tempRow.getValues().get(tempColumn).getValue() : null; tempComparisonResult = new TestComparisonUndeterminedResult(tempParameter, tempExpectedValue); tempComparisonMap.put(tempParameter, tempComparisonResult); tempColumn++; } } else { ValueOrEnumValueOrOperationCollection tempExpectedValue = null; if (aTest.getDefaultResultColumn() != null) { // the last column MUST be the result column tempExpectedValue = tempRow.getValues().get(tempRow.getValues().size() - 1).getValue(); } tempComparisonResult = new TestComparisonUndeterminedResult(ParameterUtil.DEFAULT_PARAMETER_NAME, tempExpectedValue); tempComparisonMap.put(ParameterUtil.DEFAULT_PARAMETER_NAME, tempComparisonResult); } } TestSubResult tempSubResult; if (tempException != null) { tempSubResult = new TestExceptionSubResult(tempException, tempComparisonMap, tempFixtureInstance, tempFixtureMethodName, tempDuration); handlePossibleAbortException(tempException); } else { tempSubResult = new TestExecutedSubResult(tempComparisonMap, tempFixtureInstance, tempFixtureMethodName, tempDuration); } tempSubResults.add(tempSubResult); if (currentCallback != null) { currentCallback.onCallbackProcessingStart(); currentCallback.onTableTestRowFinish(aTest, tempRow, tempSubResult); currentCallback.onCallbackProcessingEnd(); } if (checkForAbortion()) { break; } } Long tempOuterDuration = System.nanoTime() - tempOuterStart; List<ExtendedResult> tempExtendedResult = null; if (shouldExecuteFixtures()) { try { tempExtendedResult = tempFixtureInstance .retrieveExtendedResults(evaluateTestSubResultsToFixtureInvocationResult(tempSubResults)); } catch (Throwable exc) { exc.printStackTrace(); } } TestResult tempReturn = new TestResult(tempSubResults, tempFixtureInstance, tempFixtureMethodName, currentPhase == Phase.DRY_RUN ? null : tempOuterDuration, tempExtendedResult); if (currentCallback != null) { currentCallback.onCallbackProcessingStart(); currentCallback.onTableTestFinish(aTest, tempReturn); currentCallback.onCallbackProcessingEnd(); } if (tempFixtureInstance != null) { tempFixtureInstance.release(); } return tempReturn; } /** * Loads a class by resolving a given {@link JvmType}. * * @param aType * the type to load * @return the class * @throws ClassNotFoundException */ protected Class<?> getClassForJvmType(JvmType aType) throws ClassNotFoundException { // TODO replace with call to our own classloader return javaClassLoader.loadClass(aType.getQualifiedName()); } /** * Executes a call. * * @param aCall * the call to execute * @return the results (multiple if the call has an execution multiplier) */ protected List<CallResult> executeCall(Call aCall) { int tempCount = 1; if (aCall.getMultiplier() != null && aCall.getMultiplier().getCount() != null) { try { tempCount = (Integer) valueConverter.convertValue(Integer.class, aCall.getMultiplier().getCount(), conversionContextProvider.get() .withUnresolvableVariableHandlingPolicy(UnresolvableVariableHandling.EXCEPTION)); } catch (UnresolvableVariableException exc) { // should never happen, since constant values are not allowed to be variables which still need resolving throw new ThisShouldNeverHappenException(exc); } catch (UnexecutableException exc) { // should never happen, since constant values are not allowed to be unexecuted operations throw new ThisShouldNeverHappenException(exc); } } List<CallResult> tempResults = new ArrayList<CallResult>(); for (int i = 0; i < tempCount; i++) { tempResults.add(executeCallSingle(aCall)); } return tempResults; } /** * Executes a call (a single time, doesn't honor the multiplier). * * @param aCall * the call to execute * @return the result */ protected CallResult executeCallSingle(Call aCall) { modelChecker.check(aCall); if (currentCallback != null) { currentCallback.onCallbackProcessingStart(); currentCallback.onCallStart(aCall); currentCallback.onCallbackProcessingEnd(); } List<UpdatedVariable> tempUpdatedVariables = new ArrayList<UpdatedVariable>(); if (aCall.getResults() != null && aCall.getResults().size() > 0) { for (NamedCallResult tempNamedResult : aCall.getResults()) { String tempResultName = IntegrityDSLUtil .getExpectedResultNameStringFromTestResultName(tempNamedResult.getName()); tempUpdatedVariables .add(new UpdatedVariable(tempNamedResult.getTarget().getName(), tempResultName, null)); } } else if (aCall.getResult() != null) { tempUpdatedVariables.add(new UpdatedVariable(aCall.getResult().getName(), null, null)); } CallResult tempReturn; String tempFixtureMethodName = aCall.getDefinition().getFixtureMethod().getMethod().getSimpleName(); FixtureWrapper<?> tempFixtureInstance = null; if (!shouldExecuteFixtures()) { tempReturn = new de.gebit.integrity.runner.results.call.UndeterminedResult(tempUpdatedVariables, tempFixtureMethodName); } else { pauseIfRequiredByRemoteClient(false); long tempDuration = 0; List<ExtendedResult> tempExtendedResults = null; try { Map<String, Object> tempParameters = parameterResolver.createParameterMap(aCall, true, UnresolvableVariableHandling.RESOLVE_TO_NULL_VALUE); tempFixtureInstance = wrapperFactory.newFixtureWrapper(aCall.getDefinition().getFixtureMethod()); tempFixtureInstance.announceCallResults(aCall.getResult(), aCall.getResults()); tempDuration = System.nanoTime(); Object tempResult; try { tempResult = tempFixtureInstance.execute(tempParameters); } finally { tempDuration = System.nanoTime() - tempDuration; } if (shouldExecuteFixtures()) { // Perform the call to retrieve extended results from the fixture. If this crashes, log the stack // trace to stdout, but ignore it otherwise - it's non-critical to the actual test result. try { tempExtendedResults = tempFixtureInstance .retrieveExtendedResults(FixtureInvocationResult.SUCCESS); } catch (Throwable exc) { exc.printStackTrace(); } } if (aCall.getResults() != null && aCall.getResults().size() > 0) { Map<String, Object> tempFixtureResultMap = ParameterUtil .getValuesFromNamedResultContainer(tempResult); for (UpdatedVariable tempUpdatedVariable : tempUpdatedVariables) { Object tempSingleFixtureResult = tempFixtureResultMap .get(tempUpdatedVariable.getParameterName()); tempUpdatedVariable.setValue(tempSingleFixtureResult); setVariableValue(tempUpdatedVariable.getTargetVariable(), tempSingleFixtureResult, true); } tempReturn = new de.gebit.integrity.runner.results.call.SuccessResult(tempUpdatedVariables, tempFixtureInstance, tempFixtureMethodName, tempDuration, tempExtendedResults); } else if (aCall.getResult() != null) { tempUpdatedVariables.get(0).setValue(tempResult); tempReturn = new de.gebit.integrity.runner.results.call.SuccessResult(tempUpdatedVariables, tempFixtureInstance, tempFixtureMethodName, tempDuration, tempExtendedResults); setVariableValue(aCall.getResult().getName(), tempResult, true); } else { tempReturn = new de.gebit.integrity.runner.results.call.SuccessResult(tempUpdatedVariables, tempFixtureInstance, tempFixtureMethodName, tempDuration, tempExtendedResults); } } catch (Throwable exc) { if (shouldExecuteFixtures()) { try { tempExtendedResults = tempFixtureInstance .retrieveExtendedResults(FixtureInvocationResult.EXCEPTION); } catch (Throwable exc2) { exc2.printStackTrace(); } } tempReturn = new de.gebit.integrity.runner.results.call.ExceptionResult(exc, tempUpdatedVariables, tempFixtureInstance, tempFixtureMethodName, tempDuration, tempExtendedResults); handlePossibleAbortException(exc); } } if (currentCallback != null) { currentCallback.onCallbackProcessingStart(); currentCallback.onCallFinish(aCall, tempReturn); currentCallback.onCallbackProcessingEnd(); } if (tempFixtureInstance != null) { tempFixtureInstance.release(); } return tempReturn; } /** * Test execution is splitted in phases. * * * @author Rene Schneider - initial API and implementation * */ protected enum Phase { /** * The dry run is used to build up the {@link SetList}. In this phase, the whole model is walked, but no forks * are being started and no test/call fixtures are being executed. */ DRY_RUN, /** * The actual test run. */ TEST_RUN; } private FixtureInvocationResult evaluateTestSubResultsToFixtureInvocationResult( List<TestSubResult> someSubResults) { boolean tempHasFailure = false; for (TestSubResult tempSubResult : someSubResults) { if (tempSubResult instanceof TestExceptionSubResult) { return FixtureInvocationResult.EXCEPTION; } else { if (!tempSubResult.wereAllComparisonsSuccessful()) { tempHasFailure = true; } } } return tempHasFailure ? FixtureInvocationResult.FAILURE : FixtureInvocationResult.SUCCESS; } /** * Determines whether we should actually execute fixture method calls at the moment. * * @return true if calls should be executed, false otherwise */ protected boolean shouldExecuteFixtures() { if (currentPhase == Phase.DRY_RUN) { return false; } else { if (MY_FORK_NAME != null) { return (forkInExecution != null && IntegrityDSLUtil.getQualifiedForkName(forkInExecution).equals(MY_FORK_NAME)); } else { return (forkInExecution == null); } } } /** * Pauses execution (blocks the method call) if the remoting client requested to do so via execution control or * breakpoints. * * @param aForkSyncFlag * true if we are pausing for fork synchronization reasons */ protected void pauseIfRequiredByRemoteClient(boolean aForkSyncFlag) { if (remotingServer == null) { return; } Integer tempLastTestOrCallEntryRef = setList.getLastCreatedEntryId(SetListEntryTypes.CALL, SetListEntryTypes.TEST); if (tempLastTestOrCallEntryRef != null && breakpoints.contains(tempLastTestOrCallEntryRef)) { // we are at a breakpoint, so we need to wait, but don't have to do anything else here } else if (shallWaitBeforeNextStep && shouldExecuteFixtures()) { resetWaitBeforeNextStep(); } else { // do not wait remotingServer.updateExecutionState(ExecutionStates.RUNNING); return; } try { waitForContinue(aForkSyncFlag); } catch (InterruptedException exc) { // just continue } } /** * Pause execution (blocks method call) until continuation is triggered by remoting. * * @param aForkSyncFlag * true if we are pausing for fork synchronization reasons * @throws InterruptedException */ protected void waitForContinue(boolean aForkSyncFlag) throws InterruptedException { if (aForkSyncFlag) { remotingServer.updateExecutionState(ExecutionStates.PAUSED_SYNC); } else { remotingServer.updateExecutionState(ExecutionStates.PAUSED); } executionWaiter.acquire(); executionWaiter.drainPermits(); remotingServer.updateExecutionState(ExecutionStates.RUNNING); } /** * Removes a specific breakpoint. * * @param anEntryReference * the setlist entry reference at which the breakpoint is set */ protected void removeBreakpoint(int anEntryReference) { // forward to forks for (Entry<ForkDefinition, Fork> tempForkEntry : forkMap.entrySet()) { tempForkEntry.getValue().getClient().createBreakpoint(anEntryReference); } // then perform for ourself if (breakpoints.remove(anEntryReference)) { remotingServer.confirmBreakpointRemoval(anEntryReference); } } /** * Creates a new breapoint. * * @param anEntryReference * the setlist entry reference at which the breakpoint will be created */ protected void createBreakpoint(int anEntryReference) { // forward to forks for (Entry<ForkDefinition, Fork> tempForkEntry : forkMap.entrySet()) { tempForkEntry.getValue().getClient().createBreakpoint(anEntryReference); } // then perform for ourself if (breakpoints.add(anEntryReference)) { remotingServer.confirmBreakpointCreation(anEntryReference); } } /** * The listener used to respond on actions triggered by remoting clients. * * * @author Rene Schneider - initial API and implementation * */ protected class RemotingListener implements IntegrityRemotingServerListener { @Override public void onConnectionSuccessful(IntegrityRemotingVersionMessage aRemoteVersion, Endpoint anEndpoint) { // nothing to do } @Override public void onSetListRequest(Endpoint anEndpoint) { synchronized (setListWaiter) { while (setList == null) { try { setListWaiter.wait(); } catch (InterruptedException exc) { // don't care } } anEndpoint.sendMessage(new SetListBaselineMessage(setList)); for (Integer tempBreakpoint : breakpoints) { anEndpoint.sendMessage(new BreakpointUpdateMessage(BreakpointActions.CREATE, tempBreakpoint)); } } } @Override public void onRunCommand(Endpoint anEndpoint) { if (!isFork() && forkInExecution != null) { // if we're the master and a fork is active, we're waiting for a fork, thus this command // is meant for the fork Fork tempFork = forkMap.get(forkInExecution); tempFork.getClient().controlExecution(ExecutionCommands.RUN); } else { executionWaiter.release(); } } @Override public void onConnectionLost(Endpoint anEndpoint) { // I don't care } @Override public void onPauseCommand(Endpoint anEndpoint) { if (!isFork() && forkInExecution != null) { // if we're the master and a fork is active, we're waiting for a fork, thus this command // is meant for the fork forkMap.get(forkInExecution).getClient().controlExecution(ExecutionCommands.PAUSE); } else { scheduleWaitBeforeNextStep(); } } @Override public void onStepIntoCommand(Endpoint anEndpoint) { if (!isFork() && forkInExecution != null) { // if we're the master and a fork is active, we're waiting for a fork, thus this command // is meant for the fork Fork tempFork = forkMap.get(forkInExecution); tempFork.getClient().controlExecution(ExecutionCommands.STEP_INTO); } else { scheduleWaitBeforeNextStep(); executionWaiter.release(); } } @Override public void onCreateBreakpoint(Integer anEntryReference, Endpoint anEndpoint) { if (anEntryReference == null) { scheduleWaitBeforeNextStep(); } else { createBreakpoint(anEntryReference); } } @Override public void onRemoveBreakpoint(Integer anEntryReference, Endpoint anEndpoint) { if (anEntryReference == null) { resetWaitBeforeNextStep(); } else { removeBreakpoint(anEntryReference); } } @Override public void onVariableUpdateRetrieval(String aVariableName, Serializable aValue) { setVariableValue(aVariableName, aValue, false); } @Override public void onShutdownRequest() { if (isFork()) { System.err.println("RECEIVED SHUTDOWN REQUEST FROM MASTER PROCESS - THIS FORK WILL NOW TERMINATE!"); } else { System.err.println("RECEIVED SHUTDOWN REQUEST FROM CLIENT - THIS PROCESS WILL NOW TERMINATE!"); } // Shutdown hook(s) will be called after this command automatically! System.exit(-1); } } public static boolean isFork() { return MY_FORK_NAME != null; } /** * Creates a new fork instance. This starts up the forked JVM and connects to it for remote control. * * @param aSuiteCall * the suite call that shall be run on the fork * @return the new fork * @throws ForkException * if any problem arises during forking */ @SuppressWarnings("unchecked") protected Fork createFork(Suite aSuiteCall) throws ForkException { ForkDefinition tempForkDef = aSuiteCall.getFork(); Class<? extends Forker> tempForkerClass = DefaultForker.class; if (tempForkDef.getForkerClass() != null) { try { tempForkerClass = (Class<? extends Forker>) getClassForJvmType(tempForkDef.getForkerClass().getType()); } catch (ClassCastException exc) { throw new ForkException( "Could not create fork '" + tempForkDef.getName() + "': forker class not usable.", exc); } catch (ClassNotFoundException exc) { throw new ForkException( "Could not create fork '" + tempForkDef.getName() + "': forker class not found.", exc); } } if (tempForkerClass.getConstructors().length != 1) { throw new ForkException("Found illegal number of constructors in forker class (must be exactly one!)"); } // Forker can be parameterized Constructor<? extends Forker> tempConstructor = (Constructor<? extends Forker>) tempForkerClass .getConstructors()[0]; Object[] tempParameters = new Object[tempConstructor.getParameterTypes().length]; try { for (int i = 0; i < tempConstructor.getParameterTypes().length; i++) { for (Annotation tempAnnotation : tempConstructor.getParameterAnnotations()[i]) { if (ForkerParameter.class.isAssignableFrom(tempAnnotation.annotationType())) { String tempName = ((ForkerParameter) tempAnnotation).name(); if (tempName != null) { for (ForkParameter tempParameter : tempForkDef.getParameters()) { String tempParamName = IntegrityDSLUtil .getParamNameStringFromParameterName(tempParameter.getName()); if (tempName.equals(tempParamName)) { Class<?> tempTargetType = tempConstructor.getParameterTypes()[i]; tempParameters[i] = valueConverter.convertValue(tempTargetType, tempParameter.getValue(), conversionContextProvider.get().withUnresolvableVariableHandlingPolicy( UnresolvableVariableHandling.EXCEPTION)); break; } } } } } } } catch (ConversionException exc) { throw new ForkException( "Could not create fork '" + tempForkDef.getName() + "': failed to resolve forker parameters.", exc); } catch (UnresolvableVariableException exc) { throw new ForkException( "Could not create fork '" + tempForkDef.getName() + "': failed to resolve forker parameters.", exc); } catch (UnexecutableException exc) { throw new ForkException( "Could not create fork '" + tempForkDef.getName() + "': failed to resolve forker parameters.", exc); } Forker tempForker = null; try { tempForker = tempConstructor.newInstance(tempParameters); } catch (InstantiationException exc) { throw new ForkException( "Could not create fork '" + tempForkDef.getName() + "': forker class not instantiable.", exc); } catch (IllegalAccessException exc) { throw new ForkException( "Could not create fork '" + tempForkDef.getName() + "': forker class not instantiable.", exc); } catch (IllegalArgumentException exc) { throw new ForkException( "Could not create fork '" + tempForkDef.getName() + "': forker class not instantiable.", exc); } catch (InvocationTargetException exc) { throw new ForkException( "Could not create fork '" + tempForkDef.getName() + "': forker class not instantiable.", exc); } Fork tempFork = new Fork(aSuiteCall.getFork(), tempForker, commandLineArguments, remotingServer != null ? remotingServer.getPort() : IntegrityRemotingConstants.DEFAULT_PORT, currentCallback, setList, remotingServer, new ForkCallback() { @Override public void onSetVariableValue(String aQualifiedVariableName, Object aValue, boolean aDoSendUpdateFlag) { setVariableValue(aQualifiedVariableName, aValue, aDoSendUpdateFlag); } @Override public void onForkExit(Fork aFork) { for (Entry<ForkDefinition, Fork> tempEntry : forkMap.entrySet()) { if (tempEntry.getValue() == aFork) { forkMap.remove(tempEntry.getKey()); diedForks.add(tempEntry.getKey()); return; } } } }); injector.injectMembers(tempFork); tempFork.start(); long tempTimeout = System.getProperty(FORK_CONNECTION_TIMEOUT_PROPERTY) != null ? Integer.parseInt(System.getProperty(FORK_CONNECTION_TIMEOUT_PROPERTY)) : getForkConnectionTimeoutDefault(); long tempStartTime = System.nanoTime(); while (System.nanoTime() - tempStartTime < (tempTimeout * 1000 * 1000000)) { try { if (!tempFork.isAlive() || tempFork.connect(getForkSingleConnectTimeout(), javaClassLoader)) { break; } } catch (IOException exc) { // this is expected -> will simply retry } try { Thread.sleep(getForkConnectDelay()); } catch (InterruptedException exc) { // ignored } } if (tempFork.isAlive() && tempFork.isConnected()) { // initially, we'll send a snapshot of all current non-encapsulated variable values to the fork // (encapsulated values are predefined in the test script and thus already known to the fork) for (Entry<VariableOrConstantEntity, Object> tempEntry : variableManager.getAllEntries()) { if (!(tempEntry.getValue() instanceof ValueOrEnumValueOrOperation) && !(tempEntry.getKey() instanceof ConstantEntity)) { tempFork.updateVariableValue(tempEntry.getKey(), tempEntry.getValue()); } } // and the fork will also need all current breakpoints for (Integer tempBreakpoint : breakpoints) { tempFork.getClient().createBreakpoint(tempBreakpoint); } // and the magic pause-on-next-instruction "breakpoint" too if (shallWaitBeforeNextStep) { tempFork.getClient().createBreakpoint(null); } // and now we'll wait until the fork is paused while (tempFork.isAlive() && tempFork.isConnected() && tempFork.getExecutionState() != ExecutionStates.PAUSED_SYNC) { try { Thread.sleep(getForkPauseWaitInterval()); } catch (InterruptedException exc) { // nothing to do here } } // a last sanity check if (tempFork.isAlive() && tempFork.isConnected()) { return tempFork; } } try { tempFork.kill(); } catch (InterruptedException exc) { exc.printStackTrace(); } throw new ForkException("Could not successfully establish a control connection to the fork."); } /** * Schedules a "wait for the master" before the next execution step. */ protected void scheduleWaitBeforeNextStep() { shallWaitBeforeNextStep = true; } /** * Resets a scheduled interruption (see {@link #scheduleWaitBeforeNextStep()}). */ protected void resetWaitBeforeNextStep() { shallWaitBeforeNextStep = false; } /** * Checks and ensures that the specified object has no validation errors. * * @param anObject * Object to be validated. */ protected void checkForValidationError(EObject anObject) { Diagnostic tempDiagnostic = Diagnostician.INSTANCE.validate(anObject); if (tempDiagnostic == null || (tempDiagnostic.getSeverity() & BasicDiagnostic.ERROR) == 0) { return; } StringBuilder tempResult = new StringBuilder(); Deque<Diagnostic> tempStack = new LinkedList<Diagnostic>(); ModelSourceInformationElement tempModelInfo = modelSourceExplorer.determineSourceInformation(anObject); final ICompositeNode tempConflictOrigin = NodeModelUtils.getNode(anObject); tempResult.append("Validation Error at " + tempModelInfo); tempStack.addAll(tempDiagnostic.getChildren()); while (!tempStack.isEmpty()) { tempResult.append("\n\t"); Diagnostic tempCurrent = tempStack.poll(); tempStack.addAll(tempCurrent.getChildren()); tempResult.append(tempCurrent.getMessage()); } throw new ValidationException(tempResult.toString(), tempConflictOrigin); } }