Skip to content

Commit

Permalink
Fix for #917: infer return type of Spock's ValueRecorder.record method
Browse files Browse the repository at this point in the history
  • Loading branch information
eric-milles committed Jun 24, 2019
1 parent f7c0d3b commit d39bd78
Show file tree
Hide file tree
Showing 5 changed files with 165 additions and 86 deletions.
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -1998,21 +1998,18 @@ public void testSpock_GRE558() throws Exception {
assumeFalse(isAtLeastGroovy(30)); // TODO: Remove when spock-core supports Groovy 3

IPath[] paths = createSimpleProject("Project", true);
env.addJar(paths[0], "lib/spock-core-1.2-groovy-2.4.jar");
env.addJar(paths[0], "lib/spock-core-1.3-groovy-2.4.jar");
env.addEntry(paths[0], JavaCore.newContainerEntry(new Path("org.eclipse.jdt.junit.JUNIT_CONTAINER/4")));

env.addGroovyClass(paths[1], "", "MyTest",
//@formatter:off
"import org.junit.runner.RunWith\n" +
"import spock.lang.Specification\n" +
"\n" +
"final class MyTest extends Specification {\n" +
" def aField\n" +
" def aMethod() {\n" +
" expect:\n" +
" println 'hello'\n" +
"final class MyTest extends spock.lang.Specification {\n" +
" def prop\n" +
" def meth() {\n" +
" expect:\n" +
" 'hello' != 'world'\n" +
" }\n" +
" static void main(String[] argv) {\n" +
" static main(args) {\n" +
" print 'success'\n" +
" }\n" +
"}\n");
Expand All @@ -2034,35 +2031,27 @@ public void testSpock_GRE605_1() throws Exception {
assumeFalse(isAtLeastGroovy(30)); // TODO: Remove when spock-core supports Groovy 3

IPath[] paths = createSimpleProject("Project", true);
env.addJar(paths[0], "lib/spock-core-1.2-groovy-2.4.jar");
env.addJar(paths[0], "lib/spock-core-1.3-groovy-2.4.jar");
env.addEntry(paths[0], JavaCore.newContainerEntry(new Path("org.eclipse.jdt.junit.JUNIT_CONTAINER/4")));

env.addGroovyClass(paths[1], "", "FoobarSpec",
//@formatter:off
"import spock.lang.Specification\n" +
"\n" +
"class FoobarSpec extends Specification {\n" +
" \n" +
" Foobar barbar\n" +
" \n" +
" def example() {\n" +
" when: \n" +
" def foobar = new Foobar()\n" +
" \n" +
" then:\n" +
" foobar.baz == 42\n" +
" }\n" +
"class FoobarSpec extends spock.lang.Specification {\n" +
" private Foobar field\n" +
" def example() {\n" +
" when: \n" +
" def foobar = new Foobar()\n" +
" \n" +
" then:\n" +
" foobar.baz == 42\n" +
" }\n" +
"}");
//@formatter:on

env.addGroovyClass(paths[1], "", "Foobar",
//@formatter:off
"class Foobar {\n" +
"\n" +
"def baz = 42\n" +
"//def quux = 36\n" +
"\n" +
" def baz = 42\n" +
"}\n");
//@formatter:on

Expand All @@ -2077,10 +2066,8 @@ public void testSpock_GRE605_1() throws Exception {
env.addGroovyClass(paths[1], "", "Foobar",
//@formatter:off
"class Foobar {\n" +
"\n" +
"def baz = 42\n" +
"def quux = 36\n" +
"\n" +
" def baz = 42\n" +
" def xyz = 36\n" +
"}\n");
//@formatter:on

Expand All @@ -2106,34 +2093,27 @@ public void testSpock_GRE605_2() throws Exception {
assumeFalse(isAtLeastGroovy(30)); // TODO: Remove when spock-core supports Groovy 3

IPath[] paths = createSimpleProject("Project", true);
env.addJar(paths[0], "lib/spock-core-1.2-groovy-2.4.jar");
env.addJar(paths[0], "lib/spock-core-1.3-groovy-2.4.jar");
env.addEntry(paths[0], JavaCore.newContainerEntry(new Path("org.eclipse.jdt.junit.JUNIT_CONTAINER/4")));

env.addGroovyClass(paths[1], "", "FoobarSpec",
//@formatter:off
"import spock.lang.Specification\n" +
"\n" +
"class FoobarSpec extends Specification {\n" +
" \n" +
" Foobar foob\n" +
" def example() {\n" +
" when: \n" +
" def foobar = new Foobar()\n" +
" \n" +
" then:\n" +
" foobar.baz == 42\n" +
" }\n" +
"class FoobarSpec extends spock.lang.Specification {\n" +
" private Foobar field\n" +
" def example() {\n" +
" given: \n" +
" def foobar = new Foobar()\n" +
" \n" +
" expect:\n" +
" foobar.baz == 42\n" +
" }\n" +
"}");
//@formatter:on

env.addGroovyClass(paths[1], "", "Foobar",
//@formatter:off
"class Foobar {\n" +
"\n" +
"def baz = 42\n" +
"//def quux = 36\n" +
"\n" +
" def baz = 42\n" +
"}\n");
//@formatter:on

Expand All @@ -2144,10 +2124,8 @@ public void testSpock_GRE605_2() throws Exception {
env.addGroovyClass(paths[1], "", "Foobar",
//@formatter:off
"class Foobar {\n" +
"\n" +
"def baz = 42\n" +
"def quux = 36\n" +
"\n" +
" def baz = 42\n" +
" def xyz = 36\n" +
"}\n");
//@formatter:on

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
* 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
* https://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,
Expand All @@ -21,39 +21,26 @@
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.Path;
import org.eclipse.jdt.core.JavaCore;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

public final class SpockInferencingTests extends InferencingTestSuite {

private String xforms;

@Before
public void setUp() throws Exception {
assumeFalse(isAtLeastGroovy(30)); // TODO: Remove when spock-core supports Groovy 3

IPath projectPath = project.getFullPath();
env.addJar(projectPath, "lib/spock-core-1.2-groovy-2.4.jar");
env.addJar(projectPath, "lib/spock-core-1.3-groovy-2.4.jar");
env.addEntry(projectPath, JavaCore.newContainerEntry(new Path("org.eclipse.jdt.junit.JUNIT_CONTAINER/4")));

xforms = System.setProperty("greclipse.globalTransformsInReconcile", "org.spockframework.compiler.SpockTransform");
}

@After
public void tearDown() {
if (xforms == null) {
System.clearProperty("greclipse.globalTransformsInReconcile");
} else {
System.setProperty("greclipse.globalTransformsInReconcile", xforms);
}
}

@Test
public void testBasics() throws Exception {
createUnit("foo", "Bar", "package foo; class Bar {\n Integer baz\n}");

String source =
//@formatter:off
"final class SpockTests extends spock.lang.Specification {\n" +
" void 'test the basics'() {\n" +
" given:\n" +
Expand All @@ -66,6 +53,7 @@ public void testBasics() throws Exception {
" bar != new foo.Bar(baz:42)\n" +
" }\n" +
"}\n";
//@formatter:on

int offset = source.indexOf("bar");
assertType(source, offset, offset + 3, "foo.Bar");
Expand All @@ -74,9 +62,75 @@ public void testBasics() throws Exception {
assertType(source, offset, offset + 3, "foo.Bar");
}

@Test
public void testEqualsCheck() throws Exception {
createUnit("foo", "Bar", "package foo; class Bar {\n Integer baz\n}");

String source =
//@formatter:off
"final class SpockTests extends spock.lang.Specification {\n" +
" void 'test the property'() {\n" +
" given:\n" +
" def bar = new foo.Bar()\n" +
" \n" +
" expect:\n" +
" !bar.equals(null)\n" +
" }\n" +
"}\n";
//@formatter:on

int offset = source.lastIndexOf("equals");
assertType(source, offset, offset + 6, "java.lang.Boolean");
assertDeclaringType(source, offset, offset + 6, "java.lang.Object");
}

@Test
public void testGetterCheck() throws Exception {
createUnit("foo", "Bar", "package foo; class Bar {\n Integer baz\n}");

String source =
//@formatter:off
"final class SpockTests extends spock.lang.Specification {\n" +
" void 'test the property'() {\n" +
" given:\n" +
" def bar = new foo.Bar(baz: 42)\n" +
" \n" +
" expect:\n" +
" bar.getBaz() == 42\n" +
" }\n" +
"}\n";
//@formatter:on

int offset = source.lastIndexOf("getBaz");
assertType(source, offset, offset + 6, "java.lang.Integer");
assertDeclaringType(source, offset, offset + 6, "foo.Bar");
}

@Test
public void testPropertyCheck() throws Exception {
createUnit("foo", "Bar", "package foo; class Bar {\n Integer baz\n}");

String source =
//@formatter:off
"final class SpockTests extends spock.lang.Specification {\n" +
" void 'test the property'() {\n" +
" given:\n" +
" def bar = new foo.Bar(baz: 42)\n" +
" \n" +
" expect:\n" +
" bar.baz == 42\n" +
" }\n" +
"}\n";
//@formatter:on

int offset = source.lastIndexOf("baz");
assertType(source, offset, offset + 3, "java.lang.Integer");
}

@Test // https://github.com/groovy/groovy-eclipse/issues/812
public void testDataTable() {
public void testDataTableChecks() {
String source =
//@formatter:off
"final class SpockTests extends spock.lang.Specification {\n" +
" @spock.lang.Unroll\n" +
" void 'test #a == #b'() {\n" +
Expand All @@ -88,6 +142,7 @@ public void testDataTable() {
" 2 | a\n" +
" }\n" +
"}\n";
//@formatter:on

int offset = source.indexOf("a == b");
assertType(source, offset, offset + 1, "java.lang.Object");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1429,17 +1429,26 @@ public void visitMethodCallExpression(MethodCallExpression node) {
visitMethodOverrides(type);
}

MethodNode meth; // if return type depends on any Closure argument return types, deal with that now
if (t.declaration instanceof MethodNode && (meth = (MethodNode) t.declaration).getGenericsTypes() != null &&
Arrays.stream(meth.getParameters()).anyMatch(p -> p.getType().equals(VariableScope.CLOSURE_CLASS_NODE))) {
scope.setMethodCallArgumentTypes(getMethodCallArgumentTypes(node));
scope.setMethodCallGenericsTypes(getMethodCallGenericsTypes(node));
try {
boolean isStatic = (node.getObjectExpression() instanceof ClassExpression);
returnType = lookupExpressionType(node.getMethod(), objExprType, isStatic, scope).type;
} finally {
scope.setMethodCallArgumentTypes(null);
scope.setMethodCallGenericsTypes(null);
if (t.declaration instanceof MethodNode) {
MethodNode meth = (MethodNode) t.declaration;
// if return type depends on any Closure argument return types, deal with that
if (meth.getGenericsTypes() != null && Arrays.stream(meth.getParameters())
.anyMatch(p -> p.getType().equals(VariableScope.CLOSURE_CLASS_NODE))) {
scope.setMethodCallArgumentTypes(getMethodCallArgumentTypes(node));
scope.setMethodCallGenericsTypes(getMethodCallGenericsTypes(node));
try {
boolean isStatic = (node.getObjectExpression() instanceof ClassExpression);
returnType = lookupExpressionType(node.getMethod(), objExprType, isStatic, scope).type;
} finally {
scope.setMethodCallArgumentTypes(null);
scope.setMethodCallGenericsTypes(null);
}
} else if (t.declaringType.getName().equals("org.spockframework.runtime.ValueRecorder")) {
switch (meth.getName()) {
case "record":
case "realizeNas":
returnType = primaryTypeStack.removeLast();
}
}
}

Expand Down Expand Up @@ -2374,7 +2383,44 @@ private boolean isPrimaryExpression(Expression expr) {
result[0] = (expr == statement.getExpression());
}
});
return result[0];
if (result[0]) return true;
}
return isSpockValueRecorderArgument(expr);
}

/**
* Spock rewrites the "expect" statements from <code>foo.bar == baz</code> to:
* <pre>
* org.spockframework.runtime.SpockRuntime.verifyCondition(
* $spock_errorCollector,
* $spock_valueRecorder.reset(),
* foo == bar.baz, 14, 5, null,
* $spock_valueRecorder.record(
* $spock_valueRecorder.startRecordingValue(3),
* (
* $spock_valueRecorder.record(
* $spock_valueRecorder.startRecordingValue(1),
* $spock_valueRecorder.record(
* $spock_valueRecorder.startRecordingValue(0), foo).bar
* ) == $spock_valueRecorder.record($spock_valueRecorder.startRecordingValue(2), baz)
* )
* )
* )
* </pre>
*
* ValueRecorder.record is defined as: <code>public Object record(int index, Object value)</code>.
* So, we want to save the inferred type of the last argument so it can be used as the return type.
*/
private boolean isSpockValueRecorderArgument(Expression expr) {
VariableScope.CallAndType cat = scopes.getLast().getEnclosingMethodCallExpression();
if (cat != null && cat.declaration instanceof MethodNode && cat.declaringType.getName().equals("org.spockframework.runtime.ValueRecorder")) {
ArgumentListExpression args = (ArgumentListExpression) cat.call.getArguments();
MethodNode meth = (MethodNode) cat.declaration;
switch (meth.getName()) {
case "record":
case "realizeNas":
return expr == DefaultGroovyMethods.last(args.getExpressions());
}
}
return false;
}
Expand Down
Loading

0 comments on commit d39bd78

Please sign in to comment.