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);
+ }
+}