/******************************************************************************** * CruiseControl, a Continuous Integration Toolkit * Copyright (c) 2001, ThoughtWorks, Inc. * 200 E. Randolph, 25th Floor * Chicago, IL 60601 USA * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * + Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * + Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following * disclaimer in the documentation and/or other materials provided * with the distribution. * * + Neither the name of ThoughtWorks, Inc., CruiseControl, nor the * names of its contributors may be used to endorse or promote * products derived from this software without specific prior * written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ********************************************************************************/ package net.sourceforge.cruisecontrol.sourcecontrols; import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.nullValue; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertThat; import static org.junit.Assert.fail; import java.io.BufferedReader; import java.io.ByteArrayInputStream; import java.io.File; import java.io.FilenameFilter; import java.io.IOException; import java.io.InputStreamReader; import java.io.StringReader; import java.text.ParseException; import java.util.ArrayList; import java.util.Arrays; import java.util.Calendar; import java.util.Date; import java.util.GregorianCalendar; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.TimeZone; import net.sourceforge.cruisecontrol.CruiseControlException; import net.sourceforge.cruisecontrol.Modification; import net.sourceforge.cruisecontrol.testutil.TestUtil; import org.jdom.JDOMException; import org.junit.After; import org.junit.Before; import org.junit.Test; // TODO: Split this up into separate tests public class SVNTest { private static final File buildTargetDirectory = TestUtil.getTargetDir(); private static final FilenameFilter dotSVNFilter = new FilenameFilter() { public boolean accept(final File currentDirectory, final String name) { if (".svn".equals(name)) { return true; } return false; } }; private SVN svn; private TimeZone originalTimeZone; @Before public void setUp() { svn = new SVN(); svn.setLocalWorkingCopy(findLocalWorkingCopy()); originalTimeZone = TimeZone.getDefault(); } @After public void tearDown() { TimeZone.setDefault(originalTimeZone); } @Test(expected = CruiseControlException.class) public void failsValidationWhenNoAttributesAreSet() throws CruiseControlException { final SVN svn = new SVN(); svn.validate(); } @Test public void validatesIfAtLeastRepositoryLocationSet() { svn.setRepositoryLocation("http://svn.collab.net/repos/svn"); try { svn.validate(); } catch (CruiseControlException e) { fail("should not throw an exception when at least the 'repositoryLocation' attribute is set"); } } // TODO: validate the repository location // @Test(expected = CruiseControlException.class) // public void failsValidationForInvalidRepositoryLocation() throws CruiseControlException { // svn.setRepositoryLocation("invalid repository location"); // svn.validate(); // } @Test(expected = CruiseControlException.class) public void failsValidationForInvalidLocalWorkingCopy() throws CruiseControlException { svn.setLocalWorkingCopy("invalid directory"); svn.validate(); } @Test public void validatesIfAtLeastLocalWorkingCopySet() { svn.setLocalWorkingCopy("."); try { svn.validate(); } catch (CruiseControlException e) { fail("should not throw an exception when at least a valid 'localWorkingCopy' attribute is set"); } } @Test(expected = CruiseControlException.class) public void failsValidationIfLocalWorkingCopySetToFileInsteadOfDirectory() throws CruiseControlException, IOException { File tempFile = File.createTempFile("temp", "txt"); tempFile.deleteOnExit(); svn.setLocalWorkingCopy(tempFile.getAbsolutePath()); svn.validate(); } @Test public void buildPropGetCommandWhereOnlyLocalWorkingCopySet() throws CruiseControlException { svn.setLocalWorkingCopy("."); String[] actualCommand = svn.buildPropgetCommand().getCommandline(); String[] expectedCommand = new String[] { "svn", "propget", "-R", "--non-interactive", "svn:externals" }; assertThat(actualCommand, equalTo(expectedCommand)); } @Test public void buildPropGetCommandWhereOnlyRepositoryLocationSet() throws CruiseControlException { svn.setRepositoryLocation("http://svn.collab.net/repos/svn"); String[] actualCommand = svn.buildPropgetCommand().getCommandline(); String[] expectedCommand = new String[] { "svn", "propget", "-R", "--non-interactive", "svn:externals", "http://svn.collab.net/repos/svn" }; assertThat(actualCommand, equalTo(expectedCommand)); } @Test public void parsingTheResultsOfExecutingPropGetForSVNExternals() throws Exception { String testPropgetResult = ". - shared/build\tsvn://mybank.org/svnbank/trunk/java/shared/build\n" + "shared/tool/jfcunit_2.08\tsvn://mybank.org/svnbank/trunk/java/shared/tool/jfcunit_2.08\n" + "shared/lib/jnlp-1_2-dev\tsvn://mybank.org/svnbank/trunk/java/shared/lib/jnlp-1_2-dev\n"; final HashMap<String, List<String[]>> directories = new HashMap<String, List<String[]>>(); final BufferedReader reader = new BufferedReader(new InputStreamReader(new ByteArrayInputStream( testPropgetResult.getBytes("UTF-8")), "UTF-8")); try { SVN.parsePropgetReader(reader, directories); } finally { reader.close(); } assertEquals(1, directories.keySet().size()); String directory = directories.keySet().iterator().next(); List externals = directories.get(directory); assertEquals("Wrong number of externals", 3, externals.size()); String[] firstEntry = (String[]) externals.get(0); assertEquals("Wrong external: " + Arrays.asList(firstEntry).toString(), 2, firstEntry.length); assertEquals("Wrong externalSvnURL", "svn://mybank.org/svnbank/trunk/java/shared/build", firstEntry[1]); } @Test public void formattingSVNDate() { GregorianCalendar cal = new GregorianCalendar(2007, Calendar.JULY, 11, 12, 32, 45); cal.setTimeZone(TimeZone.getTimeZone("GMT")); Date date = cal.getTime(); assertThat("Windows SVN date format does not match", SVN.formatSVNDate(date, true), equalTo("\"{2007-07-11T12:32:45Z}\"")); assertThat("non-Windows SVN date format does not match", SVN.formatSVNDate(date, false), equalTo("{2007-07-11T12:32:45Z}")); } @Test public void testBuildHistoryCommand() throws CruiseControlException { svn.setLocalWorkingCopy("."); Date checkTime = new Date(); long tenMinutes = 10 * 60 * 1000; Date lastBuild = new Date(checkTime.getTime() - tenMinutes); String[] expectedCmd = new String[] { "svn", "log", "--non-interactive", "--xml", "-v", "-r", SVN.formatSVNDate(lastBuild, false) + ":" + SVN.formatSVNDate(checkTime, false) }; String[] actualCmd = svn.buildHistoryCommand(SVN.formatSVNDate(lastBuild, false), SVN.formatSVNDate(checkTime, false)).getCommandline(); assertThat(actualCmd, equalTo(expectedCmd)); expectedCmd = new String[] { "svn", "log", "--non-interactive", "--xml", "-v", "-r", SVN.formatSVNDate(lastBuild, false) + ":" + SVN.formatSVNDate(checkTime, false), "external/path" }; actualCmd = svn.buildHistoryCommand(SVN.formatSVNDate(lastBuild, false), SVN.formatSVNDate(checkTime, false), "external/path").getCommandline(); assertThat(actualCmd, equalTo(expectedCmd)); svn.setRepositoryLocation("http://svn.collab.net/repos/svn"); expectedCmd = new String[] { "svn", "log", "--non-interactive", "--xml", "-v", "-r", SVN.formatSVNDate(lastBuild, false) + ":" + SVN.formatSVNDate(checkTime, false), "http://svn.collab.net/repos/svn" }; actualCmd = svn.buildHistoryCommand(SVN.formatSVNDate(lastBuild, false), SVN.formatSVNDate(checkTime, false)) .getCommandline(); assertThat(actualCmd, equalTo(expectedCmd)); expectedCmd = new String[] { "svn", "log", "--non-interactive", "--xml", "-v", "-r", SVN.formatSVNDate(lastBuild, false) + ":" + SVN.formatSVNDate(checkTime, false), "http://svn.collab.net/repos/external" }; actualCmd = svn.buildHistoryCommand(SVN.formatSVNDate(lastBuild, false), SVN.formatSVNDate(checkTime, false), "http://svn.collab.net/repos/external").getCommandline(); assertThat(actualCmd, equalTo(expectedCmd)); svn.setUsername("lee"); svn.setPassword("secret"); expectedCmd = new String[] { "svn", "log", "--non-interactive", "--xml", "-v", "-r", SVN.formatSVNDate(lastBuild, false) + ":" + SVN.formatSVNDate(checkTime, false), "--no-auth-cache", "--username", "lee", "--password", "secret", "http://svn.collab.net/repos/svn" }; actualCmd = svn.buildHistoryCommand(SVN.formatSVNDate(lastBuild, false), SVN.formatSVNDate(checkTime, false)) .getCommandline(); assertThat(actualCmd, equalTo(expectedCmd)); svn.setUsername(null); svn.setPassword(null); final String testConfDir = "myConfigDir"; svn.setConfigDir(testConfDir); expectedCmd = new String[] { "svn", "log", "--non-interactive", "--xml", "-v", "-r", SVN.formatSVNDate(lastBuild, false) + ":" + SVN.formatSVNDate(checkTime, false), "--config-dir", testConfDir, "http://svn.collab.net/repos/svn" }; actualCmd = svn.buildHistoryCommand(SVN.formatSVNDate(lastBuild, false), SVN.formatSVNDate(checkTime, false)) .getCommandline(); assertThat(actualCmd, equalTo(expectedCmd)); } @Test public void testParseModifications() throws JDOMException, ParseException, IOException { String svnLog = "<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>\n" + "<log>\n" + " <logentry revision=\"663\">\n" + " <author>lee</author>\n" + " <date>2003-04-30T10:01:42.349105Z</date>\n" + " <paths>\n" + " <path action=\"A\">/trunk/playground/aaa/ccc</path>\n" + " <path action=\"M\">/trunk/playground/aaa/ccc/d.txt</path>\n" + " <path action=\"A\">/trunk/playground/bbb</path>\n" + " </paths>\n" + " <msg>bli</msg>\n" + " </logentry>\n" + " <logentry revision=\"664\">\n" + " <author>etienne</author>\n" + " <date>2003-04-30T10:03:14.100900Z</date>\n" + " <paths>\n" + " <path action=\"A\">/trunk/playground/aaa/f.txt</path>\n" + " </paths>\n" + " <msg>bla</msg>\n" + " </logentry>\n" + " <logentry revision=\"665\">\n" + " <author>martin</author>\n" + " <date>2003-04-30T10:04:48.050619Z</date>\n" + " <paths>\n" + " <path action=\"D\">/trunk/playground/bbb</path>\n" + " </paths>\n" + " <msg>blo</msg>\n" + " </logentry>\n" + "</log>"; Modification[] modifications = SVN.SVNLogXMLParser.parse(new StringReader(svnLog)); assertEquals(5, modifications.length); Modification modification = createModification(SVN.getOutDateFormatter().parse("2003-04-30T10:01:42.349"), "lee", "bli", "663", "", "/trunk/playground/aaa/ccc", "added"); assertThat(modifications[0], equalTo(modification)); modification = createModification(SVN.getOutDateFormatter().parse("2003-04-30T10:01:42.349"), "lee", "bli", "663", "", "/trunk/playground/aaa/ccc/d.txt", "modified"); assertThat(modifications[1], equalTo(modification)); modification = createModification(SVN.getOutDateFormatter().parse("2003-04-30T10:01:42.349"), "lee", "bli", "663", "", "/trunk/playground/bbb", "added"); assertThat(modifications[2], equalTo(modification)); modification = createModification(SVN.getOutDateFormatter().parse("2003-04-30T10:03:14.100"), "etienne", "bla", "664", "", "/trunk/playground/aaa/f.txt", "added"); assertThat(modifications[3], equalTo(modification)); modification = createModification(SVN.getOutDateFormatter().parse("2003-04-30T10:04:48.050"), "martin", "blo", "665", "", "/trunk/playground/bbb", "deleted"); assertThat(modifications[4], equalTo(modification)); modifications = SVN.SVNLogXMLParser.parse(new StringReader(svnLog), "external/path"); assertEquals(5, modifications.length); modification = createModification(SVN.getOutDateFormatter().parse("2003-04-30T10:01:42.349"), "lee", "bli", "663", "", "/external/path:/trunk/playground/aaa/ccc", "added"); assertThat(modifications[0], equalTo(modification)); modifications = SVN.SVNLogXMLParser.parse(new StringReader(svnLog), null); assertEquals(5, modifications.length); modification = createModification(SVN.getOutDateFormatter().parse("2003-04-30T10:01:42.349"), "lee", "bli", "663", "", "/trunk/playground/aaa/ccc/d.txt", "modified"); assertThat(modifications[1], equalTo(modification)); } @Test(expected = ParseException.class) public void testConvertDateIllegalArgument() throws ParseException { SVN.SVNLogXMLParser.convertDate("2003-04-30T10:01:42.349105"); } @Test public void testParseEmptyModifications() throws JDOMException, ParseException, IOException { String svnLog = "<?xml version=\"1.0\" encoding = \"ISO-8859-1\"?>\n " + "<log>\n" + "</log>"; Modification[] modifications = SVN.SVNLogXMLParser.parse(new StringReader(svnLog)); assertEquals(0, modifications.length); } @Test public void testChangeWithoutReadAccessToChangedFileShouldResultInNoModificationReported() throws ParseException, JDOMException, IOException { String svnLog = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" + "<log>\n" + " <logentry revision=\"1234\">\n" + " <msg></msg>\n" + " </logentry>\n" + "</log>"; Modification[] modifications = SVN.SVNLogXMLParser.parse(new StringReader(svnLog)); assertEquals(0, modifications.length); } @Test public void testParseAndFilter() throws ParseException, JDOMException, IOException { String svnLog = "<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>\n" + "<log>\n" + " <logentry revision=\"663\">\n" + " <author>lee</author>\n" + " <date>2003-08-02T10:01:13.349105Z</date>\n" + " <paths>\n" + " <path action=\"A\">/trunk/playground/bbb</path>\n" + " </paths>\n" + " <msg>bli</msg>\n" + " </logentry>\n" + " <logentry revision=\"664\">\n" + " <author>etienne</author>\n" + " <date>2003-07-29T17:45:12.100900Z</date>\n" + " <paths>\n" + " <path action=\"A\">/trunk/playground/aaa/f.txt</path>\n" + " </paths>\n" + " <msg>bla</msg>\n" + " </logentry>\n" + " <logentry revision=\"665\">\n" + " <author>martin</author>\n" + " <date>2003-07-29T18:15:11.100900Z</date>\n" + " <paths>\n" + " <path action=\"D\">/trunk/playground/ccc</path>\n" + " </paths>\n" + " <msg>blo</msg>\n" + " </logentry>\n" + "</log>"; TimeZone.setDefault(TimeZone.getTimeZone("GMT+0:00")); Date julyTwentynineSixPM2003 = new GregorianCalendar(2003, Calendar.JULY, 29, 18, 0, 0).getTime(); List modifications = SVN.SVNLogXMLParser.parseAndFilter(new StringReader(svnLog), julyTwentynineSixPM2003); assertEquals(2, modifications.size()); Modification modification = createModification(SVN.getOutDateFormatter().parse("2003-08-02T10:01:13.349"), "lee", "bli", "663", "", "/trunk/playground/bbb", "added"); assertThat((Modification) modifications.get(0), equalTo(modification)); modification = createModification(SVN.getOutDateFormatter().parse("2003-07-29T18:15:11.100"), "martin", "blo", "665", "", "/trunk/playground/ccc", "deleted"); assertThat((Modification) modifications.get(1), equalTo(modification)); Date julyTwentyeightZeroPM2003 = new GregorianCalendar(2003, Calendar.JULY, 28, 0, 0, 0).getTime(); modifications = SVN.SVNLogXMLParser.parseAndFilter(new StringReader(svnLog), julyTwentyeightZeroPM2003); assertEquals(3, modifications.size()); modifications = SVN.SVNLogXMLParser.parseAndFilter(new StringReader(svnLog), julyTwentynineSixPM2003, "external/path"); assertEquals(2, modifications.size()); modification = createModification(SVN.getOutDateFormatter().parse("2003-08-02T10:01:13.349"), "lee", "bli", "663", "", "/external/path:/trunk/playground/bbb", "added"); assertThat((Modification) modifications.get(0), equalTo(modification)); } @Test public void testFormatDatesForSvnLog() { TimeZone.setDefault(TimeZone.getTimeZone("GMT+10:00")); Date maySeventeenSixPM2001 = new GregorianCalendar(2001, Calendar.MAY, 17, 18, 0, 0).getTime(); assertThat(SVN.formatSVNDate(maySeventeenSixPM2001, false), equalTo("{2001-05-17T08:00:00Z}")); Date maySeventeenEightAM2001 = new GregorianCalendar(2001, Calendar.MAY, 17, 8, 0, 0).getTime(); assertThat(SVN.formatSVNDate(maySeventeenEightAM2001, false), equalTo("{2001-05-16T22:00:00Z}")); TimeZone.setDefault(TimeZone.getTimeZone("GMT-10:00")); Date marchTwelfFourPM2003 = new GregorianCalendar(2003, Calendar.MARCH, 12, 16, 0, 0).getTime(); assertThat(SVN.formatSVNDate(marchTwelfFourPM2003, false), equalTo("{2003-03-13T02:00:00Z}")); Date marchTwelfTenAM2003 = new GregorianCalendar(2003, Calendar.MARCH, 12, 10, 0, 0).getTime(); assertThat(SVN.formatSVNDate(marchTwelfTenAM2003, false), equalTo("{2003-03-12T20:00:00Z}")); } @Test public void testSetProperty() throws ParseException { svn.setProperty("hasChanges?"); final List<Modification> noModifications = new ArrayList<Modification>(); svn.fillPropertiesIfNeeded(noModifications); assertEquals(null, svn.getProperties().get("hasChanges?")); final List<Modification> hasModifications = new ArrayList<Modification>(); hasModifications.add(createModification(SVN.getOutDateFormatter().parse("2003-08-02T10:01:13.349"), "lee", "bli", "333", "", "/trunk/playground/bbb", "deleted")); hasModifications.add(createModification(SVN.getOutDateFormatter().parse("2003-08-02T10:01:13.349"), "lee", "bli", "663", "", "/trunk/playground/bbb", "added")); svn.fillPropertiesIfNeeded(hasModifications); final Map<String, String> properties = svn.getProperties(); assertThat(properties.get("hasChanges?"), equalTo("true")); assertThat(properties.get("svnrevision"), equalTo("663")); } @Test public void testSetPropertyIgnoresPriorState() throws ParseException { testSetProperty(); svn.fillPropertiesIfNeeded(new ArrayList<Modification>()); assertFalse(svn.getProperties().containsKey("hasChanges?")); } @Test public void testSetPropertyOnDelete() throws ParseException { svn.setPropertyOnDelete("hasDeletions?"); final List<Modification> noModifications = new ArrayList<Modification>(); svn.fillPropertiesIfNeeded(noModifications); assertThat(svn.getProperties().get("hasDeletions?"), nullValue()); final List<Modification> noDeletions = new ArrayList<Modification>(); noDeletions.add(createModification(SVN.getOutDateFormatter().parse("2003-08-02T10:01:13.349"), "lee", "bli", "663", "", "/trunk/playground/bbb", "added")); svn.fillPropertiesIfNeeded(noDeletions); assertThat(svn.getProperties().get("hasDeletions?"), nullValue()); final List<Modification> hasDeletions = new ArrayList<Modification>(); hasDeletions.add(createModification(SVN.getOutDateFormatter().parse("2003-08-02T10:01:13.349"), "lee", "bli", "663", "", "/trunk/playground/aaa", "added")); hasDeletions.add(createModification(SVN.getOutDateFormatter().parse("2003-08-02T10:01:13.349"), "lee", "bli", "663", "", "/trunk/playground/bbb", "deleted")); svn.fillPropertiesIfNeeded(hasDeletions); assertThat(svn.getProperties().get("hasDeletions?"), equalTo("true")); } private static Modification createModification(Date date, String user, String comment, String revision, String folder, String file, String type) { Modification modification = new Modification("svn"); Modification.ModifiedFile modifiedFile = modification.createModifiedFile(file, folder); modifiedFile.action = type; modifiedFile.revision = revision; modification.modifiedTime = date; modification.userName = user; modification.comment = comment; modification.revision = revision; return modification; } @Test public void testParseInfo() throws JDOMException, IOException { String svnInfo = "<?xml version=\"1.0\"?>\n" + "<info>\n" + "<entry kind=\"dir\" path=\".\" revision=\"12345\">\n" + "<url>https://example.org/svn/playground-project</url>\n" + "<repository>\n" + "<root>https://example.org/svn</root>\n" + "<uuid>e6710e3c-8f79-4e94-9235-f6793330c154</uuid>\n" + "</repository>\n" + "<wc-info>\n" + "<schedule>normal</schedule>\n" + "</wc-info>\n" + "<commit revision=\"12345\">\n" + "<author>joebloggs</author>\n" + "<date>2007-07-11T08:31:58.089161Z</date>\n" + "</commit>\n" + "</entry>\n" + "</info>"; String currentRevision = SVN.SVNInfoXMLParser.parse(new StringReader(svnInfo)); assertThat(currentRevision, equalTo("12345")); } @Test public void testBuildInfoCommand() throws CruiseControlException { svn.setLocalWorkingCopy("."); String[] expectedCmd = { "svn", "info", "--xml" }; String[] actualCmd = svn.buildInfoCommand(null).getCommandline(); assertThat(actualCmd, equalTo(expectedCmd)); expectedCmd = new String[] { "svn", "info", "--xml", "foo" }; actualCmd = svn.buildInfoCommand("foo").getCommandline(); assertThat(actualCmd, equalTo(expectedCmd)); } private static String findLocalWorkingCopy() { File temp = buildTargetDirectory; for (; temp.isDirectory(); temp = temp.getParentFile()) { if (temp == null || temp.getParentFile() == null) { throw new AssertionError("Could not find a valid Subversion local working copy in any parent of the " + "build target directory " + buildTargetDirectory.getAbsolutePath()); } if (temp.list(dotSVNFilter).length == 1) { return temp.getAbsolutePath(); } } return "THIS WILL NEVER BE REACHED"; } }