Jenkins scripted pipleline #NonCPS and StackOverflowError - groovy

I have the simple pipeline script:
#!groovy
#org.jenkinsci.plugins.workflow.libs.Library('Some#lib')
import com.cloudbees.groovy.cps.NonCPS
node() {
echo CheekyEnum.getByName('name1').getName()
}
enum CheekyEnum {
ENUM_1('name1', 'f1'),
ENUM_2('name2', 'f2')
String name
String field
CheekyEnum(String name, String field) {
this.name = name
this.field = field
}
static CheekyEnum getByName(String name) {
return values().find { it.name == name }
}
String getName() {
return name
}
}
When I run it everything works OK, but if there is a little change in method getName()
#NonCPS
String getName() {
return name
}
I get a pretty long error stacktrace:
java.lang.StackOverflowError
at java.lang.ClassLoader.loadClass(ClassLoader.java:398)
at java.lang.ClassLoader.loadClass(ClassLoader.java:351)
at org.jenkinsci.plugins.scriptsecurity.sandbox.groovy.SandboxResolvingClassLoader.lambda$loadClass$0(SandboxResolvingClassLoader.java:51)
at org.jenkinsci.plugins.scriptsecurity.sandbox.groovy.SandboxResolvingClassLoader.lambda$load$2(SandboxResolvingClassLoader.java:85)
at com.github.benmanes.caffeine.cache.BoundedLocalCache.lambda$doComputeIfAbsent$14(BoundedLocalCache.java:2337)
at java.util.concurrent.ConcurrentHashMap.compute(ConcurrentHashMap.java:1892)
at com.github.benmanes.caffeine.cache.BoundedLocalCache.doComputeIfAbsent(BoundedLocalCache.java:2335)
at com.github.benmanes.caffeine.cache.BoundedLocalCache.computeIfAbsent(BoundedLocalCache.java:2318)
at com.github.benmanes.caffeine.cache.LocalCache.computeIfAbsent(LocalCache.java:111)
at com.github.benmanes.caffeine.cache.LocalManualCache.get(LocalManualCache.java:54)
at org.jenkinsci.plugins.scriptsecurity.sandbox.groovy.SandboxResolvingClassLoader.load(SandboxResolvingClassLoader.java:79)
...
Why? Doesn't #NonCPS just make method excluded from CPS transformation?

enum is per se a serializable type. So you should better create a wrapper function for it:
import com.cloudbees.groovy.cps.NonCPS
node() {
echo getName(CheekyEnum.getByName('name1'))
}
...
#NonCPS
String getName(CheekyEnum cheeky) {
return cheeky.name
}
The related StackOverflowError could be a bug/smell in the workflow-cps-plugin. Please take a look at its Technical design
Pipeline scripts may mark designated methods with the annotation #NonCPS. These are then compiled normally (except for sandbox security checks).
AFAICS you're running inside a Groovy sandbox. The SandboxInterceptor
is probably generating this stack overflow. Running outside the sandbox should fix your issue as well.
BTW you can also read Pipeline CPS Method Mismatches for a better understanding of what can be called in non-CPS transformed code.

Related

Riddle me this: Inconsistent groovy meta programming behaviour

I stumbled across this when updating a large app from groovy 2 to 3 (and also to corresponding newer spock and geb versions).
This code behaves strange and also a different kind of strange in groovy 2 versus groovy 4.
I think we are running without "indy" here. I guess because all the transitive dependencies of our large app bring in specific groovy jars without indy. I should probably goe through them carefully and adapt our gradle build so that only "indy" versions of all jars are picked.
class A {
def foo() {
bar('hello')
beep(Optional.of('hello'))
}
protected void bar(String value) { println 'A.bar' }
protected void beep(Optional<String> value) { println 'A.beep' }
}
class B extends A {
protected void bar(String value) { println 'B.bar' }
protected void beep(Optional<String> value) { println 'B.beep' }
}
class C extends B implements GroovyInterceptable {
def invokeMethod(String name, Object args) {
super."$name"(*args)
}
}
static void main(String[] args) {
new C().foo()
println '---'
C c = new C()
c.bar('hello')
c.beep(Optional.of('hello'))
}
Output for groovy 2.5.15:
B.bar
A.beep
---
A.bar
A.beep
Output for groovy 4.0.0:
A.bar
A.beep
---
A.bar
A.beep
What I would have expected:
B.bar
B.beep
---
B.bar
B.beep
What's going on here? Bug or some strange, but expected corner case?
Where is the difference in behavior in between groovy 2 and 4 documented?
In our real app there was a difference already in between groovy 2 and 3 but I have been unable so far to create example code for that.
Is there a way to call the original method inside of invokeMethod? (Can't find anything in the docs, which are very sparse btw.)
I get your 3.0.9 output for Groovy 2.5.16, 3.0.10 and 4.0.1 -- indy enabled for all three.
Your implementation of invokeMethod relies on the behavior of ScriptBytecodeAdapter#invokeMethodOnSuperN which is what is behind super."$name"(*args). When handling "bar" message, the meta-method index has B.bar(java.lang.String) for "this" and B.super$2$bar(java.lang.String) for "super". super$2$bar is a meta-object protocol (MOP) method that provides the necessary INVOKESPECIAL instruction to reach A#bar(java.lang.String).
If you want the output of all calls to be from B then you can use this."$name"(*args) instead. In your specific case, there is no need to implement C as GroovyInterceptable and to try and route "foo", "bar" and "beep" yourself.
You can make your code produce the expected output by making the B class compiled statically:
import groovy.transform.CompileStatic
class A {
def foo() {
bar('hello')
beep(Optional.of('hello'))
}
protected void bar(String value) { println 'A.bar' }
protected void beep(Optional<String> value) { println 'A.beep' }
}
#CompileStatic
class B extends A {
protected void bar(String value) { println 'B.bar' }
protected void beep(Optional<String> value) { println 'B.beep' }
}
class C extends B implements GroovyInterceptable {
def invokeMethod(String name, Object args) {
super."$name"(*args)
}
}
static void main(String[] args) {
new C().foo()
println '---'
C c = new C()
c.bar('hello')
c.beep(Optional.of('hello'))
}
Output:
B.bar
B.beep
---
B.bar
B.beep
As it was mentioned by emilies in his answer, in the MOP use case scenario something like this happens:
c.bar('Hello')
invokeMethod('bar', ['Hello'] as Object[])
super."bar"(['Hello'] as Object[])
This super."bar"(['Hello'] as Object[]) is represented by B.super$2$bar(java.lang.String) method object which forces A.bar(java.lang.String) to be invoked right in the next call frame.
However, if you make the B class to be compiled statically, the method that is found to satisfy the super."bar"(['Hello'] as Object[]) expression, in that case, is B.bar(java.lang.String), and thus it gets invoked directly.
Regarding the differences between Groovy 2.5 and Groovy >=3.0, it looks like you have encountered a compiler bug. The bar('hello') inside the A.foo() method ignores the MOP and goes directly to this.bar(java.lang.String) which in this case is B.bar(java.lang.String).
It looks like it happens for the java.lang.String type (didn't check other types). However, when the type is java.util.Optional, then a call like beep(Optional.of('Hello')) inside the A.foo() method goes through the MOP and thus it discovers B.super$2$beep(java.util.Optional) method to be invoked:

Groovy execute code using custom annotation

I would like to execute a function (with parameters) through an annotation tag in a groovy script.
If we execute a method in our groovy script with this annotation it would print in the console (stderr) a custom message like:
warning: '<function_name>' is deprecated [[Use '<Deprecated.instead>' instead.][More info: '<Deprecated.more_info>']]
So, I have created a custom annotation like this
public #interface Deprecated {
public String instead() default null
public String more_info() default null
}
The goal is to use it like this:
def new_call() {
//new version of the method
}
#Deprecated(instead="new_call")
def call() {
//do something
}
In my example, it would output like this:
warning: 'call' is deprecated. Use 'new_call' instead.
I saw this post Groovy: How to call annotated methods, it's over 7 years old now but looks good so i'll look deeper.
I saw also Delegate.deprecated but i'm not sure if that's what i want
I'm not sure I am doing right. So if you have any advice or suggestions, I'll be happy to hear you.
Simple AOP Approach
This is very-very basic implementation with groovy out-of the box.
Deprecated Annotation
#Target([ElementType.METHOD])
#Retention(RetentionPolicy.RUNTIME)
#interface Deprecated {
String instead() default 'null'
String more_info() default 'null'
}
Class which should get this functionality
The class has to implement GroovyInterceptable - invokeMethod.
class SomeClass implements GroovyInterceptable {
#Override
def invokeMethod(String name, args) {
DeprecatedInterception.apply(this, name, args)
}
def new_call() {
println('new_call invoked')
}
#Deprecated(instead = 'new_call', more_info = '... the reason')
def depr_call() {
println('depr_call invoked')
}
}
Interception Util
import org.codehaus.groovy.reflection.CachedMethod
class DeprecatedInterception {
static apply(Object owner, String methodName, Object args) {
MetaMethod metaMethod = owner.metaClass.getMetaMethod(methodName, args)
Deprecated d = extractAnnotation(metaMethod)
if (d) {
println("warning: '$methodName' is deprecated. Use '${d.instead()}' instead. More info: '${d.more_info()}'")
}
// handle methods with var-args
metaMethod.isVargsMethod() ?
metaMethod.doMethodInvoke(owner, args) :
metaMethod.invoke(owner, args)
}
static Deprecated extractAnnotation(MetaMethod metaMethod) {
if (metaMethod instanceof CachedMethod) {
metaMethod.getCachedMethod()?.getAnnotation(Deprecated)
} else {
null
}
}
}
Simple Test
Just check that no exceptions/errors..
class TestWarnings {
#Test
void test() {
new SomeClass().with {
new_call()
depr_call()
}
}
}
Output:
new_call invoked
warning: 'depr_call' is deprecated. Use 'new_call' instead. More info: '... the reason'
depr_call invoked
Disclaimer
This should work for most cases, but has some limitations:
will not work for static methods (unless invoked on Object instance)
you have to implement GroovyInterceptable per each class, to apply
you might faced with some side-effects in some groovy syntax or features (at least I've found the issue with vararg methods invocation, but this already fixed)
So this should be tested and potentially improved before widely using for some production projects.
Other options:
Shortly, because implementation might be more complex (not sure, at least I not able to provide some example in a short time), but potentially this is more solid.
Adding AST Transformations.
Use some AOP library.

How to provide java security for white listed packages using java security manager?

Problem:
I am trying to provide restriction (blacklisting ) all and allow only what I provided when we execute groovy using GroovyClassLoader
I am able to execute custom policy using with limited permission for GroovyClassLoader only.
Now I am trying to provide package restriction going to use as part of groovy execution. Let say If I allowed com.x.y this package if any package other then this used in groovy should throw SecurityException
I have tried to achieve the same with custom security manager and overriding the checkPackageAccess but didn't get success.
public TestSecurityManager extends SecurityManager{
List<String> whiteListedPkgList;
public void checkPackageAccess(String pkg){
if(!pkg.startWith(any of given white list pkg)){
throw new SecurityException("Access Denied");
}
//If package not belong to whilelisted package list throw security exception
}
}
When I tried using above approach we need to provide all packages for execution like com, java etc instead of java.nio.file. in whitelist list
UPDATE
If we try to allow package like com.x.y using white list comparison using start with check access pkg, it will allow that package but later on it will throw security exception for com package.
Could any one help for the same how we can achieve it ?
Thanks in advance
If you're able to, instead of using Java's SecurityManager, using the Groovy DSL features you can more easily achieve this.
See https://www.groovy-lang.org/dsls.html#_secure_ast_customizer
Example:
import groovy.transform.CompileStatic
import org.codehaus.groovy.control.CompilerConfiguration
import org.codehaus.groovy.control.customizers.CompilationCustomizer
import org.codehaus.groovy.control.customizers.SecureASTCustomizer
#CompileStatic
class Main {
static final CompilationCustomizer scz = new SecureASTCustomizer().with {
closuresAllowed = false // user will not be able to write closures
methodDefinitionAllowed = false // user will not be able to define methods
importsWhitelist = [] // empty whitelist means imports are disallowed
staticImportsWhitelist = [] // same for static imports
staticStarImportsWhitelist = ['java.lang.Math'] // only java.lang.Math is allowed
constantTypesClassesWhiteList = [
Integer,
Float,
Long,
Double,
BigDecimal,
Integer.TYPE,
Long.TYPE,
Float.TYPE,
Double.TYPE,
Object,
String,
].asImmutable() as List<Class>
// method calls are only allowed if the receiver is of one of those types
// be careful, it's not a runtime type!
receiversClassesWhiteList = [
Math,
Integer,
Float,
Double,
Long,
BigDecimal,
PrintStream,
Object,
].asImmutable() as List<Class>
it
}
static void main(args) {
def configuration = new CompilerConfiguration()
configuration.addCompilationCustomizers(scz)
// evaluate sandboxed code
new GroovyShell(configuration).evaluate(
""" println 'hello world' """)
}
}
If all you need is to whitelist certain classes, you can also try writing your own class loader and using that to evalute the sandboxed script:
class MyClassLoader extends ClassLoader {
Set<String> whiteListPackages = [
'java.lang.', 'java.util.', 'groovy.', 'org.codehaus.groovy.', 'Script'
]
MyClassLoader(ClassLoader parent) {
super(parent)
}
#Override
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
if (!whiteListPackages.any { okPkg -> name.startsWith(okPkg) }) {
throw new ClassNotFoundException('Access is forbidden')
}
return super.loadClass(name, resolve)
}
}
def shell = new GroovyShell(new MyClassLoader(GroovySystem.classLoader))
// evaluate the script with our own classloader
shell.evaluate('''
println 'hello'
println([1,2,3])
// This line throws an error because the `java.net` package is not whitelisted
println(new URL('https://groovy-lang.org'))
''')

GroovyScriptEngine throws MultipleCompilationErrorsException while loading class that uses other class' static inner class

I'm running into a problem with GroovyScriptEngine - it seems not to be able to work with inner classes. Anyone know whether there's some limitation in GroovyScriptEngine or a workaround?
I have a directory with these two files:
// MyClass.groovy
public class MyClass {
MyOuter m1;
MyOuter.MyInner m2;
}
and
// MyOuter.groovy
public class MyOuter {
public static class MyInner {}
}
I have a following test class:
import java.io.File;
import java.net.MalformedURLException;
import java.net.URL;
import groovy.util.GroovyScriptEngine;
public class TestGroovyScriptEngine {
public static void main(String[] args) throws MalformedURLException, ClassNotFoundException {
final File myGroovySourceDir = new File("C:/MyGroovySourceDir");
final URL[] urls = { myGroovySourceDir.toURL() };
GroovyScriptEngine groovyScriptEngine = new GroovyScriptEngine(urls,
Thread.currentThread().getContextClassLoader());
Class<?> clazz = groovyScriptEngine.getGroovyClassLoader().loadClass("MyClass");
}
}
When I run it I get the following compilation error:
Exception in thread "main" org.codehaus.groovy.control.MultipleCompilationErrorsException: startup failed:
C:\MyGroovySourceDir\MyClass.groovy: 3: unable to resolve class MyOuter.MyInner
# line 3, column 2.
MyOuter.MyInner m2;
^
1 error
at org.codehaus.groovy.control.ErrorCollector.failIfErrors(ErrorCollector.java:311)
at org.codehaus.groovy.control.CompilationUnit.applyToSourceUnits(CompilationUnit.java:983)
at org.codehaus.groovy.control.CompilationUnit.doPhaseOperation(CompilationUnit.java:633)
at org.codehaus.groovy.control.CompilationUnit.compile(CompilationUnit.java:582)
at groovy.lang.GroovyClassLoader.doParseClass(GroovyClassLoader.java:354)
at groovy.lang.GroovyClassLoader.access$300(GroovyClassLoader.java:87)
at groovy.lang.GroovyClassLoader$5.provide(GroovyClassLoader.java:323)
at groovy.lang.GroovyClassLoader$5.provide(GroovyClassLoader.java:320)
at org.codehaus.groovy.runtime.memoize.ConcurrentCommonCache.getAndPut(ConcurrentCommonCache.java:147)
at groovy.lang.GroovyClassLoader.parseClass(GroovyClassLoader.java:318)
at groovy.util.GroovyScriptEngine$ScriptClassLoader.doParseClass(GroovyScriptEngine.java:248)
at groovy.util.GroovyScriptEngine$ScriptClassLoader.parseClass(GroovyScriptEngine.java:235)
at groovy.lang.GroovyClassLoader.parseClass(GroovyClassLoader.java:307)
at groovy.lang.GroovyClassLoader.recompile(GroovyClassLoader.java:811)
at groovy.lang.GroovyClassLoader.loadClass(GroovyClassLoader.java:767)
at groovy.lang.GroovyClassLoader.loadClass(GroovyClassLoader.java:836)
at groovy.lang.GroovyClassLoader.loadClass(GroovyClassLoader.java:824)
I would have expected a "clean compile", but the inner class seems to be causing problems.
My groovy classes compile fine at the command line using groovyc, or in Eclipse.
You have faced an edge case here. To clarify what happens let's define the initial conditions:
you have a Java (or Groovy) class that gets executed inside JVM
you have two Groovy classes that get loaded outside of the JVM
The problem you have described does not exist if you put these two Groovy classes inside the same path you execute your Java class from - in this case IDE takes care to compile these Groovy classes and put them to the classpath of a JVM that gets started to run your Java test class.
But this is not your case and you are trying to load these two Groovy classes outside the running JVM using GroovyClassLoader (which extends URLClassLoader btw). I will try to explain in the simplest possible words what happened that adding field of type MyOuter does not throw any compilation error, but MyOuter.MyInner does.
When you execute:
Class<?> clazz = groovyScriptEngine.getGroovyClassLoader().loadClass("MyClass");
Groovy class loader goes to script file lookup part, because it was not able to find MyClass in the current classpath. This is the part responsible for it:
// at this point the loading from a parent loader failed
// and we want to recompile if needed.
if (lookupScriptFiles) {
// try groovy file
try {
// check if recompilation already happened.
final Class classCacheEntry = getClassCacheEntry(name);
if (classCacheEntry != cls) return classCacheEntry;
URL source = resourceLoader.loadGroovySource(name);
// if recompilation fails, we want cls==null
Class oldClass = cls;
cls = null;
cls = recompile(source, name, oldClass);
} catch (IOException ioe) {
last = new ClassNotFoundException("IOException while opening groovy source: " + name, ioe);
} finally {
if (cls == null) {
removeClassCacheEntry(name);
} else {
setClassCacheEntry(cls);
}
}
}
Source: src/main/groovy/lang/GroovyClassLoader.java#L733-L753
Here URL source = resourceLoader.loadGroovySource(name); it loads the full file URL to the source file and here cls = recompile(source, name, oldClass); it executes class compilation.
There are several phases involved in Groovy class compilation. One of them is Phase.SEMANTIC_ANALYSIS which analyses class fields and their types for instance. At this point ClassCodeVisitorSupport executes visitClass(ClassNode node) for MyClass class and following line
node.visitContents(this);
starts class contents processing. If we take a look at the source code of this method:
public void visitContents(GroovyClassVisitor visitor) {
// now let's visit the contents of the class
for (PropertyNode pn : getProperties()) {
visitor.visitProperty(pn);
}
for (FieldNode fn : getFields()) {
visitor.visitField(fn);
}
for (ConstructorNode cn : getDeclaredConstructors()) {
visitor.visitConstructor(cn);
}
for (MethodNode mn : getMethods()) {
visitor.visitMethod(mn);
}
}
Source: src/main/org/codehaus/groovy/ast/ClassNode.java#L1066-L108
we will see that it analyses and process class properties, fields, constructors and methods. At this phase it resolves all types defined for these elements. It sees that there are two properties m1 and m2 with types MyOuter and MyOuter.MyInner accordingly, and it executes visitor.visitProperty(pn); for them. This method executes the one we are looking for - resolve()
private boolean resolve(ClassNode type, boolean testModuleImports, boolean testDefaultImports, boolean testStaticInnerClasses) {
resolveGenericsTypes(type.getGenericsTypes());
if (type.isResolved() || type.isPrimaryClassNode()) return true;
if (type.isArray()) {
ClassNode element = type.getComponentType();
boolean resolved = resolve(element, testModuleImports, testDefaultImports, testStaticInnerClasses);
if (resolved) {
ClassNode cn = element.makeArray();
type.setRedirect(cn);
}
return resolved;
}
// test if vanilla name is current class name
if (currentClass == type) return true;
String typeName = type.getName();
if (genericParameterNames.get(typeName) != null) {
GenericsType gt = genericParameterNames.get(typeName);
type.setRedirect(gt.getType());
type.setGenericsTypes(new GenericsType[]{ gt });
type.setGenericsPlaceHolder(true);
return true;
}
if (currentClass.getNameWithoutPackage().equals(typeName)) {
type.setRedirect(currentClass);
return true;
}
return resolveNestedClass(type) ||
resolveFromModule(type, testModuleImports) ||
resolveFromCompileUnit(type) ||
resolveFromDefaultImports(type, testDefaultImports) ||
resolveFromStaticInnerClasses(type, testStaticInnerClasses) ||
resolveToOuter(type);
}
Source: src/main/org/codehaus/groovy/control/ResolveVisitor.java#L343-L378
This method gets executed for both MyOuter and MyOuter.MyInner classes. It is worth mentioning that class resolving mechanism only checks if given class is available in the classpath and it does not load or parse any classes. That is why MyOuter gets recognized when this method reaches resolveToOuter(type). If we take a quick look at its source code we will understand why it works for this class:
private boolean resolveToOuter(ClassNode type) {
String name = type.getName();
// We do not need to check instances of LowerCaseClass
// to be a Class, because unless there was an import for
// for this we do not lookup these cases. This was a decision
// made on the mailing list. To ensure we will not visit this
// method again we set a NO_CLASS for this name
if (type instanceof LowerCaseClass) {
classNodeResolver.cacheClass(name, ClassNodeResolver.NO_CLASS);
return false;
}
if (currentClass.getModule().hasPackageName() && name.indexOf('.') == -1) return false;
LookupResult lr = null;
lr = classNodeResolver.resolveName(name, compilationUnit);
if (lr!=null) {
if (lr.isSourceUnit()) {
SourceUnit su = lr.getSourceUnit();
currentClass.getCompileUnit().addClassNodeToCompile(type, su);
} else {
type.setRedirect(lr.getClassNode());
}
return true;
}
return false;
}
Source: src/main/org/codehaus/groovy/control/ResolveVisitor.java#L725-L751
When Groovy class loader tries to resolve MyOuter type name it reaches
lr = classNodeResolver.resolveName(name, compilationUnit);
which locates script with a name MyOuter.groovy and it creates a SourceUnit object associated with this script file name. It is simply something like saying "OK, this class is not in my classpath at the moment, but there is a source file I can see that once compiled it will provide a valid type of name MyOuter". This is why it finally reaches:
currentClass.getCompileUnit().addClassNodeToCompile(type, su);
where currentClass is an object associated with MyClass type - it adds this source unit to MyClass compilation unit, so it gets compiled with the MyClass class. And this is the point where resolving
MyOuter m1
class property ends.
In the next step it picks MyOuter.MyInner m2 property and it tries to resolve its type. Keep in mind - MyOuter got resolved correctly, but it didn't get loaded to the classpath, so it's static inner class does not exist in any scope, yet. It goes through the same resolving strategies as MyOuter, but any of them works for MyOuter.MyInner class. And this is why ResolveVisitor.resolveOrFail() eventually throws this compilation exception.
Workaround
OK, so we know what happens, but is there anything we can do about it? Luckily, there is a workaround for this problem. You can run your program and load MyClass successfully only if you load MyOuter class to Groovy script engine first:
import java.io.File;
import java.net.MalformedURLException;
import java.net.URL;
import groovy.util.GroovyScriptEngine;
public class TestGroovyScriptEngine {
public static void main(String[] args) throws MalformedURLException, ClassNotFoundException {
final File myGroovySourceDir = new File("C:/MyGroovySourceDir");
final URL[] urls = { myGroovySourceDir.toURL() };
GroovyScriptEngine groovyScriptEngine = new GroovyScriptEngine(urls,
Thread.currentThread().getContextClassLoader());
groovyScriptEngine.getGroovyClassLoader().loadClass("MyOuter");
Class<?> clazz = groovyScriptEngine.getGroovyClassLoader().loadClass("MyClass");
}
}
Why does it work? Well, semantic analysis of MyOuter class does not cause any problems, because all types are known at this stage. This is why loading MyOuter class succeeds and it results in Groovy script engine instance knows what MyOuter and MyOuter.MyInner types are. So when you next load MyClass from the same Groovy script engine it will apply different resolving strategy - it will find both classes available to the current compilation unit and it wont have to resolve MyOuter class based on its Groovy script file.
Debugging
If you want to examine this use case better it is worth to run a debugger and see analyze what happens at the runtime. You can create a breakpoint at line 357 of ResolveVisitor.java file for instance, to see described scenario in action. Keep in mind one thing though - resolveFromDefaultImports(type, testDefaultImports) will try to lookup MyClass and MyOuter classes by applying default packages like java.util, java.io, groovy.lang etc. This resolve strategy kicks in before resolveToOuter(type) so you have to patiently jump through them. But it is worth it to see and get a better understanding about how things work. Hope it helps!

JMockit MockUp persisting between Spock tests

I'm using a Spock test written in Groovy to test some Java code. I'm using JMockit to mock methods in the java code, as Spock only handles mocking Groovy classes. I'm running into a problem where a JMockit MockUp is persisting between tests. Such a mock instance should only exist for the test (per JMockit documentation), but this isn't working, and I imagine it's because it's not using the JMockit test runner, and rather the Spock test runner.
Here is the simplest example of the problem I'm facing. I have a simple method returning a string, I can change the return value of the method with MockUp but it still exists for the third test, which doesn't expect it to be used.
Java Class
public class ClassToTest {
public String method() {
return "original";
}
}
Spock Test
class ClassToTestSpec extends Specification {
void "first test"() {
when:
String result = new ClassToTest().method()
then:
result == "original"
}
void "second test"() {
setup:
new MockUp<ClassToTest>() {
#Mock
public String method() {
return "mocked"
}
}
when:
String result = new ClassToTest().method()
then:
result == "mocked"
}
void "third test"() {
when:
String result = new ClassToTest().method()
then:
result == "original"
}
}
The third test fails, because ClassToTest.method() still returns the String "mocked" rather than "original". Using a debugger I have validated that the Mocked method is called twice.
Question
Is there any way to manually remove a class MockUp in JMockit? Thanks.
You can call the MockUp.tearDown method on the created mockup object, to manually undo its effects.
Not exactly an answer to the question - because I still don't know if JMockit's MockUp can be manually removed. But thanks to #PeterNiederwieser's comments, I found that you can actually create a partial mock for a Java class. Below is the change to the second test from above.
void "second test"() {
setup:
ClassToTest test = Spy(ClassToTest) {
method() >> "mocked"
}
when:
String result = test.method()
then:
result == "mocked"
}
Peter mentioned reconsidering how and what to test if a Spy() is necessary, but for my use case this is preferred.

Resources