package hudson.plugins.mercurial;
/*
* The MIT License
*
* Copyright 2016 CloudBees, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
import com.cloudbees.hudson.plugins.folder.computed.FolderComputation;
import hudson.EnvVars;
import hudson.console.AnnotatedLargeText;
import hudson.scm.ChangeLogSet;
import hudson.scm.SCM;
import hudson.tools.ToolProperty;
import hudson.triggers.SCMTrigger;
import hudson.util.LogTaskListener;
import hudson.util.StreamTaskListener;
import hudson.util.VersionNumber;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.TreeSet;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.annotation.Nonnull;
import jenkins.branch.BranchSource;
import jenkins.scm.api.SCMEvent;
import jenkins.scm.api.SCMEvents;
import jenkins.util.VirtualFile;
import org.apache.commons.io.FileUtils;
import org.jenkinsci.plugins.scriptsecurity.scripts.ScriptApproval;
import org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition;
import org.jenkinsci.plugins.workflow.job.WorkflowJob;
import org.jenkinsci.plugins.workflow.job.WorkflowRun;
import org.jenkinsci.plugins.workflow.multibranch.WorkflowMultiBranchProject;
import org.jenkinsci.plugins.workflow.test.steps.SemaphoreStep;
import org.junit.Test;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.*;
import static org.junit.Assume.assumeThat;
import org.junit.ClassRule;
import org.junit.Rule;
import org.jvnet.hudson.test.BuildWatcher;
import org.jvnet.hudson.test.JenkinsRule;
public class PipelineTest {
@ClassRule public static BuildWatcher buildWatcher = new BuildWatcher();
@Rule public JenkinsRule r = new JenkinsRule();
@Rule public MercurialSampleRepoRule sampleRepo = new MercurialSampleRepoRule();
@Rule public MercurialSampleRepoRule otherRepo = new MercurialSampleRepoRule();
@Test public void multipleSCMs() throws Exception {
sampleRepo.init();
otherRepo.hg("init");
otherRepo.write("otherfile", "");
otherRepo.hg("add", "otherfile");
otherRepo.hg("commit", "--message=init");
WorkflowJob p = r.jenkins.createProject(WorkflowJob.class, "demo");
p.addTrigger(new SCMTrigger(""));
p.setQuietPeriod(3); // so it only does one build
p.setDefinition(new CpsFlowDefinition(
"node {\n" +
" ws {\n" +
" dir('main') {\n" +
" checkout([$class: 'MercurialSCM', source: $/" + sampleRepo.fileUrl() + "/$])\n" +
" }\n" +
" dir('other') {\n" +
" checkout([$class: 'MercurialSCM', source: $/" + otherRepo.fileUrl() + "/$, clean: true])\n" +
" try {\n" + // TODO or use fileExists
" readFile 'unversioned'\n" +
" error 'unversioned did exist'\n" +
" } catch (FileNotFoundException x) {\n" +
" echo 'unversioned did not exist'\n" +
" }\n" +
" writeFile text: '', file: 'unversioned'\n" +
" }\n" +
" archive '**'\n" +
" }\n" +
"}"));
WorkflowRun b = r.assertBuildStatusSuccess(p.scheduleBuild2(0));
VirtualFile artifacts = b.getArtifactManager().root();
assertTrue(artifacts.child("main/file").isFile());
assertTrue(artifacts.child("other/otherfile").isFile());
r.assertLogContains("unversioned did not exist", b);
sampleRepo.write("file2", "");
sampleRepo.hg("add", "file2");
sampleRepo.hg("commit", "--message=file2");
otherRepo.write("otherfile2", "");
otherRepo.hg("add", "otherfile2");
otherRepo.hg("commit", "--message=otherfile2");
sampleRepo.notifyCommit(r);
otherRepo.notifyCommit(r);
FileUtils.copyFile(p.getSCMTrigger().getLogFile(), System.out);
b = r.assertBuildStatusSuccess(p.getLastBuild());
assertEquals(2, b.number);
artifacts = b.getArtifactManager().root();
assertTrue(artifacts.child("main/file2").isFile());
assertTrue(artifacts.child("other/otherfile2").isFile());
r.assertLogContains("unversioned did not exist", b);
Iterator<? extends SCM> scms = p.getSCMs().iterator();
assertTrue(scms.hasNext());
assertEquals(sampleRepo.fileUrl(), ((MercurialSCM) scms.next()).getSource());
assertTrue(scms.hasNext());
assertEquals(otherRepo.fileUrl(), ((MercurialSCM) scms.next()).getSource());
assertFalse(scms.hasNext());
List<ChangeLogSet<? extends ChangeLogSet.Entry>> changeSets = b.getChangeSets();
assertEquals(2, changeSets.size());
ChangeLogSet<? extends ChangeLogSet.Entry> changeSet = changeSets.get(0);
assertEquals(b, changeSet.getRun());
assertEquals("hg", changeSet.getKind());
Iterator<? extends ChangeLogSet.Entry> iterator = changeSet.iterator();
assertTrue(iterator.hasNext());
ChangeLogSet.Entry entry = iterator.next();
assertEquals("[file2]", entry.getAffectedPaths().toString());
assertFalse(iterator.hasNext());
changeSet = changeSets.get(1);
iterator = changeSet.iterator();
assertTrue(iterator.hasNext());
entry = iterator.next();
assertEquals("[otherfile2]", entry.getAffectedPaths().toString());
assertFalse(iterator.hasNext());
}
@Test public void exactRevisionMercurial() throws Exception {
sampleRepo.init();
ScriptApproval sa = ScriptApproval.get();
sa.approveSignature("staticField hudson.model.Items XSTREAM2");
sa.approveSignature("method com.thoughtworks.xstream.XStream toXML java.lang.Object");
sampleRepo.write("Jenkinsfile", "echo hudson.model.Items.XSTREAM2.toXML(scm); semaphore 'wait'; node {checkout scm; echo readFile('file')}");
sampleRepo.write("file", "initial content");
sampleRepo.hg("commit", "--addremove", "--message=flow");
WorkflowMultiBranchProject mp = r.jenkins.createProject(WorkflowMultiBranchProject.class, "p");
String instName = "caching";
r.jenkins.getDescriptorByType(MercurialInstallation.DescriptorImpl.class).setInstallations(
new MercurialInstallation(instName, "", "hg", false, true, false, null, Collections.<ToolProperty<?>> emptyList()));
mp.getSourcesList().add(new BranchSource(new MercurialSCMSource(null, instName, sampleRepo.fileUrl(), null, null, null, null, null, true)));
WorkflowJob p = scheduleAndFindBranchProject(mp, "default");
SemaphoreStep.waitForStart("wait/1", null);
WorkflowRun b1 = p.getLastBuild();
assertNotNull(b1);
assertEquals(1, b1.getNumber());
sampleRepo.write("Jenkinsfile", "node {checkout scm; echo readFile('file').toUpperCase()}");
sampleRepo.write("file", "subsequent content");
sampleRepo.hg("commit", "--message=tweaked");
SemaphoreStep.success("wait/1", null);
sampleRepo.notifyCommit(r);
showIndexing(mp);
WorkflowRun b2 = p.getLastBuild();
assertEquals(2, b2.getNumber());
r.assertLogContains("initial content", r.assertBuildStatusSuccess(b1));
r.assertLogContains("SUBSEQUENT CONTENT", r.assertBuildStatusSuccess(b2));
List<ChangeLogSet<? extends ChangeLogSet.Entry>> changeSets = b2.getChangeSets();
/* TODO JENKINS-29326 analogue, as per SubversionSCM:
assertEquals(1, changeSets.size());
*/
ChangeLogSet<? extends ChangeLogSet.Entry> changeSet = changeSets.get(0);
assertEquals(b2, changeSet.getRun());
assertEquals("hg", changeSet.getKind());
Iterator<? extends ChangeLogSet.Entry> iterator = changeSet.iterator();
assertTrue(iterator.hasNext());
ChangeLogSet.Entry entry = iterator.next();
assertEquals("tweaked", entry.getMsg());
assertEquals("[Jenkinsfile, file]", new TreeSet<>(entry.getAffectedPaths()).toString());
assertFalse(iterator.hasNext());
}
@Test public void modernHook() throws Exception {
String instName = "caching";
MercurialInstallation installation = new MercurialInstallation(instName, "", "hg", false, true, false, null,
Collections.<ToolProperty<?>>emptyList());
LogTaskListener listener = new LogTaskListener(Logger.getLogger(getClass().getName()), Level.INFO);
final HgExe hg = new HgExe(installation, null, r.jenkins.createLauncher(
listener), r.jenkins, listener, new EnvVars());
String version = hg.version();
// I could not find the exact version when the new hooks were added, but not found on any 2.x
// and found in all the 3.x versions I could get my hands on
assumeThat("Need mercurial 3.0ish to have in-process hooks, have " + version,
new VersionNumber(version).isNewerThan(new VersionNumber("3.0")),is(true));
sampleRepo.init();
sampleRepo.write("Jenkinsfile", "node {checkout scm; echo readFile('file')}");
sampleRepo.write("file", "initial content");
sampleRepo.hg("commit", "--addremove", "--message=flow");
WorkflowMultiBranchProject mp = r.jenkins.createProject(WorkflowMultiBranchProject.class, "p");
r.jenkins.getDescriptorByType(MercurialInstallation.DescriptorImpl.class).setInstallations(installation);
installation.forNode(r.jenkins, StreamTaskListener.fromStdout());
mp.getSourcesList().add(new BranchSource(new MercurialSCMSource(null, instName, sampleRepo.fileUrl(), null, null, null, null, null, true)));
WorkflowJob p = scheduleAndFindBranchProject(mp, "default");
r.waitUntilNoActivity();
WorkflowRun b1 = p.getLastBuild();
assertNotNull(b1);
assertEquals(1, b1.getNumber());
// capture the watermark before the event triggered
long watermark = SCMEvents.getWatermark();
sampleRepo.registerHook(r);
sampleRepo.write("Jenkinsfile", "node {checkout scm; echo readFile('file').toUpperCase()}");
sampleRepo.write("file", "subsequent content");
sampleRepo.hg("commit", "--message=tweaked");
// wait for the event to have completed processing
SCMEvents.awaitAll(watermark, 5, TimeUnit.SECONDS);
// ensure the queue has picked up the job being scheduled as we are at full tilt
r.jenkins.getQueue().maintain();
// now show the events log
showEvents(mp);
// and wait for the build to finish
r.waitUntilNoActivity();
WorkflowRun b2 = p.getLastBuild();
assertEquals(2, b2.getNumber());
r.assertLogContains("initial content", r.assertBuildStatusSuccess(b1));
r.assertLogContains("SUBSEQUENT CONTENT", r.assertBuildStatusSuccess(b2));
List<ChangeLogSet<? extends ChangeLogSet.Entry>> changeSets = b2.getChangeSets();
/* TODO JENKINS-29326 analogue, as per SubversionSCM:
assertEquals(1, changeSets.size());
*/
ChangeLogSet<? extends ChangeLogSet.Entry> changeSet = changeSets.get(0);
assertEquals(b2, changeSet.getRun());
assertEquals("hg", changeSet.getKind());
Iterator<? extends ChangeLogSet.Entry> iterator = changeSet.iterator();
assertTrue(iterator.hasNext());
ChangeLogSet.Entry entry = iterator.next();
assertEquals("tweaked", entry.getMsg());
assertEquals("[Jenkinsfile, file]", new TreeSet<>(entry.getAffectedPaths()).toString());
assertFalse(iterator.hasNext());
}
// Copied from WorkflowMultiBranchProjectTest; do not want to depend on that due to its dependency on git:
public static @Nonnull WorkflowJob scheduleAndFindBranchProject(@Nonnull WorkflowMultiBranchProject mp, @Nonnull String name) throws Exception {
mp.scheduleBuild2(0).getFuture().get();
return findBranchProject(mp, name);
}
public static @Nonnull WorkflowJob findBranchProject(@Nonnull WorkflowMultiBranchProject mp, @Nonnull String name) throws Exception {
WorkflowJob p = mp.getItem(name);
showIndexing(mp);
if (p == null) {
fail(name + " project not found");
}
return p;
}
static void showIndexing(@Nonnull WorkflowMultiBranchProject mp) throws Exception {
FolderComputation<?> indexing = mp.getIndexing();
System.out.println("---%<--- " + indexing.getUrl());
indexing.writeWholeLogTo(System.out);
System.out.println("---%<--- ");
}
static void showEvents(@Nonnull WorkflowMultiBranchProject mp) throws Exception {
AnnotatedLargeText<FolderComputation<WorkflowJob>> events = mp.getComputation().getEventsText();
System.out.println("---%<--- " + mp.getComputation().getUrl() + " EVENTS");
events.writeLogTo(0, System.out);
System.out.println("---%<--- ");
}
}