From fa208d78ef3181cf76b37caff2651640ca89e080 Mon Sep 17 00:00:00 2001 From: Nikita Gryzlov Date: Thu, 26 Mar 2020 17:04:42 +0300 Subject: [PATCH] =?UTF-8?q?=D0=9F=D0=B5=D1=80=D0=B5=D0=B5=D0=B7=D0=B4=20?= =?UTF-8?q?=D0=BD=D0=B0=20=D0=B3=D1=80=D0=B0=D0=B4=D0=BB=D1=8C-=D1=80?= =?UTF-8?q?=D0=B5=D0=BB=D1=8C=D1=81=D1=8B=20=D1=81=20=D1=82=D0=B5=D1=81?= =?UTF-8?q?=D1=82=D0=B0=D0=BC=D0=B8=20=D0=B8=20=D0=BE=D0=B1=D1=8A=D0=B5?= =?UTF-8?q?=D0=BA=D1=82=D0=B0=D0=BC=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 2 + build.gradle.kts | 57 ++++++++++-- jenkinsResources/core-version.txt | 2 +- .../jenkins/library/IStepExecutor.groovy | 11 +++ .../jenkins/library/StepExecutor.groovy | 25 +++++ .../configuration/JobConfiguration.groovy | 8 ++ .../library/ioc/ContextRegistry.groovy | 17 ++++ .../jenkins/library/ioc/DefaultContext.groovy | 17 ++++ .../jenkins/library/ioc/IContext.groovy | 7 ++ .../pulsar/jenkins/library/steps/Cmd.groovy | 34 +++++++ .../groovy/RuleBootstrapper.groovy | 25 +++++ test/integration/groovy/cmdTest.groovy | 39 ++++++++ .../pulsar/jenkins/library/steps/CmdTest.java | 91 +++++++++++++++++++ vars/cmd.groovy | 14 +-- 14 files changed, 334 insertions(+), 15 deletions(-) create mode 100644 src/ru/pulsar/jenkins/library/IStepExecutor.groovy create mode 100644 src/ru/pulsar/jenkins/library/StepExecutor.groovy create mode 100644 src/ru/pulsar/jenkins/library/configuration/JobConfiguration.groovy create mode 100644 src/ru/pulsar/jenkins/library/ioc/ContextRegistry.groovy create mode 100644 src/ru/pulsar/jenkins/library/ioc/DefaultContext.groovy create mode 100644 src/ru/pulsar/jenkins/library/ioc/IContext.groovy create mode 100644 src/ru/pulsar/jenkins/library/steps/Cmd.groovy create mode 100644 test/integration/groovy/RuleBootstrapper.groovy create mode 100644 test/integration/groovy/cmdTest.groovy create mode 100644 test/unit/groovy/ru/pulsar/jenkins/library/steps/CmdTest.java diff --git a/.gitignore b/.gitignore index 425e857..2e4da7e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ .idea/ .gradle/ +build/ + *.iml diff --git a/build.gradle.kts b/build.gradle.kts index f6382eb..6127970 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -2,19 +2,60 @@ import com.mkobit.jenkins.pipelines.http.AnonymousAuthentication plugins { java + groovy + jacoco id("com.mkobit.jenkins.pipelines.shared-library") version "0.10.1" - id("com.github.ben-manes.versions") version "0.21.0" + id("com.github.ben-manes.versions") version "0.28.0" } java { sourceCompatibility = JavaVersion.VERSION_1_8 } +val junitVersion = "5.6.1" +val spockVersion = "1.3-groovy-2.4" +val groovyVersion = "2.4.19" +val slf4jVersion = "1.8.0-beta4" + dependencies { - val spock = "org.spockframework:spock-core:1.2-groovy-2.4" - testImplementation(spock) - testImplementation("org.assertj:assertj-core:3.12.2") - integrationTestImplementation(spock) + implementation("org.codehaus.groovy", "groovy-all", groovyVersion) + + testImplementation("org.junit.jupiter", "junit-jupiter-api", junitVersion) + testRuntimeOnly("org.junit.jupiter", "junit-jupiter-engine", junitVersion) + + testImplementation("org.assertj", "assertj-core", "3.15.0") + testImplementation("org.mockito", "mockito-core", "3.3.3") + + testImplementation("org.spockframework", "spock-core", spockVersion) + integrationTestImplementation("org.spockframework", "spock-core", spockVersion) + integrationTestImplementation("org.codehaus.groovy", "groovy-all", groovyVersion) + + integrationTestImplementation("org.slf4j", "slf4j-api", slf4jVersion) + integrationTestImplementation("org.slf4j", "slf4j-simple", slf4jVersion) +} + +tasks.test { + useJUnitPlatform() + + testLogging { + events("passed", "skipped", "failed") + } + + reports { + html.isEnabled = true + } +} + +tasks.check { + dependsOn(tasks.jacocoTestReport) + dependsOn(tasks.integrationTest) +} + +tasks.jacocoTestReport { + reports { + xml.isEnabled = true + xml.destination = File("$buildDir/reports/jacoco/test/jacoco.xml") + } } jenkinsIntegration { @@ -28,9 +69,9 @@ sharedLibrary { coreVersion.set(jenkinsIntegration.downloadDirectory.file("core-version.txt").map { it.asFile.readText().trim() }) // TODO: retrieve downloaded plugin resource pluginDependencies { - dependency("org.jenkins-ci.plugins", "pipeline-build-step", "2.9") - dependency("org.6wind.jenkins", "lockable-resources", "2.5") - val declarativePluginsVersion = "1.3.9" + dependency("org.jenkins-ci.plugins", "pipeline-build-step", "2.12") + dependency("org.6wind.jenkins", "lockable-resources", "2.7") + val declarativePluginsVersion = "1.6.0" dependency("org.jenkinsci.plugins", "pipeline-model-api", declarativePluginsVersion) dependency("org.jenkinsci.plugins", "pipeline-model-declarative-agent", "1.1.1") dependency("org.jenkinsci.plugins", "pipeline-model-definition", declarativePluginsVersion) diff --git a/jenkinsResources/core-version.txt b/jenkinsResources/core-version.txt index a505dfd..3a5be56 100644 --- a/jenkinsResources/core-version.txt +++ b/jenkinsResources/core-version.txt @@ -1 +1 @@ -2.164.3 \ No newline at end of file +2.228 \ No newline at end of file diff --git a/src/ru/pulsar/jenkins/library/IStepExecutor.groovy b/src/ru/pulsar/jenkins/library/IStepExecutor.groovy new file mode 100644 index 0000000..3c019b4 --- /dev/null +++ b/src/ru/pulsar/jenkins/library/IStepExecutor.groovy @@ -0,0 +1,11 @@ +package ru.pulsar.jenkins.library + +interface IStepExecutor { + + boolean isUnix() + + int sh(String script, boolean returnStatus, String encoding) + + int bat(String script, boolean returnStatus, String encoding) + +} \ No newline at end of file diff --git a/src/ru/pulsar/jenkins/library/StepExecutor.groovy b/src/ru/pulsar/jenkins/library/StepExecutor.groovy new file mode 100644 index 0000000..5ca3b61 --- /dev/null +++ b/src/ru/pulsar/jenkins/library/StepExecutor.groovy @@ -0,0 +1,25 @@ +package ru.pulsar.jenkins.library + +class StepExecutor implements IStepExecutor { + + private steps + + StepExecutor(steps) { + this.steps = steps + } + + @Override + boolean isUnix() { + return steps.isUnix() + } + + @Override + int sh(String script, boolean returnStatus, String encoding) { + steps.sh script: script, returnStatus: returnStatus, encoding: encoding + } + + @Override + int bat(String script, boolean returnStatus, String encoding) { + steps.bat script: script, returnStatus: returnStatus, encoding: encoding + } +} diff --git a/src/ru/pulsar/jenkins/library/configuration/JobConfiguration.groovy b/src/ru/pulsar/jenkins/library/configuration/JobConfiguration.groovy new file mode 100644 index 0000000..3fd0eff --- /dev/null +++ b/src/ru/pulsar/jenkins/library/configuration/JobConfiguration.groovy @@ -0,0 +1,8 @@ +package ru.pulsar.jenkins.library.configuration + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + +@JsonIgnoreProperties(ignoreUnknown = true) +class JobConfiguration { + +} \ No newline at end of file diff --git a/src/ru/pulsar/jenkins/library/ioc/ContextRegistry.groovy b/src/ru/pulsar/jenkins/library/ioc/ContextRegistry.groovy new file mode 100644 index 0000000..c1dcfd6 --- /dev/null +++ b/src/ru/pulsar/jenkins/library/ioc/ContextRegistry.groovy @@ -0,0 +1,17 @@ +package ru.pulsar.jenkins.library.ioc + +class ContextRegistry implements Serializable { + private static IContext context + + static void registerContext(IContext context) { + ContextRegistry.context = context + } + + static void registerDefaultContext(Object steps) { + context = new DefaultContext(steps) + } + + static IContext getContext() { + return context + } +} diff --git a/src/ru/pulsar/jenkins/library/ioc/DefaultContext.groovy b/src/ru/pulsar/jenkins/library/ioc/DefaultContext.groovy new file mode 100644 index 0000000..deeca48 --- /dev/null +++ b/src/ru/pulsar/jenkins/library/ioc/DefaultContext.groovy @@ -0,0 +1,17 @@ +package ru.pulsar.jenkins.library.ioc + +import ru.pulsar.jenkins.library.IStepExecutor +import ru.pulsar.jenkins.library.StepExecutor + +class DefaultContext implements IContext, Serializable { + private steps + + DefaultContext(steps) { + this.steps = steps + } + + @Override + IStepExecutor getStepExecutor() { + return new StepExecutor(this.steps) + } +} diff --git a/src/ru/pulsar/jenkins/library/ioc/IContext.groovy b/src/ru/pulsar/jenkins/library/ioc/IContext.groovy new file mode 100644 index 0000000..4510f63 --- /dev/null +++ b/src/ru/pulsar/jenkins/library/ioc/IContext.groovy @@ -0,0 +1,7 @@ +package ru.pulsar.jenkins.library.ioc + +import ru.pulsar.jenkins.library.IStepExecutor + +interface IContext { + IStepExecutor getStepExecutor() +} \ No newline at end of file diff --git a/src/ru/pulsar/jenkins/library/steps/Cmd.groovy b/src/ru/pulsar/jenkins/library/steps/Cmd.groovy new file mode 100644 index 0000000..8dd2656 --- /dev/null +++ b/src/ru/pulsar/jenkins/library/steps/Cmd.groovy @@ -0,0 +1,34 @@ +package ru.pulsar.jenkins.library.steps + +import ru.pulsar.jenkins.library.IStepExecutor +import ru.pulsar.jenkins.library.ioc.ContextRegistry + +class Cmd implements Serializable { + + private String script; + private boolean returnStatus + private String encoding = 'UTF-8' + + Cmd(String script, boolean returnStatus = false) { + this.script = script + this.returnStatus = returnStatus + }; + + int run() { + IStepExecutor steps = ContextRegistry.getContext().getStepExecutor() + + int returnValue + + if (steps.isUnix()) { + returnValue = steps.sh("$script", returnStatus, encoding) + } else { + returnValue = steps.bat("chcp 65001 > nul \n$script", returnStatus, encoding) + } + + if (!returnStatus && returnValue != 0) { + throw new Error("Returned status code <$returnValue> is not equal to 0."); + } + + return returnValue + } +} diff --git a/test/integration/groovy/RuleBootstrapper.groovy b/test/integration/groovy/RuleBootstrapper.groovy new file mode 100644 index 0000000..5eef2b8 --- /dev/null +++ b/test/integration/groovy/RuleBootstrapper.groovy @@ -0,0 +1,25 @@ +import com.mkobit.jenkins.pipelines.codegen.LocalLibraryRetriever +import org.jenkinsci.plugins.workflow.libs.GlobalLibraries +import org.jenkinsci.plugins.workflow.libs.LibraryConfiguration +import org.jenkinsci.plugins.workflow.libs.LibraryRetriever +import org.jvnet.hudson.test.JenkinsRule + +final class RuleBootstrapper { + private RuleBootstrapper() { + } + + /** + * This demonstrates how you can can configure the {@link JenkinsRule} to use the local source code + * as a {@link LibraryConfiguration}. In this example we are making it implicitly loaded. + */ + static void setup(JenkinsRule rule) { + rule.timeout = 30 + final LibraryRetriever retriever = new LocalLibraryRetriever() + final LibraryConfiguration localLibrary = + new LibraryConfiguration('testLibrary', retriever) + localLibrary.implicit = true + localLibrary.defaultVersion = 'unused' + localLibrary.allowVersionOverride = false + GlobalLibraries.get().libraries = [localLibrary] + } +} \ No newline at end of file diff --git a/test/integration/groovy/cmdTest.groovy b/test/integration/groovy/cmdTest.groovy new file mode 100644 index 0000000..3e6b560 --- /dev/null +++ b/test/integration/groovy/cmdTest.groovy @@ -0,0 +1,39 @@ +import org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition +import org.jenkinsci.plugins.workflow.job.WorkflowJob +import org.jenkinsci.plugins.workflow.job.WorkflowRun +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.jvnet.hudson.test.JenkinsRule + +class cmdTest { + + @Rule + public JenkinsRule rule = new JenkinsRule() + + @Before + void configureGlobalGitLibraries() { + RuleBootstrapper.setup(rule) + } + + @Test + void "cmd should echo something"() { + def pipeline = ''' + pipeline { + agent any + stages { + stage('test') { + steps { + cmd("echo helloWorld") + } + } + } + } + '''.stripIndent() + final CpsFlowDefinition flow = new CpsFlowDefinition(pipeline, true) + final WorkflowJob workflowJob = rule.createProject(WorkflowJob, 'project') + workflowJob.definition = flow + + rule.assertLogContains('helloWorld', rule.buildAndAssertSuccess(workflowJob)) + } +} \ No newline at end of file diff --git a/test/unit/groovy/ru/pulsar/jenkins/library/steps/CmdTest.java b/test/unit/groovy/ru/pulsar/jenkins/library/steps/CmdTest.java new file mode 100644 index 0000000..6e4fd00 --- /dev/null +++ b/test/unit/groovy/ru/pulsar/jenkins/library/steps/CmdTest.java @@ -0,0 +1,91 @@ +package ru.pulsar.jenkins.library.steps; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import ru.pulsar.jenkins.library.IStepExecutor; +import ru.pulsar.jenkins.library.ioc.ContextRegistry; +import ru.pulsar.jenkins.library.ioc.IContext; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.catchThrowable; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.contains; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +class CmdTest { + + private IStepExecutor steps; + + @BeforeEach + void setUp() { + IContext context = mock(IContext.class); + steps = mock(IStepExecutor.class); + + when(context.getStepExecutor()).thenReturn(steps); + + ContextRegistry.registerContext(context); + } + + @Test + void runOk() { + // given + final String script = "echo hello"; + Cmd cmd = new Cmd(script); + + // when + int run = cmd.run(); + + // then + verify(steps).isUnix(); + verify(steps).bat(contains(script), eq(false), anyString()); + + assertThat(run).isEqualTo(0); + } + + @Test + void runFailNoReturn() { + // given + final String script = "false"; + Cmd cmd = new Cmd(script); + + when(steps.bat(anyString(), anyBoolean(), anyString())).thenReturn(1); + when(steps.sh(anyString(), anyBoolean(), anyString())).thenReturn(1); + + // when + Throwable thrown = catchThrowable(cmd::run); + assertThat(thrown).hasMessageContaining("<1>"); + + // then + verify(steps).isUnix(); + assertThat(steps).satisfiesAnyOf( + steps -> verify(steps).bat(contains(script), eq(false), anyString()), + steps -> verify(steps).sh(contains(script), eq(false), anyString()) + ); + } + + @Test + void runPassAndReturn() { + // given + final String script = "false"; + Cmd cmd = new Cmd(script, true); + + when(steps.bat(anyString(), anyBoolean(), anyString())).thenReturn(1); + when(steps.sh(anyString(), anyBoolean(), anyString())).thenReturn(1); + + // when + int run = cmd.run(); + + // then + verify(steps).isUnix(); + assertThat(steps).satisfiesAnyOf( + steps -> verify(steps).bat(contains(script), eq(true), anyString()), + steps -> verify(steps).sh(contains(script), eq(true), anyString()) + ); + + assertThat(run).isEqualTo(1); + } +} \ No newline at end of file diff --git a/vars/cmd.groovy b/vars/cmd.groovy index e7629d1..c924119 100644 --- a/vars/cmd.groovy +++ b/vars/cmd.groovy @@ -1,7 +1,9 @@ -def call(String script, boolean returnStatus = false) { - if (isUnix()) { - sh script: "$script", returnStatus: returnStatus, encoding: "UTF-8" - } else { - bat script: "chcp 65001 > nul \n$script", returnStatus: returnStatus, encoding: "UTF-8" - } +import ru.pulsar.jenkins.library.steps.Cmd +import ru.pulsar.jenkins.library.ioc.ContextRegistry + +int call(String script, boolean returnStatus = false) { + ContextRegistry.registerDefaultContext(this) + + def cmd = new Cmd(script, returnStatus) + cmd.run() }