/*
* Copyright 2009 the original author or 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 org.spockframework.runtime;
import org.junit.runner.Description;
import org.spockframework.runtime.extension.IMethodInterceptor;
import org.spockframework.runtime.extension.MethodInvocation;
import org.spockframework.runtime.model.*;
import org.spockframework.util.CollectionUtil;
import org.spockframework.util.InternalSpockError;
import spock.lang.Specification;
import static org.spockframework.runtime.RunStatus.*;
/**
* Executes a single Spec. Notifies its supervisor about overall execution
* progress and every invocation of Spec code.
* Supervisor also determines the error strategy.
*
* @author Peter Niederwieser
*/
public class BaseSpecRunner {
protected static final Object[] EMPTY_ARGS = new Object[0];
protected final SpecInfo spec;
protected final IRunSupervisor supervisor;
protected FeatureInfo currentFeature;
protected IterationInfo currentIteration;
protected Specification sharedInstance;
protected Specification currentInstance;
protected int runStatus = OK;
public BaseSpecRunner(SpecInfo spec, IRunSupervisor supervisor) {
this.spec = spec;
this.supervisor = supervisor;
}
public int run() {
// Sometimes a spec run is requested even though the spec has been excluded
// (e.g. if JUnit is in control). In such a case, the best thing we can do
// is to treat the spec as skipped.
if (spec.isExcluded() || spec.isSkipped()) {
supervisor.specSkipped(spec);
return OK;
}
createSpecInstance(true);
runSharedInitializer();
runSpec();
return resetStatus(SPEC);
}
private void runSpec() {
if (runStatus != OK) return;
supervisor.beforeSpec(spec);
invoke(this, createMethodInfoForDoRunSpec());
supervisor.afterSpec(spec);
}
private MethodInfo createMethodInfoForDoRunSpec() {
MethodInfo result = new MethodInfo() {
@Override
public Object invoke(Object target, Object... arguments) {
doRunSpec();
return null;
}
};
result.setParent(spec);
result.setKind(MethodKind.SPEC_EXECUTION);
result.setDescription(spec.getDescription());
for (IMethodInterceptor interceptor : spec.getInterceptors())
result.addInterceptor(interceptor);
return result;
}
public void doRunSpec() {
runSetupSpec();
runFeatures();
runCleanupSpec();
}
private void createSpecInstance(boolean shared) {
if (runStatus != OK) return;
try {
if (shared) {
sharedInstance = (Specification) spec.getReflection().newInstance();
currentInstance = sharedInstance;
} else {
currentInstance = (Specification) spec.getReflection().newInstance();
}
} catch (Throwable t) {
throw new InternalSpockError("Failed to instantiate spec '%s'", t).withArgs(spec.getName());
}
getSpecificationContext().setCurrentSpec(spec);
getSpecificationContext().setSharedInstance(sharedInstance);
}
private void runSharedInitializer() {
runSharedInitializer(spec);
}
private void runSharedInitializer(SpecInfo spec) {
if (spec == null) return;
invoke(this, createMethodInfoForDoRunSharedInitializer(spec), spec);
}
private MethodInfo createMethodInfoForDoRunSharedInitializer(final SpecInfo spec) {
MethodInfo result = new MethodInfo() {
@Override
public Object invoke(Object target, Object... arguments) {
doRunSharedInitializer(spec);
return null;
}
};
result.setParent(spec);
result.setKind(MethodKind.SHARED_INITIALIZER);
result.setDescription(spec.getDescription());
for (IMethodInterceptor interceptor : spec.getSharedInitializerInterceptors())
result.addInterceptor(interceptor);
return result;
}
public void doRunSharedInitializer(SpecInfo spec) {
runSharedInitializer(spec.getSuperSpec());
if (runStatus != OK) return;
invoke(currentInstance, spec.getSharedInitializerMethod());
}
private void runSetupSpec() {
runSetupSpec(spec);
}
private void runSetupSpec(SpecInfo spec) {
if (spec == null) return;
invoke(this, createMethodInfoForDoRunSetupSpec(spec), spec);
}
private MethodInfo createMethodInfoForDoRunSetupSpec(final SpecInfo spec) {
MethodInfo result = new MethodInfo() {
@Override
public Object invoke(Object target, Object... arguments) {
doRunSetupSpec(spec);
return null;
}
};
result.setParent(spec);
result.setKind(MethodKind.SETUP_SPEC);
result.setDescription(spec.getDescription());
for (IMethodInterceptor interceptor : spec.getSetupSpecInterceptors())
result.addInterceptor(interceptor);
return result;
}
public void doRunSetupSpec(SpecInfo spec) {
runSetupSpec(spec.getSuperSpec());
for (MethodInfo method : spec.getSetupSpecMethods()) {
if (runStatus != OK) return;
invoke(currentInstance, method);
}
}
private void runFeatures() {
for (FeatureInfo feature : spec.getAllFeaturesInExecutionOrder()) {
if (resetStatus(FEATURE) != OK) return;
currentFeature = feature;
runFeature();
currentFeature = null;
}
}
private void runCleanupSpec() {
currentInstance = sharedInstance;
runCleanupSpec(spec);
}
private void runCleanupSpec(SpecInfo spec) {
if (spec == null) return;
invoke(this, createMethodForDoRunCleanupSpec(spec), spec);
}
private MethodInfo createMethodForDoRunCleanupSpec(final SpecInfo spec) {
MethodInfo result = new MethodInfo() {
@Override
public Object invoke(Object target, Object... arguments) {
doRunCleanupSpec(spec);
return null;
}
};
result.setParent(spec);
result.setKind(MethodKind.CLEANUP_SPEC);
result.setDescription(spec.getDescription());
for (IMethodInterceptor interceptor : spec.getCleanupSpecInterceptors())
result.addInterceptor(interceptor);
return result;
}
public void doRunCleanupSpec(SpecInfo spec) {
for (MethodInfo method : spec.getCleanupSpecMethods()) {
if (action(runStatus) == ABORT) return;
invoke(currentInstance, method);
}
runCleanupSpec(spec.getSuperSpec());
}
private void runFeature() {
if (runStatus != OK) return;
if (currentFeature.isExcluded()) return;
if (currentFeature.isSkipped()) {
supervisor.featureSkipped(currentFeature);
return;
}
supervisor.beforeFeature(currentFeature);
invoke(this, createMethodInfoForDoRunFeature());
supervisor.afterFeature(currentFeature);
}
private MethodInfo createMethodInfoForDoRunFeature() {
MethodInfo result = new MethodInfo() {
@Override
public Object invoke(Object target, Object... arguments) {
doRunFeature();
return null;
}
};
result.setParent(currentFeature.getParent());
result.setKind(MethodKind.FEATURE_EXECUTION);
result.setFeature(currentFeature);
result.setDescription(currentFeature.getDescription());
for (IMethodInterceptor interceptor : currentFeature.getInterceptors())
result.addInterceptor(interceptor);
return result;
}
public void doRunFeature() {
currentFeature.setIterationNameProvider(new SafeIterationNameProvider(currentFeature.getIterationNameProvider()));
if (currentFeature.isParameterized())
runParameterizedFeature();
else runSimpleFeature();
}
private void runSimpleFeature() {
if (runStatus != OK) return;
initializeAndRunIteration(EMPTY_ARGS, 1);
resetStatus(ITERATION);
}
protected void initializeAndRunIteration(Object[] dataValues, int estimatedNumIterations) {
if (runStatus != OK) return;
createSpecInstance(false);
runInitializer();
runIteration(dataValues, estimatedNumIterations);
}
private void runIteration(Object[] dataValues, int estimatedNumIterations) {
if (runStatus != OK) return;
currentIteration = createIterationInfo(dataValues, estimatedNumIterations);
getSpecificationContext().setCurrentIteration(currentIteration);
supervisor.beforeIteration(currentIteration);
invoke(this, createMethodInfoForDoRunIteration());
supervisor.afterIteration(currentIteration);
getSpecificationContext().setCurrentIteration(null);
currentIteration = null;
}
private IterationInfo createIterationInfo(Object[] dataValues, int estimatedNumIterations) {
IterationInfo result = new IterationInfo(currentFeature, dataValues, estimatedNumIterations);
String iterationName = currentFeature.getIterationNameProvider().getName(result);
result.setName(iterationName);
Description description = Description.createTestDescription(spec.getReflection(),
iterationName, currentFeature.getFeatureMethod().getAnnotations());
result.setDescription(description);
return result;
}
private MethodInfo createMethodInfoForDoRunIteration() {
MethodInfo result = new MethodInfo() {
@Override
public Object invoke(Object target, Object... arguments) {
doRunIteration();
return null;
}
};
result.setParent(currentFeature.getParent());
result.setKind(MethodKind.ITERATION_EXECUTION);
result.setFeature(currentFeature);
result.setDescription(currentFeature.getDescription());
for (IMethodInterceptor interceptor : currentFeature.getIterationInterceptors())
result.addInterceptor(interceptor);
return result;
}
public void doRunIteration() {
runSetup();
runFeatureMethod();
runCleanup();
}
protected int resetStatus(int scope) {
if (scope(runStatus) <= scope) runStatus = OK;
return runStatus;
}
protected void runParameterizedFeature() {
throw new UnsupportedOperationException("This runner cannot run parameterized features");
}
private void runInitializer() {
runInitializer(spec);
}
private void runInitializer(SpecInfo spec) {
if (spec == null) return;
invoke(this, createMethodInfoForDoRunInitializer(spec), spec);
}
private MethodInfo createMethodInfoForDoRunInitializer(final SpecInfo spec) {
MethodInfo result = new MethodInfo() {
@Override
public Object invoke(Object target, Object... arguments) {
doRunInitializer(spec);
return null;
}
};
result.setParent(currentFeature.getParent());
result.setKind(MethodKind.INITIALIZER);
result.setFeature(currentFeature);
result.setDescription(currentFeature.getDescription());
for (IMethodInterceptor interceptor : spec.getInitializerInterceptors())
result.addInterceptor(interceptor);
return result;
}
public void doRunInitializer(SpecInfo spec) {
runInitializer(spec.getSuperSpec());
if (runStatus != OK) return;
invoke(currentInstance, spec.getInitializerMethod());
}
private void runSetup() {
runSetup(spec);
}
private void runSetup(SpecInfo spec) {
if (spec == null) return;
invoke(this, createMethodInfoForDoRunSetup(spec), spec);
}
private MethodInfo createMethodInfoForDoRunSetup(final SpecInfo spec) {
MethodInfo result = new MethodInfo() {
@Override
public Object invoke(Object target, Object... arguments) {
doRunSetup(spec);
return null;
}
};
result.setParent(currentFeature.getParent());
result.setKind(MethodKind.SETUP);
result.setFeature(currentFeature);
result.setDescription(currentFeature.getDescription());
for (IMethodInterceptor interceptor : spec.getSetupInterceptors())
result.addInterceptor(interceptor);
return result;
}
public void doRunSetup(SpecInfo spec) {
runSetup(spec.getSuperSpec());
for (MethodInfo method : spec.getSetupMethods()) {
if (runStatus != OK) return;
invoke(currentInstance, method);
}
}
private void runFeatureMethod() {
if (runStatus != OK) return;
invoke(currentInstance, currentFeature.getFeatureMethod(), currentIteration.getDataValues());
}
private void runCleanup() {
runCleanup(spec);
}
private void runCleanup(SpecInfo spec) {
if (spec == null) return;
invoke(this, createMethodInfoForDoRunCleanup(spec), spec);
}
private MethodInfo createMethodInfoForDoRunCleanup(final SpecInfo spec) {
MethodInfo result = new MethodInfo() {
@Override
public Object invoke(Object target, Object... arguments) {
doRunCleanup(spec);
return null;
}
};
result.setParent(currentFeature.getParent());
result.setKind(MethodKind.CLEANUP);
result.setFeature(currentFeature);
result.setDescription(currentFeature.getDescription());
for (IMethodInterceptor interceptor : spec.getCleanupInterceptors())
result.addInterceptor(interceptor);
return result;
}
public void doRunCleanup(SpecInfo spec) {
if (spec.getIsBottomSpec()) {
runIterationCleanups();
if (action(runStatus) == ABORT) return;
}
for (MethodInfo method : spec.getCleanupMethods()) {
if (action(runStatus) == ABORT) return;
invoke(currentInstance, method);
}
runCleanup(spec.getSuperSpec());
}
private void runIterationCleanups() {
for (Runnable cleanup : currentIteration.getCleanups()) {
if (action(runStatus) == ABORT) return;
try {
cleanup.run();
} catch (Throwable t) {
ErrorInfo error = new ErrorInfo(CollectionUtil.getFirstElement(spec.getCleanupMethods()), t);
runStatus = supervisor.error(error);
}
}
}
private void invoke(Object target, MethodInfo method, Object... arguments) {
if (method == null || method.isExcluded()) return;
// fast lane
if (method.getInterceptors().isEmpty()) {
invokeRaw(target, method, arguments);
return;
}
// slow lane
MethodInvocation invocation = new MethodInvocation(currentFeature,
currentIteration, sharedInstance, currentInstance, target, method, arguments);
try {
invocation.proceed();
} catch (Throwable t) {
ErrorInfo error = new ErrorInfo(method, t);
runStatus = supervisor.error(error);
}
}
protected Object invokeRaw(Object target, MethodInfo method, Object... arguments) {
try {
return method.invoke(target, arguments);
} catch (Throwable t) {
runStatus = supervisor.error(new ErrorInfo(method, t));
return null;
}
}
protected SpecificationContext getSpecificationContext() {
return (SpecificationContext) currentInstance.getSpecificationContext();
}
}