How can I monkey patch a function or method in groovy? - groovy

I want to modify an existing method in some Groovy code (Jenkins Pipeline) in order to extend its behaviour without changing the code that is consuming it.
Example the "sh" function where I do want to do something like (pseudocode):
old_sh = sh
def sh (args) {
print(1)
old_sh(args)
print(2)
}

You can achieve this using metaClass like so (note that the decorator can be used to decorate any method or closure not just sh):
def sh(Map args) {
println "sh: $args"
}
sh(a: 'A', b: 'B', c: 2)
def decorator = { c, Map args ->
println 1
c(args)
println 2
}
def decoratedSH = decorator.curry(this.&sh)
this.metaClass.sh = decoratedSH
sh(a: 'A', b: 'B', c: 2)

Related

Finding list/map of free variable(s) of a closure in groovy

This is my simple groovy script;
def fourtify(String str) {
def clsr = {
str*4
}
return clsr
}
def c = fourtify("aa")
println("binding variables: ${c.getBinding().getVariables()}")
...
All I'm trying to do here is being able to access the free variable "str" using the closure instance to understand how closure works behind the scenes a bit more better. Like, perhaps, Python's locals() method.
Is there a way to do this?
The closure you have defined does not store anything in binding object - it simply returns String passed as str variable, repeated 4 times.
This binding object stores all variables that were defined without specifying their types or using def keyword. It is done via Groovy metaprogramming feature (getProperty and setProperty methods to be more specific). So when you define a variable s like:
def clsr = {
s = str*4
return s
}
then this closure will create a binding with key s and value evaluated from expression str * 4. This binding object is nothing else than a map that is accessed via getProperty and setProperty method. So when Groovy executes s = str * 4 it calls setProperty('s', str * 4) because variable/property s is not defined. If we make a slightly simple change like:
def clsr = {
def s = str*4 // or String s = str * 4
return s
}
then binding s won't be created, because setProperty method does not get executed.
Another comment to your example. If you want to see anything in binding object, you need to call returned closure. In example you have shown above the closure gets returned, but it never gets called. If you do:
def c = fourtify("aa")
c.call()
println("binding variables: ${c.getBinding().getVariables()}")
then your closure gets called and binding object will contain bindings (if any set). Now, if you modify your example to something like this:
def fourtify(String str) {
def clsr = {
def n = 4 // it does not get stored as binding
s = str * n
return s
}
return clsr
}
def c = fourtify("aa")
c.call()
println("binding variables: ${c.getBinding().getVariables()}")
you will see following output in return:
binding variables: [args:[], s:aaaaaaaa]
Hope it helps.
in your example str is a parameter of the method/function fortify
however maybe following example will give you better Closure understanding:
def c={ String s,int x-> return s*x }
println( c.getClass().getSuperclass() ) // groovy.lang.Closure
println( c.getMaximumNumberOfParameters() ) // 2
println( c.getParameterTypes() ) // [class java.lang.String, int]
the locals() Python's function better matches groovy.lang.Script.getBinding()
and here is a simple example with script:
Script scr = new GroovyShell().parse('''
println this.getBinding().getVariables() // print "s" and "x"
z = s*(x+1) // declare a new script-level var "z"
println this.getBinding().getVariables() // print "s", "x", and "z"
return s*x
''')
scr.setBinding( new Binding([
"s":"ab",
"x":4
]) )
println scr.run() // abababab
println scr.getBinding().getVariables() // print "s", "x", and "z"

Can we write junit tests for any groovy script which does not have any class and methods? If yes then how?

I have a situation where I have to write a code for a groovy script which is not written inside a class . Its just a plain groovy script. For some reason I can't show the actual code .But it would simply like this:
def var = 10
return var
You can create an object of the class and call run() method on it. This will instantiate the class and run the method in it.
You do not have to write tests specially in the case that you mentioned.
Use in-built assert for the same.
def var = 10
var++
assert 11 == var, 'Value mismatch for var'
One style is to write micro-tests inline, using the assert feature (as mentioned elsewhere):
def reverseString = { s ->
s?.reverse()
}
// test_reverseString
assert 'cba' == reverseString('abc')
assert '' == reverseString('')
assert null == reverseString(null)
def sumList = { list ->
(list == null) ? 0 :
(list.isEmpty()) ? 0 :
list.sum()
}
// test_sumList
assert 6 == sumList([1,2,3])
assert 0 == sumList([])
assert 0 == sumList(null)
// ---- main
println reverseString('esrever')
println sumList([1,2,3,4])
It is relatively easy to modify this code so that only the tests execute (e.g. based on a command-line argument).

If I define method with (arg, ...args), the parameters will reverse

for instance:
def m(arg, ...args) {
println "arg: $arg"
println "args: $args"
}
m('arg', k:'v')
output:
arg:['k':'v']
args:['arg']
I think the right output should be
args:['arg']
arg:['k':'v']
Groovy has a special ordering rules for map parameters, if they take the first position in the arguments list
def fn(Map params, ...args) {
println "params = $params and args = $args"
}
Then, calling the method with:
fn(1, 2, 3, something:'else')
Will print:
params = [something:else] and args = [1, 2, 3]
Groovy also has special ordering rules for Closure parameters, in that if they are the last parameter:
def fn2(a, b, Closure cl) {
cl(a, b)
}
Then you can place them outside the parentheses when calling the method, ie:
println fn2(1, 2) { a, b -> a + b } // prints 3
Because you have omitted types on all your parameters, it's just sticking the map as the first parameter

groovy method that take closure with one or two arguments

I would like to write a method that take a Closure as argument and pass to it tow arguments, but who write that closure can specify one or two arguments as he prefer
I tried in this way:
def method(Closure c){
def firstValue = 'a'
def secondValue = 'b'
c(firstValue, secondValue);
}
//execute
method { a ->
println "I just need $a"
}
method { a, b ->
println "I need both $a and $b"
}
If I try to execute this code the result is:
Caught: groovy.lang.MissingMethodException: No signature of method: clos2$_run_closure1.call() is applicable for argument types: (java.lang.String, java.lang.String) values: [a, b]
Possible solutions: any(), any(), dump(), dump(), doCall(java.lang.Object), any(groovy.lang.Closure)
at clos2.method(clos2.groovy:4)
at clos2.run(clos2.groovy:11)
How can I do it?
You can ask for the maximumNumberOfParameters of the Closure before calling it:
def method(Closure c){
def firstValue = 'a'
def secondValue = 'b'
if (c.maximumNumberOfParameters == 1)
c(firstValue)
else
c(firstValue, secondValue)
}
//execute
method { a ->
println "I just need $a"
}
method { a, b ->
println "I need both $a and $b"
}
Output:
I just need a
I need both a and b
The simplest is to give it a default value:
method { a, b=nil ->
println "I just need $a"
}
You can also use an array:
method { Object[] a ->
println "I just need $a"
}

How do I create and access the global variables in Groovy?

I need to store a value in a variable in one method and then I need to use that value from that variable in another method or closure. How can I share this value?
In a Groovy script the scoping can be different than expected. That is because a Groovy script in itself is a class with a method that will run the code, but that is all done runtime. We can define a variable to be scoped to the script by either omitting the type definition or in Groovy 1.8 we can add the #Field annotation.
import groovy.transform.Field
var1 = 'var1'
#Field String var2 = 'var2'
def var3 = 'var3'
void printVars() {
println var1
println var2
println var3 // This won't work, because not in script scope.
}
def i_am_not_global = 100 // This will not be accessible inside the function
i_am_global = 200 // this is global and will be even available inside the
def func()
{
log.info "My value is 200. Here you see " + i_am_global
i_am_global = 400
//log.info "if you uncomment me you will get error. Since i_am_not_global cant be printed here " + i_am_not_global
}
def func2()
{
log.info "My value was changed inside func to 400 . Here it is = " + i_am_global
}
func()
func2()
here i_am_global variable is a global variable used by func and then again available to func2
if you declare variable with def it will be local, if you don't use def its global
class Globals {
static String ouch = "I'm global.."
}
println Globals.ouch
Like all OO languages, Groovy has no concept of "global" by itself (unlike, say, BASIC, Python or Perl).
If you have several methods that need to share the same variable, use a field:
class Foo {
def a;
def foo() {
a = 1;
}
def bar() {
print a;
}
}
Just declare the variable at class or script scope, then access it from inside your methods or closures. Without an example, it's hard to be more specific for your particular problem though.
However, global variables are generally considered bad form.
Why not return the variable from one function, then pass it into the next?
I think you are talking about class level variables.
As mentioned above using global variable/class level variables are not a good practice.
If you really want to use it. and if you are sure that there will not be impact...
Declare any variable out side the method. at the class level with out the variable type
eg:
{
method()
{
a=10
print(a)
}
// def a or int a wont work
a=0
}
def sum = 0
// This method stores a value in a global variable.
def add =
{
input1 , input2 ->
sum = input1 + input2;
}
// This method uses stored value.
def multiplySum =
{
input1 ->
return sum*input1;
}
add(1,2);
multiplySum(10);
Could not figure out what you want, but you need something like this ? :
​def a = { b -> b = 1 }
​bValue = a()
println b // prints 1
Now bValue contains the value of b which is a variable in the closure a. Now you can do anything with bValue Let me know if i have misunderstood your question

Resources