/*
* The MIT License
*
* Copyright (c) 2004-2010, Sun Microsystems, Inc., Kohsuke Kawaguchi, Bruce Chapman, Yahoo! 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.
*/
package hudson.scm;
import antlr.ANTLRException;
import com.gargoylesoftware.htmlunit.HttpMethod;
import com.gargoylesoftware.htmlunit.WebConnection;
import com.gargoylesoftware.htmlunit.WebRequestSettings;
import com.gargoylesoftware.htmlunit.WebResponse;
import hudson.FilePath;
import hudson.model.AbstractProject;
import hudson.model.Cause;
import hudson.model.FreeStyleBuild;
import hudson.model.FreeStyleProject;
import hudson.model.ParametersAction;
import hudson.model.Result;
import hudson.model.StringParameterValue;
import hudson.model.TaskListener;
import hudson.triggers.SCMTrigger;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.ExecutionException;
import org.jvnet.hudson.test.Bug;
import org.jvnet.hudson.test.Email;
import org.jvnet.hudson.test.HudsonHomeLoader.CopyExisting;
import org.tmatesoft.svn.core.SVNDepth;
import org.tmatesoft.svn.core.auth.ISVNAuthenticationManager;
import org.tmatesoft.svn.core.internal.wc.admin.SVNAdminAreaFactory;
import org.tmatesoft.svn.core.wc.SVNClientManager;
import org.tmatesoft.svn.core.wc.SVNCommitClient;
import org.tmatesoft.svn.core.wc.SVNStatus;
/**
* @author Kohsuke Kawaguchi
*/
public class SubversionCommitTest extends AbstractSubversionTest {
private static final int LOG_LIMIT = 1000;
String kind = ISVNAuthenticationManager.PASSWORD;
/**
* {@link SubversionSCM#pollChanges(AbstractProject, hudson.Launcher, FilePath, TaskListener)} should notice
* if the workspace and the current configuration is inconsistent and schedule a new build.
*/
@Email("http://www.nabble.com/Proper-way-to-switch---relocate-SVN-tree---tt21173306.html")
public void testPollingAfterRelocation() throws Exception {
// fetch the current workspace
FreeStyleProject p = createFreeStyleProject();
p.setScm(loadSvnRepo());
p.scheduleBuild2(0, new Cause.UserCause()).get();
// as a baseline, this shouldn't detect any change
TaskListener listener = createTaskListener();
assertFalse(p.pollSCMChanges(listener));
// now switch the repository to a new one.
// this time the polling should indicate that we need a new build
p.setScm(loadSvnRepo());
assertTrue(p.pollSCMChanges(listener));
// build it once again to switch
p.scheduleBuild2(0, new Cause.UserCause()).get();
// then no more change should be detected
assertFalse(p.pollSCMChanges(listener));
}
public void testURLWithVariable() throws Exception {
FreeStyleProject p = createFreeStyleProject();
String url = "http://svn.codehaus.org/sxc/tags/sxc-0.5/sxc-core/src/test/java/com/envoisolutions/sxc/builder/";
p.setScm(new SubversionSCM("$REPO" + url.substring(10)));
String var = url.substring(0, 10);
FreeStyleBuild b = p.scheduleBuild2(0, new Cause.UserCause(),
new ParametersAction(new StringParameterValue("REPO", var))).get();
System.out.println(b.getLog(LOG_LIMIT));
assertBuildStatus(Result.SUCCESS, b);
assertTrue(b.getWorkspace().child("Node.java").exists());
}
/**
* Test that multiple repository URLs are all polled.
*/
@Bug(3168)
public void testPollMultipleRepositories() throws Exception {
// fetch the current workspaces
FreeStyleProject p = createFreeStyleProject();
String svnBase = "file://" + new CopyExisting(getClass().getResource("/svn-repo.zip")).allocate()
.toURI()
.toURL()
.getPath();
SubversionSCM scm = new SubversionSCM(
Arrays.asList(new SubversionSCM.ModuleLocation(svnBase + "trunk", null),
new SubversionSCM.ModuleLocation(svnBase + "branches", null)),
false, false, null, null, null, null, null);
p.setScm(scm);
p.scheduleBuild2(0, new Cause.UserCause()).get();
// as a baseline, this shouldn't detect any change
TaskListener listener = createTaskListener();
assertFalse(p.pollSCMChanges(listener));
createCommit(scm, "branches/foo");
assertTrue("any change in any of the repository should be detected", p.pollSCMChanges(listener));
assertFalse("no change since the last polling", p.pollSCMChanges(listener));
createCommit(scm, "trunk/foo");
assertTrue("another change in the repo should be detected separately", p.pollSCMChanges(listener));
}
/**
* Makes sure that Subversion doesn't check out workspace in 1.6
*/
@Email("http://www.nabble.com/SVN-1.6-td24081571.html")
public void testWorkspaceVersion() throws Exception {
FreeStyleProject p = createFreeStyleProject();
p.setScm(loadSvnRepo());
FreeStyleBuild b = p.scheduleBuild2(0).get();
SVNClientManager wc = SubversionSCM.createSvnClientManager((AbstractProject) null);
SVNStatus st = wc.getStatusClient().doStatus(new File(b.getWorkspace().getRemote() + "/a"), false);
int wcf = st.getWorkingCopyFormat();
System.out.println(wcf);
assertEquals(SVNAdminAreaFactory.WC_FORMAT_14, wcf);
}
private static String readFileAsString(File file)
throws java.io.IOException {
BufferedReader reader = null;
StringBuilder fileData = new StringBuilder(1000);
try {
reader = new BufferedReader(new FileReader(file));
char[] buf = new char[1024];
int numRead = 0;
while ((numRead = reader.read(buf)) != -1) {
fileData.append(buf, 0, numRead);
}
} finally {
if (reader != null) {
reader.close();
}
}
return fileData.toString();
}
/**
* Makes sure the symbolic link is checked out correctly. There seems to be
* @throws IOException
* @throws ExecutionException
* @throws InterruptedException
*/
@Bug(3904)
//TODO fix when svn repository for the tests will be created
public void ignore_testSymbolicLinkCheckout() throws IOException, InterruptedException, ExecutionException {
// Only perform if symlink behavior is enabled
if (!"true".equals(System.getProperty("svnkit.symlinks"))) {
return;
}
FreeStyleProject p = createFreeStyleProject();
p.setScm(new SubversionSCM("https://svn.java.net/svn/hudson~svn/trunk/hudson/test-projects/issue-3904"));
FreeStyleBuild b = p.scheduleBuild2(0, new Cause.UserCause()).get();
File source = new File(b.getWorkspace().getRemote() + "/readme.txt");
File linked = new File(b.getWorkspace().getRemote() + "/linked.txt");
assertEquals("Files '" + source + "' and '" + linked + "' are not identical from user view.",
readFileAsString(source), readFileAsString(linked));
}
public void testExcludeByUser() throws InterruptedException, ExecutionException, IOException {
FreeStyleProject p = createFreeStyleProject("testExcludeByUser");
p.setScm(new SubversionSCM(
Arrays.asList(new SubversionSCM.ModuleLocation(
"https://datacard.googlecode.com/svn/trunk@145", null)),
true, null, "", "bg_one@mail.ru", "", "")
);
// Do a build to force the creation of the workspace. This works around
// pollChanges returning true when the workspace does not exist.
p.scheduleBuild2(0).get();
boolean foundChanges = p.pollSCMChanges(createTaskListener());
assertFalse("Polling found changes that should have been ignored", foundChanges);
}
/**
* Test excluded regions
*/
@Bug(6030)
public void testExcludedRegions() throws Exception {
// SLAVE_DEBUG_PORT = 8001;
File repo = new CopyExisting(getClass().getResource("HUDSON-6030.zip")).allocate();
SubversionSCM scm = new SubversionSCM(
SubversionSCM.ModuleLocation.parse(new String[]{"file:///" + repo.getPath()},
new String[]{"."}, null, null),
true, false, null, ".*//*bar", "", "", "", "");
FreeStyleProject p = createFreeStyleProject("testExcludedRegions");
p.setScm(scm);
assertBuildStatusSuccess(p.scheduleBuild2(0).get());
// initial polling on the master for the code path that doesn't find any change
PollingResult pollResult = p.poll(createTaskListener());
if (pollResult.getChange() != PollingResult.Change.INCOMPARABLE) {
boolean hasChanges = pollResult.hasChanges();
assertFalse("Polling should not have any changes for an initially created slave",
hasChanges);
}
createCommit(scm, "bar");
// initial polling on the master for the code path that doesn't find any change
pollResult = p.poll(createTaskListener());
if (pollResult.getChange() != PollingResult.Change.INCOMPARABLE) {
boolean hasChanges = pollResult.hasChanges();
assertFalse("Polling found changes that should have been ignored",
hasChanges);
}
createCommit(scm, "foo");
// polling on the slave for the code path that doesn't find any change
assertTrue("Polling didn't find a change it should have found.",
p.poll(createTaskListener()).hasChanges());
}
/**
* Test included regions
*/
@Bug(6030)
public void testIncludedRegions() throws Exception {
// SLAVE_DEBUG_PORT = 8001;
File repo = new CopyExisting(getClass().getResource("HUDSON-6030.zip")).allocate();
SubversionSCM scm = new SubversionSCM(
SubversionSCM.ModuleLocation.parse(new String[]{"file:///" + repo.getPath()},
new String[]{"."}, null, null),
true, false, null, "", "", "", "", ".*//*foo");
FreeStyleProject p = createFreeStyleProject("testIncludedRegions");
p.setScm(scm);
assertBuildStatusSuccess(p.scheduleBuild2(0).get());
// initial polling on the slave for the code path that doesn't find any change
PollingResult pollResult = p.poll(createTaskListener());
if (pollResult.getChange() != PollingResult.Change.INCOMPARABLE) {
boolean hasChanges = pollResult.hasChanges();
assertFalse(hasChanges);
}
createCommit(scm, "bar");
// polling on the slave for the code path that does have a change but should be excluded.
pollResult = p.poll(createTaskListener());
if (pollResult.getChange() != PollingResult.Change.INCOMPARABLE) {
boolean hasChanges = pollResult.hasChanges();
assertFalse("Polling found changes that should have been ignored",
hasChanges);
}
createCommit(scm, "foo");
// polling on the slave for the code path that doesn't find any change
assertTrue("Polling didn't find a change it should have found.",
p.poll(createTaskListener()).hasChanges());
}
/**
* Tests a checkout triggered from the post-commit hook
*/
//TODO fix when svn repository for the tests will be created
public void ignore_testPostCommitTrigger() throws Exception {
FreeStyleProject p = createPostCommitTriggerJob();
FreeStyleBuild b = sendCommitTrigger(p, true);
assertTrue(getActualRevision(b, "https://svn.java.net/svn/hudson~svn/trunk/hudson/test-projects/trivial-ant").longValue()
<= 13000);
}
/**
* Tests a checkout triggered from the post-commit hook without revision
* information.
*/
//TODO fix when svn repository for the tests will be created
public void ignore_testPostCommitTriggerNoRevision() throws Exception {
FreeStyleProject p = createPostCommitTriggerJob();
FreeStyleBuild b = sendCommitTrigger(p, false);
assertTrue(
getActualRevision(b, "https://svn.java.net/svn/hudson~svn/trunk/hudson/test-projects/trivial-ant").longValue() > 13000);
}
/**
* Loads a test Subversion repository into a temporary directory, and creates {@link SubversionSCM} for it.
*/
private SubversionSCM loadSvnRepo() throws Exception {
return new SubversionSCM(
"file://" + new CopyExisting(getClass().getResource("/svn-repo.zip")).allocate().toURI().toURL().getPath()
+ "trunk/a", "a");
}
private FreeStyleBuild sendCommitTrigger(FreeStyleProject p, boolean includeRevision) throws Exception {
String repoUUID = "71c3de6d-444a-0410-be80-ed276b4c234a";
WebClient wc = new WebClient();
WebRequestSettings wr = new WebRequestSettings(new URL(getURL() + "subversion/" + repoUUID + "/notifyCommit"),
HttpMethod.POST);
wr.setRequestBody("A trunk/hudson/test-projects/trivial-ant/build.xml");
wr.setAdditionalHeader("Content-Type", "text/plain;charset=UTF-8");
if (includeRevision) {
wr.setAdditionalHeader("X-Hudson-Subversion-Revision", "13000");
}
WebConnection conn = wc.getWebConnection();
WebResponse resp = conn.getResponse(wr);
assertTrue(isGoodHttpStatus(resp.getStatusCode()));
waitUntilNoActivity();
FreeStyleBuild b = p.getLastBuild();
assertNotNull(b);
assertBuildStatus(Result.SUCCESS, b);
return b;
}
/**
* Manufactures commits by adding files in the given names.
*/
private void createCommit(SubversionSCM scm, String... paths) throws Exception {
FreeStyleProject forCommit = createFreeStyleProject();
forCommit.setScm(scm);
forCommit.setAssignedLabel(hudson.getSelfLabel());
FreeStyleBuild b = assertBuildStatusSuccess(forCommit.scheduleBuild2(0).get());
SVNClientManager svnm = SubversionSCM.createSvnClientManager((AbstractProject) null);
List<File> added = new ArrayList<File>();
for (String path : paths) {
FilePath newFile = b.getWorkspace().child(path);
added.add(new File(newFile.getRemote()));
if (!newFile.exists()) {
newFile.touch(System.currentTimeMillis());
svnm.getWCClient()
.doAdd(new File(newFile.getRemote()), false, false, false, SVNDepth.INFINITY, false, false);
} else {
newFile.write("random content", "UTF-8");
}
}
SVNCommitClient cc = svnm.getCommitClient();
cc.doCommit(added.toArray(new File[added.size()]), false, "added", null, null, false, false, SVNDepth.EMPTY);
}
private Long getActualRevision(FreeStyleBuild b, String url) throws Exception {
SVNRevisionState revisionState = b.getAction(SVNRevisionState.class);
if (revisionState == null) {
throw new Exception("No revision found!");
}
return revisionState.revisions.get(url);
}
private FreeStyleProject createPostCommitTriggerJob() throws IOException, ANTLRException {
// Disable crumbs because HTMLUnit refuses to mix request bodies with
// request parameters
hudson.setCrumbIssuer(null);
FreeStyleProject p = createFreeStyleProject();
String url = "https://svn.java.net/svn/hudson~svn/trunk/hudson/test-projects/trivial-ant";
SCMTrigger trigger = new SCMTrigger("0 */6 * * *");
p.setScm(new SubversionSCM(url));
p.addTrigger(trigger);
trigger.start(p, true);
return p;
}
}