Skip to content
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

Merged
merged 1 commit into from
May 21, 2019

Conversation

basil
Copy link
Member

@basil basil commented Feb 20, 2019

See JENKINS-36013. The Swarm Plugin doesn't have any integration tests that automatically run the Swarm Client against a real Jenkins master. Such tests would be highly desirable to prevent future regressions.

This change adds two new test classes: SwarmClientIntegrationTest (which tests the Swarm Client using a Freestyle job) and PipelineTest (which tests the Swarm Client using a Pipeline job). The tests work by downloading the Swarm Client from the Jenkins master and using it to connect to Jenkins.

<configuration>
<warSourceDirectory>../client/target</warSourceDirectory>
</configuration>
</plugin>
Copy link
Member Author

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 the maven-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.

Copy link
Member

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.

Copy link
Member Author

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.

It is safe, for the reasons explained in my top-level reply.

@Test
public void buildShellScriptAfterRestart() throws Exception {
Assume.assumeNotNull(
System.getProperty("port"), "This test requires a fixed port to be available.");
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This tests that a Swarm Client launched with -deleteExistingClients can reconnect to a Jenkins master after the master has been restarted. The -deleteExistingClients only works in this use case when the Jenkins master has the same port before and after the restart; however, RestartableJenkinsRule chooses a random port each time. Therefore, we are forced to skip this test unless the tests are being run with -Dport=<port-number> to fix the port before and after the restart. Unfortunately, this means this test cannot run on ci.jenkins.io since AFAIK there is no isolation between builds there (so tests cannot assume that a particular port will be free).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

RestartableJenkinsRule chooses a random port each time

That ought to be fixable, just like it already keeps the same $JENKINS_HOME.

Copy link
Member Author

Choose a reason for hiding this comment

The 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.

Copy link
Member

Choose a reason for hiding this comment

The 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 jenkins-test-harness.version. Though I should probably just incrementalify it (JEP-305) to make this easier. Give me a moment.

Copy link
Member Author

Choose a reason for hiding this comment

The 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 jenkins-test-harness.version.

Thanks! I did as you suggested in #83.

Copy link
Member

@oleg-nenashev oleg-nenashev left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @basil ! It would be great to have such tests, but I do not think they justify inclusion of the client code into the plugin. It may cause various issues, e.g. remoting lib conflicts for newer Jenkins versions.

I highly recommend starting an external process or a Docker fixture if you want to test the connection logic

<configuration>
<warSourceDirectory>../client/target</warSourceDirectory>
</configuration>
</plugin>
Copy link
Member

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.

@basil
Copy link
Member Author

basil commented Mar 21, 2019

Thanks @basil ! It would be great to have such tests, but I do not think they justify inclusion of the client code into the plugin.

Thanks for the review, @oleg-nenashev. The client JAR is already included in the plugin as of #62, which made the client JAR available from the master via HTTP at $JENKINS_URL/swarm/swarm-client.jar. #62 did this by putting the client JAR in the base directory of the .hpi, which made it available through HTTP. Note that this did not place the JAR on the classpath, which would have required putting it in the classes or WEB-INF/lib directory of the .hpi.

#62 successfully made the client JAR available via HTTP from the .hpi in production. Tests, however, use the .hpl rather than the .hpi, so #62 did not successfully make the client JAR available via HTTP from tests. My change merely brings the .hpl into parity with the .hpi, thereby completing the implementation of #62. Downloading the client JAR via HTTP now works in both production context and test context.

It may cause various issues, e.g. remoting lib conflicts for newer Jenkins versions.

No, because warSourceDirectory is a "single directory for extra files to include in the WAR" whose default value is ${basedir}/src/main/webapp, which is not on the production classpath. These extra files are only available through HTTP requests to $JENKINS_URL/swarm/, just as in production context.

I verified my understanding experimentally both in production context (where the existing logic from #61 applies) and in test context (where my new logic applies) as follows:

  1. From the Jenkins script console, I imported hudson.plugins.swarm.DownloadClientAction. This class is only available in the plugin and not the client, so it should be on the master's classpath. The import succeeded, as expected.
  2. From the Jenkins script console, I imported hudson.plugins.swarm.Candidate. This class is only available in the client and not the plugin, so it should not be on the master's classpath. The import failed, as expected.

Based on the above reasoning and experimental verification, I am confident this is not an issue.

I highly recommend starting an external process or a Docker fixture if you want to test the connection logic

I am already doing the former (starting an external process) in this change. See the new TestUtils#createSwarmClient method I added.

Copy link
Member

@jglick jglick left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good to see this! Left some minor comments.

@Test
public void buildShellScriptAfterRestart() throws Exception {
Assume.assumeNotNull(
System.getProperty("port"), "This test requires a fixed port to be available.");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

RestartableJenkinsRule chooses a random port each time

That ought to be fixable, just like it already keeps the same $JENKINS_HOME.

plugin/pom.xml Outdated
<dependency>
<groupId>org.jenkins-ci.plugins.workflow</groupId>
<artifactId>workflow-support</artifactId>
<version>2.13</version>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Introduce a POM property to ensure that the unclassified artifact is also in the same version.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BTW the GH feature of marking a conversation as resolved works very nicely in my experience to hide obsolete discussions.

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

private final ProcessDestroyer processDestroyer = new ProcessDestroyer();
private final TemporaryDirectoryAllocator tmpDir = new TemporaryDirectoryAllocator();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Better to use org.junit.TemporaryFolder.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

/** Executes a shell script build on a Swarm Client agent. */
@Test
public void buildShellScript() throws Exception {
story.addStep(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you are in Java 8, it is nicer to use .then.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

project.setDefinition(new CpsFlowDefinition(getFlow(node, 0), true));

WorkflowRun build = project.scheduleBuild2(0).waitForStart();
story.j.waitForCompletion(build);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WorkflowRun build = story.j.buildAndAssertSuccess(project);

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

File output = new File(tmpDir, "swarm-client.jar");
try (InputStream inputStream = input.openStream();
FileOutputStream outputStream = new FileOutputStream(output)) {
ByteStreams.copy(inputStream, outputStream);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think there i a FileUtils.copy or similar that makes that simpler.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

return output;
}

/** Wait for the agent with the given name to come online against the given Jenkins instance. */
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is already a method for this in JenkinsRule.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried using JenkinsRule#waitOnline, but I wasn't able to make it work. That method takes as input a Slave, and I don't have a Slave to pass into it because the Slave is created by the Swarm plugin and not my test code.

StringBuilder sb = new StringBuilder();
sb.append("\"" + System.getProperty("java.home") + "/bin/java\"");
sb.append(" -Djava.awt.headless=true");
sb.append(" -jar \"" + swarmClientJar.getAbsolutePath() + "\"");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
sb.append(" -jar \"" + swarmClientJar.getAbsolutePath() + "\"");
sb.append(" -jar \"" + swarmClientJar + "\"");

(toString already gets the path)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

sb.append(" " + arg);
}

ProcessBuilder pb = new ProcessBuilder(Util.tokenize(sb.toString()));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, simpler to construct a List<String> argv to begin with, and dispense with the quoting.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

plugin/pom.xml Outdated
<dependency>
<groupId>org.jenkins-ci.plugins</groupId>
<artifactId>scm-api</artifactId>
<version>1.3</version>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Things like this you probably want in dependencyManagement, since they are not direct dependencies, pending JENKINS-47498.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

@basil
Copy link
Member Author

basil commented Apr 30, 2019

Good to see this! Left some minor comments.

Thanks for the review!

project.setDefinition(new CpsFlowDefinition(getFlow(node, 1), true));

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

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that since this is waiting in node but not sh it only exercises part of jenkinsci/workflow-durable-task-step-plugin#104 (when jenkinsci/workflow-durable-task-step-plugin@58b127f was added).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that since this is waiting in node but not sh it only exercises part of jenkinsci/workflow-durable-task-step-plugin#104

Good point. I added a new test, PipelineJobTest#buildShellScriptAcrossDisconnect, which waits in sh (essentially a port of ExecutorStepTest#buildShellScriptAcrossDisconnect).

@basil
Copy link
Member Author

basil commented May 21, 2019

@oleg-nenashev I think I addressed all of your feedback above. Do you have any objection to proceeding with this change?

Copy link
Member

@oleg-nenashev oleg-nenashev left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the explanation in #81 (comment) . I think we can go forward with this PR

@basil basil merged commit c0e44ac into jenkinsci:master May 21, 2019
@basil basil deleted the testing branch June 2, 2019 14:59
@basil basil mentioned this pull request Jun 2, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants