Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Data table cells can reference previous ones #526

Merged
merged 1 commit into from
Nov 13, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions spock-core/src/main/java/org/spockframework/compiler/AstUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
import org.codehaus.groovy.ast.*;
import org.codehaus.groovy.ast.expr.*;
import org.codehaus.groovy.ast.stmt.*;
import org.codehaus.groovy.runtime.dgmimpl.arrays.*;

import org.objectweb.asm.Opcodes;

import org.spockframework.lang.Wildcard;
Expand All @@ -36,6 +38,8 @@
* @author Peter Niederwieser
*/
public abstract class AstUtil {
private static String GET_AT_METHOD_NAME = new IntegerArrayGetAtMetaMethod().getName();

/**
* Tells whether the given node has an annotation of the given type.
*
Expand Down Expand Up @@ -312,6 +316,12 @@ public static void fixUpLocalVariables(Variable[] localVariables, VariableScope
fixUpLocalVariables(Arrays.asList(localVariables), scope, isClosureScope);
}

public static MethodCallExpression createGetAtMethod(Expression expression, int index) {
return new MethodCallExpression(expression,
GET_AT_METHOD_NAME,
new ConstantExpression(index));
}

/**
* Fixes up scope references to variables that used to be free or class variables,
* and have been changed to local variables.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@
import org.spockframework.runtime.model.DataProviderMetadata;
import org.spockframework.util.*;

import static org.spockframework.compiler.AstUtil.createGetAtMethod;

/**
*
* @author Peter Niederwieser
Expand Down Expand Up @@ -77,7 +79,6 @@ private void rewriteWhereStat(ListIterator<Statement> stats) throws InvalidSpecC
if (binExpr == null || binExpr.getClass() != BinaryExpression.class) // don't allow subclasses like DeclarationExpression
notAParameterization(stat);

@SuppressWarnings("ConstantConditions")
int type = binExpr.getOperation().getType();

if (type == Types.LEFT_SHIFT) {
Expand All @@ -86,25 +87,29 @@ private void rewriteWhereStat(ListIterator<Statement> stats) throws InvalidSpecC
rewriteSimpleParameterization(binExpr, stat);
else if (leftExpr instanceof ListExpression)
rewriteMultiParameterization(binExpr, stat);
else notAParameterization(stat);
else
notAParameterization(stat);
} else if (type == Types.ASSIGN)
rewriteDerivedParameterization(binExpr, stat);
else if (getOrExpression(binExpr) != null) {
stats.previous();
rewriteTableLikeParameterization(stats);
}
else notAParameterization(stat);
else
notAParameterization(stat);
}

private void createDataProviderMethod(Expression dataProviderExpr, int nextDataVariableIndex) {
instanceFieldAccessChecker.check(dataProviderExpr);

dataProviderExpr = dataProviderExpr.transformExpression(new DataTablePreviousVariableTransformer());

MethodNode method =
new MethodNode(
InternalIdentifiers.getDataProviderName(whereBlock.getParent().getAst().getName(), dataProviderCount++),
Opcodes.ACC_PUBLIC | Opcodes.ACC_SYNTHETIC,
ClassHelper.OBJECT_TYPE,
Parameter.EMPTY_ARRAY,
getPreviousParameters(nextDataVariableIndex),
ClassNode.EMPTY_ARRAY,
new BlockStatement(
Arrays.<Statement> asList(
Expand All @@ -116,6 +121,14 @@ Arrays.<Statement> asList(
whereBlock.getParent().getParent().getAst().addMethod(method);
}

private Parameter[] getPreviousParameters(int nextDataVariableIndex) {
Parameter[] results = new Parameter[nextDataVariableIndex];
for (int i = 0; i < nextDataVariableIndex; i++)
results[i] = new Parameter(ClassHelper.DYNAMIC_TYPE,
dataProcessorVars.get(i).getName());
return results;
}

private AnnotationNode createDataProviderAnnotation(Expression dataProviderExpr, int nextDataVariableIndex) {
AnnotationNode ann = new AnnotationNode(resources.getAstNodeCache().DataProviderMetadata);
ann.addMember(DataProviderMetadata.LINE, new ConstantExpression(dataProviderExpr.getLineNumber()));
Expand Down Expand Up @@ -171,10 +184,7 @@ private void rewriteMultiParameterization(BinaryExpression binExpr, Statement en
new DeclarationExpression(
dataVar,
Token.newSymbol(Types.ASSIGN, -1, -1),
new MethodCallExpression(
new VariableExpression(dataProcessorParameter),
"getAt",
new ConstantExpression(i))));
createGetAtMethod(new VariableExpression(dataProcessorParameter), i)));
exprStat.setSourcePosition(enclosingStat);
dataProcessorStats.add(exprStat);
}
Expand Down Expand Up @@ -300,7 +310,7 @@ private void verifyDataProcessorVariable(VariableExpression varExpr) {
return;
}

if (getDataProcessorVariable(varExpr.getName()) != null) {
if (isDataProcessorVariable(varExpr.getName())) {
resources.getErrorReporter().error(varExpr, "Duplicate declaration of data variable '%s'", varExpr.getName());
return;
}
Expand All @@ -312,11 +322,11 @@ private void verifyDataProcessorVariable(VariableExpression varExpr) {
}
}

private VariableExpression getDataProcessorVariable(String name) {
private boolean isDataProcessorVariable(String name) {
for (VariableExpression var : dataProcessorVars)
if (var.getName().equals(name)) return var;

return null;
if (var.getName().equals(name))
return true;
return false;
}

private void handleFeatureParameters() {
Expand All @@ -329,7 +339,7 @@ private void handleFeatureParameters() {

private void checkAllParametersAreDataVariables(Parameter[] parameters) {
for (Parameter param : parameters)
if (getDataProcessorVariable(param.getName()) == null)
if (!isDataProcessorVariable(param.getName()))
resources.getErrorReporter().error(param, "Parameter '%s' does not refer to a data variable", param.getName());
}

Expand Down Expand Up @@ -386,4 +396,27 @@ public void visitBlockStatement(BlockStatement stat) {
AstUtil.fixUpLocalVariables(dataProcessorVars, stat.getVariableScope(), false);
}
}

private class DataTablePreviousVariableTransformer extends ClassCodeExpressionTransformer {
private int depth = 0, rowIndex = 0;

@Override
protected SourceUnit getSourceUnit() { return null; }

@Override
public Expression transform(Expression expression) {
if ((expression instanceof VariableExpression) && isDataProcessorVariable(expression.getText())) {
return AstUtil.createGetAtMethod(expression, rowIndex);
}

depth++;
Expression transform = super.transform(expression);
depth--;

if (depth == 0)
rowIndex++;

return transform;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,10 @@

package org.spockframework.runtime;

import java.util.Iterator;
import java.util.List;
import java.util.*;

import static org.spockframework.runtime.RunStatus.*;
import org.spockframework.runtime.model.*;
import org.spockframework.runtime.GroovyRuntimeUtil;

/**
* Adds the ability to run parameterized features.
Expand Down Expand Up @@ -50,21 +48,43 @@ private Object[] createDataProviders() {
List<DataProviderInfo> dataProviderInfos = currentFeature.getDataProviders();
Object[] dataProviders = new Object[dataProviderInfos.size()];

for (int i = 0; i < dataProviderInfos.size(); i++) {
MethodInfo method = dataProviderInfos.get(i).getDataProviderMethod();
Object provider = invokeRaw(sharedInstance, method, EMPTY_ARGS);
if (runStatus != OK) return null;
if (provider == null) {
runStatus = supervisor.error(
new ErrorInfo(method, new SpockExecutionException("Data provider is null")));
return null;
if (!dataProviderInfos.isEmpty()) {
for (int i = 0; i < dataProviderInfos.size(); i++) {
DataProviderInfo dataProviderInfo = dataProviderInfos.get(i);

MethodInfo method = dataProviderInfo.getDataProviderMethod();
Object[] arguments = Arrays.copyOf(dataProviders, getDataTableOffset(dataProviderInfo));
Object provider = invokeRaw(sharedInstance, method, arguments);

if (runStatus != OK)
return null;
else if (provider == null) {
SpockExecutionException error = new SpockExecutionException("Data provider is null!");
runStatus = supervisor.error(new ErrorInfo(method, error));
return null;
}
dataProviders[i] = provider;
}
dataProviders[i] = provider;
}

return dataProviders;
}

private int getDataTableOffset(DataProviderInfo dataProviderInfo) {
int result = 0;
for (String variableName : dataProviderInfo.getDataVariables()) {
for (String parameterName : currentFeature.getParameterNames()) {
if (variableName.equals(parameterName))
return result;
else
result++;
}
}
throw new IllegalStateException(String.format("Variable name not defined (%s not in %s)!",
dataProviderInfo.getDataVariables(),
currentFeature.getParameterNames()));
}

private Iterator[] createIterators(Object[] dataProviders) {
if (runStatus != OK) return null;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,7 @@
import java.io.File;
import java.lang.annotation.Annotation;
import java.lang.reflect.*;
import java.util.ArrayList;
import java.util.List;
import java.util.*;

import org.spockframework.gentyref.GenericTypeReflector;

Expand Down Expand Up @@ -117,17 +116,18 @@ public static File getClassFile(Class<?> clazz) {
return clazzFile.isFile() ? clazzFile : null;
}

@Nullable
public static Object getDefaultValue(Class<?> type) {
if (!type.isPrimitive()) return null;

if (type == boolean.class) return false;
if (type == int.class) return 0;
if (type == long.class) return 0l;
if (type == float.class) return 0f;
if (type == double.class) return 0d;
if (type == char.class) return (char) 0;
if (type == short.class) return (short) 0;
if (type == byte.class) return (byte) 0;
else if (type == boolean.class) return false;
else if (type == int.class) return 0;
else if (type == long.class) return 0L;
else if (type == float.class) return 0F;
else if (type == double.class) return 0D;
else if (type == char.class) return (char) 0;
else if (type == short.class) return (short) 0;
else if (type == byte.class) return (byte) 0;

assert type == void.class;
return null;
Expand All @@ -150,6 +150,7 @@ public static Class[] getTypes(Object... objects) {
@Nullable
public static Object invokeMethod(@Nullable Object target, Method method, @Nullable Object... args) {
try {
validateArguments(method, args);
return method.invoke(target, args);
} catch (IllegalAccessException e) {
ExceptionUtil.sneakyThrow(e);
Expand All @@ -160,11 +161,45 @@ public static Object invokeMethod(@Nullable Object target, Method method, @Nulla
}
}

public static void validateArguments(Method method, Object[] args) {
if (!hasValidArguments(args, method.getParameterTypes()))
throw new IllegalArgumentException(
String.format("Method '%s(%s)' can't be called with parameters '%s'!",
method.getName(), Arrays.toString(method.getParameterTypes()), Arrays.toString(args)));
}

public static boolean hasValidArguments(Object[] args, Class<?>[] parameterTypes) {
if (parameterTypes.length != args.length)
return false;

for (int i = 0; i < parameterTypes.length; i++) {
if (!isAssignable(parameterTypes[i], args[i]))
return false;
}

return true;
}

public static boolean isAssignable(Class<?> type, @Nullable Object arg) {
if (arg == null)
return !type.isPrimitive();

type = getWrapperType(type);
Class<?> argType = getWrapperType(arg.getClass());
return type.isAssignableFrom(argType);
}

@SuppressWarnings("ConstantConditions")
private static Class<?> getWrapperType(Class<?> type) {
return type.isPrimitive() ? getDefaultValue(type).getClass()
: type;
}

public static List<Class<?>> eraseTypes(List<Type> types) {
List<Class<?>> result = new ArrayList<Class<?>>();
for (Type type : types) {
result.add(GenericTypeReflector.erase(type));
}
return result;
}
}
}
Loading