From 8c434f5e7f29c4f481742713b91fd52b2418b387 Mon Sep 17 00:00:00 2001 From: Sergei Parshev Date: Tue, 5 Nov 2019 20:17:31 -0800 Subject: [PATCH] MPL-32 Moved to CPS tests by default --- .../devops/mpl/modules/Build/BuildTest.groovy | 11 ++-- .../devops/mpl/testing/MPLTestBase.groovy | 49 ++------------- .../mpl/testing/MPLTestBaseNonCPS.groovy | 41 ++++++++++++ .../mpl/testing/MPLTestBaseSetup.groovy | 63 +++++++++++++++++++ .../devops/mpl/testing/MPLTestHelper.groovy | 37 ++++++++++- .../mpl/testing/MPLTestHelperNonCPS.groovy | 43 +++++++++++++ vars/MPLModule.groovy | 13 ++-- 7 files changed, 201 insertions(+), 56 deletions(-) create mode 100644 test/groovy/com/griddynamics/devops/mpl/testing/MPLTestBaseNonCPS.groovy create mode 100644 test/groovy/com/griddynamics/devops/mpl/testing/MPLTestBaseSetup.groovy create mode 100644 test/groovy/com/griddynamics/devops/mpl/testing/MPLTestHelperNonCPS.groovy diff --git a/test/groovy/com/griddynamics/devops/mpl/modules/Build/BuildTest.groovy b/test/groovy/com/griddynamics/devops/mpl/modules/Build/BuildTest.groovy index cf84930..f5a0dba 100644 --- a/test/groovy/com/griddynamics/devops/mpl/modules/Build/BuildTest.groovy +++ b/test/groovy/com/griddynamics/devops/mpl/modules/Build/BuildTest.groovy @@ -1,5 +1,5 @@ // -// Copyright (c) 2018 Grid Dynamics International, Inc. All Rights Reserved +// Copyright (c) 2019 Grid Dynamics International, Inc. All Rights Reserved // https://www.griddynamics.com // // Classification level: Public @@ -73,20 +73,21 @@ class BuildTest extends MPLTestBase { assertThat(helper.callStack) .filteredOn { c -> c.methodName == 'tool' } .filteredOn { c -> c.argsToString().contains('Maven 3') } + .as('Maven 3 tool used') .isNotEmpty() assertThat(helper.callStack) - .as('Shell execution should contain mvn command and default clean install') .filteredOn { c -> c.methodName == 'sh' } .filteredOn { c -> c.argsToString().startsWith('mvn') } .filteredOn { c -> c.argsToString().contains('clean install') } + .as('Shell execution should contain mvn command and default clean install') .isNotEmpty() assertThat(helper.callStack) - .as('Default mvn run without settings provided') .filteredOn { c -> c.methodName == 'sh' } .filteredOn { c -> c.argsToString().startsWith('mvn') } .filteredOn { c -> ! c.argsToString().contains('-s ') } + .as('Default mvn run without settings provided') .isNotEmpty() assertJobStatusSuccess() @@ -103,9 +104,9 @@ class BuildTest extends MPLTestBase { printCallStack() assertThat(helper.callStack) - .as('Changing maven tool name') .filteredOn { c -> c.methodName == 'tool' } .filteredOn { c -> c.argsToString().contains('Maven 2') } + .as('Changing maven tool name') .isNotEmpty() assertJobStatusSuccess() @@ -122,9 +123,9 @@ class BuildTest extends MPLTestBase { printCallStack() assertThat(helper.callStack) - .as('Providing setings file should set the maven opetion') .filteredOn { c -> c.methodName == 'sh' } .filteredOn { c -> c.argsToString().contains("-s '/test-settings.xml'") } + .as('Providing setings file should set the maven opetion') .isNotEmpty() assertJobStatusSuccess() diff --git a/test/groovy/com/griddynamics/devops/mpl/testing/MPLTestBase.groovy b/test/groovy/com/griddynamics/devops/mpl/testing/MPLTestBase.groovy index fe6bcb2..90e6df0 100644 --- a/test/groovy/com/griddynamics/devops/mpl/testing/MPLTestBase.groovy +++ b/test/groovy/com/griddynamics/devops/mpl/testing/MPLTestBase.groovy @@ -23,57 +23,18 @@ package com.griddynamics.devops.mpl.testing -import com.lesfurets.jenkins.unit.BasePipelineTest +import com.griddynamics.devops.mpl.testing.MPLTestBaseSetup +import com.lesfurets.jenkins.unit.cps.BasePipelineTestCPS -import com.griddynamics.devops.mpl.MPLConfig -import com.griddynamics.devops.mpl.MPLManager -import com.griddynamics.devops.mpl.Helper +import com.griddynamics.devops.mpl.testing.MPLTestHelper -import java.security.AccessController -import java.security.PrivilegedAction -import org.codehaus.groovy.runtime.InvokerHelper - -abstract class MPLTestBase extends BasePipelineTest { +abstract class MPLTestBase extends BasePipelineTestCPS { MPLTestBase() { helper = new MPLTestHelper() } void setUp() throws Exception { super.setUp() - - // Overriding Helper to find the right resources in the loaded libs - Helper.metaClass.static.getModulesList = { String path -> - def modules = [] - def resourcesFolder = helper.getLibraryClassLoader().getResource('.').getFile() - MPLManager.instance.getModulesLoadPaths().each { modulesPath -> - def libPath = modulesPath + '/' + path - helper.getLibraryClassLoader().getResources(libPath).each { res -> - def libname = res.getFile().substring(resourcesFolder.length()) - libname = libname.substring(0, Math.max(libname.indexOf('@'), 0)) - modules += [[libname + '/' + libPath, res.text]] - } - } - return modules - } - - // Replacing runModule function to mock it - Helper.metaClass.static.runModule = { String source, String path, Map vars = [:] -> - def binding = new Binding() - this.binding.variables.each { k, v -> binding.setVariable(k, v) } - vars.each { k, v -> binding.setVariable(k, v) } - def loader = AccessController.doPrivileged(new PrivilegedAction() { - public GroovyClassLoader run() { - return new GroovyClassLoader(helper.getLibraryClassLoader(), helper.getLibraryConfig()) - } - }) - def script = InvokerHelper.createScript(loader.parseClass(new GroovyCodeSource(source, path, '/mpl/modules'), false), binding) - script.metaClass.invokeMethod = helper.getMethodInterceptor() - script.metaClass.static.invokeMethod = helper.getMethodInterceptor() - script.metaClass.methodMissing = helper.getMethodMissingInterceptor() - script.run() - } - - // Show the dump of the configuration during unit tests execution - Helper.metaClass.static.configEntrySet = { Map config -> config.entrySet() } + MPLTestBaseSetup.setUp(helper, this.binding) } } diff --git a/test/groovy/com/griddynamics/devops/mpl/testing/MPLTestBaseNonCPS.groovy b/test/groovy/com/griddynamics/devops/mpl/testing/MPLTestBaseNonCPS.groovy new file mode 100644 index 0000000..8513ac2 --- /dev/null +++ b/test/groovy/com/griddynamics/devops/mpl/testing/MPLTestBaseNonCPS.groovy @@ -0,0 +1,41 @@ +// +// Copyright (c) 2018 Grid Dynamics International, Inc. All Rights Reserved +// https://www.griddynamics.com +// +// Classification level: Public +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// $Id: $ +// @Project: MPL +// @Description: Shared Jenkins Modular Pipeline Library +// + +package com.griddynamics.devops.mpl.testing + +import com.griddynamics.devops.mpl.testing.MPLTestBaseSetup +import com.lesfurets.jenkins.unit.BasePipelineTest + +import com.griddynamics.devops.mpl.testing.MPLTestHelper + +// It's not recommended to use non-cps class for testing - use MPLTestBase instead +abstract class MPLTestBaseNonCPS extends BasePipelineTest { + MPLTestBaseNonCPS() { + helper = new MPLTestHelperNonCPS() + } + + void setUp() throws Exception { + super.setUp() + MPLTestBaseSetup.setUp(helper, this.binding) + } +} diff --git a/test/groovy/com/griddynamics/devops/mpl/testing/MPLTestBaseSetup.groovy b/test/groovy/com/griddynamics/devops/mpl/testing/MPLTestBaseSetup.groovy new file mode 100644 index 0000000..40363dd --- /dev/null +++ b/test/groovy/com/griddynamics/devops/mpl/testing/MPLTestBaseSetup.groovy @@ -0,0 +1,63 @@ +// +// Copyright (c) 2019 Grid Dynamics International, Inc. All Rights Reserved +// https://www.griddynamics.com +// +// Classification level: Public +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// $Id: $ +// @Project: MPL +// @Description: Shared Jenkins Modular Pipeline Library +// + +package com.griddynamics.devops.mpl.testing + +import com.griddynamics.devops.mpl.MPLManager +import com.griddynamics.devops.mpl.Helper + +import org.codehaus.groovy.runtime.InvokerHelper + +abstract class MPLTestBaseSetup { + static void setUp(helper, context) throws Exception { + // Overriding Helper to find the right resources in the loaded libs + Helper.metaClass.static.getModulesList = { String path -> + def modules = [] + def resourcesFolder = helper.getLibraryClassLoader().getResource('.').getFile() + MPLManager.instance.getModulesLoadPaths().each { modulesPath -> + def libPath = modulesPath + '/' + path + helper.getLibraryClassLoader().getResources(libPath).each { res -> + def libname = res.getFile().substring(resourcesFolder.length()) + libname = libname.substring(0, Math.max(libname.indexOf('@'), 0)) + modules += [[libname + '/' + libPath, res.text]] + } + } + return modules + } + + // Replacing runModule function to mock it + Helper.metaClass.static.runModule = { String source, String path, Map vars = [:] -> + def binding = new Binding() + context.variables.each { k, v -> binding.setVariable(k, v) } + vars.each { k, v -> binding.setVariable(k, v) } + def script = InvokerHelper.createScript(helper.getLibraryClassLoader().parseClass(new GroovyCodeSource(source, path, '/mpl/modules'), false), binding) + script.metaClass.invokeMethod = helper.getMethodInterceptor() + script.metaClass.static.invokeMethod = helper.getMethodInterceptor() + script.metaClass.methodMissing = helper.getMethodMissingInterceptor() + script.run() + } + + // Show the dump of the configuration during unit tests execution + Helper.metaClass.static.configEntrySet = { Map config -> config.entrySet() } + } +} diff --git a/test/groovy/com/griddynamics/devops/mpl/testing/MPLTestHelper.groovy b/test/groovy/com/griddynamics/devops/mpl/testing/MPLTestHelper.groovy index 12720b8..b00b79f 100644 --- a/test/groovy/com/griddynamics/devops/mpl/testing/MPLTestHelper.groovy +++ b/test/groovy/com/griddynamics/devops/mpl/testing/MPLTestHelper.groovy @@ -23,11 +23,15 @@ package com.griddynamics.devops.mpl.testing -import com.lesfurets.jenkins.unit.PipelineTestHelper +import com.lesfurets.jenkins.unit.cps.PipelineTestHelperCPS import com.lesfurets.jenkins.unit.MethodSignature +import com.cloudbees.groovy.cps.impl.CpsCallableInvocation +import com.cloudbees.groovy.cps.Envs +import com.cloudbees.groovy.cps.Continuation + @groovy.transform.InheritConstructors -class MPLTestHelper extends PipelineTestHelper { +class MPLTestHelper extends PipelineTestHelperCPS { public getLibraryConfig() { gse.getConfig() } @@ -40,4 +44,33 @@ class MPLTestHelper extends PipelineTestHelper { return // Skipping methods already existing in the list allowedMethodCallbacks.put(methodSignature, closure) } + + // To make NotSerializableException more readable + @Override + Object callClosure(Closure closure, Object[] args = null) { + try { + callClosure2(closure, args) + } catch(CpsCallableInvocation e) { + def next = e.invoke(Envs.empty(), null, Continuation.HALT) + while(next.yield==null) { + try { + this.roundtripSerialization(next.e) + } catch (exception) { + throw new Exception("Exception during serialization in `${next.e.closureOwner().class.name}` for class ${exception.getMessage()}", exception) + } + next = next.step() + } + return next.yield.replay() + } + } + + Object callClosure2(Closure closure, Object[] args = null) { + if (!args) { + return closure.call() + } else if (args.size() > closure.maximumNumberOfParameters) { + return closure.call(args) + } else { + return closure.call(*args) + } + } } diff --git a/test/groovy/com/griddynamics/devops/mpl/testing/MPLTestHelperNonCPS.groovy b/test/groovy/com/griddynamics/devops/mpl/testing/MPLTestHelperNonCPS.groovy new file mode 100644 index 0000000..58303d6 --- /dev/null +++ b/test/groovy/com/griddynamics/devops/mpl/testing/MPLTestHelperNonCPS.groovy @@ -0,0 +1,43 @@ +// +// Copyright (c) 2018 Grid Dynamics International, Inc. All Rights Reserved +// https://www.griddynamics.com +// +// Classification level: Public +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// $Id: $ +// @Project: MPL +// @Description: Shared Jenkins Modular Pipeline Library +// + +package com.griddynamics.devops.mpl.testing + +import com.lesfurets.jenkins.unit.PipelineTestHelper +import com.lesfurets.jenkins.unit.MethodSignature + +@groovy.transform.InheritConstructors +class MPLTestHelperNonCPS extends PipelineTestHelper { + public getLibraryConfig() { + gse.getConfig() + } + public getLibraryClassLoader() { + gse.groovyClassLoader + } + + void registerAllowedMethod(MethodSignature methodSignature, Closure closure) { + if( isMethodAllowed(methodSignature.name, methodSignature.args) ) + return // Skipping methods already existing in the list + allowedMethodCallbacks.put(methodSignature, closure) + } +} diff --git a/vars/MPLModule.groovy b/vars/MPLModule.groovy index 5cbfe48..fed30c7 100644 --- a/vars/MPLModule.groovy +++ b/vars/MPLModule.groovy @@ -47,7 +47,7 @@ def call(String name = env.STAGE_NAME, cfg = null) { cfg = cfg.clone() else cfg = MPLConfig.create(cfg) - + // Trace of the running modules to find loops // Also to make ability to use lib module from overridden one def active_modules = MPLManager.instance.getActiveModules() @@ -64,10 +64,13 @@ def call(String name = env.STAGE_NAME, cfg = null) { module_src = readFile(project_path) } else { // Searching for the not executed module from the loaded libraries - module_src = Helper.getModulesList(module_path).find { it -> - module_path = "library:${it.first()}".toString() - ! active_modules.contains(module_path) - }?.last() + def modules = Helper.getModulesList(module_path) + for( def i = 0; i < modules.size(); i++ ) { + if( ! active_modules.contains(module_path) ) { + module_path = "library:${modules[i].first()}".toString() + module_src = modules[i].last() + } + } } if( ! module_src )