I have a groovy utils file with a few methods in it how can one method call another - jenkins

Is it possible to have a single .groovy file that has some utility functions defined and have one of those functions call another in that file?
note: for context, this is being used for Jenkins Pipeline library under vars folder. I wanted to have a function used for param validation call another function in the same groovy script file.
i.e. have the someFunction make use of the doSomething function, some pseudo code below.
//utils.groovy
def doSomething(def a) {
def aPrime = a
if (a == 'somethingSpecial') {
//handle it
//some logic goes here
aPrime = b
}
return aPrime
}
def someFunction(def x) {
y = doSomething(x);
more stuff.. using y
return someResult
}
def dodad() {
...
}
def whatsIt(){
...
}
In my actual code I get error like No signature of method: groovysh_evaluate.myCommonFunct() is applicable for argument types: () values: []

Nevermind this does work.
I got the error when I tried to run the contents of the file locally in groovysh. But no errors when it ran in the Jenkins pipeline

Related

mocking custom steps in jenkins vars files

In my Jenkins shared library, I have tons of groovy files in the /vars dir that define custom steps.
Many of them have multiple methods defined, and one method in a file may call another in the same file.
I am looking for a way to mock these local methods, so I can unit test each method, specifically those that call the others, without actually invoking them.
Say the structure is like this:
// vars/step.groovy
def method1() {
def someVar
result = method2(someVar)
if (result) {
echo 'ok'
}
else {
echo 'no'
}
}
def method2(value) {
if (value == 1) {
return true
}
else {
return false
}
}
Obviously this is a very simplified example. But what I need is a way to mock method2 so that I can test method1 with result of both true and false, without actually invoking method2.
I have tried the helper.registerAllowedMethod pattern, but that doesn't seem to apply to local methods. I've tried Mockito and Spock but they seem like overkill for what I need, and too much change to inject for simple cases.
I've also tried defining the methods locally in the test script with mock closures, but I can't find the right place to do that and/or the correct syntax.
I hope there's a way to do something like this:
// test/com/myOrg/stepTest.groovy
import org.junit.*
import com.lesfurets.jenkins.unit.*
import com.lesfurets.jenkins.unit.BasePipelineTest
import static groovy.test.GroovyAssert.*
class stepTest extends BasePipelineTest {
def step
#Before
void setUp() {
super.setUp()
step = loadScript("vars/step.groovy")
}
#Test
void method1Test_true () {
helper.registerAllowedMethod('method2', [], { true }
result = step.method1()
assert 'ok' == result
}
#Test
void method1Test_false () {
helper.registerAllowedMethod('method2', [], { false }
result = step.method1()
assert 'no' == result
}
}
UPDATE: one thing i've recently noticed is, in the stacktrace, the method2 local function is not listed. It's as if the local function is instantiated "inline" or in some way that it is not actually a new call, the execution just flows into it. I don't know the technical term. But it explains why the mock of method2 never gets hit: it is never called.
method1.call()
method1.successfullyMockedExternalFunction()
// i would expect method2 to be here, but it's not - the next stack items are functions _inside_ method2.
method1.functionInsideMethod2()
UPDATE 2:
This got some attention in the JPU GitHub repo.

Jenkins file, check if Globals variable exists

I have jenkinsfile with defined Globals varible for timeout
class Globals {
static String TEST_TIMEOUT = ""
}
I am using functions from shared library
I am using the global variable to set a timeout for function. Since the shared library used by other projects that doesn't define the Globals variable I defined environment variable in the function file to be used as default value for time out.
env.TESTS_TIME_OUT="10080"
Then in function I want to check if Globals variable exists, I want to use the value as time out, if not the to use the default value.
if(Globals.TEST_TIMEOUT){
env.TESTS_TIME_OUT= "${Globals.TEST_TIMEOUT}"
}
timeout(time: "${env.TESTS_TIME_OUT}", unit: 'MINUTES') {
.
.
.
}
I`ve done it before with success on env parameters, but this time I am getting an error
No such field found: field java.lang.Class TEST_TIMEOUT
Any ideas how to solve this ? Or Any other way to check if Globals variable exists ?
Thank you
You can catch groovy.lang.MissingPropertyException which will be thrown if either Globals or Globals.TEST_TIMEOUT does not exist:
try {
env.TESTS_TIME_OUT = Globals.TEST_TIMEOUT
}
catch( groovy.lang.MissingPropertyException e ) {
env.TESTS_TIME_OUT = "10080"
}
You could even move this pattern into a generic function...
def getPropOrDefault( Closure c, def defaultVal ) {
try {
return c()
}
catch( groovy.lang.MissingPropertyException e ) {
return defaultVal
}
}
... which could be called like this:
env.TESTS_TIME_OUT = getPropOrDefault({ Globals.TEST_TIMEOUT }, '10080')
This could be useful if there are many different globals that you want to treat similar. Safes you from writing many try/catch blocks.
The closure is required to make sure that the expression Globals.TEST_TIMEOUT will be evaluated inside of the try/catch block of getPropOrDefault instead of before the function call.

Mocking findFiles in JenkinsPipelineUnit

Currently I'm trying to register findFiles step.
My set up is as follows:
src/
test/
groovy/
TestJavaLib.groovy
vars/
javaLib.groovy
javaApp.jenkinsfile
Inside TestJavaApp.groovy I have:
...
import com.lesfurets.jenkins.unit.RegressionTest
import com.lesfurets.jenkins.unit.BasePipelineTest
class TestJavaLibraryPipeline extends BasePipelineTest implements RegressionTest {
// Some overridden setUp() which loads shared libs
// and registers methods referenced in javaLib.groovy
void registerPipelineMethods() {
...
def fileList = [new File("testFile1"), new File("testFile2")]
helper.registerAllowedMethod('findFiles', { f -> return fileList })
...
}
}
and my javaLib.groovy contains this currently failing part:
...
def pomFiles = findFiles glob: "target/publish/**/${JOB_BASE_NAME}*.pom"
if (pomFiles.length < 1) { // Fails with java.lang.NullPointerException: Cannot get property 'length' on null object
error("no pom file found")
}
...
I have tried multiple closures returning various objects, but everytime I get NPE.
Question is - how to correctly register "findFiles" method?
N.B. That I'm very new to mocking and closures in groovy.
Looking at the source code and examples on GitHub, I see a few overloads of the method (here):
void registerAllowedMethod(String name, List<Class> args = [], Closure closure)
void registerAllowedMethod(MethodSignature methodSignature, Closure closure)
void registerAllowedMethod(MethodSignature methodSignature, Function callback)
void registerAllowedMethod(MethodSignature methodSignature, Consumer callback)
It doesn't look like you are registering the right signature with your call. I'm actually surprised you aren't getting a MissingMethodException with your current call pattern.
You need to add the rest of the method signature during registration. The findFiles method is taking a Map of parameters (glob: "target/publish/**/${JOB_BASE_NAME}*.pom" is a map literal in Groovy). One way to register that type would be like this:
helper.registerAllowedMethod('findFiles', [Map.class], { f -> return fileList })
I also faced the same issue. However, I was able to mock the findFiles() method using the following method signature:
helper.registerAllowedMethod(method('findFiles', Map.class), {map ->
return [['path':'testPath/test.zip']]
})
So I found a way on how to mock findFiles when I needed length property:
helper.registerAllowedMethod('findFiles', [Map.class], { [length: findFilesLength ?: 1] })
This also allows to change findFilesLength variable in tests to test different conditions in pipeline like the one in my OP.

How to access Job DSL's readFileFromWorkspace from a helper class?

I have a large number of Jenkins job definitions in Job DSL that all rely on some common functionality that I implemented in helper classes. This is the essence of the jobDsl step running these scripts:
jobDsl {
additionalClasspath('jobdsl/src/main/groovy')
targets('jobdsl/*.groovy')
sandbox(true)
}
One of the helper classes in jobdsl/src/main/groovy needs to read a file from the workspace, but it cannot access the readFileFromWorkspace function.
So this one wouldn't work:
class MyHelper {
static Closure processFile(String src) {
...
def txt = readFileFromWorkspace(src)
...
}
}
I have to take a closure parameter instead:
class MyHelper {
static Closure processFile(String src, Closure rffw) {
...
def txt = rffw(src)
...
}
}
Which makes the code calling this helper bloated:
MyHelper.processFile('foo.txt', { readFileFromWorkspace(it) })
Is there a way to make my class see readFileFromWorkspace? Actually, I couldn't even figure out to which class does this function belong to. Or whether is it a real function at all or something "magicly" defined by the DSL.
HelperClass is present in other file which is out of Job-dsl context. So to make it visible, try doing as below.
class MyHelper {
static Closure processFile(String src, def dslFactory) {
...
def txt = dslFactory.readFileFromWorkspace(src)
...
}
}
MyHelper.processFile('foo.txt', this)
The above code should work for you, else please revert to me if you encounter any problems.

How to dynamically add all methods of a class into another class

I have a global shared library on Jenkins implicitly loaded on all pipelines, then my Jenkinsfile is like that:
new com.company.Pipeline()()
And then the shared library has on directory src/com/company some files, below the Pipeline.groovy class:
package com.company
import static Utils.*
def call() {
// some stuff here...
}
The problem is, this way I have to static declare all methods, thus I lose the context and cannot access jenkins' methods easly without the Pipeline class' instance. As you can see here they passing this to the method mvn.
Thinking of avoid this I was wondering about dynamically add all methods as closures by calling Utils.install this instead of using import static Utils.*, then my Utils.groovy is something like that:
package com.company
private Utils() {}
static def install(def instance) {
def utils = new Utils()
// Some extra check needed here I know, but it is not the problem now
for (def method in (utils.metaClass.methods*.name as Set) - (instance.metaClass.methods*.name as Set)) {
def closure = utils.&"$method"
closure.delegate = instance
instance.metaClass."$method" = closure
}
}
def someMethod() {
// here I want to use sh(), tool(), and other stuff freely.
}
But it raises an GStringImpl cannot be cast to String error, I believe .& do not work with variables, how can I convert a method into closure having the method name on a variable? I have the MetaMethod mostly being a CachedMethod instance, if it were possible to turn it a ClosureMetaMethod instance maybe the problem can be solved, but whenever I search for method to closure conversion for groovy I just found the .& solution!
If I use instance.metaClass.someMethod = utils.&someMethod it do work, but I want it to be dinamic as I add new methods without needing to worry about sharing it.
There is a way to do it dynamically. Notation utils.&someMethod returns a MethodClosure object that can be simply instantiated with its constructor:
MethodClosure(Object owner, String method)
Consider following example:
class Utils {
def foo() {
println "Hello, Foo!"
}
def bar() {
println "Hello, Bar!"
}
}
class Consumer {
}
def instance = new Consumer()
def utils = new Utils()
(utils.metaClass.methods*.name - instance.metaClass.methods*.name).each { method ->
def closure = new MethodClosure(utils, method)
closure.delegate = instance
instance.metaClass."$method" = closure
}
instance.foo() // Prints "Hello, Foo!"
instance.bar() // Prints "Hello, Bar!"
In this example I use def closure = new MethodClosure(utils, method) to get object method reference and then add this method to instance object. I hope it helps.

Resources