Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Proposal : a way to test small parts a big pipeline #168

Closed
wants to merge 4 commits into from

Conversation

kassovix
Copy link

@kassovix kassovix commented Jan 16, 2020

I am using this kind of trick to test small parts of a big pipeline, or helper scripts that uses the pipeline execution context.
Tell me if you find this usage usefull (see TestRunClosure for examples).
I am not an expert of Groovy reflection, maybe there a simpler way to achieve this, please let me know.
Regards

PS : you have down a very good job with this library ;)

}

@Test
void should_execute_without_errors() throws Exception {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just an UX question: Do you want to test an exactly closure?
Or you would like to test an "inline" script

runScript("""
   echo Hello World!
""")

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My use case is to test a part of code called in a big pipeline, that's why I did it with a closure.
But an inline script may also be interesting, I will give it a try!


@Test
void should_execute_without_errors() throws Exception {
runScript({ script ->
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like the idea. Can I just use something like this?

runScript({
  echo "Hello World!"
})

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is exactly what I was trying to do in the first place, but I could not find a way to plug the framework on the closure (see these 3 lines). It fails with this error:
No signature of method: com.lesfurets.jenkins.TestRunClosure.echo()

runScript({ script ->
script.customMethod test: 'value'
})
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could you please add a test case with a shared library?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried but I am not sure how to implement this. Could you take a look at my last commit?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just tried locally your changes, I was unable to load library with @Library annotation, but successfully loaded it dynamically.

Good news classes are working fine, but methods from vars not exists.

I believe you have to dig into groovy interceptors

@Test
    void should_use_library_sayHello() {
        String sharedLibs = this.class.getResource('/libs/commons@master').getFile()

        def library = library('commons')
                .defaultVersion('<notNeeded>')
                .allowOverride(true)
                .implicit(false)
                .targetPath('<notNeeded>')
                .retriever(projectSource(sharedLibs))
                .build()

        helper.registerSharedLibrary(library)

        helper.registerAllowedMethod("library", [String.class], {String expression ->
            helper.getLibLoader().loadLibrary(expression)
            println helper.getLibLoader().libRecords
            return new LibClassLoader(helper,null)
        })

        runScript({ script ->
            def lib = script.library 'commons'
            def utils = lib.net.courtanet.jenkins.Utils.new(script)
            assert utils.gitTools() == [branch: 'master']
            script.sayHello()
        })
    }

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can only suggest to look at https://github.com/jenkinsci/JenkinsPipelineUnit/blob/master/src/main/groovy/com/lesfurets/jenkins/unit/PipelineTestHelper.groovy#L355
it calls private instance of GroovyScriptEngine which contains all the groovy magic with pipeline methods interception and loading of shared libraries.

the workaround might be to save your closure to a String and load it via loadScript

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no need to save closure in a file. We just need an instance of Script (the script could be empty) loaded with JPU's custom GroovyScriptEngine

so mocked pipeline methods would work, and library classes would be available. And then we just rehydrate the closure to this empty script. (Inspired with previously implemented support
of declarative pipeline ) #13

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your suggestion works like a charms 👌
I just committed the fix (sorry for the delay...)

@stchar
Copy link
Contributor

stchar commented Feb 17, 2020

@kassovix, This is weirdly but it works

  • Closure runner
--- a/src/main/groovy/com/lesfurets/jenkins/unit/BasePipelineTest.groovy
+++ b/src/main/groovy/com/lesfurets/jenkins/unit/BasePipelineTest.groovy
@@ -1,6 +1,7 @@
 package com.lesfurets.jenkins.unit
 
 import static org.assertj.core.api.Assertions.assertThat
+import static groovy.lang.Closure.DELEGATE_ONLY
 
 abstract class BasePipelineTest {
 
@@ -181,17 +182,11 @@ abstract class BasePipelineTest {
      * @return the return value of the script
      */
     Object runScript(Closure scriptRun) {
-        final Script script = new Script(binding) {
-            @Override
-            Object run() {
-                scriptRun.call(this)
-                return null
-            }
-        }
-        script.metaClass.invokeMethod = helper.getMethodInterceptor()
-        script.metaClass.static.invokeMethod = helper.getMethodInterceptor()
-        script.metaClass.methodMissing = helper.getMethodMissingInterceptor()
-        return runScript(script)
+        File file = File.createTempFile("temp",".groovy")
+        def script = loadScript(file.absolutePath)
+        def rehydrate = scriptRun.rehydrate(script, script, script)
+        rehydrate.resolveStrategy = DELEGATE_ONLY
+        rehydrate.call()
     }

  • test cases
void should_execute_without_errors() throws Exception {
         runScript({ script ->
-            script.echo 'Test'
+            echo 'Test'
         })
         printCallStack()
     }
 
         helper.registerSharedLibrary(library)
-        runScript({ script ->
-
-            script.sayHello()
+        runScript({
+            sayHello()
         })

@stchar
Copy link
Contributor

stchar commented Jul 15, 2020

hi @kassovix I'm going to release a new version of JPU next week, would you like to finish this feature? Do you need any help?

@kassovix
Copy link
Author

Thanks for your consideration but I won't have enough time to spend on this until next week.
You can proceed without this PR, and this will be for the following release ;)
Regards.

@reinholdfuereder
Copy link
Contributor

Is this related or similar to #382 and #365?

@stchar
Copy link
Contributor

stchar commented Feb 17, 2022

@reinholdfuereder yep this is definitely same as #382. I would close this one in favor of #382.

@stchar stchar closed this Feb 17, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants