001: /********************************************************************************
002: * CruiseControl, a Continuous Integration Toolkit
003: * Copyright (c) 2001, ThoughtWorks, Inc.
004: * 200 E. Randolph, 25th Floor
005: * Chicago, IL 60601 USA
006: * All rights reserved.
007: *
008: * Redistribution and use in source and binary forms, with or without
009: * modification, are permitted provided that the following conditions
010: * are met:
011: *
012: * + Redistributions of source code must retain the above copyright
013: * notice, this list of conditions and the following disclaimer.
014: *
015: * + Redistributions in binary form must reproduce the above
016: * copyright notice, this list of conditions and the following
017: * disclaimer in the documentation and/or other materials provided
018: * with the distribution.
019: *
020: * + Neither the name of ThoughtWorks, Inc., CruiseControl, nor the
021: * names of its contributors may be used to endorse or promote
022: * products derived from this software without specific prior
023: * written permission.
024: *
025: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
026: * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
027: * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
028: * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR
029: * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
030: * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
031: * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
032: * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
033: * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
034: * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
035: * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
036: ********************************************************************************/package net.sourceforge.cruisecontrol.sourcecontrols;
037:
038: import junit.framework.TestCase;
039: import net.sourceforge.cruisecontrol.CruiseControlException;
040: import net.sourceforge.cruisecontrol.Modification;
041: import org.jdom.JDOMException;
042:
043: import java.io.File;
044: import java.io.IOException;
045: import java.io.StringReader;
046: import java.text.ParseException;
047: import java.util.TimeZone;
048: import java.util.Date;
049: import java.util.GregorianCalendar;
050: import java.util.List;
051: import java.util.Calendar;
052: import java.util.Arrays;
053: import java.util.ArrayList;
054: import java.util.Map;
055:
056: /**
057: * @see <a href="http://subversion.tigris.org/">subversion.tigris.org</a>
058: * @author <a href="etienne.studer@canoo.com">Etienne Studer</a>
059: */
060: public class SVNTest extends TestCase {
061: private SVN svn;
062: private TimeZone originalTimeZone;
063:
064: protected void setUp() throws Exception {
065: svn = new SVN();
066: originalTimeZone = TimeZone.getDefault();
067: }
068:
069: protected void tearDown() throws Exception {
070: TimeZone.setDefault(originalTimeZone);
071: }
072:
073: public void testValidate() throws IOException {
074: try {
075: svn.validate();
076: fail("should throw an exception when no attributes are set");
077: } catch (CruiseControlException e) {
078: // expected
079: }
080:
081: svn.setRepositoryLocation("http://svn.collab.net/repos/svn");
082: try {
083: svn.validate();
084: } catch (CruiseControlException e) {
085: fail("should not throw an exception when at least the 'repositoryLocation' attribute "
086: + "is set");
087: }
088:
089: svn = new SVN();
090: svn.setLocalWorkingCopy("invalid directory");
091: try {
092: svn.validate();
093: fail("should throw an exception when an invalid 'localWorkingCopy' attribute is set");
094: } catch (CruiseControlException e) {
095: // expected
096: }
097:
098: File tempFile = File.createTempFile("temp", "txt");
099: tempFile.deleteOnExit();
100:
101: svn = new SVN();
102: svn.setLocalWorkingCopy(tempFile.getParent());
103: try {
104: svn.validate();
105: } catch (CruiseControlException e) {
106: fail("should not throw an exception when at least a valid 'localWorkingCopy' "
107: + "attribute is set");
108: }
109:
110: svn = new SVN();
111: svn.setLocalWorkingCopy(tempFile.getAbsolutePath());
112: try {
113: svn.validate();
114: fail("should throw an exception when 'localWorkingCopy' is file instead of directory.");
115: } catch (CruiseControlException e) {
116: // expected
117: }
118: }
119:
120: public void testBuildPropgetCommand() throws CruiseControlException {
121: svn.setLocalWorkingCopy(".");
122:
123: String[] expectedCmd = new String[] { "svn", "propget", "-R",
124: "--non-interactive", "svn:externals" };
125: String[] actualCmd = svn.buildPropgetCommand().getCommandline();
126: assertArraysEquals(expectedCmd, actualCmd);
127:
128: svn.setRepositoryLocation("http://svn.collab.net/repos/svn");
129:
130: expectedCmd = new String[] { "svn", "propget", "-R",
131: "--non-interactive", "svn:externals",
132: "http://svn.collab.net/repos/svn" };
133: actualCmd = svn.buildPropgetCommand().getCommandline();
134: assertArraysEquals(expectedCmd, actualCmd);
135: }
136:
137: public void testFormatSVNDateForWindows() {
138: GregorianCalendar cal = new GregorianCalendar(2007,
139: Calendar.JULY, 11, 12, 32, 45);
140: cal.setTimeZone(TimeZone.getTimeZone("GMT"));
141: Date date = cal.getTime();
142:
143: assertEquals("\"{2007-07-11T12:32:45Z}\"", SVN.formatSVNDate(
144: date, true));
145: }
146:
147: public void testFormatSVNDateForNonWindows() {
148: GregorianCalendar cal = new GregorianCalendar(2007,
149: Calendar.JULY, 11, 12, 32, 45);
150: cal.setTimeZone(TimeZone.getTimeZone("GMT"));
151: Date date = cal.getTime();
152:
153: assertEquals("{2007-07-11T12:32:45Z}", SVN.formatSVNDate(date,
154: false));
155: }
156:
157: public void testBuildHistoryCommand() throws CruiseControlException {
158: svn.setLocalWorkingCopy(".");
159:
160: Date checkTime = new Date();
161: long tenMinutes = 10 * 60 * 1000;
162: Date lastBuild = new Date(checkTime.getTime() - tenMinutes);
163:
164: String[] expectedCmd = new String[] {
165: "svn",
166: "log",
167: "--non-interactive",
168: "--xml",
169: "-v",
170: "-r",
171: SVN.formatSVNDate(lastBuild, false) + ":"
172: + SVN.formatSVNDate(checkTime, false) };
173: String[] actualCmd = svn.buildHistoryCommand(
174: SVN.formatSVNDate(lastBuild, false),
175: SVN.formatSVNDate(checkTime, false)).getCommandline();
176: assertArraysEquals(expectedCmd, actualCmd);
177:
178: expectedCmd = new String[] {
179: "svn",
180: "log",
181: "--non-interactive",
182: "--xml",
183: "-v",
184: "-r",
185: SVN.formatSVNDate(lastBuild, false) + ":"
186: + SVN.formatSVNDate(checkTime, false),
187: "external/path" };
188: actualCmd = svn.buildHistoryCommand(
189: SVN.formatSVNDate(lastBuild, false),
190: SVN.formatSVNDate(checkTime, false), "external/path")
191: .getCommandline();
192: assertArraysEquals(expectedCmd, actualCmd);
193:
194: svn.setRepositoryLocation("http://svn.collab.net/repos/svn");
195:
196: expectedCmd = new String[] {
197: "svn",
198: "log",
199: "--non-interactive",
200: "--xml",
201: "-v",
202: "-r",
203: SVN.formatSVNDate(lastBuild, false) + ":"
204: + SVN.formatSVNDate(checkTime, false),
205: "http://svn.collab.net/repos/svn" };
206: actualCmd = svn.buildHistoryCommand(
207: SVN.formatSVNDate(lastBuild, false),
208: SVN.formatSVNDate(checkTime, false)).getCommandline();
209: assertArraysEquals(expectedCmd, actualCmd);
210:
211: expectedCmd = new String[] {
212: "svn",
213: "log",
214: "--non-interactive",
215: "--xml",
216: "-v",
217: "-r",
218: SVN.formatSVNDate(lastBuild, false) + ":"
219: + SVN.formatSVNDate(checkTime, false),
220: "http://svn.collab.net/repos/external" };
221: actualCmd = svn.buildHistoryCommand(
222: SVN.formatSVNDate(lastBuild, false),
223: SVN.formatSVNDate(checkTime, false),
224: "http://svn.collab.net/repos/external")
225: .getCommandline();
226: assertArraysEquals(expectedCmd, actualCmd);
227:
228: svn.setUsername("lee");
229: svn.setPassword("secret");
230:
231: expectedCmd = new String[] {
232: "svn",
233: "log",
234: "--non-interactive",
235: "--xml",
236: "-v",
237: "-r",
238: SVN.formatSVNDate(lastBuild, false) + ":"
239: + SVN.formatSVNDate(checkTime, false),
240: "--username", "lee", "--password", "secret",
241: "http://svn.collab.net/repos/svn" };
242: actualCmd = svn.buildHistoryCommand(
243: SVN.formatSVNDate(lastBuild, false),
244: SVN.formatSVNDate(checkTime, false)).getCommandline();
245: assertArraysEquals(expectedCmd, actualCmd);
246: }
247:
248: public void testParseModifications() throws JDOMException,
249: ParseException, IOException {
250: String svnLog = "<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>\n"
251: + "<log>\n"
252: + " <logentry revision=\"663\">\n"
253: + " <author>lee</author>\n"
254: + " <date>2003-04-30T10:01:42.349105Z</date>\n"
255: + " <paths>\n"
256: + " <path action=\"A\">/trunk/playground/aaa/ccc</path>\n"
257: + " <path action=\"M\">/trunk/playground/aaa/ccc/d.txt</path>\n"
258: + " <path action=\"A\">/trunk/playground/bbb</path>\n"
259: + " </paths>\n"
260: + " <msg>bli</msg>\n"
261: + " </logentry>\n"
262: + " <logentry revision=\"664\">\n"
263: + " <author>etienne</author>\n"
264: + " <date>2003-04-30T10:03:14.100900Z</date>\n"
265: + " <paths>\n"
266: + " <path action=\"A\">/trunk/playground/aaa/f.txt</path>\n"
267: + " </paths>\n"
268: + " <msg>bla</msg>\n"
269: + " </logentry>\n"
270: + " <logentry revision=\"665\">\n"
271: + " <author>martin</author>\n"
272: + " <date>2003-04-30T10:04:48.050619Z</date>\n"
273: + " <paths>\n"
274: + " <path action=\"D\">/trunk/playground/bbb</path>\n"
275: + " </paths>\n"
276: + " <msg>blo</msg>\n"
277: + " </logentry>\n" + "</log>";
278:
279: Modification[] modifications = SVN.SVNLogXMLParser
280: .parse(new StringReader(svnLog));
281: assertEquals(5, modifications.length);
282:
283: Modification modification = createModification(
284: SVN.getOutDateFormatter().parse(
285: "2003-04-30T10:01:42.349"), "lee", "bli",
286: "663", "", "/trunk/playground/aaa/ccc", "added");
287: assertEquals(modification, modifications[0]);
288:
289: modification = createModification(SVN.getOutDateFormatter()
290: .parse("2003-04-30T10:01:42.349"), "lee", "bli", "663",
291: "", "/trunk/playground/aaa/ccc/d.txt", "modified");
292: assertEquals(modification, modifications[1]);
293:
294: modification = createModification(SVN.getOutDateFormatter()
295: .parse("2003-04-30T10:01:42.349"), "lee", "bli", "663",
296: "", "/trunk/playground/bbb", "added");
297: assertEquals(modification, modifications[2]);
298:
299: modification = createModification(SVN.getOutDateFormatter()
300: .parse("2003-04-30T10:03:14.100"), "etienne", "bla",
301: "664", "", "/trunk/playground/aaa/f.txt", "added");
302: assertEquals(modification, modifications[3]);
303:
304: modification = createModification(SVN.getOutDateFormatter()
305: .parse("2003-04-30T10:04:48.050"), "martin", "blo",
306: "665", "", "/trunk/playground/bbb", "deleted");
307: assertEquals(modification, modifications[4]);
308:
309: modifications = SVN.SVNLogXMLParser.parse(new StringReader(
310: svnLog), "external/path");
311: assertEquals(5, modifications.length);
312:
313: modification = createModification(SVN.getOutDateFormatter()
314: .parse("2003-04-30T10:01:42.349"), "lee", "bli", "663",
315: "", "/external/path:/trunk/playground/aaa/ccc", "added");
316: assertEquals(modification, modifications[0]);
317:
318: modifications = SVN.SVNLogXMLParser.parse(new StringReader(
319: svnLog), null);
320: assertEquals(5, modifications.length);
321: modification = createModification(SVN.getOutDateFormatter()
322: .parse("2003-04-30T10:01:42.349"), "lee", "bli", "663",
323: "", "/trunk/playground/aaa/ccc/d.txt", "modified");
324: assertEquals(modification, modifications[1]);
325: }
326:
327: public void testConvertDateIllegalArgument() {
328: try {
329: Date d = SVN.SVNLogXMLParser
330: .convertDate("2003-04-30T10:01:42.349105");
331: fail("expected ParseException for date without Z but got "
332: + d);
333: } catch (ParseException e) {
334: assertTrue(true);
335: }
336: }
337:
338: public void testParseEmptyModifications() throws JDOMException,
339: ParseException, IOException {
340: String svnLog = "<?xml version=\"1.0\" encoding = \"ISO-8859-1\"?>\n "
341: + "<log>\n" + "</log>";
342:
343: Modification[] modifications = SVN.SVNLogXMLParser
344: .parse(new StringReader(svnLog));
345: assertEquals(0, modifications.length);
346: }
347:
348: public void testChangeWithoutReadAccessToChangedFileShouldResultInNoModificationReported()
349: throws ParseException, JDOMException, IOException {
350: String svnLog = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
351: + "<log>\n" + " <logentry revision=\"1234\">\n"
352: + " <msg></msg>\n" + " </logentry>\n"
353: + "</log>";
354: Modification[] modifications = SVN.SVNLogXMLParser
355: .parse(new StringReader(svnLog));
356: assertEquals(0, modifications.length);
357: }
358:
359: public void testParseAndFilter() throws ParseException,
360: JDOMException, IOException {
361: String svnLog = "<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>\n"
362: + "<log>\n"
363: + " <logentry revision=\"663\">\n"
364: + " <author>lee</author>\n"
365: + " <date>2003-08-02T10:01:13.349105Z</date>\n"
366: + " <paths>\n"
367: + " <path action=\"A\">/trunk/playground/bbb</path>\n"
368: + " </paths>\n"
369: + " <msg>bli</msg>\n"
370: + " </logentry>\n"
371: + " <logentry revision=\"664\">\n"
372: + " <author>etienne</author>\n"
373: + " <date>2003-07-29T17:45:12.100900Z</date>\n"
374: + " <paths>\n"
375: + " <path action=\"A\">/trunk/playground/aaa/f.txt</path>\n"
376: + " </paths>\n"
377: + " <msg>bla</msg>\n"
378: + " </logentry>\n"
379: + " <logentry revision=\"665\">\n"
380: + " <author>martin</author>\n"
381: + " <date>2003-07-29T18:15:11.100900Z</date>\n"
382: + " <paths>\n"
383: + " <path action=\"D\">/trunk/playground/ccc</path>\n"
384: + " </paths>\n"
385: + " <msg>blo</msg>\n"
386: + " </logentry>\n" + "</log>";
387:
388: TimeZone.setDefault(TimeZone.getTimeZone("GMT+0:00"));
389: Date julyTwentynineSixPM2003 = new GregorianCalendar(2003,
390: Calendar.JULY, 29, 18, 0, 0).getTime();
391:
392: List modifications = SVN.SVNLogXMLParser.parseAndFilter(
393: new StringReader(svnLog), julyTwentynineSixPM2003);
394: assertEquals(2, modifications.size());
395:
396: Modification modification = createModification(
397: SVN.getOutDateFormatter().parse(
398: "2003-08-02T10:01:13.349"), "lee", "bli",
399: "663", "", "/trunk/playground/bbb", "added");
400: assertEquals(modification, modifications.get(0));
401:
402: modification = createModification(SVN.getOutDateFormatter()
403: .parse("2003-07-29T18:15:11.100"), "martin", "blo",
404: "665", "", "/trunk/playground/ccc", "deleted");
405: assertEquals(modification, modifications.get(1));
406:
407: Date julyTwentyeightZeroPM2003 = new GregorianCalendar(2003,
408: Calendar.JULY, 28, 0, 0, 0).getTime();
409:
410: modifications = SVN.SVNLogXMLParser.parseAndFilter(
411: new StringReader(svnLog), julyTwentyeightZeroPM2003);
412: assertEquals(3, modifications.size());
413:
414: modifications = SVN.SVNLogXMLParser.parseAndFilter(
415: new StringReader(svnLog), julyTwentynineSixPM2003,
416: "external/path");
417: assertEquals(2, modifications.size());
418:
419: modification = createModification(SVN.getOutDateFormatter()
420: .parse("2003-08-02T10:01:13.349"), "lee", "bli", "663",
421: "", "/external/path:/trunk/playground/bbb", "added");
422: assertEquals(modification, modifications.get(0));
423: }
424:
425: public void testFormatDatesForSvnLog() {
426: TimeZone.setDefault(TimeZone.getTimeZone("GMT+10:00"));
427:
428: Date maySeventeenSixPM2001 = new GregorianCalendar(2001,
429: Calendar.MAY, 17, 18, 0, 0).getTime();
430: assertEquals("{2001-05-17T08:00:00Z}", SVN.formatSVNDate(
431: maySeventeenSixPM2001, false));
432:
433: Date maySeventeenEightAM2001 = new GregorianCalendar(2001,
434: Calendar.MAY, 17, 8, 0, 0).getTime();
435: assertEquals("{2001-05-16T22:00:00Z}", SVN.formatSVNDate(
436: maySeventeenEightAM2001, false));
437:
438: TimeZone.setDefault(TimeZone.getTimeZone("GMT-10:00"));
439:
440: Date marchTwelfFourPM2003 = new GregorianCalendar(2003,
441: Calendar.MARCH, 12, 16, 0, 0).getTime();
442: assertEquals("{2003-03-13T02:00:00Z}", SVN.formatSVNDate(
443: marchTwelfFourPM2003, false));
444:
445: Date marchTwelfTenAM2003 = new GregorianCalendar(2003,
446: Calendar.MARCH, 12, 10, 0, 0).getTime();
447: assertEquals("{2003-03-12T20:00:00Z}", SVN.formatSVNDate(
448: marchTwelfTenAM2003, false));
449: }
450:
451: public void testSetProperty() throws ParseException {
452: svn.setProperty("hasChanges?");
453:
454: List noModifications = new ArrayList();
455: svn.fillPropertiesIfNeeded(noModifications);
456: assertEquals(null, svn.getProperties().get("hasChanges?"));
457:
458: List hasModifications = new ArrayList();
459: hasModifications.add(createModification(
460: SVN.getOutDateFormatter().parse(
461: "2003-08-02T10:01:13.349"), "lee", "bli",
462: "333", "", "/trunk/playground/bbb", "deleted"));
463: hasModifications.add(createModification(
464: SVN.getOutDateFormatter().parse(
465: "2003-08-02T10:01:13.349"), "lee", "bli",
466: "663", "", "/trunk/playground/bbb", "added"));
467: svn.fillPropertiesIfNeeded(hasModifications);
468: Map properties = svn.getProperties();
469: assertEquals("true", properties.get("hasChanges?"));
470: assertEquals("663", properties.get("svnrevision"));
471: }
472:
473: public void testSetPropertyIgnoresPriorState()
474: throws ParseException {
475: testSetProperty();
476: svn.fillPropertiesIfNeeded(new ArrayList());
477: assertFalse(svn.getProperties().containsKey("hasChanges?"));
478:
479: }
480:
481: public void testSetPropertyOnDelete() throws ParseException {
482: svn.setPropertyOnDelete("hasDeletions?");
483:
484: List noModifications = new ArrayList();
485: svn.fillPropertiesIfNeeded(noModifications);
486: assertEquals(null, svn.getProperties().get("hasDeletions?"));
487:
488: List noDeletions = new ArrayList();
489: noDeletions.add(createModification(SVN.getOutDateFormatter()
490: .parse("2003-08-02T10:01:13.349"), "lee", "bli", "663",
491: "", "/trunk/playground/bbb", "added"));
492: svn.fillPropertiesIfNeeded(noDeletions);
493: assertEquals(null, svn.getProperties().get("hasDeletions?"));
494:
495: List hasDeletions = new ArrayList();
496: hasDeletions.add(createModification(SVN.getOutDateFormatter()
497: .parse("2003-08-02T10:01:13.349"), "lee", "bli", "663",
498: "", "/trunk/playground/aaa", "added"));
499: hasDeletions.add(createModification(SVN.getOutDateFormatter()
500: .parse("2003-08-02T10:01:13.349"), "lee", "bli", "663",
501: "", "/trunk/playground/bbb", "deleted"));
502: svn.fillPropertiesIfNeeded(hasDeletions);
503: assertEquals("true", svn.getProperties().get("hasDeletions?"));
504: }
505:
506: private static Modification createModification(Date date,
507: String user, String comment, String revision,
508: String folder, String file, String type) {
509: Modification modification = new Modification("svn");
510: Modification.ModifiedFile modifiedFile = modification
511: .createModifiedFile(file, folder);
512: modifiedFile.action = type;
513: modifiedFile.revision = revision;
514:
515: modification.modifiedTime = date;
516: modification.userName = user;
517: modification.comment = comment;
518: modification.revision = revision;
519: return modification;
520: }
521:
522: private static void assertArraysEquals(Object[] expected,
523: Object[] actual) {
524: assertEquals("array lengths mismatch! was "
525: + Arrays.asList(actual), expected.length, actual.length);
526: for (int i = 0; i < expected.length; i++) {
527: assertEquals(expected[i], actual[i]);
528: }
529: }
530:
531: public void testParseInfo() throws JDOMException, IOException {
532: String svnInfo = "<?xml version=\"1.0\"?>\n"
533: + "<info>\n"
534: + "<entry kind=\"dir\" path=\".\" revision=\"12345\">\n"
535: + "<url>https://example.org/svn/playground-project</url>\n"
536: + "<repository>\n"
537: + "<root>https://example.org/svn</root>\n"
538: + "<uuid>e6710e3c-8f79-4e94-9235-f6793330c154</uuid>\n"
539: + "</repository>\n" + "<wc-info>\n"
540: + "<schedule>normal</schedule>\n" + "</wc-info>\n"
541: + "<commit revision=\"12345\">\n"
542: + "<author>joebloggs</author>\n"
543: + "<date>2007-07-11T08:31:58.089161Z</date>\n"
544: + "</commit>\n" + "</entry>\n" + "</info>";
545: String currentRevision = SVN.SVNInfoXMLParser
546: .parse(new StringReader(svnInfo));
547: assertEquals("12345", currentRevision);
548: }
549:
550: public void testBuildInfoCommand() throws CruiseControlException {
551: svn.setLocalWorkingCopy(".");
552: String[] expectedCmd = { "svn", "info", "--xml" };
553: String[] actualCmd = svn.buildInfoCommand(null)
554: .getCommandline();
555: assertArraysEquals(expectedCmd, actualCmd);
556:
557: expectedCmd = new String[] { "svn", "info", "--xml", "foo" };
558: actualCmd = svn.buildInfoCommand("foo").getCommandline();
559: assertArraysEquals(expectedCmd, actualCmd);
560: }
561: }
|