Possibility to disable the property syntax for accessing a getter in groovy? - groovy

Let's I have a groovy class like:
class SomeClass {
String myProperty = 'foo'
}
Usually in groovy is will be totally valid to access the value using the property name or the getter - which usually gives the same result for SomeClass:
SomeClass someClass = new SomeClass()
assert someClass.myProperty == 'foo'
assert someClass.getMyProperty() == 'foo'
However - due to a flaw in the Jenkins Pipeline implementation - sometimes(!) you are forced to use the getter - as the plain property access will not work (when using some class hierarchy), see: JENKINS-47143. Bad thing is that the same code may work for some jobs while it doesn't for others:
SomeClass someClass = new SomeClass()
assert someClass.myProperty == 'foo' // sometimes throws 'property not found' error
assert someClass.getMyProperty() == 'foo'
Now I already have couple of unit tests for our Jenkins shared library - but what is missing would be a way to detect a property access, in short: A way to prohibit the property access so the unit tests will already complain in advance.

The following code:
class SomeClass {
String myProperty = 'foo'
}
SomeClass.metaClass.getProperty = { String name ->
throw new RuntimeException("tried to get property ${name}, property access only allowed via getXX() methods")
}
def s = new SomeClass()
println(s.myProperty) // a
println(s.getMyProperty()) // b
will throw an exception for line a but not throw an exception for line b. I suspect this will not be possible if SomeClass was written in java, but assuming a groovy class this could be a way to accomplish what you want.
Running the above will result in:
─➤ groovy solution.groovy
Caught: java.lang.RuntimeException: tried to get property myProperty, property access only allowed via getXX() methods
java.lang.RuntimeException: tried to get property myProperty, property access only allowed via getXX() methods
at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
...

Related

Groovy Power Assert Null Check

I have a Groovy class which receives an argument to its constructor and checks it with a Power Assert statement like so:
public MyClass(Map args) {
assert args.firstArg
}
Normally the value of args.firstArg is an instance of another Groovy class within my project. When I use a real instance of this class, the assertion passes. However when I use the Spock Mocking framework to pass in a mocked instance of the method, the assertion fails:
Assertion failed:
assert args.firstArg
| |
| Mock for type 'OtherClass' named 'mockOtherClass'
['firstArg':Mock for type 'OtherClass' named 'mockOtherClass']
However, when I change the code to
assert args.firstArg != null
then my code works.
Up until now, I had naively thought that
assert args.firstArg
was just shorthand for doing a null check.
Based on what I have observed passing in mocked class instances this is not the case, and I was wondering if someone can help me to understand what the difference is.
#hayfreed: Actually, I do not like to speculate, so please next time (or this time, in case my guess is wrong) provide more than just a set of incoherent code snippets when asking questions on StackOverflow. Ideally, provide an MCVE (please read!).
Let us create an MCVE first, shall we?
package de.scrum_master.stackoverflow.q62543765
class OtherClass {}
package de.scrum_master.stackoverflow.q62543765
class MyClass {
MyClass(Map args) {
assert args.firstArg
}
}
package de.scrum_master.stackoverflow.q62543765
import spock.lang.Specification
class MyClassTest extends Specification {
def "do not use mock"() {
when:
new MyClass([firstArg: new OtherClass(), secondArg: "foo"])
then:
noExceptionThrown()
when:
new MyClass([firstArg: null, secondArg: "foo"])
then:
thrown AssertionError
when:
new MyClass([secondArg: "foo"])
then:
thrown AssertionError
}
def "use mock"() {
when:
new MyClass([firstArg: Mock(OtherClass), secondArg: "foo"])
then:
noExceptionThrown()
}
}
This test passes. But what if we change OtherClass to be a collection type instead?
package de.scrum_master.stackoverflow.q62543765
class OtherClass extends ArrayList<String> {}
The test fails in the feature method not using the mock:
Expected no exception to be thrown, but got 'org.codehaus.groovy.runtime.powerassert.PowerAssertionError'
at spock.lang.Specification.noExceptionThrown(Specification.java:118)
at de.scrum_master.stackoverflow.q62543765.MyClassTest.do not use mock(MyClassTest.groovy:10)
Caused by: Assertion failed:
assert args.firstArg
| |
| []
['firstArg':[], 'secondArg':'foo']
at de.scrum_master.stackoverflow.q62543765.MyClass.<init>(MyClass.groovy:5)
at de.scrum_master.stackoverflow.q62543765.MyClassTest.do not use mock(MyClassTest.groovy:8)
So you see that Groovy truth is more than a null check. E.g. collection types yield false for assertions also if the collection is empty.
But back to your problem. How could the test for the mock fail? My best guess, not having seen the code for your OtherClass, is that the class implements an asBoolean method, as also described in the manual section about Groovy truth:
package de.scrum_master.stackoverflow.q62543765
class OtherClass {
boolean asBoolean(){
true
}
}
Now the test failure looks much like yours:
Expected no exception to be thrown, but got 'org.codehaus.groovy.runtime.powerassert.PowerAssertionError'
at spock.lang.Specification.noExceptionThrown(Specification.java:118)
at de.scrum_master.stackoverflow.q62543765.MyClassTest.use mock(MyClassTest.groovy:28)
Caused by: Assertion failed:
assert args.firstArg
| |
| Mock for type 'OtherClass'
['firstArg':Mock for type 'OtherClass', 'secondArg':'foo']
at de.scrum_master.stackoverflow.q62543765.MyClass.<init>(MyClass.groovy:5)
at de.scrum_master.stackoverflow.q62543765.MyClassTest.use mock(MyClassTest.groovy:26)
How would you fix that? Just stub the asBoolean method to return true:
def "use mock"() {
given:
def otherClass = Mock(OtherClass) {
asBoolean() >> true
}
when:
new MyClass([firstArg: otherClass, secondArg: "foo"])
then:
noExceptionThrown()
}

Groovy AS keyword for Map > Class binding

Given the following classes:
class A {
B b
int data
int data2
}
class B {
C c
String data
}
class C {
Date data
}
The code works fine:
Date now = new Date()
def a = [ data:42, data2:84, b:[ data:'BBB', c:[ data:now ] ] ] as A
assert a.b.c.data == now
assert a.data == 42
assert a.data2 == 84
Now if I omit the data2:84, the code still works fine except for the last assert of course.
BUT! If I "misspell" the property name like:
def a = [ data:42, data22:84, b:[ data:'BBB', c:[ data:now ] ] ] as A
I'm getting
java.lang.NullPointerException: Cannot get property 'c' on null object
and what I see is that only the A class gets instantiated with a no-arg constructor, and b and c properties are all nulls.
So, skipping != misspelling.
Hence 2 questions:
(rather phylosophycal). is this the expected behaviour? Shouldn't the misspelled props be skipped?
How can the as keyword be made "lenient" to skip the unknown props?
TIA
UPDATE:
A JIRA task is created https://issues.apache.org/jira/browse/GROOVY-9348
There is one main difference. When you use a map constructor that contains existing class fields, a regular A object is initialized. Here is what println a.dump() produces in such a case.
<A#7bab3f1a b=B#1e1a0406 data=42 data2=84>
However, if you put entries to your map that are not represented by the class fields, Groovy does not initialize A object but creates a proxy of A class instead.
<A1_groovyProxy#537f60bf $closures$delegate$map=[data:42, data22:84, b:[data:BBB, c:[data:Fri Dec 20 13:39:50 CET 2019]]] b=null data=0 data2=0>
This proxy does not initialize fields at all, but it stores your map passed with a constructor as an internal $closures$delegate$map field.
Take a look at the following analysis I made using your example.
DefaultGroovyMethods.asType(Map map, Class<?> clazz) throws internally GroovyCastException
org.codehaus.groovy.runtime.typehandling.GroovyCastException: Cannot cast object '{data=42, data22=84, b={data=BBB, c={data=Fri Dec 20 12:22:28 CET 2019}}}' with class 'java.util.LinkedHashMap' to class 'A' due to: org.codehaus.groovy.runtime.metaclass.MissingPropertyExceptionNoStack: No such property: data22 for class: A
Possible solutions: data2, data
Source: https://github.com/apache/groovy/blob/GROOVY_2_5_8/src/main/java/org/codehaus/groovy/runtime/DefaultGroovyMethods.java#L11813
The exception is caught and the fallback method is called:
return (T) ProxyGenerator.INSTANCE.instantiateAggregateFromBaseClass(map, clazz);
Source: https://github.com/apache/groovy/blob/GROOVY_2_5_8/src/main/java/org/codehaus/groovy/runtime/DefaultGroovyMethods.java#L11816
ProxyGenerator initializes ProxyGeneratorAdapter in the following method:
public GroovyObject instantiateAggregate(Map closureMap, List<Class> interfaces, Class clazz, Object[] constructorArgs) {
if (clazz != null && Modifier.isFinal(clazz.getModifiers())) {
throw new GroovyCastException("Cannot coerce a map to class " + clazz.getName() + " because it is a final class");
}
Map<Object,Object> map = closureMap != null ? closureMap : EMPTY_CLOSURE_MAP;
ProxyGeneratorAdapter adapter = createAdapter(map, interfaces, null, clazz);
return adapter.proxy(map, constructorArgs);
}
Source: https://github.com/apache/groovy/blob/GROOVY_2_5_8/src/main/groovy/groovy/util/ProxyGenerator.java#L162
This method has potentially a bug: in the createAdapter(map, interfaces, null, clazz) part, the null value represents delegatingClass object. When it is null, there is no delegation mechanism applied in the generated proxy class.
Last but not least, adapter.proxy(map, constructorArgs) instantiates a new object, which string dump representation looks like this:
<A1_groovyProxy#537f60bf $closures$delegate$map=[data:42, data22:84, b:[data:BBB, c:[data:Fri Dec 20 13:29:06 CET 2019]]] b=null data=0 data2=0>
As you can see, the map passed to the constructor is stored as $closure$delegate$map field. All A class values are initialized with their default values (null for b field, and 0 for remaining int fields.)
Now, ProxyGenerator class has a method that creates an adapter that supports delegation:
public GroovyObject instantiateDelegateWithBaseClass(Map closureMap, List<Class> interfaces, Object delegate, Class baseClass, String name) {
Map<Object,Object> map = closureMap != null ? closureMap : EMPTY_CLOSURE_MAP;
ProxyGeneratorAdapter adapter = createAdapter(map, interfaces, delegate.getClass(), baseClass);
return adapter.delegatingProxy(delegate, map, (Object[])null);
}
Source: https://github.com/apache/groovy/blob/GROOVY_2_5_8/src/main/groovy/groovy/util/ProxyGenerator.java#L203
I'm guessing that maybe if the ProxyGeneratorAdapter with a non-null delegatingClass was used, calling a.b would use a value from the internal delegate map, instead of the b field value. That's only my assumption.
The question is: is this a bug? It depends. As cfrick mentioned in one of the comments, initializing A with an incorrect map throws an explicit error, and you are done. Here this exception is suppressed and from the caller perspective, you have no idea what happened in the background. I run those tests using Groovy 2.5.8 and 3.0.0-RC1, the same behavior in both versions. Reporting this is an issue in the Apache Groovy JIRA project sounds reasonable, so you can get feedback from the Groovy core maintainers.

How to create instance of WCMUsePojo in my Prosper spec?

I have a functioning WCMUsePojo Groovy class which is called from a sightly html component. I am trying to create an instance of my WCMUsePojo class for testing based on the content from the Prosper setup method.
It's basically the same type of question as How can I create an instance of WCMUsePojo in a servlet for a specific page? But I don't see it answered and this is specifically about how to unit test methods in WCMUsePojo classes within the Prosper framework. Is there a Java or Groovy equivalent to Sightly's data-sly-use attribute?
def setupSpec() {
pageBuilder.content {
page_with_new_gridwrapper {
'jcr:content'{
'gridpar' {
'my_gridwrapper'('sling:resourceType':'my/components/my_gridwrapper') {
}
}
}
}
}
}
def "Test Page with New Grid Container"(){
Page page = pageManager.getPage("/content/page_with_new_gridwrapper")
// the following 2 lines return null :-(
// but I would prefer these to return an object with the type MyGridContainerHelper
MyGridContainerHelper cmp = page.getContentResource().getChild("gridpar/my_gridwrapper").adaptTo(MyGridContainerHelper.class)
Component cmp2 = WCMUtils.getComponent(page.getContentResource().getChild("gridpar/my_gridwrapper"))
expect:
page != null //passes
page.getContentResource().getChild("gridpar/my_gridwrapper") != null //passes
cmp != null // fails
cmp2 != null // fails
cmp.resourceType == "my/components/my_gridwrapper" // fails
}
To adapt an instance of MyGridContainerHelper from a resource object, you can implement your helper class using Sling Models rather than extending WCMUsePojo. Prosper supports registration of #org.apache.sling.models.annotations.Model-annotated classes by using the following syntax in the setupSpec block of your specification:
slingContext.addModelsForPackage("helper.class.package.name")
This eliminates the need to manually construct a Bindings object and initialize the POJO; the Sling model factory does all the work for you. Since Sightly's "use" attribute supports any class that is adaptable from a Resource or SlingHttpServletRequest, no additional changes are required for your existing Sightly template.
I ended up instantiating the object and calling the init method passing in a SimpleBindings object containing the resource I was testing with. This seems to work well for my purposes.
MyGridContainerHelper gridContainer = new MyGridContainerHelper();
SimpleBindings bindings = new SimpleBindings()
bindings.put("resource", page.getContentResource().getChild("gridpar/my_gridwrapper"))
gridContainer.init(bindings)

how to detect caller instance in SoapUI groovy script?

A SoapUI project can run random script upon load.
Load Script is invoked with log and project variables.
In my shared lib I have method - addAsserts() that traverses the whole project and adds schema compliance assertions to SOAP test steps. In my Load Script I call shared method
addAsserts(this)
passing 'this' as a parameter and set closure.delegate to it inside addAsserts method to make 'project' variable accessible within the closure scope
addAsserts method is defined in sharedUtil.groovy:
static def addAsserts(that){
def closure={
project.testSuites.each { testSuiteName, testSuiteObject ->
testSuiteObject.testCases.each { testCaseName, testCaseObject ->
testCaseObject.testSteps.each { testStepName, testStepObject ->
if ("class com.eviware.soapui.impl.wsdl.teststeps.WsdlTestRequestStep" == testStepObject.getClass().toString() ) {
log.info "adding 'Schema Compliance' assertion to ${testSuiteName}/${testCaseName}/${testStepName}"
testStepObject.addAssertion('Schema Compliance')
}
}
}
}
}//closure
closure.delegate=that // <--- i would like NOT to pass 'that' as parameter
// but rather detect in runtime with some kind of
// getCallerInstance() method
return closure.call()
}
QUESTION:
Is it possible to detect caller instance in runtime with some kind of getCallerInstance() method ?
No, I don't believe this is possible. Wasn't in Java either (you can find out the name/method of the calling class using some horrible stacktrace hacking, but not the instance of the class itself)
Edit...
It might be possible with a Category (but I am not experienced with SoapUI, so I don't know if this technique would fit)
Say we have a class Example defined like so:
class Example {
String name
}
We can then write a class very similar to your example code, which in this case will set the delegate of the closure, and the closure will print out the name property of the delegate (as we have set the resolve strategy to DELEGATE_ONLY)
class AssetAddingCategory {
static def addAsserts( that ) {
def closure = {
"Name of object: $name"
}
closure.delegate = that
closure.resolveStrategy = Closure.DELEGATE_ONLY
closure.call()
}
}
Later on in our code, it is then possible to do:
def tim = new Example( name:'tim' )
use( AssetAddingCategory ) {
println tim.addAsserts()
}
And this will print out
Name of object: tim

groovy "with" block usage query

I am trying to use the with block in Groovy to easily initalize my class, but I am getting the following error. Could anyone tell me what I am doing wrong?
MyXMLTemplate template = new MyXMLTemplate ().with {
TxId = 'mnop'
oapTxId = 'abcd'
}
The error I get is:
org.codehaus.groovy.runtime.typehandling.GroovyCastException: Cannot cast object 'abcd' with class 'java.lang.String' to class 'org.example.MyXMLTemplate'
at org.codehaus.groovy.runtime.typehandling.DefaultTypeTransformation.castToType(DefaultTypeTransformation.java:331)
at org.codehaus.groovy.runtime.ScriptBytecodeAdapter.castToType(ScriptBytecodeAdapter.java:599)
I am using groovy 1.8.0
You need to return the template itself from the with block:
MyXMLTemplate template = new MyXMLTemplate ().with {
TxId = 'mnop'
oapTxId = 'abcd'
it
}
It's hard to see what the problem is without seeing the definition of your class. I'll assume that TxId and oapTxId are both properties of the class.
I suspect your error is caused by oapTxId being of type MyXMLTemplate, and so not assignable from String.
Incidetally, as your with block is just initializing class properties, you could use the more idiomatic constructor and setters approach:
MyXmlTemplate template = new MyXMLTemplate(TxId: 'mnop', oapTxId : 'abcd')

Resources