Skip to content

Commit

Permalink
[FIXED JENKINS-3907] Let all runs of a matrix build update to the sam…
Browse files Browse the repository at this point in the history
…e Mercurial revision
  • Loading branch information
willemv committed Mar 17, 2013
1 parent 8f3d32c commit e611bd6
Show file tree
Hide file tree
Showing 4 changed files with 142 additions and 29 deletions.
5 changes: 5 additions & 0 deletions pom.xml
Expand Up @@ -36,6 +36,11 @@
<name>David M. Carr</name>
<email>david@carrclan.us</email>
</developer>
<developer>
<id>willemv</id>
<name>Willem Verstraeten</name>
<email>willem.verstraeten@gmail.com</email>
</developer>
</developers>
<licenses>
<license>
Expand Down
4 changes: 2 additions & 2 deletions src/main/java/hudson/plugins/mercurial/HgExe.java
Expand Up @@ -72,8 +72,8 @@ public class HgExe {
public final TaskListener listener;
private final Capability capability;

public HgExe(MercurialSCM scm, Launcher launcher, AbstractBuild build, TaskListener listener, EnvVars env) throws IOException, InterruptedException {
this(scm,launcher,build.getBuiltOn(),listener,env);
public HgExe(MercurialSCM scm, Launcher launcher, AbstractBuild build, TaskListener listener) throws IOException, InterruptedException {
this(scm,launcher,build.getBuiltOn(),listener,build.getEnvironment(listener));
}

public HgExe(MercurialSCM scm, Launcher launcher, Node node, TaskListener listener, EnvVars env) throws IOException, InterruptedException {
Expand Down
61 changes: 34 additions & 27 deletions src/main/java/hudson/plugins/mercurial/MercurialSCM.java
Expand Up @@ -8,6 +8,7 @@
import hudson.Launcher;
import hudson.Launcher.ProcStarter;
import hudson.Util;
import hudson.matrix.MatrixRun;
import hudson.model.*;
import hudson.plugins.mercurial.browser.HgBrowser;
import hudson.plugins.mercurial.browser.HgWeb;
Expand Down Expand Up @@ -217,7 +218,7 @@ static ProcStarter launch(Launcher launcher) {
public SCMRevisionState calcRevisionsFromBuild(AbstractBuild<?, ?> build, Launcher launcher, TaskListener listener)
throws IOException, InterruptedException {
// tag action is added during checkout, so this shouldn't be called, but just in case.
HgExe hg = new HgExe(this, launcher, build, listener, build.getEnvironment(listener));
HgExe hg = new HgExe(this, launcher, build, listener);
String tip = hg.tip(workspace2Repo(build.getWorkspace()), null);
String rev = hg.tipNumber(workspace2Repo(build.getWorkspace()), null);
return tip != null && rev != null ? new MercurialTagAction(tip, rev, subdir) : null;
Expand Down Expand Up @@ -385,14 +386,15 @@ public boolean checkout(AbstractBuild<?,?> build, Launcher launcher, FilePath wo
throw new AbortException("Failed to determine whether workspace can be reused");
}

String revToBuild = getRevToBuild(build, build.getEnvironment(listener));
if (canReuseExistingWorkspace) {
update(build, launcher, repository, listener);
update(build, launcher, repository, listener, revToBuild);
} else {
clone(build, launcher, repository, listener);
clone(build, launcher, repository, listener, revToBuild);
}

try {
determineChanges(build, launcher, listener, changelogFile, repository);
determineChanges(build, launcher, listener, changelogFile, repository, revToBuild);
} catch (IOException e) {
listener.error("Failed to capture change log");
e.printStackTrace(listener.getLogger());
Expand All @@ -408,14 +410,13 @@ private boolean canReuseWorkspace(FilePath repo,
if (!new FilePath(repo, ".hg/hgrc").exists()) {
return false;
}

boolean jobUsesSharing = new FilePath(repo, ".hg/sharedpath").exists();
if (jobShouldUseSharing != jobUsesSharing) {
return false;
}

EnvVars env = build.getEnvironment(listener);
HgExe hg = new HgExe(this,launcher,build,listener,env);
HgExe hg = new HgExe(this,launcher,build,listener);
String upstream = hg.config(repo, "paths.default");
if (upstream == null) {
return false;
Expand All @@ -430,8 +431,7 @@ private boolean canReuseWorkspace(FilePath repo,
return false;
}

private void determineChanges(AbstractBuild<?, ?> build, Launcher launcher, BuildListener listener, File changelogFile, FilePath repository) throws IOException, InterruptedException {

private void determineChanges(AbstractBuild<?, ?> build, Launcher launcher, BuildListener listener, File changelogFile, FilePath repository, String revToBuild) throws IOException, InterruptedException {
AbstractBuild<?, ?> previousBuild = build.getPreviousBuild();
MercurialTagAction prevTag = previousBuild != null ? findTag(previousBuild) : null;
if (prevTag == null) {
Expand All @@ -457,7 +457,7 @@ private void determineChanges(AbstractBuild<?, ?> build, Launcher launcher, Buil
ArgumentListBuilder args = findHgExe(build, listener, false);
args.add("log");
args.add("--template", MercurialChangeSet.CHANGELOG_TEMPLATE);
args.add("--rev", getBranch(env) + ":0");
args.add("--rev", revToBuild + ":0");
args.add("--follow");
args.add("--prune", prevTag.getId());

Expand Down Expand Up @@ -485,17 +485,12 @@ private void determineChanges(AbstractBuild<?, ?> build, Launcher launcher, Buil
}
}

/*
* Updates the current repository.
*/
private void update(AbstractBuild<?, ?> build, Launcher launcher, FilePath repository, BuildListener listener)
throws InterruptedException, IOException {
EnvVars env = build.getEnvironment(listener);

HgExe hg = new HgExe(this, launcher, build, listener, env);
private void update(AbstractBuild<?, ?> build, Launcher launcher, FilePath repository, BuildListener listener, String toRevision)
throws IOException, InterruptedException {
HgExe hg = new HgExe(this, launcher, build, listener);
Node node = Computer.currentComputer().getNode(); // XXX why not build.getBuiltOn()?
try {
pull(launcher, repository, listener, new PrintStream(new NullOutputStream()), node, getBranch(env));
pull(launcher, repository, listener, new PrintStream(new NullOutputStream()), node, toRevision);
} catch (IOException e) {
if (causedByMissingHg(e)) {
listener.error("Failed to pull because hg could not be found;" +
Expand All @@ -504,11 +499,11 @@ private void update(AbstractBuild<?, ?> build, Launcher launcher, FilePath repos
e.printStackTrace(listener.error("Failed to pull"));
}
throw new AbortException("Failed to pull");
}
}

int updateExitCode;
try {
updateExitCode = hg.run("update", "--clean", "--rev", getBranch(env)).pwd(repository).join();
updateExitCode = hg.run("update", "--clean", "--rev", toRevision).pwd(repository).join();
} catch (IOException e) {
listener.error("Failed to update");
e.printStackTrace(listener.getLogger());
Expand All @@ -525,7 +520,7 @@ private void update(AbstractBuild<?, ?> build, Launcher launcher, FilePath repos
hg.run("--config", "extensions.relink=", "relink", cachedSource.getRepoLocation()).pwd(repository).join(); // ignore failures
}
}

if(clean) {
if (hg.cleanAll().pwd(repository).join() != 0) {
listener.error("Failed to clean unversioned files");
Expand All @@ -540,10 +535,22 @@ private void update(AbstractBuild<?, ?> build, Launcher launcher, FilePath repos
}
}

private String getRevToBuild(AbstractBuild<?, ?> build, EnvVars env) {
String revToBuild = getBranch(env);
if (build instanceof MatrixRun) {
MatrixRun matrixRun = (MatrixRun) build;
MercurialTagAction parentRevision = matrixRun.getParentBuild().getAction(MercurialTagAction.class);
if (parentRevision != null && parentRevision.getId() != null) {
revToBuild = parentRevision.getId();
}
}
return revToBuild;
}

/**
* Start from scratch and clone the whole repository.
*/
private void clone(AbstractBuild<?,?> build, Launcher launcher, FilePath repository, BuildListener listener)
private void clone(AbstractBuild<?, ?> build, Launcher launcher, FilePath repository, BuildListener listener, String toRevision)
throws InterruptedException, IOException {
try {
repository.deleteRecursive();
Expand All @@ -565,13 +572,13 @@ private void clone(AbstractBuild<?,?> build, Launcher launcher, FilePath reposit
args.add(cachedSource.getRepoLocation());
} else {
args.add("clone");
args.add("--rev", getBranch(env));
args.add("--rev", toRevision);
args.add("--noupdate");
args.add(cachedSource.getRepoLocation());
}
} else {
args.add("clone");
args.add("--rev", getBranch(env));
args.add("--rev", toRevision);
args.add("--noupdate");
args.add(source);
}
Expand Down Expand Up @@ -610,9 +617,9 @@ private void clone(AbstractBuild<?,?> build, Launcher launcher, FilePath reposit

ArgumentListBuilder upArgs = new ArgumentListBuilder();
upArgs.add("update");
upArgs.add("--rev", getBranch(env));
upArgs.add("--rev", toRevision);
if (hg.run(upArgs).pwd(repository).join() != 0) {
throw new AbortException("Failed to update " + source + " to rev " + getBranch(env));
throw new AbortException("Failed to update " + source + " to rev " + toRevision);
}

String tip = hg.tip(repository, null);
Expand Down
101 changes: 101 additions & 0 deletions src/test/java/hudson/plugins/mercurial/MatrixProjectTest.java
@@ -0,0 +1,101 @@
package hudson.plugins.mercurial;

import hudson.Launcher;
import hudson.Proc;
import hudson.matrix.*;
import hudson.model.*;
import org.jvnet.hudson.test.FakeLauncher;
import org.jvnet.hudson.test.PretendSlave;
import org.jvnet.hudson.test.TestBuilder;

import java.io.File;
import java.io.IOException;
import java.util.*;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Future;

public class MatrixProjectTest extends MercurialTestCase {
private File repo;
private MatrixProject matrixProject;

@Override
protected void setUp() throws Exception {
super.setUp();
repo = createTmpDir();

createPretendSlave("slave_one");
createPretendSlave("slave_two");

matrixProject = createMatrixProject("matrix_test");
matrixProject.setScm(new MercurialSCM(null, repo.getPath(), null, null, null, null, false));
matrixProject.setAxes(new AxisList(new LabelAxis("label", Arrays.asList("slave_one", "slave_two"))));

hg(repo, "init");
touchAndCommit(repo, "a");
}

public void testAllRunsBuildSameRevisionOnClone() throws Exception {
assertAllMatrixRunsBuildSameMercurialRevision();
}

public void testAllRunsBuildSameRevisionOnUpdate() throws Exception {
//schedule an initial build, to test update behavior later in the test
assertBuildStatusSuccess(matrixProject.scheduleBuild2(0, new Cause.UserCause(), new Action[0]));
touchAndCommit(repo, "ab");

assertAllMatrixRunsBuildSameMercurialRevision();
}

private void assertAllMatrixRunsBuildSameMercurialRevision() throws Exception {
//set the second slave offline, to give us the opportunity to push changes to the original Mercurial repository
//between the scheduling of the build and the actual run.
Node slaveTwo = hudson.getNode("slave_two");
slaveTwo.toComputer().setTemporarilyOffline(true, null);

final CountDownLatch firstBuild = new CountDownLatch(1);

matrixProject.getBuildersList().add(new TestBuilder() {
@Override
public boolean perform(AbstractBuild<?, ?> build, Launcher launcher, BuildListener listener) throws InterruptedException, IOException {
firstBuild.countDown();
return true;
}
});

Future<MatrixBuild> matrixBuildFuture = matrixProject.scheduleBuild2(0, new Cause.UserCause(), new Action[]{});
firstBuild.await();

//push an extra commit to the central repository
touchAndCommit(repo, "b");

//let the second slave start the build that was scheduled before this commit
slaveTwo.toComputer().setTemporarilyOffline(false, null);

MatrixBuild r = matrixBuildFuture.get();
this.assertBuildStatus(Result.SUCCESS, r);
List<MatrixRun> runs = r.getRuns();
Set<String> builtIds = new HashSet<String>();
for (MatrixRun run : runs) {
MercurialTagAction builtRevision = run.getAction(MercurialTagAction.class);
String buildId = builtRevision.getId();
builtIds.add(buildId);
}

//check that all runs built the same mercurial revision
assertEquals("All runs should build the same Mercurial revision, but they built " + builtIds.toString(),
1, builtIds.size());
}


private PretendSlave createPretendSlave(String slaveName) throws Exception {
PretendSlave slave = new PretendSlave(slaveName, this.createTmpDir().getPath(), "", createComputerLauncher(null), new NoopFakeLaunher());
hudson.addNode(slave);
return slave;
}

private static class NoopFakeLaunher implements FakeLauncher {
public Proc onLaunch(Launcher.ProcStarter p) throws IOException {
return null;
}
}
}

0 comments on commit e611bd6

Please sign in to comment.