Skip to content

Commit

Permalink
Add integration tests for both Freestyle and Pipeline jobs
Browse files Browse the repository at this point in the history
  • Loading branch information
basil committed Apr 30, 2019
1 parent 2c4b52f commit 6a96558
Show file tree
Hide file tree
Showing 5 changed files with 484 additions and 0 deletions.
88 changes: 88 additions & 0 deletions plugin/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,47 @@
</artifactItems>
</configuration>
</plugin>
<plugin>
<groupId>org.jenkins-ci.tools</groupId>
<artifactId>maven-hpi-plugin</artifactId>
<version>3.3</version>
<configuration>
<warSourceDirectory>../client/target</warSourceDirectory>
</configuration>
</plugin>
</plugins>
</build>
<properties>
<workflow-support-plugin.version>2.13</workflow-support-plugin.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.jenkins-ci</groupId>
<artifactId>symbol-annotation</artifactId>
<version>1.5</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jenkins-ci.plugins</groupId>
<artifactId>scm-api</artifactId>
<version>1.3</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jenkins-ci.plugins</groupId>
<artifactId>structs</artifactId>
<version>1.5</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jenkins-ci.plugins</groupId>
<artifactId>script-security</artifactId>
<version>1.24</version>
<scope>test</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>com.google.code.findbugs</groupId>
Expand All @@ -64,5 +103,54 @@
<version>3.0.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.jenkins-ci.plugins.workflow</groupId>
<artifactId>workflow-api</artifactId>
<version>2.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jenkins-ci.plugins.workflow</groupId>
<artifactId>workflow-basic-steps</artifactId>
<version>2.3</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jenkins-ci.plugins.workflow</groupId>
<artifactId>workflow-cps</artifactId>
<version>2.28</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jenkins-ci.plugins.workflow</groupId>
<artifactId>workflow-durable-task-step</artifactId>
<version>2.4</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jenkins-ci.plugins.workflow</groupId>
<artifactId>workflow-job</artifactId>
<version>2.9</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jenkins-ci.plugins.workflow</groupId>
<artifactId>workflow-step-api</artifactId>
<version>2.7</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jenkins-ci.plugins.workflow</groupId>
<artifactId>workflow-support</artifactId>
<version>${workflow-support-plugin.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jenkins-ci.plugins.workflow</groupId>
<artifactId>workflow-support</artifactId>
<version>${workflow-support-plugin.version}</version>
<classifier>tests</classifier>
<scope>test</scope>
</dependency>
</dependencies>
</project>
205 changes: 205 additions & 0 deletions plugin/src/test/java/hudson/plugins/swarm/PipelineJobTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
package hudson.plugins.swarm;

import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;

import hudson.Functions;
import hudson.model.Computer;
import hudson.model.Node;
import hudson.plugins.swarm.test.ProcessDestroyer;
import hudson.plugins.swarm.test.TestUtils;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition;
import org.jenkinsci.plugins.workflow.job.WorkflowJob;
import org.jenkinsci.plugins.workflow.job.WorkflowRun;
import org.jenkinsci.plugins.workflow.test.steps.SemaphoreStep;
import org.junit.Assume;
import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.jvnet.hudson.test.BuildWatcher;
import org.jvnet.hudson.test.RestartableJenkinsRule;

public class PipelineJobTest {

@Rule public RestartableJenkinsRule story = new RestartableJenkinsRule();

@ClassRule public static BuildWatcher buildWatcher = new BuildWatcher();

@ClassRule public static TemporaryFolder temporaryFolder = new TemporaryFolder();

private final ProcessDestroyer processDestroyer = new ProcessDestroyer();

/** Executes a shell script build on a Swarm Client agent. */
@Test
public void buildShellScript() throws Exception {
story.then(
s -> {
Node node =
TestUtils.createSwarmClient(story.j, processDestroyer, temporaryFolder);

WorkflowJob project = story.j.createProject(WorkflowJob.class);
project.setConcurrentBuild(false);
project.setDefinition(new CpsFlowDefinition(getFlow(node, 0), true));

WorkflowRun build = story.j.buildAndAssertSuccess(project);
story.j.assertLogContains("ON_SWARM_CLIENT=true", build);
tearDown();
});
}

/**
* Executes a shell script build on a Swarm Client agent that has disconnected while the Jenkins
* master is still running.
*/
@Test
public void buildShellScriptAfterDisconnect() throws Exception {
story.then(
s -> {
Node node =
TestUtils.createSwarmClient(story.j, processDestroyer, temporaryFolder);

WorkflowJob project = story.j.createProject(WorkflowJob.class);
project.setConcurrentBuild(false);
project.setDefinition(new CpsFlowDefinition(getFlow(node, 1), true));

WorkflowRun build = project.scheduleBuild2(0).waitForStart();
SemaphoreStep.waitForStart("wait-0/1", build);
tearDown();

TestUtils.createSwarmClient(
node.getNodeName(), story.j, processDestroyer, temporaryFolder);
SemaphoreStep.success("wait-0/1", null);
story.j.assertBuildStatusSuccess(story.j.waitForCompletion(build));
story.j.assertLogContains("ON_SWARM_CLIENT=true", build);
tearDown();
});
}

/** Same as the preceding test, but waits in "sh" rather than "node." */
@Test
public void buildShellScriptAcrossDisconnect() throws Exception {
Assume.assumeFalse(
"TODO not sure how to write a corresponding batch script", Functions.isWindows());
story.then(
s -> {
Node node =
TestUtils.createSwarmClient(story.j, processDestroyer, temporaryFolder);

WorkflowJob project = story.j.createProject(WorkflowJob.class);
File f1 = new File(story.j.jenkins.getRootDir(), "f1");
File f2 = new File(story.j.jenkins.getRootDir(), "f2");
new FileOutputStream(f1).close();
project.setConcurrentBuild(false);

StringBuilder sb = new StringBuilder();
sb.append("node('" + node.getNodeName() + "') {\n");
sb.append(
" sh 'touch \""
+ f2
+ "\"; while [ -f \""
+ f1
+ "\" ]; do sleep 1; done; echo finished waiting; rm \""
+ f2
+ "\"'\n");
sb.append(" echo 'OK, done'\n");
sb.append("}\n");
project.setDefinition(new CpsFlowDefinition(sb.toString(), true));

WorkflowRun build = project.scheduleBuild2(0).waitForStart();
while (!f2.isFile()) {
Thread.sleep(100);
}
assertTrue(build.isBuilding());
Computer computer = node.toComputer();
assertNotNull(computer);
tearDown();
while (computer.isOnline()) {
Thread.sleep(100);
}

TestUtils.createSwarmClient(
node.getNodeName(), story.j, processDestroyer, temporaryFolder);
while (computer.isOffline()) {
Thread.sleep(100);
}
assertTrue(f2.isFile());
assertTrue(f1.delete());
while (f2.isFile()) {
Thread.sleep(100);
}

story.j.assertBuildStatusSuccess(story.j.waitForCompletion(build));
story.j.assertLogContains("finished waiting", build);
story.j.assertLogContains("OK, done", build);
tearDown();
});
}

/**
* Starts a Jenkins job on a Swarm Client agent, restarts Jenkins while the job is running, and
* verifies that the job continues running on the same agent after Jenkins has been restarted.
*/
@Test
public void buildShellScriptAfterRestart() throws Exception {
Assume.assumeNotNull(
System.getProperty("port"), "This test requires a fixed port to be available.");

story.then(
s -> {
// "-deleteExistingClients" is needed so that the Swarm Client can connect
// after the restart.
Node node =
TestUtils.createSwarmClient(
story.j,
processDestroyer,
temporaryFolder,
"-deleteExistingClients");

WorkflowJob project = story.j.createProject(WorkflowJob.class, "test");
project.setConcurrentBuild(false);
project.setDefinition(new CpsFlowDefinition(getFlow(node, 1), true));

WorkflowRun build = project.scheduleBuild2(0).waitForStart();
SemaphoreStep.waitForStart("wait-0/1", build);
});
story.then(
s -> {
SemaphoreStep.success("wait-0/1", null);
WorkflowJob project =
story.j.jenkins.getItemByFullName("test", WorkflowJob.class);
assertNotNull(project);
WorkflowRun build = project.getBuildByNumber(1);
story.j.assertBuildStatusSuccess(story.j.waitForCompletion(build));
story.j.assertLogContains("ON_SWARM_CLIENT=true", build);
tearDown();
});
}

private static String getFlow(Node node, int numSemaphores) {
StringBuilder sb = new StringBuilder();
sb.append("node('" + node.getNodeName() + "') {\n");
for (int i = 0; i < numSemaphores; i++) {
sb.append(" semaphore 'wait-" + i + "'\n");
}
// TODO: Once JENKINS-41854 is fixed, remove the next two lines.
sb.append("}\n");
sb.append("node('" + node.getNodeName() + "') {\n");
sb.append(
" isUnix() ? sh('echo ON_SWARM_CLIENT=$ON_SWARM_CLIENT') : bat('echo ON_SWARM_CLIENT=%ON_SWARM_CLIENT%')");
sb.append("}\n");

return sb.toString();
}

private void tearDown() throws IOException {
try {
processDestroyer.clean();
} catch (InterruptedException e) {
e.printStackTrace(System.err);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package hudson.plugins.swarm;

import hudson.Functions;
import hudson.model.FreeStyleBuild;
import hudson.model.FreeStyleProject;
import hudson.model.Node;
import hudson.plugins.swarm.test.ProcessDestroyer;
import hudson.plugins.swarm.test.TestUtils;
import hudson.tasks.BatchFile;
import hudson.tasks.CommandInterpreter;
import hudson.tasks.Shell;
import java.io.IOException;
import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.jvnet.hudson.test.BuildWatcher;
import org.jvnet.hudson.test.JenkinsRule;

public class SwarmClientIntegrationTest {

@Rule public JenkinsRule j = new JenkinsRule();

@ClassRule public static BuildWatcher buildWatcher = new BuildWatcher();

@ClassRule public static TemporaryFolder temporaryFolder = new TemporaryFolder();

private final ProcessDestroyer processDestroyer = new ProcessDestroyer();

/** Executes a shell script build on a Swarm Client agent. */
@Test
public void buildShellScript() throws Exception {
Node node = TestUtils.createSwarmClient(j, processDestroyer, temporaryFolder);

FreeStyleProject project = j.createFreeStyleProject();
project.setConcurrentBuild(false);
project.setAssignedNode(node);
CommandInterpreter command =
Functions.isWindows()
? new BatchFile("echo ON_SWARM_CLIENT=%ON_SWARM_CLIENT%")
: new Shell("echo ON_SWARM_CLIENT=$ON_SWARM_CLIENT");
project.getBuildersList().add(command);

FreeStyleBuild build = j.buildAndAssertSuccess(project);
j.assertLogContains("ON_SWARM_CLIENT=true", build);
tearDown();
}

private void tearDown() throws IOException {
try {
processDestroyer.clean();
} catch (InterruptedException e) {
e.printStackTrace(System.err);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package hudson.plugins.swarm.test;

import java.util.HashSet;
import java.util.Set;

/**
* Records processes started during Swarm Client integration tests so that they can be destroyed at
* the end of each test method.
*/
public final class ProcessDestroyer {

private final Set<Process> processes = new HashSet<>();

/** Record a spawned process for later cleanup. */
public void record(Process process) {
processes.add(process);
}

/** Clean up all processes that were spawned during the test. */
public void clean() throws InterruptedException {
for (Process process : processes) {
process.destroy();
process.waitFor();
}
processes.clear();
}
}
Loading

0 comments on commit 6a96558

Please sign in to comment.