-
Notifications
You must be signed in to change notification settings - Fork 99
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add integration tests for both Freestyle and Pipeline jobs #81
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Note that since this is waiting in There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Good point. I added a new test, |
||
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."); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This tests that a Swarm Client launched with There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
That ought to be fixable, just like it already keeps the same There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good point. I'll work on that in a downstream PR so as to keep this PR from developing a dependency on a Jenkins test harness change. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sure. If you want CI on that, mvn -f jenkins-test-harness clean install source:jar-no-fork deploy:deploy -DaltDeploymentRepository=maven.jenkins-ci.org::default::https://repo.jenkins-ci.org/snapshots/ -DskipTests and specify the timestamped snapshot for There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Thanks! I did as you suggested in #83. |
||
|
||
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(); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This change is necessary to allow the Swarm Client JAR to be downloaded from integration test context. Normally, this JAR is made available through the
.hpi
file via themaven-dependency-plugin
(see lines 25–51 above). However, in integration test context the.hpi
file is ignored and the test uses the.hpl
version of the plugin. As such we need to customize the.hpl
metadata to include the Swarm Client JAR on its classpath.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is not safe, because it also injects the client's classes to the plugin. And not all this libs are guaranteed to be compatible.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is safe, for the reasons explained in my top-level reply.