All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.lesfurets.jenkins.unit.cps.PipelineTestHelperCPS.groovy Maven / Gradle / Ivy

package com.lesfurets.jenkins.unit.cps

import org.codehaus.groovy.control.CompilerConfiguration
import org.codehaus.groovy.control.customizers.ImportCustomizer

import com.cloudbees.groovy.cps.*
import com.cloudbees.groovy.cps.impl.CpsCallableInvocation
import com.lesfurets.jenkins.unit.PipelineTestHelper

class PipelineTestHelperCPS extends PipelineTestHelper {

    protected Class scriptBaseClass = MockPipelineScriptCPS.class

    private GroovyScriptEngine gse

    protected parallelInterceptor = { Map m ->
        // If you have many steps in parallel and one of the step in Jenkins fails, the other tasks keep runnning in Jenkins.
        // Since here the parallel steps are executed sequentially, we are hiding the error to let other steps run
        // and we make the job failing at the end.
        List exceptions = []
        m.forEach { String parallelName, Closure closure ->
            try {
                def result
                try {
                    result = closure.call()
                } catch(CpsCallableInvocation e) {
                    result = e.invoke(null, null, Continuation.HALT).run().yield.replay()
                }
                return result
            } catch (e) {
                delegate.binding.currentBuild.result = 'FAILURE'
                exceptions.add("$parallelName - ${e.getMessage()}")
            }
        }
        if (exceptions) {
            throw new Exception(exceptions.join(','))
        }
    }

    /**
     * Method interceptor for any method called in executing script.
     * Calls are logged on the call stack.
     */
    protected methodInterceptor = { String name, args ->
        // register method call to stack
        int depth = Thread.currentThread().stackTrace.findAll { it.className == delegate.class.name }.size()
        this.registerMethodCall(delegate, depth, name, args)
        // check if it is to be intercepted
        def intercepted = this.getAllowedMethodEntry(name, args)
        if (intercepted != null && intercepted.value) {
            intercepted.value.delegate = delegate
            return intercepted.value.call(*args)
        }
        // if not search for the method declaration
        MetaMethod m = delegate.metaClass.getMetaMethod(name, *args)

        // Fix for GString - String incompatibility in method invocation
        def argsWithoutGstring = args?.collect { it instanceof GString ? it as String : it }?.toArray()
        // ...and call it. If we cannot find it, delegate call to methodMissing
        def result
        if (m) {
            // Call cps steps until it yields a result
            try {
                result = m.invoke(delegate, *argsWithoutGstring)
            } catch (CpsCallableInvocation e) {
                result = e.invoke(null, null, Continuation.HALT).run().yield.replay()
            }
        } else {
            result = delegate.metaClass.invokeMissingMethod(delegate, name, args)
        }
        return result
    }

    PipelineTestHelperCPS build() {
        ImportCustomizer customizer = new ImportCustomizer()
        imports.each { k, v -> customizer.addImport(k, v) }

        CompilerConfiguration configuration = new CompilerConfiguration()
        configuration.setDefaultScriptExtension(scriptExtension)
        configuration.setScriptBaseClass(scriptBaseClass.getName())
        configuration.addCompilationCustomizers(customizer)
        // Add transformer for CPS compilation
        configuration.addCompilationCustomizers(new CpsTransformer())

        GroovyClassLoader cLoader = new GroovyClassLoader(baseClassloader, configuration)
        gse = new GroovyScriptEngine(scriptRoots, cLoader)

        gse.setConfig(configuration)
        this.registerAllowedMethod("parallel", [Map.class], parallelInterceptor)
        return this
    }

    /**
     * Load and run script with given binding context
     * @param scriptName path of the script
     * @param binding
     * @return loaded and run script
     */
    Script loadScript(String scriptName, Binding binding) {
        Objects.requireNonNull(binding)
        binding.setVariable("_TEST_HELPER", this)
        Script script = gse.createScript(scriptName, binding)
        script.metaClass.invokeMethod = methodInterceptor
        script.metaClass.static.invokeMethod = methodInterceptor
        // Probably unnecessary
        try {
            script.run()
        } catch (CpsCallableInvocation inv) {
            println inv
        }
        return script
    }

    /**
     * Marshall - Unmarshall object to test serializability
     * @param object to marshall
     * @return unmarshalled object
     */
    def  T roundtripSerialization(T object) {
        def baos = new ByteArrayOutputStream()
        new ObjectOutputStream(baos).writeObject(object)

        def ois = new ObjectInputStreamWithLoader(
                        new ByteArrayInputStream(baos.toByteArray()),
                        this.gse.groovyClassLoader)
        return (T) ois.readObject()
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy