diff --git a/Jenkinsfile b/Jenkinsfile index a229fa51..b844d664 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -1 +1,15 @@ buildPlugin() + +stage("UI tests") { + node('docker && highmem') { + checkout scm + docker.image('jenkins/ath:acceptance-test-harness-1.69').inside('-v /var/run/docker.sock:/var/run/docker.sock --shm-size 2g') { + sh """ + mvn clean package -DskipTests # Build .hpi before running ATH so the snapshot is consumed instead of latest released + eval \$(vnc.sh) + mvn test -B -Dmaven.test.failure.ignore=true -DforkCount=1 -Ptest-ath + """ + } + junit '**/target/surefire-reports/**/*.xml' + } +} diff --git a/pom.xml b/pom.xml index b3fc426b..26c55bbe 100644 --- a/pom.xml +++ b/pom.xml @@ -16,6 +16,8 @@ 2.150.3 8 -Xmx1024m + + !ExternalWorkspaceManagerPluginTest External Workspace Manager Plugin @@ -41,8 +43,8 @@ scm:git:git://github.com/jenkinsci/${project.artifactId}-plugin.git scm:git:git@github.com:jenkinsci/${project.artifactId}-plugin.git http://github.com/jenkinsci/${project.artifactId}-plugin - HEAD - + HEAD + @@ -149,6 +151,150 @@ tests test + + org.jenkins-ci + acceptance-test-harness + 1.69 + test + + + commons-codec + commons-codec + + + commons-net + commons-net + + + commons-httpclient + commons-httpclient + + + org.sonatype.sisu + sisu-guice + + + org.jenkins-ci.main + remoting + + + org.jenkins-ci + version-number + + + args4j + args4j + + + com.github.jnr + jnr-unixsocket + + + com.github.jnr + jnr-ffi + + + com.github.jnr + jnr-constants + + + com.fasterxml.jackson.core + jackson-annotations + + + jdk.tools + jdk.tools + + + org.seleniumhq.selenium + htmlunit-driver + + + com.google.guava + guava + + + org.slf4j + slf4j-log4j12 + + + org.apache.httpcomponents + httpcore + + + + + + test-ath + + ExternalWorkspaceManagerPluginTest + + + + + + com.google.guava + guava + 25.0-jre + test + + + net.lightbody.bmp + browsermob-core + 2.1.5 + test + + + com.fasterxml.jackson.core + jackson-core + + + org.bouncycastle + bcpkix-jdk15on + + + com.jcraft + jzlib + + + org.apache.commons + commons-lang3 + + + + + com.fasterxml.jackson.core + jackson-annotations + 2.8.9 + test + + + org.apache.httpcomponents + httpcore + 4.4.12 + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + false + + + ${jenkins.version} + target/external-workspace-manager.hpi + firefox + + + + + + + + diff --git a/src/test/java/org/jenkinsci/plugins/ewm/acceptance/ExternalWorkspaceManagerPluginTest.java b/src/test/java/org/jenkinsci/plugins/ewm/acceptance/ExternalWorkspaceManagerPluginTest.java new file mode 100644 index 00000000..aaca5292 --- /dev/null +++ b/src/test/java/org/jenkinsci/plugins/ewm/acceptance/ExternalWorkspaceManagerPluginTest.java @@ -0,0 +1,170 @@ +package org.jenkinsci.plugins.ewm.acceptance; + +import org.jenkinsci.plugins.ewm.acceptance.config.ExternalGlobalConfig; +import org.jenkinsci.plugins.ewm.acceptance.config.ExternalNodeConfig; +import org.jenkinsci.test.acceptance.junit.AbstractJUnitTest; +import org.jenkinsci.test.acceptance.junit.WithPlugins; +import org.jenkinsci.test.acceptance.plugins.maven.MavenInstallation; +import org.jenkinsci.test.acceptance.po.Build; +import org.jenkinsci.test.acceptance.po.Slave; +import org.jenkinsci.test.acceptance.po.WorkflowJob; +import org.jenkinsci.test.acceptance.slave.LocalSlaveController; +import org.jenkinsci.test.acceptance.slave.SlaveController; +import org.junit.Before; +import org.junit.ClassRule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.openqa.selenium.By; + +import java.util.concurrent.ExecutionException; + +import static org.apache.commons.io.FileUtils.listFiles; +import static org.apache.commons.io.filefilter.FileFilterUtils.directoryFileFilter; +import static org.apache.commons.io.filefilter.FileFilterUtils.nameFileFilter; +import static org.hamcrest.Matchers.hasSize; +import static org.hamcrest.core.StringContains.containsString; +import static org.junit.Assert.assertThat; + +/** + * Acceptance tests for External Workspace Manager Plugin. + * + * @author Alexandru Somai + */ +@WithPlugins({"workflow-aggregator", "external-workspace-manager", "run-selector", "ws-cleanup", "git"}) +public class ExternalWorkspaceManagerPluginTest extends AbstractJUnitTest { + + @ClassRule + public static TemporaryFolder tmp = new TemporaryFolder(); + + private static final String DISK_POOL_ID = "diskpool1"; + private static final String DISK_ONE = "disk1"; + private static final String DISK_TWO = "disk2"; + private static final String MOUNT_FROM_MASTER_TO_DISK_ONE = "/fake-mount-point-1"; + private static final String MOUNT_FROM_MASTER_TO_DISK_TWO = "/fake-mount-point-2"; + + private String fakeNodeMountingPoint; + + @Before + public void setUp() throws Exception { + MavenInstallation.installMaven(jenkins, "M3", "3.1.0"); + + setUpGlobalConfig(); + + fakeNodeMountingPoint = tmp.newFolder().getAbsolutePath(); + setUpNode("linux", fakeNodeMountingPoint); + setUpNode("test", fakeNodeMountingPoint); + } + + @Test + public void shareWorkspaceOneJobTwoNodes() { + WorkflowJob job = createWorkflowJob(String.format( + "def extWorkspace = exwsAllocate '%s' \n" + + "node ('linux') { \n" + + " exws (extWorkspace) { \n" + + " writeFile file: 'marker', text: 'content' \n" + + " } \n" + + "} \n" + + "node ('test') { \n" + + " exws (extWorkspace) { \n" + + " def content = readFile(file: 'marker') \n" + + " if (content != 'content') error('Content mismatch: ' + content) \n" + + " } \n" + + "}", DISK_POOL_ID)); + + Build build = job.startBuild(); + build.shouldSucceed(); + assertThat(build.getConsole(), containsString(String.format("Running in %s/%s/%s", fakeNodeMountingPoint, job.name, build.getNumber()))); + + verifyExternalWorkspacesAction(job.name, build); + } + + @Test + public void shareWorkspaceTwoJobsTwoNodes() { + WorkflowJob upstreamJob = createWorkflowJob(String.format( + "def extWorkspace = exwsAllocate '%s' \n" + + "node ('linux') { \n" + + " exws (extWorkspace) { \n" + + " writeFile file: 'marker', text: 'content' \n " + + " } \n" + + "}", DISK_POOL_ID)); + + Build upstreamBuild = upstreamJob.startBuild(); + upstreamBuild.shouldSucceed(); + assertThat(upstreamBuild.getConsole(), containsString(String.format("Running in %s/%s/%s", fakeNodeMountingPoint, upstreamJob.name, upstreamBuild.getNumber()))); + verifyExternalWorkspacesAction(upstreamJob.name, upstreamBuild); + + WorkflowJob downstreamJob = createWorkflowJob(String.format("" + + "def run = selectRun '%s' \n" + + "def extWorkspace = exwsAllocate selectedRun: run \n" + + "node ('test') { \n" + + " exws (extWorkspace) { \n" + + " def content = readFile(file: 'marker') \n" + + " if (content != 'content') error('Content mismatch: ' + content) \n" + + " } \n" + + "}", upstreamJob.name)); + + Build downstreamBuild = downstreamJob.startBuild(); + downstreamBuild.shouldSucceed(); + assertThat(downstreamBuild.getConsole(), containsString(String.format("Running in %s/%s/%s", fakeNodeMountingPoint, upstreamJob.name, upstreamBuild.getNumber()))); + verifyExternalWorkspacesAction(upstreamJob.name, downstreamBuild); + } + + @Test + public void externalWorkspaceCleanup() { + WorkflowJob job = createWorkflowJob(String.format("" + + "def extWorkspace = exwsAllocate '%s' \n" + + "node ('linux') { \n" + + " exws (extWorkspace) { \n" + + " try { \n" + + " writeFile file: 'foobar.txt', text: 'any' \n" + + " } finally { \n" + + " step ([$class: 'WsCleanup']) \n" + + " } \n" + + " } \n" + + "}", DISK_POOL_ID)); + + Build build = job.startBuild(); + build.shouldSucceed(); + String console = build.getConsole(); + assertThat(console, containsString(String.format("Running in %s/%s/%s", fakeNodeMountingPoint, job.name, build.getNumber()))); + assertThat(console, containsString("[WS-CLEANUP] Deleting project workspace")); + assertThat(console, containsString("[WS-CLEANUP] done")); + assertThat(listFiles(tmp.getRoot(), nameFileFilter("foobar.txt"), directoryFileFilter()), hasSize(0)); + } + + private void setUpGlobalConfig() { + jenkins.configure(); + ExternalGlobalConfig globalConfig = new ExternalGlobalConfig(jenkins.getConfigPage()); + globalConfig.addDiskPool(DISK_POOL_ID, DISK_ONE, DISK_TWO, MOUNT_FROM_MASTER_TO_DISK_ONE, MOUNT_FROM_MASTER_TO_DISK_TWO); + jenkins.save(); + } + + private void setUpNode(String label, String fakeMountingPoint) throws ExecutionException, InterruptedException { + SlaveController controller = new LocalSlaveController(); + Slave linuxSlave = controller.install(jenkins).get(); + linuxSlave.configure(); + linuxSlave.setLabels(label); + + ExternalNodeConfig nodeConfig = new ExternalNodeConfig(linuxSlave); + nodeConfig.setConfig(DISK_POOL_ID, DISK_ONE, DISK_TWO, fakeMountingPoint); + linuxSlave.save(); + } + + private WorkflowJob createWorkflowJob(String script) { + WorkflowJob job = jenkins.jobs.create(WorkflowJob.class); + job.script.set(script); + job.save(); + + return job; + } + + private void verifyExternalWorkspacesAction(String jobName, Build build) { + build.visit("exwsAllocate"); + String exwsAllocateText = driver.findElement(By.id("main-panel")).getText(); + assertThat(exwsAllocateText, containsString(String.format("Disk Pool ID: %s", DISK_POOL_ID))); + assertThat(exwsAllocateText, containsString(String.format("Disk ID: %s", DISK_ONE))); + assertThat(exwsAllocateText, containsString(String.format("Workspace path on %s: %s/%s", DISK_ONE, jobName, build.getNumber()))); + assertThat(exwsAllocateText, containsString(String.format("Complete workspace path on %s (from Jenkins master): %s/%s/%s", + DISK_ONE, MOUNT_FROM_MASTER_TO_DISK_ONE, jobName, build.getNumber()))); + } +} diff --git a/src/test/java/org/jenkinsci/plugins/ewm/acceptance/config/ExternalGlobalConfig.java b/src/test/java/org/jenkinsci/plugins/ewm/acceptance/config/ExternalGlobalConfig.java new file mode 100644 index 00000000..76f70257 --- /dev/null +++ b/src/test/java/org/jenkinsci/plugins/ewm/acceptance/config/ExternalGlobalConfig.java @@ -0,0 +1,33 @@ +package org.jenkinsci.plugins.ewm.acceptance.config; + +import org.jenkinsci.test.acceptance.po.JenkinsConfig; +import org.jenkinsci.test.acceptance.po.PageAreaImpl; + +/** + * Helper class for interacting with External Workspace Manager Plugin global config page. + * + * @author Alexandru Somai + */ +public class ExternalGlobalConfig extends PageAreaImpl { + + public ExternalGlobalConfig(JenkinsConfig context) { + super(context, "/org-jenkinsci-plugins-ewm-steps-ExwsAllocateStep"); + } + + public void addDiskPool(String diskPoolId, String diskOneId, String diskTwoId, + String mountToDiskOne, String mountToDiskTwo) { + // add disk pool + control("repeatable-add").click(); + control("diskPools/diskPoolId").set(diskPoolId); + + // add first disk + control("diskPools/repeatable-add").click(); + control("diskPools/disks/diskId").set(diskOneId); + control("diskPools/disks/masterMountPoint").set(mountToDiskOne); + + // add second disk + control("diskPools/repeatable-add").click(); + control("diskPools/disks[1]/diskId").set(diskTwoId); + control("diskPools/disks[1]/masterMountPoint").set(mountToDiskTwo); + } +} diff --git a/src/test/java/org/jenkinsci/plugins/ewm/acceptance/config/ExternalNodeConfig.java b/src/test/java/org/jenkinsci/plugins/ewm/acceptance/config/ExternalNodeConfig.java new file mode 100644 index 00000000..b56c6bef --- /dev/null +++ b/src/test/java/org/jenkinsci/plugins/ewm/acceptance/config/ExternalNodeConfig.java @@ -0,0 +1,33 @@ +package org.jenkinsci.plugins.ewm.acceptance.config; + +import org.jenkinsci.test.acceptance.po.PageAreaImpl; +import org.jenkinsci.test.acceptance.po.Slave; + +/** + * Helper class for interacting with External Workspace Manager Plugin node config page. + * + * @author Alexandru Somai + */ +public class ExternalNodeConfig extends PageAreaImpl { + + public ExternalNodeConfig(Slave context) { + super(context, "/nodeProperties/org-jenkinsci-plugins-ewm-nodes-ExternalWorkspaceProperty"); + } + + public void setConfig(String diskPoolId, String diskOneId, String diskTwoId, String fakeMountingPoint) { + // set disk pool + control("").click(); + control("repeatable-add").click(); + control("nodeDiskPools/diskPoolRefId").set(diskPoolId); + + // add first disk + control("nodeDiskPools/repeatable-add").click(); + control("nodeDiskPools/nodeDisks/diskRefId").set(diskOneId); + control("nodeDiskPools/nodeDisks/nodeMountPoint").set(fakeMountingPoint); + + // add second disk + control("nodeDiskPools/repeatable-add").click(); + control("nodeDiskPools/nodeDisks[1]/diskRefId").set(diskTwoId); + control("nodeDiskPools/nodeDisks[1]/nodeMountPoint").set(fakeMountingPoint); + } +}