Spock: Parameter type is inferred wrong when local variable with same name is present in unrelated method

Created on 22 Aug 2018  Â·  13Comments  Â·  Source: spockframework/spock

Issue description

Using a where: clause, the type of a parameter is inferred incorrectly from the type of a local variable declared in an unrelated method when using Groovy 2.5. The issue does not occur with Groovy 2.4

How to reproduce

Consider the following code:

import spock.lang.Specification
import spock.lang.Unroll

@Unroll
class MyTest extends Specification {
    def "#a <=> #b: #result"() {
        expect:
        Math.signum(a <=> b) == result

        where:
        a            | b            | result
        "abcdef12"   | "abcdef12"   | 0
    }

    private double[] someOtherMethod() {
        double[] result = new double[0]
        return result
    }
}

This works fine when using Groovy 2.4, but fails when using Groovy 2.5 – both with Spock 1.0-groovy-2.4 and 1.2-RC1-groovy-2.5:

abcdef12 <=> abcdef12: [0.0]
Condition not satisfied:

Math.signum(a <=> b) == result
     |      | |   |  |  |
     0.0    | 0   |  |  [0.0]
            |     |  false
            |     abcdef12
            abcdef12

    at MyTest.#a <=> #b: #result(MyTest.groovy:8)

Notice that result's value is printed as [0.0], which matches the type of the local variable result defined in someOtherMethod(). If that method is removed, or the result parameter in the test is renamed, the test works as expected.

Link to a gist or similar (optional)

Here's a full build to reproduce the problem: https://github.com/lptr/spock-groovy-bug

Just run ./gradlew test

Additional Environment information

Gradle 4.9, Groovy 2.5.2, Spock 1.2-RC1

Most helpful comment

This StackOverflow question also describes the same problem and points to this ticket. It has been open for 14+ months now and I think it ought to be addressed soon, especially given the fact that Groovy 3 is also affected.

All 13 comments

@paulk-asert any idea what changed with groovy 2.5 that causes this?

@lptr my suggestion instead of changing the name of the parameter would be to explicitly declare the method parameters with their correct type.

@paulk-asert In Groovy 2.5 during AST

Phase Instruction Selection

    public java.lang.Object $spock_feature_0_0proc(java.lang.Object $spock_p0, java.lang.Object $spock_p1, java.lang.Object $spock_p2) {
        java.lang.Object a = $spock_p0 
        java.lang.Object b = $spock_p1 
        java.lang.Object result = $spock_p2 
        return new java.lang.Object[]
    }

Phase Class Generation

    public java.lang.Object $spock_feature_0_0proc(java.lang.Object $spock_p0, java.lang.Object $spock_p1, java.lang.Object $spock_p2) {
        java.lang.Object a = $spock_p0 
        java.lang.Object b = $spock_p1 
        [D result = $spock_p2 
        return new java.lang.Object[]
    }

While in 2.4 it stays as java.lang.Object

@leonard84 Nothing springs to mind but looks like a bug somewhere. I'll take a look next week.

Encountered the same problem with Griffon 3.x. Workaround was to rename the variable https://github.com/griffon/griffon/commit/afed03a66db9e8d7119f692987c4bc96824e4cd6 @paulk-asert

This even occurs if parameter is typed

class DummyTest extends Specification {

    @Unroll
    def "roll #x"(Integer x) {
        expect:
        1 == x

        where:
        x << [1]
    }

    private int unused() {
        String x = "4"
        return 3
    }
}
java.lang.IllegalArgumentException: Method '$spock_feature_0_0([class java.lang.Integer])' can't be called with parameters '[1]'!

    at org.spockframework.util.ReflectionUtil.validateArguments(ReflectionUtil.java:212)
    at org.spockframework.util.ReflectionUtil.invokeMethod(ReflectionUtil.java:199)
    at org.spockframework.runtime.model.MethodInfo.invoke(MethodInfo.java:113)
    at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
    at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
    at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47)
    at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
    at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)

Just bumping this issue because I am seeing this issue as well. The following test class fails with the error below:

import spock.lang.Specification
import spock.lang.Unroll

@Unroll
class TestSpec extends Specification {

  void 'test test list'() {
    List expectedResponse = ['foo']
  }

  def 'test test'() {
    expect:
    expectedResponse

    where:
    _ | expectedResponse
    _ | ['message': "no products found"]
  }

}
Cannot cast object '{message=no products found}' with class 'java.util.LinkedHashMap' to class 'java.util.List' due to: groovy.lang.GroovyRuntimeException: Could not find matching constructor for: java.util.List(LinkedHashMap)
org.codehaus.groovy.runtime.typehandling.GroovyCastException: Cannot cast object '{message=no products found}' with class 'java.util.LinkedHashMap' to class 'java.util.List' due to: groovy.lang.GroovyRuntimeException: Could not find matching constructor for: java.util.List(LinkedHashMap)
    at TestSpec.test test(TestSpec.groovy:18)

@paulk-asert it still happens with groovy-2.5.7 :(

Do you know if there is a reason why in the statement below, the result VariableExpression has the accessedVariable set to a dynamic variable rather than the result parameter from the $spock_feature_0_0 method? Taken from the groovyConsole AST view for above:
org.spockframework.runtime.SpockRuntime.verifyCondition($spock_errorCollector, $spock_valueRecorder.reset(), 'Math.signum(a <=> b) == result', 9, 9, null, $spock_valueRecorder.record($spock_valueRecorder.startRecordingValue(8), $spock_valueRecorder.record($spock_valueRecorder.startRecordingValue(6), java.lang.Math.$spock_valueRecorder.record($spock_valueRecorder.startRecordingValue(1), 'signum')($spock_valueRecorder.record($spock_valueRecorder.startRecordingValue(4), $spock_valueRecorder.record($spock_valueRecorder.startRecordingValue(2), a) <=> $spock_valueRecorder.record($spock_valueRecorder.startRecordingValue(3), b)))) == $spock_valueRecorder.record($spock_valueRecorder.startRecordingValue(7), result)))

@paulk-asert not really, that is rather old code that I've not written. The code is generated via org.spockframework.compiler.ConditionRewriter#record.

@leonard84 is there any progress on this or maybe some workaround (except avoiding variable name equality)?

Also suffering from this…

I'd really like to migrate from Groovy 2.4.x to 2.5.x but this issue is a showstopper. We have a lot of spock based tests which break because of this.

:-(

This StackOverflow question also describes the same problem and points to this ticket. It has been open for 14+ months now and I think it ought to be addressed soon, especially given the fact that Groovy 3 is also affected.

There are enough tests to cover this, but FWIW let me just say that I also re-tested the current master against the code from the SO question I mentioned earlier and the issue there is fixed for that case. Thanks, @Vampire.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

ahlinist picture ahlinist  Â·  7Comments

marceloverdijk picture marceloverdijk  Â·  7Comments

YaroSpace picture YaroSpace  Â·  7Comments

Derbeth picture Derbeth  Â·  10Comments

lavcraft picture lavcraft  Â·  3Comments