Tests fail to run with Jenkins 2.357 and up
Expected Behavior
Test run with the most recent versions of org.jenkins-ci.main:jenkins-core package.
Actual Behavior
When using org.jenkins-ci.main:jenkins-core version 2.356 and below, tests run as expected.
When using org.jenkins-ci.main:jenkins-core version 2.357 up to 2.365, running tests produces an error for every test case:
java.lang.UnsupportedOperationException
at net.sf.cglib.asm.$ClassVisitor.visitNestMemberExperimental(ClassVisitor.java:248)
at net.sf.cglib.asm.$ClassReader.accept(ClassReader.java:651)
at net.sf.cglib.asm.$ClassReader.accept(ClassReader.java:391)
at net.sf.cglib.proxy.BridgeMethodResolver.resolveAll(BridgeMethodResolver.java:70)
at net.sf.cglib.proxy.Enhancer.emitMethods(Enhancer.java:1132)
at net.sf.cglib.proxy.Enhancer.generateClass(Enhancer.java:630)
at net.sf.cglib.core.DefaultGeneratorStrategy.generate(DefaultGeneratorStrategy.java:25)
at net.sf.cglib.core.AbstractClassGenerator.generate(AbstractClassGenerator.java:329)
at net.sf.cglib.proxy.Enhancer.generate(Enhancer.java:492)
at net.sf.cglib.core.AbstractClassGenerator$ClassLoaderData$3.apply(AbstractClassGenerator.java:93)
at net.sf.cglib.core.AbstractClassGenerator$ClassLoaderData$3.apply(AbstractClassGenerator.java:91)
at net.sf.cglib.core.internal.LoadingCache$2.call(LoadingCache.java:54)
at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
at net.sf.cglib.core.internal.LoadingCache.createEntry(LoadingCache.java:61)
at net.sf.cglib.core.internal.LoadingCache.get(LoadingCache.java:34)
at net.sf.cglib.core.AbstractClassGenerator$ClassLoaderData.get(AbstractClassGenerator.java:116)
at net.sf.cglib.core.AbstractClassGenerator.create(AbstractClassGenerator.java:291)
at net.sf.cglib.proxy.Enhancer.createHelper(Enhancer.java:480)
at net.sf.cglib.proxy.Enhancer.createClass(Enhancer.java:337)
at org.spockframework.mock.runtime.CglibMockFactory.createMock(CglibMockFactory.java:32)
at org.spockframework.mock.runtime.ProxyBasedMockFactory.create(ProxyBasedMockFactory.java:45)
at org.spockframework.mock.runtime.JavaMockFactory.createInternal(JavaMockFactory.java:58)
at org.spockframework.mock.runtime.JavaMockFactory.create(JavaMockFactory.java:38)
at org.spockframework.mock.runtime.CompositeMockFactory.create(CompositeMockFactory.java:42)
at org.spockframework.lang.SpecInternals.createMock(SpecInternals.java:46)
at org.spockframework.lang.SpecInternals.createMockImpl(SpecInternals.java:294)
at org.spockframework.lang.SpecInternals.createMockImpl(SpecInternals.java:284)
at org.spockframework.lang.SpecInternals.MockImpl(SpecInternals.java:108)
at com.homeaway.devtools.jenkins.testing.JenkinsPipelineSpecification.makeStaticJenkins(JenkinsPipelineSpecification.groovy:1054)
at com.homeaway.devtools.jenkins.testing.JenkinsPipelineSpecification.getStaticJenkins(JenkinsPipelineSpecification.groovy:1070)
at com.homeaway.devtools.jenkins.testing.JenkinsPipelineSpecification$1.getInstance(JenkinsPipelineSpecification.groovy:1103)
at jenkins.model.Jenkins.getInstanceOrNull(Jenkins.java:844)
at hudson.cli.CLICommand.<clinit>(CLICommand.java:585)
at java.base/java.lang.Class.forName(Class.java:315)
at com.homeaway.devtools.jenkins.testing.WholeClasspathPipelineExtensionDetector.getClassesWithAnnotationOfTypeInPackage(WholeClasspathPipelineExtensionDetector.java:112)
at com.homeaway.devtools.jenkins.testing.APipelineExtensionDetector.getPipelineSteps(APipelineExtensionDetector.java:81)
at com.homeaway.devtools.jenkins.testing.JenkinsPipelineSpecification.setupSpec(JenkinsPipelineSpecification.groovy:1109)
When using org.jenkins-ci.main:jenkins-core version 2.366 up to 2.395 (current latest), running tests produces an error for every test case:
java.lang.NullPointerException: Cannot invoke method getMetaMethod() on null object
at com.homeaway.devtools.jenkins.testing.JenkinsPipelineSpecification.addPipelineMocksToObjects_closure1(JenkinsPipelineSpecification.groovy:609)
at groovy.lang.Closure.call(Closure.java:420)
at groovy.lang.Closure.call(Closure.java:436)
at com.homeaway.devtools.jenkins.testing.JenkinsPipelineSpecification.addPipelineMocksToObjects(JenkinsPipelineSpecification.groovy:607)
at com.homeaway.devtools.jenkins.testing.JenkinsPipelineSpecification.setup(JenkinsPipelineSpecification.groovy:1175)
Steps to Reproduce
Current working dependencies (reduced from full set required for my project), bumping the version of org.jenkins-ci.main:jenkins-core leads to failures:
repositories {
mavenCentral()
maven { url 'https://repo.jenkins-ci.org/releases' }
maven { url 'https://repo.jenkins-ci.org/public' }
}
dependencies {
Closure withoutIcu = { exclude group: 'com.ibm.icu', module: 'icu4j' }
implementation 'org.codehaus.groovy:groovy-all:2.4.21'
implementation 'org.jenkins-ci.main:jenkins-core:2.332.1', withoutIcu
implementation 'com.cloudbees:groovy-cps:1.32@jar', withoutIcu
implementation 'org.jenkinsci.plugins:pipeline-model-definition:2.2075.vce74e77b_ce40@jar'
implementation 'org.jenkins-ci.plugins.workflow:workflow-api:1143.v2d42f1e9dea_5@jar'
implementation 'org.jenkins-ci.plugins.workflow:workflow-cps:2686.v7c37e0578401@jar'
implementation 'org.jenkins-ci.plugins.workflow:workflow-step-api:622.vb_8e7c15b_c95a_@jar'
testImplementation 'com.homeaway.devtools.jenkins:jenkins-spock:2.1.5'
testImplementation 'javax.servlet:javax.servlet-api:4.0.1'
testImplementation 'org.spockframework:spock-core:1.3-groovy-2.4'
}
Minimal failing test:
package foo.bar
import com.homeaway.devtools.jenkins.testing.JenkinsPipelineSpecification
class FooBarSpec extends JenkinsPipelineSpecification {
def 'foobar works'() {
expect:
1 == 1
}
}
Additional Information
$ java -version
openjdk version "11.0.18" 2023-01-17
OpenJDK Runtime Environment Temurin-11.0.18+10 (build 11.0.18+10)
OpenJDK 64-Bit Server VM Temurin-11.0.18+10 (build 11.0.18+10, mixed mode)
$ ./gradlew -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: 11.0.18 (Eclipse Adoptium 11.0.18+10)
OS: Linux 6.2.7-[REDACTED] amd64
Worked around second error (NullPointerException) by applying the following patch (not sure how good it is, let me know if you want me to open a PR):
--- a/src/main/groovy/com/homeaway/devtools/jenkins/testing/JenkinsPipelineSpecification.groovy
+++ b/src/main/groovy/com/homeaway/devtools/jenkins/testing/JenkinsPipelineSpecification.groovy
@@ -606,6 +606,10 @@ public abstract class JenkinsPipelineSpecification extends Specification {
_objects.each { object ->
+ if( object.metaClass == null ) {
+ return;
+ }
+
final MetaMethod originalMethodMissing = object.metaClass.getMetaMethod("methodMissing", "string", new Object[0] )
object.metaClass.methodMissing = { String _name, _args ->
With the above patch applied, still fails with the first error (UnsupportedOperationException), which could be worked around by bumping the cglib version from 3.2.7 to at least 3.2.9:
dependencies {
// ...
testImplementation 'cglib:cglib:3.2.9'
// ...
}
In Groovy every class has a metaClass.
But a class can obfuscate the call to metaClass if it has a method with this signature: get(String key)
Like org.apache.log4j.MDC and org.slf4j.MDC have. :wink:
The fix is not to check for a null, but to simply use object.getMetaClass() instead of object.metaClass.
I was able to workaround this issue by adding a Helper class to the test folder having a static initializer:
public class OutsmartJenkinsPipelineSpecification {
static {
def string = "metaClass"
string.metaClass.getMetaMethod { String it1, String it2, Object[] it3 -> }
string.metaClass.methodMissing = "methodMissing"
string.metaClass.propertyMissing = "propertyMissing"
org.slf4j.MDC.put("metaClass", string)
}
}
That adds a fake metaClass to the org.slf4j.MDC class.
(Be aware if you are using MDC while running the tests.)