package hudson.matrix;
import static org.hamcrest.CoreMatchers.containsString;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertThat;
import hudson.FilePath;
import java.util.concurrent.CountDownLatch;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.jvnet.hudson.test.JenkinsRule;
import org.jvnet.hudson.test.TestBuilder;
import hudson.model.AbstractBuild;
import hudson.Launcher;
import hudson.model.BuildListener;
import hudson.model.queue.QueueTaskFuture;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import jenkins.model.Jenkins;
/**
* Tests the custom workspace support in {@link MatrixProject}.
*
* To validate the lease behaviour, use concurrent builds to run two builds and make sure they get
* same/different workspaces.
*
* @author Kohsuke Kawaguchi
*/
public class MatrixProjectCustomWorkspaceTest {
@Rule public JenkinsRule j = new JenkinsRule();
@Rule public TemporaryFolder tmp = new TemporaryFolder();
@Test
public void customWorkspaceForParentAndChild() throws Exception {
MatrixProject p = j.createProject(MatrixProject.class);
File dir = tmp.newFolder();
p.setCustomWorkspace(dir.getPath());
p.setChildCustomWorkspace("xyz");
j.configRoundtrip(p);
configureCustomWorkspaceConcurrentBuild(p);
// all concurrent builds should build on the same one workspace
for (MatrixBuild b : runTwoConcurrentBuilds(p)) {
assertEquals(dir.getPath(), b.getWorkspace().getRemote());
for (MatrixRun r : b.getRuns()) {
assertEquals(new File(dir,"xyz").getPath(), r.getWorkspace().getRemote());
}
}
}
@Test
public void customWorkspaceForParent() throws Exception {
MatrixProject p = j.createProject(MatrixProject.class);
File dir = tmp.newFolder();
p.setCustomWorkspace(dir.getPath());
p.setChildCustomWorkspace(null);
j.configRoundtrip(p);
configureCustomWorkspaceConcurrentBuild(p);
List<MatrixBuild> bs = runTwoConcurrentBuilds(p);
// all parent builds share the same workspace
for (MatrixBuild b : bs) {
assertEquals(dir.getPath(), b.getWorkspace().getRemote());
}
// foo=1 #1 and foo=1 #2 shares the same workspace,
for (int i = 0; i < 2; i++) {
assertEquals(bs.get(0).getRuns().get(i).getWorkspace(), bs.get(1).getRuns().get(i).getWorkspace());
}
// but foo=1 #1 and foo=2 #1 shouldn't.
for (int i = 0; i < 2; i++) {
assertFalse(bs.get(i).getRuns().get(0).getWorkspace().equals(bs.get(i).getRuns().get(1).getWorkspace()));
}
}
@Test
public void customWorkspaceForChild() throws Exception {
MatrixProject p = j.createProject(MatrixProject.class);
p.setCustomWorkspace(null);
p.setChildCustomWorkspace(".");
j.configRoundtrip(p);
configureCustomWorkspaceConcurrentBuild(p);
List<MatrixBuild> bs = runTwoConcurrentBuilds(p);
// each parent gets different directory
assertFalse(bs.get(0).getWorkspace().equals(bs.get(1).getWorkspace()));
// but all #1 builds should get the same workspace
for (MatrixBuild b : bs) {
for (int i = 0; i < 2; i++) {
assertEquals(b.getWorkspace(), b.getRuns().get(i).getWorkspace());
}
}
}
/**
* Test the case where neither has custom workspace
*/
@Test
public void noCustomWorkspace() throws Exception {
MatrixProject p = j.createProject(MatrixProject.class);
p.setCustomWorkspace(null);
p.setChildCustomWorkspace(null);
j.configRoundtrip(p);
configureCustomWorkspaceConcurrentBuild(p);
List<MatrixBuild> bs = runTwoConcurrentBuilds(p);
// each parent gets different directory
assertFalse(bs.get(0).getWorkspace().equals(bs.get(1).getWorkspace()));
// and every sub-build gets a different directory
for (MatrixBuild b : bs) {
FilePath x = b.getRuns().get(0).getWorkspace();
FilePath y = b.getRuns().get(1).getWorkspace();
FilePath z = b.getWorkspace();
assertFalse(x.equals(y));
assertFalse(y.equals(z));
assertFalse(z.equals(x));
}
}
/**
* Configures MatrixProject such that two builds run concurrently.
*/
private void configureCustomWorkspaceConcurrentBuild(MatrixProject p) throws Exception {
// needs sufficient parallel execution capability
j.jenkins.setNumExecutors(10);
Method m = Jenkins.class.getDeclaredMethod("updateComputerList"); // TODO is this really necessary?
m.setAccessible(true);
m.invoke(j.jenkins);
p.setAxes(new AxisList(new TextAxis("foo", "1", "2")));
p.setConcurrentBuild(true);
final CountDownLatch latch = new CountDownLatch(4);
p.getBuildersList().add(new TestBuilder() {
@Override public boolean perform(AbstractBuild<?, ?> build, Launcher launcher, BuildListener listener) throws InterruptedException, IOException {
latch.countDown();
try {
latch.await();
} catch (InterruptedException ignoreOnTeardown) {
}
return true;
}
});
}
/**
* Runs two concurrent builds and return their results.
*/
private List<MatrixBuild> runTwoConcurrentBuilds(MatrixProject p) throws Exception {
QueueTaskFuture<MatrixBuild> f1 = p.scheduleBuild2(0);
// get one going
f1.waitForStart();
QueueTaskFuture<MatrixBuild> f2 = p.scheduleBuild2(0);
List<MatrixBuild> bs = new ArrayList<MatrixBuild>();
bs.add(j.assertBuildStatusSuccess(f1.get()));
bs.add(j.assertBuildStatusSuccess(f2.get()));
return bs;
}
@Test
public void useCombinationInWorkspaceName() throws Exception {
MatrixProject p = j.jenkins.createProject(MatrixProject.class, "defaultName");
p.setAxes(new AxisList(new TextAxis("AXIS", "VALUE")));
p.scheduleBuild2(0).get();
MatrixRun build = p.getItem("AXIS=VALUE").getLastBuild();
assertThat(build.getWorkspace().getRemote(), containsString("/workspace/defaultName/AXIS/VALUE"));
}
@Test
public void useShortWorkspaceNameGlobally() throws Exception {
MatrixConfiguration.useShortWorkspaceName = true;
MatrixProject p = j.jenkins.createProject(MatrixProject.class, "shortName");
p.setAxes(new AxisList(new TextAxis("AXIS", "VALUE")));
p.scheduleBuild2(0).get();
MatrixRun build = p.getItem("AXIS=VALUE").getLastBuild();
assertThat(build.getWorkspace().getRemote(), containsString("/workspace/shortName/" + build.getParent().getDigestName()));
p.setChildCustomWorkspace("${COMBINATION}"); // Override global value
p.scheduleBuild2(0).get();
build = p.getItem("AXIS=VALUE").getLastBuild();
assertThat(build.getWorkspace().getRemote(), containsString("/workspace/shortName/AXIS/VALUE"));
}
@Test
public void useShortWorkspaceNamePerProject() throws Exception {
MatrixProject p = j.jenkins.createProject(MatrixProject.class, "shortName");
p.setAxes(new AxisList(new TextAxis("AXIS", "VALUE")));
p.scheduleBuild2(0).get();
MatrixRun build = p.getItem("AXIS=VALUE").getLastBuild();
assertThat(build.getWorkspace().getRemote(), containsString("/workspace/shortName/AXIS/VALUE"));
p.setChildCustomWorkspace("${SHORT_COMBINATION}");
p.scheduleBuild2(0).get();
build = p.getItem("AXIS=VALUE").getLastBuild();
assertThat(build.getWorkspace().getRemote(), containsString("/workspace/shortName/" + build.getParent().getDigestName()));
}
}