package hudson.plugins.tfs;
import com.microsoft.tfs.core.clients.versioncontrol.GetOptions;
import com.microsoft.tfs.core.clients.versioncontrol.PendChangesOptions;
import com.microsoft.tfs.core.clients.versioncontrol.VersionControlConstants;
import com.microsoft.tfs.core.clients.versioncontrol.WorkspacePermissions;
import com.microsoft.tfs.core.clients.versioncontrol.soapextensions.LockLevel;
import com.microsoft.tfs.core.clients.versioncontrol.soapextensions.RecursionType;
import com.microsoft.tfs.core.clients.versioncontrol.soapextensions.VersionControlLabel;
import com.microsoft.tfs.core.clients.versioncontrol.soapextensions.Workspace;
import com.microsoft.tfs.core.clients.versioncontrol.specs.version.ChangesetVersionSpec;
import com.microsoft.tfs.jni.helpers.LocalHost;
import hudson.FilePath;
import hudson.Functions;
import hudson.Launcher;
import hudson.ProxyConfiguration;
import hudson.console.AnnotatedLargeText;
import hudson.model.AbstractBuild;
import hudson.model.BuildListener;
import hudson.model.Cause;
import hudson.model.Computer;
import hudson.model.Project;
import hudson.model.Queue;
import hudson.model.Result;
import hudson.model.TaskListener;
import hudson.model.labels.LabelAtom;
import hudson.plugins.tfs.model.MockableVersionControlClient;
import hudson.plugins.tfs.model.Server;
import hudson.plugins.tfs.util.DateUtil;
import hudson.plugins.tfs.util.XmlHelper;
import hudson.remoting.VirtualChannel;
import hudson.scm.ChangeLogSet;
import hudson.scm.PollingResult;
import hudson.scm.SCM;
import hudson.slaves.DumbSlave;
import hudson.slaves.SlaveComputer;
import hudson.triggers.SCMTrigger;
import hudson.util.Scrambler;
import hudson.util.Secret;
import jenkins.model.Jenkins;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang.StringUtils;
import org.junit.Assert;
import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.experimental.categories.Category;
import org.jvnet.hudson.test.JenkinsRecipe;
import org.jvnet.hudson.test.JenkinsRule;
import org.jvnet.hudson.test.TestBuilder;
import org.jvnet.hudson.test.recipes.LocalData;
import org.littleshoot.proxy.HttpProxyServer;
import org.littleshoot.proxy.impl.DefaultHttpProxyServer;
import org.xml.sax.SAXException;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.xpath.XPathExpressionException;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.HashSet;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
/**
* Tests that exercise real-world functionality, using temporary Jenkins instances.
* These are so-called functional (L3) tests.
* Tests may connect to a TFS server identified by the tfs_server_name property.
*/
@Category(IntegrationTests.class)
public class FunctionalTest {
/**
* A special version of {@link JenkinsRule} that assumes {@link EndToEndTfs} decorates a test,
* giving access to the {@link hudson.plugins.tfs.EndToEndTfs.RunnerImpl} and all the cool
* stuff it has.
*/
public class TfsJenkinsRule extends JenkinsRule{
/**
* https://wiki.jenkins-ci.org/display/JENKINS/Unit+Test+on+Windows#UnitTestonWindows-UnabletodeleteslaveslaveX.log
*
*/
private void purgeSlaves() {
List<Computer> disconnectingComputers = new ArrayList<Computer>();
List<VirtualChannel> closingChannels = new ArrayList<VirtualChannel>();
for (Computer computer: jenkins.getComputers()) {
if (!(computer instanceof SlaveComputer)) {
continue;
}
// disconnect slaves.
// retrieve the channel before disconnecting.
// even a computer gets offline, channel delays to close.
if (!computer.isOffline()) {
VirtualChannel ch = computer.getChannel();
computer.disconnect(null);
disconnectingComputers.add(computer);
closingChannels.add(ch);
}
}
try {
// Wait for all computers disconnected and all channels closed.
for (Computer computer: disconnectingComputers) {
computer.waitUntilOffline();
}
for (VirtualChannel ch: closingChannels) {
ch.join();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
@Override
public void after() throws Exception {
if (Functions.isWindows()) {
purgeSlaves();
}
super.after();
}
public EndToEndTfs.RunnerImpl getTfsRunner() {
EndToEndTfs.RunnerImpl result = null;
for (final JenkinsRecipe.Runner recipe : recipes) {
if (recipe instanceof EndToEndTfs.RunnerImpl) {
result = (EndToEndTfs.RunnerImpl) recipe;
break;
}
}
return result;
}
}
@Rule public TfsJenkinsRule j = new TfsJenkinsRule();
/**
* Runs the project's {@link SCMTrigger} to poll for changes, which may schedule a build.
*
* If it does schedule a build, we'll wait for that build to complete and return it;
* otherwise we return {@code null}.
*
* This assumes Jenkins (or the project/job) was configured with a quietPeriod, to give us
* time to retrieve the item from the queue (especially when execution is paused in the debugger)
* so we can wait on it.
*
* @param project The {@link Project} for which to poll and build.
* @return The {@link AbstractBuild} that resulted from the build, if applicable; otherwise {@code null}.
*/
public static AbstractBuild runScmPollTrigger(final Project project)
throws InterruptedException, ExecutionException {
final SCMTrigger scmTrigger = (SCMTrigger) project.getTrigger(SCMTrigger.class);
// This is a roundabout way of calling SCMTrigger#run(),
// because if we set SCMTrigger#synchronousPolling to true
// Trigger#checkTriggers() unconditionally runs the trigger,
// even if we set its schedule (spec) to an empty string
// (which normally disables the schedule).
// Having synchronous polling (& building!) in our tests
// is more important than skipping the usual method call chain.
// http://docs.oracle.com/javase/tutorial/java/javaOO/nested.html
final SCMTrigger.Runner runner = scmTrigger.new Runner();
runner.run();
final AbstractBuild build = waitForQueuedBuild(project);
return build;
}
public static AbstractBuild runUserTrigger(final Project project)
throws InterruptedException, ExecutionException {
final Cause.UserIdCause cause = new Cause.UserIdCause();
project.scheduleBuild(cause);
final AbstractBuild build = waitForQueuedBuild(project);
return build;
}
static AbstractBuild waitForQueuedBuild(final Project project)
throws InterruptedException, ExecutionException {
final Jenkins jenkins = (Jenkins) project.getParent();
final Queue queue = jenkins.getQueue();
final Queue.Item[] items = queue.getItems();
final boolean buildQueued = items.length == 1;
final AbstractBuild build;
if (buildQueued) {
final Queue.WaitingItem queuedItem = (Queue.WaitingItem) items[0];
// now that we have the queued item, we can "shorten the quiet period to zero"
final GregorianCalendar due = new GregorianCalendar();
due.add(Calendar.SECOND, -1);
queuedItem.timestamp = due;
// force re-evaluation of the queue, which should notice the item shouldn't wait anymore
queue.maintain();
queue.scheduleMaintenance();
final Future<? extends AbstractBuild> future = (Future) queuedItem.getFuture();
build = future.get();
}
else {
build = null;
}
return build;
}
@LocalData
@EndToEndTfs(CreateLabel.class)
@Test public void createLabel() throws ExecutionException, InterruptedException, IOException {
final Jenkins jenkins = j.jenkins;
final TaskListener taskListener = j.createTaskListener();
final EndToEndTfs.RunnerImpl tfsRunner = j.getTfsRunner();
final CreateLabel innerRunner = tfsRunner.getInnerRunner(CreateLabel.class);
final String generatedLabelName = innerRunner.getGeneratedLabelName();
final Server server = tfsRunner.getServer();
final MockableVersionControlClient vcc = server.getVersionControlClient();
final List<Project> projects = jenkins.getProjects();
final Project project = projects.get(0);
final int latestChangesetID = vcc.getLatestChangesetID();
// setup build
runUserTrigger(project);
// polling should report no changes
final PollingResult pollingResult = project.poll(taskListener);
Assert.assertEquals(PollingResult.Change.NONE, pollingResult.change);
// trigger build
final AbstractBuild build = runUserTrigger(project);
// verify new label created against latestChangesetId
Assert.assertNotNull(build);
assertBuildSuccess(build);
final ChangeLogSet changeSet = build.getChangeSet();
Assert.assertEquals(0, changeSet.getItems().length);
final TFSRevisionState revisionState = build.getAction(TFSRevisionState.class);
Assert.assertEquals(latestChangesetID, revisionState.changesetVersion);
final String owner = VersionControlConstants.AUTHENTICATED_USER;
final ChangesetVersionSpec spec = new ChangesetVersionSpec(latestChangesetID);
final VersionControlLabel[] labels = vcc.queryLabels(generatedLabelName, null, owner, false, null, spec);
Assert.assertEquals(1, labels.length);
final VersionControlLabel label = labels[0];
Assert.assertFalse(StringUtils.isEmpty(label.getComment()));
}
public void assertBuildSuccess(final AbstractBuild build) throws IOException {
final Result result = build.getResult();
if (!Result.SUCCESS.equals(result)) {
final AnnotatedLargeText logText = build.getLogText();
final ByteArrayOutputStream baos = new ByteArrayOutputStream();
logText.writeLogTo(0, baos);
final String headerTemplate = "Build result: %s\n---Log Start---\n";
final String header = String.format(headerTemplate, result);
final String message = header + baos.toString() + "---Log End---\n\n";
Assert.fail(message);
}
}
public static class CreateLabel extends CurrentChangesetInjector {
private final String generatedLabelName;
public CreateLabel(){
final Calendar now = Calendar.getInstance();
final String iso8601DateString = DateUtil.toString(now);
generatedLabelName = "CreateLabel_" + iso8601DateString.replace(':', '-');
}
public String getGeneratedLabelName() {
return generatedLabelName;
}
@Override public void decorateHome(final JenkinsRule jenkinsRule, final File home) throws Exception {
super.decorateHome(jenkinsRule, home);
final EndToEndTfs.RunnerImpl parent = getParent();
final String jobFolder = parent.getJobFolder();
final String configXmlPath = jobFolder + "config.xml";
final File configXmlFile = new File(home, configXmlPath);
final String labelNameXPath = "/project/publishers/hudson.plugins.tfs.TFSLabeler/labelName";
XmlHelper.pokeValue(configXmlFile, labelNameXPath, generatedLabelName);
}
}
@LocalData
@EndToEndTfs(CurrentChangesetInjector.class)
@Test public void agent() throws Exception {
final Jenkins jenkins = j.jenkins;
final List<Project> projects = jenkins.getProjects();
final Project project = projects.get(0);
final EndToEndTfs.RunnerImpl tfsRunner = j.getTfsRunner();
checkInEmptyFile(tfsRunner);
final LabelAtom label = new LabelAtom("agent");
final DumbSlave agent = j.createOnlineSlave(label);
project.getBuildersList().add(new TestBuilder() {
@Override
public boolean perform(final AbstractBuild<?, ?> build, final Launcher launcher, final BuildListener listener)
throws InterruptedException, IOException {
final FilePath workspace = build.getWorkspace();
final FilePath child = workspace.child("TODO.txt");
final boolean result = child.exists();
return result;
}
});
final AbstractBuild firstBuild = runScmPollTrigger(project);
Assert.assertNotNull(firstBuild);
assertBuildSuccess(firstBuild);
final FilePath workspace = firstBuild.getWorkspace();
final FilePath workspaceParent = workspace.getParent();
final FilePath assumedRootPath = workspaceParent.getParent();
final FilePath agentRootPath = agent.getRootPath();
Assert.assertEquals(agentRootPath, assumedRootPath);
assertEmptyFileIsInWorkspace(workspace);
}
@LocalData
@EndToEndTfs(EndToEndTfs.StubRunner.class)
@Test public void newJob() throws InterruptedException, ExecutionException, IOException {
final Jenkins jenkins = j.jenkins;
final TaskListener taskListener = j.createTaskListener();
final EndToEndTfs.RunnerImpl tfsRunner = j.getTfsRunner();
final Server server = tfsRunner.getServer();
final MockableVersionControlClient vcc = server.getVersionControlClient();
final List<Project> projects = jenkins.getProjects();
final Project project = projects.get(0);
int latestChangesetID;
// first poll should queue a build because we were never built
latestChangesetID = vcc.getLatestChangesetID();
final AbstractBuild firstBuild = runScmPollTrigger(project);
Assert.assertNotNull(firstBuild);
assertBuildSuccess(firstBuild);
final ChangeLogSet firstChangeSet = firstBuild.getChangeSet();
Assert.assertEquals(true, firstChangeSet.isEmptySet());
final TFSRevisionState firstRevisionState = firstBuild.getAction(TFSRevisionState.class);
Assert.assertEquals(latestChangesetID, firstRevisionState.changesetVersion);
final List<Cause> firstCauses = firstBuild.getCauses();
Assert.assertEquals(1, firstCauses.size());
final Cause firstCause = firstCauses.get(0);
Assert.assertTrue(firstCause instanceof SCMTrigger.SCMTriggerCause);
// second poll should report no changes since last build
final PollingResult secondPoll = project.poll(taskListener);
Assert.assertEquals(PollingResult.Change.NONE, secondPoll.change);
// make a change in source control
final int changeSet = checkInEmptyFile(tfsRunner);
Assert.assertTrue(changeSet >= 0);
// third poll should trigger a build
latestChangesetID = vcc.getLatestChangesetID();
final AbstractBuild secondBuild = runScmPollTrigger(project);
Assert.assertNotNull(secondBuild);
assertBuildSuccess(secondBuild);
final ChangeLogSet secondChangeSet = secondBuild.getChangeSet();
Assert.assertEquals(1, secondChangeSet.getItems().length);
final TFSRevisionState secondRevisionState = secondBuild.getAction(TFSRevisionState.class);
Assert.assertEquals(latestChangesetID, secondRevisionState.changesetVersion);
final List<Cause> secondCauses = secondBuild.getCauses();
Assert.assertEquals(1, secondCauses.size());
final Cause secondCause = secondCauses.get(0);
Assert.assertTrue(secondCause instanceof SCMTrigger.SCMTriggerCause);
final FilePath jenkinsWorkspace = secondBuild.getWorkspace();
assertEmptyFileIsInWorkspace(jenkinsWorkspace);
// force a build via a manual trigger
final AbstractBuild thirdBuild = runUserTrigger(project);
Assert.assertNotNull(thirdBuild);
assertBuildSuccess(thirdBuild);
final ChangeLogSet thirdChangeSet = thirdBuild.getChangeSet();
Assert.assertEquals(0, thirdChangeSet.getItems().length);
final TFSRevisionState thirdRevisionState = thirdBuild.getAction(TFSRevisionState.class);
Assert.assertEquals(latestChangesetID, thirdRevisionState.changesetVersion);
final List<Cause> thirdCauses = thirdBuild.getCauses();
Assert.assertEquals(1, thirdCauses.size());
final Cause thirdCause = thirdCauses.get(0);
Assert.assertTrue(thirdCause instanceof Cause.UserIdCause);
final FilePath thirdBuildWorkspace = thirdBuild.getWorkspace();
assertEmptyFileIsInWorkspace(thirdBuildWorkspace);
// finally, delete the project, which should first remove the workspace
final TeamFoundationServerScm scm = (TeamFoundationServerScm) project.getScm();
final Computer computer = Computer.currentComputer();
final String workspaceName = scm.getWorkspaceName(thirdBuild, computer);
Assert.assertTrue(jenkinsWorkspace.exists());
final String hostName = LocalHost.getShortName();
final Workspace[] workspacesBeforeDeletion = vcc.queryWorkspaces(workspaceName, VersionControlConstants.AUTHENTICATED_USER, hostName, WorkspacePermissions.NONE_OR_NOT_SUPPORTED);
Assert.assertEquals(1, workspacesBeforeDeletion.length);
project.delete();
Assert.assertFalse(jenkinsWorkspace.exists());
final Workspace[] workspacesAfterDeletion = vcc.queryWorkspaces(workspaceName, VersionControlConstants.AUTHENTICATED_USER, hostName, WorkspacePermissions.NONE_OR_NOT_SUPPORTED);
Assert.assertEquals(0, workspacesAfterDeletion.length);
}
public void assertEmptyFileIsInWorkspace(final FilePath workspace) throws IOException, InterruptedException {
final FilePath[] workspaceFiles = workspace.list("*.*", "$tf");
Assert.assertEquals(1, workspaceFiles.length);
final FilePath workspaceFile = workspaceFiles[0];
Assert.assertEquals("TODO.txt", workspaceFile.getName());
}
public static int checkInEmptyFile(final EndToEndTfs.RunnerImpl tfsRunner) throws IOException {
return checkInFile(tfsRunner, "Add a file.", null);
}
public static int checkInFile(final EndToEndTfs.RunnerImpl tfsRunner, final String changeMessage, final String fileContents) throws IOException {
final Workspace workspace = tfsRunner.getWorkspace();
final File todoFile = new File(tfsRunner.getLocalBaseFolderFile(), "TODO.txt");
final boolean alreadyExisted = todoFile.isFile();
FileUtils.writeStringToFile(todoFile, fileContents, "UTF-8");
final String[] paths = {todoFile.getAbsolutePath()};
if (alreadyExisted) {
workspace.pendEdit(
paths,
RecursionType.NONE,
LockLevel.UNCHANGED,
null,
GetOptions.NONE,
PendChangesOptions.NONE);
}
else {
workspace.pendAdd(
paths,
false,
null,
LockLevel.UNCHANGED,
GetOptions.NONE,
PendChangesOptions.NONE);
}
return tfsRunner.checkIn(tfsRunner.getTestCaseName() + " " + changeMessage);
}
@LocalData
@EndToEndTfs(CloakedPaths.class)
@Test public void cloakedPaths() throws Exception {
final Jenkins jenkins = j.jenkins;
final TaskListener taskListener = j.createTaskListener();
final EndToEndTfs.RunnerImpl tfsRunner = j.getTfsRunner();
final Server server = tfsRunner.getServer();
final String testCaseName = tfsRunner.getTestCaseName();
final Workspace workspace = tfsRunner.getWorkspace();
final List<Project> projects = jenkins.getProjects();
final Project jenkinsProject = projects.get(0);
final TeamFoundationServerScm tfsScm = (TeamFoundationServerScm) jenkinsProject.getScm();
Assert.assertNotEquals(StringUtils.EMPTY, tfsScm.getCloakedPaths());
int latestChangesetID;
// arrange: create structure
final File root = tfsRunner.getLocalBaseFolderFile();
final String[] paths = {
createWorkspaceFile(root, "root.txt"),
createWorkspaceFile(root, "A/A.txt"),
createWorkspaceFile(root, "A/1/A1.txt"),
createWorkspaceFile(root, "A/2/A2.txt"),
createWorkspaceFile(root, "B/B.txt"),
createWorkspaceFile(root, "C/C.txt"),
};
workspace.pendAdd(
paths,
false,
null,
LockLevel.UNCHANGED,
GetOptions.NONE,
PendChangesOptions.NONE);
final int structureChangeSet = tfsRunner.checkIn(testCaseName + " Create structure.");
Assert.assertTrue(structureChangeSet >= 0);
// act: poll trigger
final AbstractBuild firstBuild = runScmPollTrigger(jenkinsProject);
// assert
Assert.assertNotNull("First poll should queue a build", firstBuild);
assertBuildSuccess(firstBuild);
assertCloakedPathsWorkspaceContents(firstBuild.getWorkspace());
// arrange: make a change in a non-cloaked path (fully uncloaked)
final File aOne = new File(root, "A/1/A1.txt");
FileUtils.writeStringToFile(aOne, "Now with content!", "UTF-8");
workspace.pendEdit(
new String[]{aOne.getAbsolutePath()},
RecursionType.NONE,
LockLevel.UNCHANGED,
null,
GetOptions.NONE,
PendChangesOptions.NONE);
latestChangesetID = tfsRunner.checkIn(testCaseName + " Add content to A1.txt");
// act: poll trigger
final AbstractBuild secondBuild = runScmPollTrigger(jenkinsProject);
// assert
Assert.assertNotNull("Second poll should queue a build", secondBuild);
assertCloakedPathsWorkspaceContents(secondBuild.getWorkspace());
// arrange: make a changeset that has an item in a cloaked path
final File aTwo = new File(root, "A/2/A2.txt");
FileUtils.writeStringToFile(aOne, "Now with content!", "UTF-8");
workspace.pendEdit(
new String[]{aTwo.getAbsolutePath()},
RecursionType.NONE,
LockLevel.UNCHANGED,
null,
GetOptions.NONE,
PendChangesOptions.NONE);
latestChangesetID = tfsRunner.checkIn(testCaseName + " Add content to A2.txt");
// act: poll (no need to poll trigger)
final PollingResult thirdPoll = jenkinsProject.poll(taskListener);
// assert
Assert.assertEquals("Third poll should NOT find any significant changes", PollingResult.Change.NONE, thirdPoll.change);
// arrange: create a changeset that has changes in both cloaked and uncloaked paths
final File a = new File(root, "A/A.txt");
FileUtils.writeStringToFile(a, "Now with content!", "UTF-8");
final File b = new File(root, "B/B.txt");
FileUtils.writeStringToFile(b, "Now with content!", "UTF-8");
workspace.pendEdit(
new String[]{
a.getAbsolutePath(),
b.getAbsolutePath(),
},
RecursionType.NONE,
LockLevel.UNCHANGED,
null,
GetOptions.NONE,
PendChangesOptions.NONE);
latestChangesetID = tfsRunner.checkIn(testCaseName + " Add content to A.txt and B.txt");
// act: poll trigger
final AbstractBuild thirdBuild = runScmPollTrigger(jenkinsProject);
// assert
Assert.assertNotNull("Fourth poll should queue a build", thirdBuild);
assertCloakedPathsWorkspaceContents(thirdBuild.getWorkspace());
}
/** workspace should only contain:
root.txt
A/A.txt
A/1/A1.txt
C/C.txt
*/
private static void assertCloakedPathsWorkspaceContents(final FilePath workspace) throws Exception {
final FilePath[] workspaceFiles = workspace.list("**", "$tf");
final HashSet<String> expectedFileNames = new HashSet<String>(Arrays.asList("root.txt", "A.txt", "A1.txt", "C.txt"));
for (final FilePath workspaceFile : workspaceFiles) {
final String actualFileName = workspaceFile.getName();
if (expectedFileNames.contains(actualFileName)) {
expectedFileNames.remove(actualFileName);
}
else {
final String message = "Did not expect to find " + actualFileName + " in the workspace.";
Assert.fail(message);
}
}
Assert.assertEquals("All expected files should have been found in the workspace", 0, expectedFileNames.size());
}
static String createWorkspaceFile(final File root, final String relFilePath) throws IOException {
final File file = new File(root, relFilePath);
final File folder = file.getParentFile();
//noinspection ResultOfMethodCallIgnored
folder.mkdirs();
//noinspection ResultOfMethodCallIgnored
file.createNewFile();
return file.getAbsolutePath();
}
public static class CloakedPaths extends CurrentChangesetInjector {
@Override public void decorateHome(final JenkinsRule jenkinsRule, final File home)
throws Exception {
super.decorateHome(jenkinsRule, home);
final EndToEndTfs.RunnerImpl parent = getParent();
final String jobFolder = parent.getJobFolder();
final String configXmlPath = jobFolder + "config.xml";
final File configXmlFile = new File(home, configXmlPath);
final String projectPath = parent.getPathInTfvc();
XmlHelper.pokeValue(configXmlFile, "/project/scm/cloakedPaths/string[1]", projectPath + "/A/2");
XmlHelper.pokeValue(configXmlFile, "/project/scm/cloakedPaths/string[2]", projectPath + "/B");
}
}
/**
* If there's no SCMRevisionState present, we revert to old-school polling,
* using the timestamp of the last build to see if there have been any changes in TFVC,
* at the project's path, since the specified time.
*
* Even though the @EndToEndTfs annotation caused some commits,
* the OldPollingFallback runner poked the current time (after said commits)
* in the build.xml, which should cause polling to not find any changes.
*/
@LocalData
@EndToEndTfs(OldPollingFallback.class)
@Ignore @Test public void oldPollingFallback() throws IOException {
final Jenkins jenkins = j.jenkins;
final List<Project> projects = jenkins.getProjects();
final Project project = projects.get(0);
final TaskListener taskListener = j.createTaskListener();
final PollingResult actual = project.poll(taskListener);
Assert.assertEquals(PollingResult.NO_CHANGES, actual);
}
/**
* Injects the current time in milliseconds into the <code>/build/timestamp</code> element
* of the last <code>build.xml</code>.
*/
public static class OldPollingFallback extends EndToEndTfs.StubRunner {
@Override public void decorateHome(final JenkinsRule jenkinsRule, final File home)
throws Exception {
super.decorateHome(jenkinsRule, home);
// Add a small pause to make sure we record the timestamp one second later
// than the last check-in, otherwise we have the polling occurring on the same
// second as the check-in and finding an SCM change where there should be none.
Thread.sleep(1000 /* ms */);
final EndToEndTfs.RunnerImpl parent = getParent();
final String jobFolder = parent.getJobFolder();
final String lastBuildXmlPath = jobFolder + "builds/2015-07-15_20-37-42/build.xml";
final File lastBuildXmlFile = new File(home, lastBuildXmlPath);
final long rightNowMilliseconds = System.currentTimeMillis();
final String value = String.valueOf(rightNowMilliseconds);
XmlHelper.pokeValue(lastBuildXmlFile, "/build/timestamp", value);
}
}
/**
* As of version 3.2.0, passwords are no longer encoded but encrypted.
* Such a job should have its encoded password upgraded to encrypted
* and still be able to poll and build.
*/
@LocalData
@EndToEndTfs(UpgradeEncodedPassword.class)
@Test public void upgradeEncodedPassword()
throws IOException, XPathExpressionException, ExecutionException, InterruptedException,
SAXException, ParserConfigurationException {
final Jenkins jenkins = j.jenkins;
final TaskListener taskListener = j.createTaskListener();
final EndToEndTfs.RunnerImpl tfsRunner = j.getTfsRunner();
final EndToEndTfs.StubRunner stubRunner = tfsRunner.getInnerRunner(EndToEndTfs.StubRunner.class);
final String encryptedPassword = stubRunner.getEncryptedPassword();
final List<Project> projects = jenkins.getProjects();
final Project project = projects.get(0);
final TeamFoundationServerScm scm = (TeamFoundationServerScm) project.getScm();
final Secret passwordSecret = scm.getPassword();
Assert.assertEquals(encryptedPassword, passwordSecret.getEncryptedValue());
PollingResult actualPollingResult;
// setup build
runUserTrigger(project);
actualPollingResult = project.poll(taskListener);
Assert.assertEquals(PollingResult.Change.NONE, actualPollingResult.change);
project.save(/* force the project to be written to disk, which should encrypt the password */);
actualPollingResult = project.poll(taskListener);
Assert.assertEquals(PollingResult.Change.NONE, actualPollingResult.change);
final File home = j.jenkins.getRootDir();
final String configXmlPath = "jobs/upgradeEncodedPassword/config.xml";
final File configXmlFile = new File(home, configXmlPath);
final String userPassword = XmlHelper.peekValue(configXmlFile, "/project/scm/userPassword");
Assert.assertEquals("Encoded password should no longer be there", null, userPassword);
final String password = XmlHelper.peekValue(configXmlFile, "/project/scm/password");
Assert.assertEquals("Encrypted password should be there", encryptedPassword, password);
// TODO: Check in & record changeset, poll & assert SIGNIFICANT
// TODO: build & assert new last build recorded changeset from above
// TODO: poll & assert NONE
}
public static class UpgradeEncodedPassword extends CurrentChangesetInjector {
@Override public void decorateHome(final JenkinsRule jenkinsRule, final File home) throws Exception {
super.decorateHome(jenkinsRule, home);
final EndToEndTfs.RunnerImpl parent = getParent();
final String jobFolder = parent.getJobFolder();
final IntegrationTestHelper helper = getHelper();
final String userPassword = helper.getUserPassword();
final String scrambledPassword = Scrambler.scramble(userPassword);
final String configXmlPath = jobFolder + "config.xml";
final File configXmlFile = new File(home, configXmlPath);
XmlHelper.pokeValue(configXmlFile, "/project/scm/userPassword", scrambledPassword);
}
}
/**
* Verifies that we can still poll and GET from a server when going through a proxy server.
*/
@LocalData
@EndToEndTfs(UseWebProxyServer.class)
@Test public void useWebProxyServer() throws Exception {
final Jenkins jenkins = j.jenkins;
// double-check proxy configuration was loaded and is available
final ProxyConfiguration proxyConfiguration = jenkins.proxy;
Assert.assertNotNull(proxyConfiguration);
final String proxyServerSetting = hudson.Util.fixEmpty(proxyConfiguration.name);
Assert.assertNotNull(proxyServerSetting);
final TaskListener taskListener = j.createTaskListener();
final EndToEndTfs.RunnerImpl tfsRunner = j.getTfsRunner();
final UseWebProxyServer innerRunner = tfsRunner.getInnerRunner(UseWebProxyServer.class);
final List<Project> projects = jenkins.getProjects();
final Project project = projects.get(0);
final HttpProxyServer proxyServer = innerRunner.getServer();
final LoggingFiltersSourceAdapter adapter = innerRunner.getAdapter();
final int previousChangeSet;
try {
Assert.assertFalse(adapter.proxyWasUsed());
// setup build
runUserTrigger(project);
// first poll should report no changes since last build
final PollingResult firstPoll = project.poll(taskListener);
Assert.assertEquals(PollingResult.Change.NONE, firstPoll.change);
Assert.assertTrue(adapter.proxyWasUsed());
adapter.reset();
// make a change in source control
previousChangeSet = checkInEmptyFile(tfsRunner);
Assert.assertTrue(previousChangeSet >= 0);
Assert.assertFalse(adapter.proxyWasUsed());
// second poll should queue a build
final AbstractBuild firstBuild = runScmPollTrigger(project);
Assert.assertNotNull(firstBuild);
assertBuildSuccess(firstBuild);
Assert.assertTrue(adapter.proxyWasUsed());
}
finally {
proxyServer.stop();
}
// make a change in source control
final String fileContents = "1. Pick up vegetables.\nPrevious changeset:" + previousChangeSet;
final int changeSet = checkInFile(tfsRunner, "Now with content.", fileContents);
Assert.assertTrue(changeSet >= 0);
adapter.reset();
// third poll should claim "no changes" due to being unable to reach the proxy server
final InterceptingTaskListener itl = new InterceptingTaskListener(taskListener);
// TODO: this takes over 70 seconds to execute, because there's a retry with backoff
// We might be able to inject an interception that turns off the retry for this operation
final PollingResult thirdPoll = project.poll(itl);
Assert.assertEquals("Error during polling => NO_CHANGES", PollingResult.NO_CHANGES, thirdPoll);
final List<String> fatalErrors = itl.getFatalErrors();
Assert.assertEquals(1, fatalErrors.size());
Assert.assertFalse(adapter.proxyWasUsed());
}
public static class UseWebProxyServer extends CurrentChangesetInjector {
private final LoggingFiltersSourceAdapter adapter;
private final HttpProxyServer server;
public UseWebProxyServer() {
adapter = new LoggingFiltersSourceAdapter();
server =
DefaultHttpProxyServer
.bootstrap()
.withPort(0 /* "...let the system pick up an ephemeral port in a bind operation." */)
.withFiltersSource(adapter)
.start();
}
@Override public void decorateHome(final JenkinsRule jenkinsRule, final File home) throws Exception {
super.decorateHome(jenkinsRule, home);
final InetSocketAddress proxyAddress = server.getListenAddress();
final File proxyXmlFile = new File(home, "proxy.xml");
XmlHelper.pokeValue(proxyXmlFile, "/proxy/name", proxyAddress.getHostName());
XmlHelper.pokeValue(proxyXmlFile, "/proxy/port", Integer.toString(proxyAddress.getPort(), 10));
}
public HttpProxyServer getServer() {
return server;
}
public LoggingFiltersSourceAdapter getAdapter() {
return adapter;
}
}
/**
* Injects some values into the last <code>build.xml</code> to pretend we're up-to-date with TFS.
*/
public static class CurrentChangesetInjector extends EndToEndTfs.StubRunner {
@Override public void decorateHome(final JenkinsRule jenkinsRule, final File home)
throws Exception {
super.decorateHome(jenkinsRule, home);
final EndToEndTfs.RunnerImpl parent = getParent();
final String jobFolder = parent.getJobFolder();
final String lastBuildXmlPath = jobFolder + "builds/2015-07-15_20-37-42/build.xml";
final File lastBuildXmlFile = new File(home, lastBuildXmlPath);
final String projectPath = parent.getPathInTfvc();
final String serverUrl = getHelper().getServerUrl();
final Server server = parent.getServer();
final MockableVersionControlClient vcc = server.getVersionControlClient();
final int latestChangesetID = vcc.getLatestChangesetID();
final String changesetVersion = String.valueOf(latestChangesetID);
XmlHelper.pokeValue(lastBuildXmlFile, "/build/actions/hudson.plugins.tfs.model.WorkspaceConfiguration/projectPath", projectPath);
XmlHelper.pokeValue(lastBuildXmlFile, "/build/actions/hudson.plugins.tfs.model.WorkspaceConfiguration/serverUrl", serverUrl);
XmlHelper.pokeValue(lastBuildXmlFile, "/build/actions/hudson.plugins.tfs.TFSRevisionState/changesetVersion", changesetVersion);
XmlHelper.pokeValue(lastBuildXmlFile, "/build/actions/hudson.plugins.tfs.TFSRevisionState/projectPath", projectPath);
}
}
}