Java tutorial
/* * The MIT License * * Copyright (c) 2016, CloudBees, Inc. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package org.jenkinsci.plugins.pipeline.modeldefinition; import com.cloudbees.hudson.plugins.folder.AbstractFolder; import com.cloudbees.hudson.plugins.folder.Folder; import com.google.common.collect.ImmutableList; import hudson.Launcher; import hudson.model.ItemGroup; import hudson.model.ParameterDefinition; import hudson.model.Result; import hudson.model.Slave; import hudson.slaves.EnvironmentVariablesNodeProperty; import hudson.slaves.NodeProperty; import hudson.slaves.NodePropertyDescriptor; import hudson.util.DescribableList; import hudson.util.StreamTaskListener; import hudson.util.VersionNumber; import jenkins.plugins.git.GitSampleRepoRule; import jenkins.plugins.git.GitStep; import org.apache.commons.io.FileUtils; import org.apache.commons.io.IOUtils; import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.SystemUtils; import org.jenkinsci.plugins.docker.commons.tools.DockerTool; import org.jenkinsci.plugins.docker.workflow.client.DockerClient; import org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition; import org.jenkinsci.plugins.workflow.cps.CpsScmFlowDefinition; import org.jenkinsci.plugins.workflow.cps.global.UserDefinedGlobalVariableList; import org.jenkinsci.plugins.workflow.cps.global.WorkflowLibRepository; import org.jenkinsci.plugins.workflow.job.WorkflowJob; import org.jenkinsci.plugins.workflow.job.WorkflowRun; import org.junit.Assume; import org.junit.Before; import org.junit.ClassRule; import org.junit.Rule; import org.jvnet.hudson.test.BuildWatcher; import org.jvnet.hudson.test.JenkinsRule; import org.jvnet.hudson.test.ToolInstallations; import javax.inject.Inject; import java.io.File; import java.io.IOException; import java.net.URL; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import static org.hamcrest.Matchers.is; import static org.junit.Assert.assertNotNull; import static org.junit.Assume.assumeTrue; /** * @author Andrew Bayer */ public abstract class AbstractModelDefTest { @ClassRule public static BuildWatcher buildWatcher = new BuildWatcher(); @ClassRule public static JenkinsRule j = new JenkinsRule(); @Rule public GitSampleRepoRule sampleRepo = new GitSampleRepoRule(); @Rule public GitSampleRepoRule otherRepo = new GitSampleRepoRule(); @Inject WorkflowLibRepository globalLibRepo; @Inject UserDefinedGlobalVariableList uvl; @Before public void setUp() throws Exception { ToolInstallations.configureMaven3(); } public static final List<String> SHOULD_PASS_CONFIGS = ImmutableList.of("simplePipeline", "agentAny", "agentDocker", "agentLabel", "agentNoneWithNode", "metaStepSyntax", "postBuildAndNotifications", "simpleEnvironment", "simpleScript", "twoStagePipeline", "validStepParameters", "simpleEnvironment", "parallelPipeline", "simpleNotification", "simplePostBuild", "simpleTools", "legacyMetaStepSyntax", "globalLibrarySuccess", "perStageConfigAgent", "simpleJobProperties", "simpleTriggers", "simpleParameters", "toolsInStage", "environmentInStage", "stringsNeedingEscapeLogic"); public static Iterable<Object[]> configsWithErrors() { List<Object[]> result = new ArrayList<>(); // First element is config name, second element is expected JSON error. result.add(new Object[] { "missingStages", "At /pipeline: Missing one or more required properties: 'stages'" }); result.add( new Object[] { "missingAgent", "At /pipeline: Missing one or more required properties: 'agent'" }); result.add( new Object[] { "emptyStages", "At /pipeline/stages: Array has 0 entries, requires minimum of 1" }); result.add(new Object[] { "emptyEnvironment", "At /pipeline/environment: Array has 0 entries, requires minimum of 1" }); result.add(new Object[] { "emptyPostBuild", "At /pipeline/postBuild/conditions: Array has 0 entries, requires minimum of 1" }); result.add(new Object[] { "emptyNotifications", "At /pipeline/notifications/conditions: Array has 0 entries, requires minimum of 1" }); result.add(new Object[] { "rejectStageInSteps", "Invalid step 'stage' used - not allowed in this context - The stage step cannot be used in Declarative Pipelines" }); result.add(new Object[] { "rejectParallelMixedInSteps", "Invalid step 'parallel' used - not allowed in this context - The parallel step can only be used as the only top-level step in a stage's step block" }); result.add(new Object[] { "stageWithoutName", "At /pipeline/stages/0: Missing one or more required properties: 'name'" }); result.add(new Object[] { "emptyParallel", "Nothing to execute within stage 'foo'" }); result.add(new Object[] { "emptyJobProperties", "At /pipeline/jobProperties/properties: Array has 0 entries, requires minimum of 1" }); result.add(new Object[] { "emptyParameters", "At /pipeline/parameters/parameters: Array has 0 entries, requires minimum of 1" }); result.add(new Object[] { "emptyTriggers", "At /pipeline/triggers/triggers: Array has 0 entries, requires minimum of 1" }); result.add( new Object[] { "mixedMethodArgs", "Can't mix named and unnamed parameter definition arguments" }); result.add(new Object[] { "rejectPropertiesStepInMethodCall", "Invalid step 'properties' used - not allowed in this context - The properties step cannot be used in Declarative Pipelines" }); result.add( new Object[] { "wrongParameterNameMethodCall", "Invalid parameter 'namd', did you mean 'name'?" }); result.add(new Object[] { "invalidParameterTypeMethodCall", "Expecting class java.lang.String for parameter 'name' but got '1234' instead" }); result.add(new Object[] { "perStageConfigEmptySteps", "At /pipeline/stages/0/branches/0/steps: Array has 0 entries, requires minimum of 1" }); result.add(new Object[] { "perStageConfigMissingSteps", "At /pipeline/stages/0/branches/0: Missing one or more required properties: 'steps'" }); result.add(new Object[] { "perStageConfigUnknownSection", "At /pipeline/stages/0: additional properties are not allowed" }); result.add(new Object[] { "malformed", "Expected a ',' or '}' at character 243 of {\"pipeline\": {\n" + " \"stages\": [ {\n" + " \"name\": \"foo\",\n" + " \"branches\": [ {\n" + " \"name\": \"default\",\n" + " \"steps\": [ {\n" + " \"name\": \"echo\",\n" + " \"arguments\": {\n" + " \"isLiteral\": true,\n" + " \"value\": \"hello\"\n" + "\n" + " }]\n" + " }]\n" + " }],\n" + " \"agent\": {\n" + " \"isLiteral\": true,\n" + " \"value\": \"none\"\n" + " }\n" + "}}" }); return result; } public static Iterable<Object[]> runtimeConfigsWithErrors() { List<Object[]> result = new ArrayList<>(); for (Object[] e : configsWithErrors()) { result.add(e); } result.add(new Object[] { "notInstalledToolVersion", "Tool type 'maven' does not have an install of 'apache-maven-3.0.2' configured - did you mean 'apache-maven-3.0.1'?" }); return result; } public enum PossibleOS { WINDOWS, LINUX, MAC } protected void onAllowedOS(PossibleOS... osList) throws Exception { boolean passed = true; for (PossibleOS os : osList) { switch (os) { case LINUX: if (!SystemUtils.IS_OS_LINUX) { passed = false; } break; case WINDOWS: if (!SystemUtils.IS_OS_WINDOWS) { passed = false; } break; case MAC: if (!SystemUtils.IS_OS_MAC) { passed = false; } break; default: break; } } Assume.assumeTrue("Not on a valid OS for this test", passed); } protected String pipelineSourceFromResources(String pipelineName) throws IOException { return fileContentsFromResources(pipelineName + ".groovy"); } protected String fileContentsFromResources(String fileName) throws IOException { return fileContentsFromResources(fileName, false); } protected String fileContentsFromResources(String fileName, boolean swallowError) throws IOException { String fileContents = null; URL url = getClass().getResource("/" + fileName); if (url != null) { fileContents = IOUtils.toString(url); } if (!swallowError) { assertNotNull("No file contents for file " + fileName, fileContents); } else { assumeTrue(fileContents != null); } return fileContents; } protected void prepRepoWithJenkinsfile(String pipelineName) throws Exception { prepRepoWithJenkinsfileAndOtherFiles(pipelineName); } protected void prepRepoWithJenkinsfile(String subDir, String pipelineName) throws Exception { prepRepoWithJenkinsfileAndOtherFiles(subDir + "/" + pipelineName); } protected void prepRepoWithJenkinsfileAndOtherFiles(String pipelineName, String... otherFiles) throws Exception { sampleRepo.init(); sampleRepo.write("Jenkinsfile", pipelineSourceFromResources(pipelineName)); sampleRepo.git("add", "Jenkinsfile"); for (String otherFile : otherFiles) { if (otherFile != null) { sampleRepo.write(otherFile, fileContentsFromResources(otherFile)); sampleRepo.git("add", otherFile); } } sampleRepo.git("commit", "--message=files"); } protected void prepRepoWithJenkinsfileFromString(String jf) throws Exception { sampleRepo.init(); sampleRepo.write("Jenkinsfile", jf); sampleRepo.git("add", "Jenkinsfile"); sampleRepo.git("commit", "--message=files"); } protected WorkflowRun getAndStartBuild() throws Exception { return getAndStartBuild(null); } protected WorkflowRun getAndStartBuild(Folder folder) throws Exception { WorkflowJob p = createWorkflowJob(folder); p.setDefinition(new CpsScmFlowDefinition(new GitStep(sampleRepo.toString()).createSCM(), "Jenkinsfile")); return p.scheduleBuild2(0).waitForStart(); } protected WorkflowRun getAndStartNonRepoBuild(String pipelineScriptFile) throws Exception { return getAndStartNonRepoBuild(null, pipelineScriptFile); } protected WorkflowRun getAndStartNonRepoBuild(Folder folder, String pipelineScriptFile) throws Exception { WorkflowJob p = createWorkflowJob(folder); p.setDefinition(new CpsFlowDefinition(pipelineSourceFromResources(pipelineScriptFile))); return p.scheduleBuild2(0).waitForStart(); } private WorkflowJob createWorkflowJob(Folder folder) throws IOException { if (folder == null) { return j.createProject(WorkflowJob.class); } else { return folder.createProject(WorkflowJob.class, "test" + (folder.getItems().size() + 1)); } } protected void assumeDocker() throws Exception { Launcher.LocalLauncher localLauncher = new Launcher.LocalLauncher(StreamTaskListener.NULL); try { Assume.assumeThat("Docker working", localLauncher.launch().cmds(DockerTool.getExecutable(null, null, null, null), "ps").join(), is(0)); } catch (IOException x) { Assume.assumeNoException("have Docker installed", x); } DockerClient dockerClient = new DockerClient(localLauncher, null, null); Assume.assumeFalse("Docker version not < 1.3", dockerClient.version().isOlderThan(new VersionNumber("1.3"))); } protected void initGlobalLibrary() throws IOException { // Need to do the injection by hand because we're not running with a RestartableJenkinsRule. j.jenkins.getInjector().injectMembers(this); File vars = new File(globalLibRepo.workspace, "vars"); vars.mkdirs(); FileUtils.writeStringToFile(new File(vars, "acmeVar.groovy"), StringUtils.join(Arrays.asList("def hello(name) {echo \"Hello ${name}\"}", "def foo(x) { this.x = x+'-set'; }", "def bar() { return x+'-get' }", "def baz() { return 'nothing here' }"), "\n")); FileUtils.writeStringToFile(new File(vars, "returnAThing.groovy"), StringUtils.join(Arrays.asList("def call(a) { return \"${a} tada\" }"), "\n")); FileUtils.writeStringToFile(new File(vars, "acmeFunc.groovy"), StringUtils.join(Arrays.asList("def call(a,b) { echo \"call($a,$b)\" }"), "\n")); FileUtils.writeStringToFile(new File(vars, "acmeFuncClosure1.groovy"), StringUtils .join(Arrays.asList("def call(a, Closure body) { echo \"closure1($a)\"; body() }"), "\n")); FileUtils.writeStringToFile(new File(vars, "acmeFuncClosure2.groovy"), StringUtils .join(Arrays.asList("def call(a, b, Closure body) { echo \"closure2($a, $b)\"; body() }"), "\n")); FileUtils.writeStringToFile(new File(vars, "acmeFuncMap.groovy"), StringUtils.join(Arrays.asList("def call(m) { echo \"map call(${m.a},${m.b})\" }"), "\n")); FileUtils.writeStringToFile(new File(vars, "acmeBody.groovy"), StringUtils.join(Arrays.asList("def call(body) { ", " def config = [:]", " body.resolveStrategy = Closure.DELEGATE_FIRST", " body.delegate = config", " body()", " echo 'title was '+config.title", "}"), "\n")); // simulate the effect of push uvl.rebuild(); } protected <T extends ParameterDefinition> T getParameterOfType(List<ParameterDefinition> params, Class<T> c) { for (ParameterDefinition p : params) { if (c.isInstance(p)) { return (T) p; } } return null; } protected EnvBuilder env(Slave s) { return new EnvBuilder(s); } protected ExpectationsBuilder expect(String resource) { return expect((String) null, resource); } protected ExpectationsBuilder expect(Result result, String resource) { return expect(result, null, resource); } protected ExpectationsBuilder expect(String resourceParent, String resource) { return new ExpectationsBuilder(resourceParent, resource); } protected ExpectationsBuilder expect(Result result, String resourceParent, String resource) { return new ExpectationsBuilder(result, resourceParent, resource); } public class ExpectationsBuilder { private Result result = Result.SUCCESS; private final String resourceParent; private String resource; private List<String> logContains; private List<String> logNotContains; private WorkflowRun run; private boolean runFromRepo = true; private Folder folder; //We use the real stuff here, no mocking fluff private ExpectationsBuilder(String resourceParent, String resource) { this(Result.SUCCESS, resourceParent, resource); } private ExpectationsBuilder(Result result, String resourceParent, String resource) { this.result = result; this.resourceParent = resourceParent; this.resource = resource; } public ExpectationsBuilder runFromRepo(boolean mode) { runFromRepo = mode; return this; } public ExpectationsBuilder inFolder(Folder folder) { this.folder = folder; return this; } public ExpectationsBuilder logContains(String... logEntries) { if (this.logContains != null) { logContains.addAll(Arrays.asList(logEntries)); } else { this.logContains = new ArrayList<>(Arrays.asList(logEntries)); } return this; } public ExpectationsBuilder logNotContains(String... logEntries) { if (this.logNotContains != null) { this.logNotContains.addAll(Arrays.asList(logEntries)); } else { this.logNotContains = new ArrayList<>(Arrays.asList(logEntries)); } return this; } public void go() throws Exception { String resourceFullName = resource; if (resourceParent != null) { resourceFullName = resourceParent + "/" + resource; } if (run == null) { if (runFromRepo) { prepRepoWithJenkinsfile(resourceFullName); run = getAndStartBuild(folder); } else { run = getAndStartNonRepoBuild(folder, resourceFullName); } } else { run = run.getParent().scheduleBuild2(0).waitForStart(); } j.assertBuildStatus(result, j.waitForCompletion(run)); if (logContains != null) { for (String entry : logContains) { j.assertLogContains(entry, run); } } if (logNotContains != null) { for (String logNotContain : logNotContains) { j.assertLogNotContains(logNotContain, run); } } } public ExpectationsBuilder resetForNewRun(Result result) { this.result = result; resource = null; logContains = null; logNotContains = null; return this; } } public class EnvBuilder { private final Slave agent; private Map<String, String> env; protected EnvBuilder(Slave agent) { this.agent = agent; this.env = new HashMap<>(); env.put("ONSLAVE", "true"); } public EnvBuilder put(String key, String value) { env.put(key, value); return this; } public void set() throws IOException { List<EnvironmentVariablesNodeProperty.Entry> entries = new ArrayList<>(env.size()); for (Map.Entry<String, String> entry : env.entrySet()) { entries.add(new EnvironmentVariablesNodeProperty.Entry(entry.getKey(), entry.getValue())); } EnvironmentVariablesNodeProperty newProperty = new EnvironmentVariablesNodeProperty(entries); DescribableList<NodeProperty<?>, NodePropertyDescriptor> nodeProperties = agent.getNodeProperties(); nodeProperties.replace(newProperty); } } }