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.WorkspaceLocation;
import com.microsoft.tfs.core.clients.versioncontrol.WorkspaceOptions;
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.PendingChange;
import com.microsoft.tfs.core.clients.versioncontrol.soapextensions.PendingSet;
import com.microsoft.tfs.core.clients.versioncontrol.soapextensions.RecursionType;
import com.microsoft.tfs.core.clients.versioncontrol.soapextensions.WorkingFolder;
import com.microsoft.tfs.core.clients.versioncontrol.soapextensions.Workspace;
import hudson.plugins.tfs.model.ExtraSettings;
import hudson.plugins.tfs.model.MockableVersionControlClient;
import hudson.plugins.tfs.model.Server;
import hudson.plugins.tfs.util.XmlHelper;
import hudson.util.Secret;
import hudson.util.SecretOverride;
import org.apache.commons.io.FileUtils;
import org.junit.Assert;
import org.junit.runner.Description;
import org.jvnet.hudson.test.JenkinsRecipe;
import org.jvnet.hudson.test.JenkinsRule;
import java.io.File;
import java.io.IOException;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import java.net.URISyntaxException;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
/**
* Setup/teardown of the TFS server configured by the {@code tfs_server_name} property,
* will create the necessary structure in source control.
*/
@Documented
@JenkinsRecipe(EndToEndTfs.RunnerImpl.class)
@Target(METHOD)
@Retention(RUNTIME)
public @interface EndToEndTfs {
/**
* Specifies the class that will be given a chance to participate.
*/
Class<? extends StubRunner> value();
/**
* The {@link EndToEndTfs} annotation requires a value of type {@link Class}.
* This class provides an implementation that does almost nothing.
*/
class StubRunner extends JenkinsRecipe.Runner<EndToEndTfs> {
private RunnerImpl parent;
private IntegrationTestHelper helper;
private String encryptedPassword;
public String getEncryptedPassword() {
return encryptedPassword;
}
protected RunnerImpl getParent() {
return parent;
}
private void setParent(final RunnerImpl parent) {
this.parent = parent;
}
protected IntegrationTestHelper getHelper() {
return helper;
}
private void setHelper(final IntegrationTestHelper helper) {
this.helper = helper;
}
@Override
public void decorateHome(final JenkinsRule jenkinsRule, final File home) throws Exception {
final String jobFolder = parent.getJobFolder();
final String configXmlPath = jobFolder + "config.xml";
final File configXmlFile = new File(home, configXmlPath);
final String tfsServerUrl = helper.getServerUrl();
XmlHelper.pokeValue(configXmlFile, "/project/scm/serverUrl", tfsServerUrl);
final String projectPath = parent.getPathInTfvc();
XmlHelper.pokeValue(configXmlFile, "/project/scm/projectPath", projectPath);
final String workspaceName = "Hudson-${JOB_NAME}-${COMPUTERNAME}";
XmlHelper.pokeValue(configXmlFile, "/project/scm/workspaceName", workspaceName);
final String userName = helper.getUserName();
XmlHelper.pokeValue(configXmlFile, "/project/scm/userName", userName);
final String userPassword = helper.getUserPassword();
final SecretOverride secretOverride = new SecretOverride();
try {
final Secret secret = Secret.fromString(userPassword);
encryptedPassword = secret.getEncryptedValue();
}
finally {
try {
secretOverride.close();
} catch (IOException e) {
// ignore
}
}
final String projectScmPassword = "/project/scm/password";
final String currentPassword = XmlHelper.peekValue(configXmlFile, projectScmPassword);
if (currentPassword != null) {
XmlHelper.pokeValue(configXmlFile, projectScmPassword, encryptedPassword);
}
}
}
class RunnerImpl extends JenkinsRecipe.Runner<EndToEndTfs> {
private static final String workspaceComment = "Created by the TFS plugin for Jenkins functional tests.";
private final IntegrationTestHelper helper;
private final String serverUrl;
private File localBaseFolderFile;
private StubRunner runner;
private Server server = null;
private String testClassName;
private String testCaseName;
private String workspaceName;
private String pathInTfvc;
private Workspace workspace;
public RunnerImpl() throws URISyntaxException {
helper = new IntegrationTestHelper();
serverUrl = helper.getServerUrl();
}
@Override
public void setup(JenkinsRule jenkinsRule, EndToEndTfs recipe) throws Exception {
final Description testDescription = jenkinsRule.getTestDescription();
final Class clazz = testDescription.getTestClass();
testClassName = clazz.getSimpleName();
testCaseName = testDescription.getMethodName();
final String hostName = IntegrationTestHelper.tryToDetermineHostName();
final File currentFolder = new File("").getAbsoluteFile();
final File workspaces = new File(currentFolder, "workspaces");
// TODO: Consider NOT using the Server class
server = new Server(null, null, serverUrl, helper.getUserName(), helper.getUserPassword(), null, ExtraSettings.DEFAULT);
final MockableVersionControlClient vcc = server.getVersionControlClient();
// workspaceName MUST be unique across computers hitting the same server
workspaceName = hostName + "-" + testCaseName;
workspace = createWorkspace(vcc, workspaceName);
pathInTfvc = IntegrationTestHelper.determinePathInTfvcForTestCase(testDescription);
final File localTestClassFolder = new File(workspaces, testClassName);
localBaseFolderFile = new File(localTestClassFolder, testCaseName);
//noinspection ResultOfMethodCallIgnored
localBaseFolderFile.mkdirs();
final String localBaseFolder = localBaseFolderFile.getAbsolutePath();
final WorkingFolder workingFolder = new WorkingFolder(pathInTfvc, localBaseFolder);
workspace.createWorkingFolder(workingFolder);
// TODO: Is this necessary if we're about to delete it, anyway?
workspace.get(GetOptions.NONE);
// Delete the folder associated with this test in TFVC
workspace.pendDelete(
new String[]{pathInTfvc},
RecursionType.FULL,
LockLevel.UNCHANGED,
GetOptions.NONE,
PendChangesOptions.NONE);
checkIn("Cleaning up for the " + testCaseName + " test.");
// we don't need to verify this check-in, because a first run on a server will be a no-op
// create the folder in TFVC
workspace.pendAdd(
new String[]{localBaseFolder},
false,
null,
LockLevel.UNCHANGED,
GetOptions.NONE,
PendChangesOptions.NONE);
final int changeSet = checkIn("Setting up for the " + testCaseName + " test.");
Assert.assertTrue(changeSet >= 0);
final Class<? extends StubRunner> runnerClass = recipe.value();
if (runnerClass != null) {
runner = runnerClass.newInstance();
runner.setParent(this);
runner.setHelper(this.helper);
runner.setup(jenkinsRule, recipe);
}
}
public File getLocalBaseFolderFile() {
return localBaseFolderFile;
}
public String getPathInTfvc() {
return pathInTfvc;
}
public String getWorkspaceName() {
return workspaceName;
}
public String getTestCaseName() {
return testCaseName;
}
public String getTestClassName() {
return testClassName;
}
public String getServerUrl() {
return serverUrl;
}
public Server getServer() {
return server;
}
public String getJobFolder() {
return "jobs/" + testCaseName + "/";
}
public <T extends StubRunner> T getInnerRunner(final Class<T> type) {
return type.cast(runner);
}
public Workspace getWorkspace() {
return workspace;
}
public int checkIn(final String comment) {
return checkIn(workspace, comment);
}
static int checkIn(Workspace workspace, String comment) {
final PendingSet pendingSet = workspace.getPendingChanges();
int result = -1;
if (pendingSet != null) {
final PendingChange[] pendingChanges = pendingSet.getPendingChanges();
if (pendingChanges != null) {
result = workspace.checkIn(pendingChanges, comment);
}
}
return result;
}
static Workspace createWorkspace(final MockableVersionControlClient vcc, final String workspaceName) {
deleteWorkspace(vcc, workspaceName);
final Workspace workspace = vcc.createWorkspace(
null,
workspaceName,
VersionControlConstants.AUTHENTICATED_USER,
VersionControlConstants.AUTHENTICATED_USER,
workspaceComment,
WorkspaceLocation.LOCAL,
WorkspaceOptions.NONE);
return workspace;
}
static void deleteWorkspace(final MockableVersionControlClient vcc, final String workspaceName) {
final Workspace[] workspaces = vcc.queryWorkspaces(workspaceName, null, /* TODO: computer */null, WorkspacePermissions.NONE_OR_NOT_SUPPORTED);
for (final Workspace workspace : workspaces) {
for (WorkingFolder workingFolder : workspace.getFolders()) {
final String localItem = workingFolder.getLocalItem();
if (localItem != null) {
final File file = new File(localItem);
FileUtils.deleteQuietly(file);
}
}
vcc.deleteWorkspace(workspace);
}
}
@Override
public void decorateHome(JenkinsRule jenkinsRule, File home) throws Exception {
if (runner != null) {
runner.decorateHome(jenkinsRule, home);
}
}
@Override
public void tearDown(JenkinsRule jenkinsRule, EndToEndTfs recipe) throws Exception {
if (runner != null) {
runner.tearDown(jenkinsRule, recipe);
}
final MockableVersionControlClient vcc = server.getVersionControlClient();
deleteWorkspace(vcc, workspaceName);
if (server != null) {
server.close();
}
}
}
}