spockframework / spock

The Enterprise-ready testing and specification framework.
https://spockframework.org
Apache License 2.0
3.54k stars 467 forks source link

BiPredicate Unit Test Failed: module jdk.proxy2 does not open jdk.proxy2 to unnamed module #1412

Closed moqimoqidea closed 2 years ago

moqimoqidea commented 2 years ago

Describe the bug

java.lang.reflect.InvocationTargetException
    at org.codehaus.groovy.vmplugin.v9.Java9.of(Java9.java:160)
    at org.codehaus.groovy.vmplugin.v9.Java9.getInvokeSpecialHandle(Java9.java:179)
    at java.base/java.util.concurrent.ConcurrentHashMap.computeIfAbsent(ConcurrentHashMap.java:1708)
    at com.moqi.archive.in20210711.BiPredicateDemoTest.test a domain with or syntax should return right result(BiPredicateDemoTest.groovy:60)
Caused by: java.lang.IllegalAccessException: module jdk.proxy2 does not open jdk.proxy2 to unnamed module @7eac9008
    at java.base/java.lang.invoke.MethodHandles.privateLookupIn(MethodHandles.java:259)
    ... 4 more

To Reproduce

Preview:

package com.moqi

import spock.lang.Specification

import java.util.function.BiPredicate
import java.util.stream.Collectors

/**
 * Java 8 BiPredicate Examples:
 * https://mkyong.com/java8/java-8-bipredicate-examples/
 *
 * @author moqi* On 2021/7/11 16:05
 */
class BiPredicateDemoTest extends Specification {

    def "test a domain with or syntax should return right result"() {
        given:
        def domainList = Arrays.asList(new Domain("google.com", 1),
                new Domain("i-am-spammer.com", 10),
                new Domain("mkyong.com", 0),
                new Domain("microsoft.com", 2))
        BiPredicate<String, Integer> bi = (domain, score) -> (domain.equalsIgnoreCase("google.com") || score == 0)

        when:
        def result = filterBadDomain(domainList, bi | (domain, x) -> domain.equalsIgnoreCase("MICROSOFT.COM"))

        then:
        result == [["google.com", 1] as Domain, ["mkyong.com", 0] as Domain, ["microsoft.com", 2] as Domain]
    }

    static <T extends Domain> List<T> filterBadDomain(
            List<T> list, BiPredicate<String, Integer> biPredicate
    ) {
        return list.stream()
                .filter(x -> biPredicate.test(x.name, x.score))
                .collect(Collectors.toList())
    }

}

class Domain {
    String name
    Integer score

    Domain(String name, Integer score) {
        this.name = name
        this.score = score
    }

    boolean equals(o) {
        if (this.is(o)) return true
        if (getClass() != o.class) return false

        Domain domain = (Domain) o

        if (name != domain.name) return false
        if (score != domain.score) return false

        return true
    }

    int hashCode() {
        int result
        result = (name != null ? name.hashCode() : 0)
        result = 31 * result + (score != null ? score.hashCode() : 0)
        return result
    }

    @Override
    String toString() {
        return "Domain{" +
                "name='" + name + '\'' +
                ", score=" + score +
                '}'
    }
}

Expected behavior

The code should not fail.

Actual behavior

The code fails with the given exception.

Java version

openjdk version "17.0.1" 2021-10-19 LTS OpenJDK Runtime Environment Corretto-17.0.1.12.1 (build 17.0.1+12-LTS) OpenJDK 64-Bit Server VM Corretto-17.0.1.12.1 (build 17.0.1+12-LTS, mixed mode, sharing)

Buildtool version


Gradle 7.3.3

Build time: 2021-12-22 12:37:54 UTC Revision: 6f556c80f945dc54b50e0be633da6c62dbe8dc71

Kotlin: 1.5.31 Groovy: 3.0.9 Ant: Apache Ant(TM) version 1.10.11 compiled on July 10 2021 JVM: 17.0.1 (Amazon.com Inc. 17.0.1+12-LTS) OS: Mac OS X 12.1 x86_64

What operating system are you using

Mac

Dependencies

------------------------------------------------------------
Root project 'moqi-tool-java' - moqi-tool-java
------------------------------------------------------------

testRuntimeClasspath - Runtime classpath of source set 'test'.
+--- org.slf4j:slf4j-api:1.7.32
+--- org.slf4j:slf4j-log4j12:1.7.32
|    +--- org.slf4j:slf4j-api:1.7.32
|    \--- log4j:log4j:1.2.17
+--- org.projectlombok:lombok:1.18.20
+--- org.codehaus.groovy:groovy:3.0.9
+--- org.codehaus.groovy:groovy-sql:3.0.9
|    \--- org.codehaus.groovy:groovy:3.0.9
+--- commons-beanutils:commons-beanutils:1.9.4
|    +--- commons-logging:commons-logging:1.2
|    \--- commons-collections:commons-collections:3.2.2
+--- net.sourceforge.plantuml:plantuml:1.2021.8
+--- org.apache.commons:commons-lang3:3.12.0
+--- com.alibaba:fastjson:1.2.78
+--- com.google.guava:guava:31.0-jre
|    +--- com.google.guava:failureaccess:1.0.1
|    +--- com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava
|    +--- com.google.code.findbugs:jsr305:3.0.2
|    +--- org.checkerframework:checker-qual:3.12.0 -> 3.18.0
|    +--- com.google.errorprone:error_prone_annotations:2.7.1
|    \--- com.google.j2objc:j2objc-annotations:1.3
+--- org.openjdk.jmh:jmh-core:1.32
|    +--- net.sf.jopt-simple:jopt-simple:4.6 -> 5.0.2
|    \--- org.apache.commons:commons-math3:3.2
+--- com.google.code.gson:gson:2.8.7
+--- com.fasterxml.jackson.core:jackson-databind:2.12.4
|    +--- com.fasterxml.jackson.core:jackson-annotations:2.12.4
|    |    \--- com.fasterxml.jackson:jackson-bom:2.12.4
|    |         +--- com.fasterxml.jackson.core:jackson-annotations:2.12.4 (c)
|    |         +--- com.fasterxml.jackson.core:jackson-core:2.12.4 (c)
|    |         +--- com.fasterxml.jackson.core:jackson-databind:2.12.4 (c)
|    |         +--- com.fasterxml.jackson.dataformat:jackson-dataformat-smile:2.12.4 (c)
|    |         +--- com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.12.4 (c)
|    |         \--- com.fasterxml.jackson.dataformat:jackson-dataformat-cbor:2.12.4 (c)
|    +--- com.fasterxml.jackson.core:jackson-core:2.12.4
|    |    \--- com.fasterxml.jackson:jackson-bom:2.12.4 (*)
|    \--- com.fasterxml.jackson:jackson-bom:2.12.4 (*)
+--- com.fasterxml.jackson.core:jackson-core:2.12.4 (*)
+--- com.fasterxml.jackson.core:jackson-annotations:2.12.4 (*)
+--- org.mybatis:mybatis:3.5.7
+--- org.mapstruct:mapstruct:1.4.2.Final
+--- io.sentry:sentry:5.0.1
|    \--- com.google.code.gson:gson:2.8.5 -> 2.8.7
+--- org.elasticsearch.client:elasticsearch-rest-high-level-client:7.16.1
|    +--- org.elasticsearch:elasticsearch:7.16.1
|    |    +--- org.elasticsearch:elasticsearch-core:7.16.1
|    |    +--- org.elasticsearch:elasticsearch-secure-sm:7.16.1
|    |    +--- org.elasticsearch:elasticsearch-x-content:7.16.1
|    |    |    +--- org.elasticsearch:elasticsearch-core:7.16.1
|    |    |    +--- org.yaml:snakeyaml:1.26
|    |    |    +--- com.fasterxml.jackson.core:jackson-core:2.10.4 -> 2.12.4 (*)
|    |    |    +--- com.fasterxml.jackson.dataformat:jackson-dataformat-smile:2.10.4 -> 2.12.4
|    |    |    +--- com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.10.4 -> 2.12.4
|    |    |    \--- com.fasterxml.jackson.dataformat:jackson-dataformat-cbor:2.10.4 -> 2.12.4
|    |    +--- org.elasticsearch:elasticsearch-geo:7.16.1
|    |    +--- org.elasticsearch:elasticsearch-lz4:7.16.1
|    |    |    +--- org.lz4:lz4-java:1.8.0
|    |    |    \--- org.elasticsearch:elasticsearch-core:7.16.1
|    |    +--- org.apache.lucene:lucene-core:8.10.1
|    |    +--- org.apache.lucene:lucene-analyzers-common:8.10.1
|    |    +--- org.apache.lucene:lucene-backward-codecs:8.10.1
|    |    +--- org.apache.lucene:lucene-grouping:8.10.1
|    |    +--- org.apache.lucene:lucene-highlighter:8.10.1
|    |    +--- org.apache.lucene:lucene-join:8.10.1
|    |    +--- org.apache.lucene:lucene-memory:8.10.1
|    |    +--- org.apache.lucene:lucene-misc:8.10.1
|    |    +--- org.apache.lucene:lucene-queries:8.10.1
|    |    +--- org.apache.lucene:lucene-queryparser:8.10.1
|    |    +--- org.apache.lucene:lucene-sandbox:8.10.1
|    |    +--- org.apache.lucene:lucene-spatial3d:8.10.1
|    |    +--- org.apache.lucene:lucene-suggest:8.10.1
|    |    +--- org.elasticsearch:elasticsearch-cli:7.16.1
|    |    |    +--- net.sf.jopt-simple:jopt-simple:5.0.2
|    |    |    \--- org.elasticsearch:elasticsearch-core:7.16.1
|    |    +--- com.carrotsearch:hppc:0.8.1
|    |    +--- joda-time:joda-time:2.10.10
|    |    +--- com.tdunning:t-digest:3.2
|    |    +--- org.hdrhistogram:HdrHistogram:2.1.9
|    |    +--- org.apache.logging.log4j:log4j-api:2.11.1 -> 2.14.1
|    |    +--- net.java.dev.jna:jna:5.10.0
|    |    \--- org.elasticsearch:elasticsearch-plugin-classloader:7.16.1
|    +--- org.elasticsearch.client:elasticsearch-rest-client:7.16.1
|    |    +--- org.apache.httpcomponents:httpclient:4.5.10
|    |    +--- org.apache.httpcomponents:httpcore:4.4.12
|    |    +--- org.apache.httpcomponents:httpasyncclient:4.1.4
|    |    +--- org.apache.httpcomponents:httpcore-nio:4.4.12
|    |    +--- commons-codec:commons-codec:1.11 -> 1.15
|    |    \--- commons-logging:commons-logging:1.1.3 -> 1.2
|    +--- org.elasticsearch.plugin:mapper-extras-client:7.16.1
|    +--- org.elasticsearch.plugin:parent-join-client:7.16.1
|    +--- org.elasticsearch.plugin:aggs-matrix-stats-client:7.16.1
|    +--- org.elasticsearch.plugin:rank-eval-client:7.16.1
|    \--- org.elasticsearch.plugin:lang-mustache-client:7.16.1
|         \--- com.github.spullara.mustache.java:compiler:0.9.6
+--- org.apache.logging.log4j:log4j-core:2.14.1
|    \--- org.apache.logging.log4j:log4j-api:2.14.1
+--- com.sparkjava:spark-core:2.9.3
|    +--- org.slf4j:slf4j-api:1.7.25 -> 1.7.32
|    +--- org.eclipse.jetty:jetty-server:9.4.31.v20200723
|    |    +--- javax.servlet:javax.servlet-api:3.1.0
|    |    +--- org.eclipse.jetty:jetty-http:9.4.31.v20200723
|    |    |    +--- org.eclipse.jetty:jetty-util:9.4.31.v20200723
|    |    |    \--- org.eclipse.jetty:jetty-io:9.4.31.v20200723
|    |    |         \--- org.eclipse.jetty:jetty-util:9.4.31.v20200723
|    |    \--- org.eclipse.jetty:jetty-io:9.4.31.v20200723 (*)
|    +--- org.eclipse.jetty:jetty-webapp:9.4.31.v20200723
|    |    +--- org.eclipse.jetty:jetty-xml:9.4.31.v20200723
|    |    |    \--- org.eclipse.jetty:jetty-util:9.4.31.v20200723
|    |    \--- org.eclipse.jetty:jetty-servlet:9.4.31.v20200723
|    |         \--- org.eclipse.jetty:jetty-security:9.4.31.v20200723
|    |              \--- org.eclipse.jetty:jetty-server:9.4.31.v20200723 (*)
|    +--- org.eclipse.jetty.websocket:websocket-server:9.4.31.v20200723
|    |    +--- org.eclipse.jetty.websocket:websocket-common:9.4.31.v20200723
|    |    |    +--- org.eclipse.jetty.websocket:websocket-api:9.4.31.v20200723
|    |    |    +--- org.eclipse.jetty:jetty-util:9.4.31.v20200723
|    |    |    \--- org.eclipse.jetty:jetty-io:9.4.31.v20200723 (*)
|    |    +--- org.eclipse.jetty.websocket:websocket-client:9.4.31.v20200723
|    |    |    +--- org.eclipse.jetty:jetty-client:9.4.31.v20200723
|    |    |    |    +--- org.eclipse.jetty:jetty-http:9.4.31.v20200723 (*)
|    |    |    |    \--- org.eclipse.jetty:jetty-io:9.4.31.v20200723 (*)
|    |    |    +--- org.eclipse.jetty:jetty-xml:9.4.31.v20200723 (*)
|    |    |    +--- org.eclipse.jetty:jetty-util:9.4.31.v20200723
|    |    |    +--- org.eclipse.jetty:jetty-io:9.4.31.v20200723 (*)
|    |    |    \--- org.eclipse.jetty.websocket:websocket-common:9.4.31.v20200723 (*)
|    |    +--- org.eclipse.jetty.websocket:websocket-servlet:9.4.31.v20200723
|    |    |    +--- org.eclipse.jetty.websocket:websocket-api:9.4.31.v20200723
|    |    |    \--- javax.servlet:javax.servlet-api:3.1.0
|    |    +--- org.eclipse.jetty:jetty-servlet:9.4.31.v20200723 (*)
|    |    \--- org.eclipse.jetty:jetty-http:9.4.31.v20200723 (*)
|    \--- org.eclipse.jetty.websocket:websocket-servlet:9.4.31.v20200723 (*)
+--- com.googlecode.aviator:aviator:5.2.7
+--- com.sun.mail:javax.mail:1.6.2
|    \--- javax.activation:activation:1.1
+--- org.ow2.asm:asm:9.2
+--- org.ow2.asm:asm-commons:9.2
|    +--- org.ow2.asm:asm:9.2
|    +--- org.ow2.asm:asm-tree:9.2
|    |    \--- org.ow2.asm:asm:9.2
|    \--- org.ow2.asm:asm-analysis:9.2
|         \--- org.ow2.asm:asm-tree:9.2 (*)
+--- org.apache.httpcomponents.client5:httpclient5:5.1
|    +--- org.apache.httpcomponents.core5:httpcore5:5.1.1
|    +--- org.apache.httpcomponents.core5:httpcore5-h2:5.1.1
|    |    \--- org.apache.httpcomponents.core5:httpcore5:5.1.1
|    +--- org.slf4j:slf4j-api:1.7.25 -> 1.7.32
|    \--- commons-codec:commons-codec:1.15
+--- org.apache.httpcomponents.client5:httpclient5-fluent:5.1
|    +--- org.apache.httpcomponents.client5:httpclient5:5.1 (*)
|    \--- org.slf4j:slf4j-api:1.7.25 -> 1.7.32
+--- org.checkerframework:checker-qual:3.18.0
+--- org.spockframework:spock-bom:2.0-groovy-3.0
|    \--- org.spockframework:spock-core:2.0-groovy-3.0 (c)
+--- org.spockframework:spock-core:2.0-groovy-3.0
|    +--- org.junit:junit-bom:5.7.2
|    |    +--- org.junit.platform:junit-platform-engine:1.7.2 (c)
|    |    +--- org.junit.platform:junit-platform-testkit:1.7.2 (c)
|    |    +--- org.junit.platform:junit-platform-commons:1.7.2 (c)
|    |    \--- org.junit.platform:junit-platform-launcher:1.7.2 (c)
|    +--- org.codehaus.groovy:groovy:3.0.8 -> 3.0.9
|    +--- org.junit.platform:junit-platform-engine -> 1.7.2
|    |    +--- org.junit:junit-bom:5.7.2 (*)
|    |    +--- org.apiguardian:apiguardian-api:1.1.0
|    |    +--- org.opentest4j:opentest4j:1.2.0
|    |    \--- org.junit.platform:junit-platform-commons:1.7.2
|    |         +--- org.junit:junit-bom:5.7.2 (*)
|    |         \--- org.apiguardian:apiguardian-api:1.1.0
|    +--- org.junit.platform:junit-platform-testkit -> 1.7.2
|    |    +--- org.junit:junit-bom:5.7.2 (*)
|    |    +--- org.apiguardian:apiguardian-api:1.1.0
|    |    +--- org.assertj:assertj-core:3.16.1
|    |    +--- org.opentest4j:opentest4j:1.2.0
|    |    \--- org.junit.platform:junit-platform-launcher:1.7.2
|    |         +--- org.junit:junit-bom:5.7.2 (*)
|    |         +--- org.apiguardian:apiguardian-api:1.1.0
|    |         \--- org.junit.platform:junit-platform-engine:1.7.2 (*)
|    +--- org.hamcrest:hamcrest:2.2
|    +--- org.jetbrains:annotations:20.1.0
|    +--- org.ow2.asm:asm:9.1 -> 9.2
|    +--- net.bytebuddy:byte-buddy:1.11.0 -> 1.11.12
|    +--- cglib:cglib-nodep:3.3.0
|    \--- org.objenesis:objenesis:3.2
+--- org.hamcrest:hamcrest-core:2.2
|    \--- org.hamcrest:hamcrest:2.2
+--- cglib:cglib-nodep:3.3.0
+--- com.h2database:h2:2.0.204
+--- net.bytebuddy:byte-buddy:1.11.12
+--- org.objenesis:objenesis:3.2
\--- com.h2database:h2:1.4.200 -> 2.0.204

(c) - dependency constraint
(*) - dependencies omitted (listed previously)

A web-based, searchable dependency report is available by adding the --scan option.

Additional context

No response

leonard84 commented 2 years ago

Related to #1406 but different, I have the suspicion this is actually caused by groovy as you are not trying to create a Mock but use closure conversion. Could you try the same in a JUnit 4/5 Test?

moqimoqidea commented 2 years ago

Related to #1406 but different, I have the suspicion this is actually caused by groovy as you are not trying to create a Mock but use closure conversion. Could you try the same in a JUnit 4/5 Test?

thank you, I will try it later.

kriegaex commented 2 years ago

What is bi | (domain, x) -> domain.equalsIgnoreCase("MICROSOFT.COM") supposed to do? Should it not be either bi or the inline closure? What are you trying to achieve by trying a bitwise OR of those two closures? If you simply use one of them, the spec will run normally and fail because the condition is violated. If then you also fix the filter condition, the test passes:

  def "test a domain with or syntax should return right result"() {
    given:
    def domainList = [
      new Domain("google.com", 1),
      new Domain("i-am-spammer.com", 10),
      new Domain("mkyong.com", 0),
      new Domain("microsoft.com", 2)
    ]

    when:
    def result = filterBadDomain(domainList, (domain, score) -> !domain.equalsIgnoreCase("I-AM-SPAMMER.COM"))

    then:
    result == [["google.com", 1] as Domain, ["mkyong.com", 0] as Domain, ["microsoft.com", 2] as Domain]
  }
kriegaex commented 2 years ago

Hm, I see that Groovy seems to permit chaining of bi-predicates and/or closures using bitwise logical operators. As a Groovy non-expert, I had no idea that was possible. I also did not find any relevant documentation about this. Maybe I searched for the wrong keywords.

Anyway, without Spock, this works in JDK <16, while it fails the way you described above in JDK 16+. This holds true independent of whether we use Java lambda or Groovy closure syntax.

package org.acme

import java.util.function.BiPredicate

class CombineBiPredicatesAndClosures {
  static void main(String[] args) {
    println System.properties["java.version"]

    // Lambda syntax only works in Groovy 3.0
    // BiPredicate<String, Integer> bi = (domain, score) -> domain.equalsIgnoreCase("google.com")
    // def bi2 = (domain, score) -> score == 11

    // Closure syntax works in Groovy 2.5, too
    BiPredicate<String, Integer> bi = { domain, score -> domain.equalsIgnoreCase("google.com") }
    def bi2 = { domain, score -> score == 11 }

    def logicalOr = bi | bi2
    assert logicalOr.test("google.com", 11)
    assert logicalOr.test("google.com", 0)
    assert logicalOr.test("x", 11)
    assert !logicalOr.test("x", 0)

    def logicalAnd = bi & bi2
    assert logicalAnd.test("google.com", 11)
    assert !logicalAnd.test("google.com", 0)
    assert !logicalAnd.test("x", 11)
    assert !logicalAnd.test("x", 0)
  }
}

Try it in the Groovy Web Console, where it always works, because GWC runs on JRE 11.

So we are dealing with a Groovy issue, as it seems. As usual, @leonard84 is right in such things.

moqimoqidea commented 2 years ago

Thank you for your awesome work.

As To Reproduce said, the code is from Java 8 BiPredicate Examples, I just tested it with Spock. And after upgrading to Java17 I found that it doesn't work properly.

I'm happy to see this bug show on the table, should I go to Groovy Project to open the issue, or is there a better way? thank you.

kriegaex commented 2 years ago

As To Reproduce said, the code is from Java 8 BiPredicate Examples, I just tested it with Spock.

Thanks for bringing the original Java code to my attention again. Java functional interfaces are not my strong suit. Now I understand that BiPredicate simply has methods called or, and, negate which are meant to logically combine bi-predicates. Groovy therefore also accepts bitwise logical operators in this case, because when overloading them they happen to have the same method names, at least for bitwise AND and OR. There is however no overloading for !, only for ~ (bitwise negate, method name bitwiseNegate and therefore different from the BiPredicate method name).

Somehow Groovy has problems if the first of the two chained bi-predicates is a closure or lambda. Even without chaining, it is impossible to call bi.negate() upon it on JVM 16+. If however we change the code to read

// Use old-school anonymous class instead of closure or lambda
BiPredicate<String, Integer> bi = new BiPredicate<String, Integer>() {
  @Override
  boolean test(String domain, Integer score) {
    return domain.equalsIgnoreCase("google.com")
  }
}
def bi2 = { domain, score -> score == 11 }

it runs smoothly.

kriegaex commented 2 years ago

Sorry to spam this issue, but I was curious and tried Groovy 4.0.0-rc-2: It works! So this seems to be a Groovy 3 problem. Maybe we really open an issue and have this fix backported, if possible. Here is my Maven project which works without Surefire, JUnit or Spock. It simply uses Exec Maven Plugin to automatically run the example class during the test phase. This way we can exclude any test tools or plugins as root causes.

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>org.example</groupId>
  <artifactId>Spock_GH_1412</artifactId>
  <version>1.0-SNAPSHOT</version>

  <properties>
    <java.version>1.8</java.version>
  </properties>

  <dependencyManagement>
    <dependencies>
      <dependency>
        <groupId>org.apache.groovy</groupId>
        <artifactId>groovy-bom</artifactId>
        <version>4.0.0-rc-2</version>
        <type>pom</type>
        <scope>import</scope>
      </dependency>
    </dependencies>
  </dependencyManagement>

  <dependencies>
    <dependency>
      <groupId>org.apache.groovy</groupId>
      <artifactId>groovy</artifactId>
    </dependency>
    <dependency>
      <groupId>org.apache.groovy</groupId>
      <artifactId>groovy-json</artifactId>
    </dependency>
    <dependency>
      <groupId>org.apache.groovy</groupId>
      <artifactId>groovy-xml</artifactId>
    </dependency>
  </dependencies>

  <build>
    <plugins>
      <plugin>
        <groupId>org.codehaus.gmavenplus</groupId>
        <artifactId>gmavenplus-plugin</artifactId>
        <version>1.13.0</version>
        <configuration>
          <targetBytecode>${java.version}</targetBytecode>
        </configuration>
        <executions>
          <execution>
            <goals>
              <goal>compile</goal>
            </goals>
          </execution>
        </executions>
      </plugin>
      <plugin>
        <groupId>org.codehaus.mojo</groupId>
        <artifactId>exec-maven-plugin</artifactId>
        <version>3.0.0</version>
        <executions>
          <execution>
            <id>run-groovy-example</id>
            <phase>test</phase>
            <goals>
              <goal>java</goal>
            </goals>
            <configuration>
              <mainClass>CombineBiPredicatesAndClosures</mainClass>
            </configuration>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>

</project>

src/main/groovy/CombineBiPredicatesAndClosures.groovy

import java.util.function.BiPredicate

class CombineBiPredicatesAndClosures {
  static void main(String[] args) {
    println "Java version: ${System.properties['java.version']}"

    // Old-school anonymous class instead of closure or lambda
    /*
    BiPredicate<String, Integer> bi = new BiPredicate<String, Integer>() {
      @Override
      boolean test(String domain, Integer score) {
        return domain.equalsIgnoreCase("google.com")
      }
    }
    */

    // Lambda syntax only works in Groovy 3+
    //BiPredicate<String, Integer> bi = (domain, score) -> domain.equalsIgnoreCase("google.com")
    //def bi2 = (domain, score) -> score == 11

    // Closure syntax works in Groovy 2.5, too
    BiPredicate<String, Integer> bi = { domain, score -> domain.equalsIgnoreCase("google.com") }
    def bi2 = { domain, score -> score == 11 }

    def logicalNot = bi.negate()
    assert logicalNot.test("acme.org", 11)
    assert !logicalNot.test("google.com", 11)

    def logicalOr = bi | bi2
    assert logicalOr.test("google.com", 11)
    assert logicalOr.test("google.com", 0)
    assert logicalOr.test("x", 11)
    assert !logicalOr.test("x", 0)

    def logicalAnd = bi & bi2
    assert logicalAnd.test("google.com", 11)
    assert !logicalAnd.test("google.com", 0)
    assert !logicalAnd.test("x", 11)
    assert !logicalAnd.test("x", 0)
  }
}
kriegaex commented 2 years ago

I created GROOVY-10450. Let's see what the maintainers say.

moqimoqidea commented 2 years ago

Thank you for your awesome work again!

I aleady remove the unnecessary full quote.

It looks like this was a Groovy issue and was fixed in version 4.0.0.

moqimoqidea commented 2 years ago

Thank you for your great work, if this issue has fulfilled its historic mission, you can close it.

leonard84 commented 2 years ago

Closing as the issue is with Groovy.

kriegaex commented 1 year ago

FYI, the upstream Groovy issue was fixed in an incomplete way for 3.0.15. The commit adding the last bit was added after the release (I just retested it successfully with the latest snapshot), so unfortunately, we will have to wait for 3.0.16 for it to be fixed for good. But at least, after more than a year, there are some good news.

kriegaex commented 1 year ago

@leonard84, Groovy 3.0.16 has been released a couple of days ago, i.e. this issue is now fixed upstream. Maybe it makes sense to also bump Spock 3.x to that release. If you want to somehow refactor my sample code into a regression test, is up to you.