001: /**
002: * @copyright
003: * ====================================================================
004: * Copyright (c) 2003-2005 CollabNet. All rights reserved.
005: *
006: * This software is licensed as described in the file COPYING, which
007: * you should have received as part of this distribution. The terms
008: * are also available at http://subversion.tigris.org/license-1.html.
009: * If newer versions of this license are posted there, you may use a
010: * newer version instead, at your option.
011: *
012: * This software consists of voluntary contributions made by many
013: * individuals. For exact contribution history, see the revision
014: * history and logs, available at http://subversion.tigris.org/.
015: * ====================================================================
016: * @endcopyright
017: */package org.tigris.subversion.javahl.tests;
018:
019: import junit.framework.TestCase;
020: import junit.framework.TestSuite;
021: import org.tigris.subversion.javahl.*;
022:
023: import java.io.File;
024: import java.io.FileInputStream;
025: import java.io.FileOutputStream;
026: import java.io.IOException;
027: import java.util.HashMap;
028: import java.util.Iterator;
029: import java.util.Map;
030:
031: /**
032: * common base class for the javahl binding tests
033: */
034: public class SVNTests extends TestCase {
035: /**
036: * our admin object, mostly used for creating,dumping and loading
037: * repositories
038: */
039: protected SVNAdmin admin;
040: /**
041: * the subversion client, what we want to test.
042: */
043: protected SVNClientInterface client;
044: /**
045: * the root directory. All other files and directories will created in
046: * here
047: */
048: protected File rootDir;
049: /**
050: * the base name of the test. Together with the testCounter this will make
051: * up the directory name of the test.
052: */
053: protected String testBaseName;
054: /**
055: * this counter will be incremented for every test in one suite (test class)
056: */
057: protected static int testCounter;
058: /**
059: * the file in which the sample repository has been dumped.
060: */
061: protected File greekDump;
062: /**
063: * the directory of the sample repository.
064: */
065: protected File greekRepos;
066: /**
067: * the initial working copy of the sample repository.
068: */
069: protected WC greekWC;
070: /**
071: * the directory "svn-test-work/local_tmp" in the rootDir. This
072: * will be used for the sample repository and its dumpfile and for
073: * the config directory
074: */
075: protected File localTmp;
076: /**
077: * the directory "repositories" in the rootDir. All test repositories will
078: * be created here.
079: */
080: protected File repositories;
081: /**
082: * the directory "working_copies" in the rootDir. All test working copies
083: * will be created here.
084: */
085: protected File workingCopies;
086: /**
087: * the directory "config" in the localTmp. It will be used as the
088: * configuration directory for all the tests.
089: */
090: protected File conf;
091: /**
092: * standard log message. Used for all commits.
093: */
094: protected String logMessage = "Log Message";
095: /**
096: * the map of all items expected to be received by the callback for the
097: * log message. After each commit, this will be cleared
098: */
099: protected Map expectedCommitItems;
100:
101: /**
102: * common root directory for all tests. Can be set by the command line or
103: * by the system property "test.rootdir". If not set, the current directory
104: * of this process is used
105: */
106: protected static String rootDirectoryName;
107: /**
108: * common root URL for all tests. Can be set by the command line or by the
109: * system property "test.rooturl". If not set, the file url of the
110: * rootDirectoryName is used.
111: */
112: protected static String rootUrl;
113:
114: /**
115: * retrieve the root directory and the root url from the command line
116: * arguments
117: * @param args command line arguments
118: */
119: protected static void processArgs(String[] args) {
120: if (args == null)
121: return;
122: for (int i = 0; i < args.length; i++) {
123: String arg = args[i];
124: if ("-d".equals(arg)) {
125: if (i + 1 < args.length) {
126: rootDirectoryName = args[++i];
127: }
128: }
129: if ("-u".equals(arg)) {
130: if (i + 1 < args.length) {
131: rootUrl = args[++i];
132: }
133: }
134: }
135: }
136:
137: /**
138: * Main method, will call all tests of all test classes
139: * @param args command line arguments
140: */
141: public static void main(String[] args) {
142: processArgs(args);
143: junit.textui.TestRunner.run(suite());
144: }
145:
146: /**
147: * build a test suite with all test of all test classes known
148: * @return complete test suite
149: */
150: public static TestSuite suite() {
151: TestSuite suite = new TestSuite();
152: suite.addTestSuite(BasicTests.class);
153: suite.addTestSuite(SVNAdminTests.class);
154: return suite;
155: }
156:
157: /**
158: * Initialize one test object
159: */
160: protected SVNTests() {
161: // if not already set, get a usefull value for rootDir
162: if (rootDirectoryName == null)
163: rootDirectoryName = System.getProperty("test.rootdir");
164: if (rootDirectoryName == null)
165: rootDirectoryName = System.getProperty("user.dir");
166: rootDir = new File(rootDirectoryName);
167:
168: // if not alread set, get a usefull value for root url
169: if (rootUrl == null)
170: rootUrl = System.getProperty("test.rooturl");
171: if (rootUrl == null) {
172: // if no root url, set build a file url
173: rootUrl = rootDir.toURI().toString();
174: // java may have a different view about the number of '/' to follow
175: // "file:" than subversion. We convert to the subversion view.
176: if (rootUrl.startsWith("file:///"))
177: ; // this is the form subversion needs
178: else if (rootUrl.startsWith("file://"))
179: rootUrl = rootUrl.replaceFirst("file://", "file:///");
180: else if (rootUrl.startsWith("file:/"))
181: rootUrl = rootUrl.replaceFirst("file:/", "file:///");
182: }
183: }
184:
185: /**
186: * Standard initialization of one test
187: * @throws Exception
188: */
189: protected void setUp() throws Exception {
190: super .setUp();
191:
192: // create a clean directory for the config files and the sample
193: // repository
194: //
195: // ### The path is now "svn-test-work/local_tmp", however, I'm
196: // ### not sure how to update this code for that.
197: localTmp = new File(rootDir, "local_tmp");
198: if (localTmp.exists())
199: removeDirectoryWithContent(localTmp);
200: localTmp.mkdir();
201: conf = new File(localTmp, "config");
202: conf.mkdir();
203:
204: // create and configure the needed subversion objects
205: admin = new SVNAdmin();
206: client = new SVNClientSynchronized();
207: client.notification2(new MyNotifier());
208: client.commitMessageHandler(new MyCommitMessage());
209: client.username("jrandom");
210: client.password("rayjandom");
211: client.setConfigDirectory(conf.getAbsolutePath());
212: expectedCommitItems = new HashMap();
213:
214: // build and dump the sample repository
215: File greekFiles = buildGreekFiles();
216: greekRepos = new File(localTmp, "repos");
217: greekDump = new File(localTmp, "greek_dump");
218: admin.create(greekRepos.getAbsolutePath(), true, false, null,
219: SVNAdmin.BDB);
220: addExpectedCommitItem(greekFiles.getAbsolutePath(), null, null,
221: NodeKind.none, CommitItemStateFlags.Add);
222: client.doImport(greekFiles.getAbsolutePath(),
223: makeReposUrl(greekRepos), null, true);
224: admin.dump(greekRepos.getAbsolutePath(), new FileOutputer(
225: greekDump), new IgnoreOutputer(), null, null, false);
226:
227: // create the directory for the repositories and the working copies
228: //
229: // ### The paths are now "svn-test-work/repositories" and
230: // ### "svn-test-work/repositories". However, I'm not sure
231: // ### how to update this code for that.
232: repositories = new File(rootDir, "repositories");
233: repositories.mkdirs();
234: workingCopies = new File(rootDir, "working_copies");
235: workingCopies.mkdirs();
236: }
237:
238: /**
239: * build a sample directory with test files to be used as import for
240: * the sample repository. Create also the master working copy test set.
241: * @return the sample repository
242: * @throws IOException
243: */
244: private File buildGreekFiles() throws IOException {
245: File greekFiles = new File(localTmp, "greek_files");
246: greekFiles.mkdir();
247: greekWC = new WC();
248: greekWC.addItem("", null);
249: greekWC.addItem("iota", "This is the file 'iota'.");
250: greekWC.addItem("A", null);
251: greekWC.addItem("A/mu", "This is the file 'mu'.");
252: greekWC.addItem("A/B", null);
253: greekWC.addItem("A/B/lambda", "This is the file 'lambda'.");
254: greekWC.addItem("A/B/E", null);
255: greekWC.addItem("A/B/E/alpha", "This is the file 'alpha'.");
256: greekWC.addItem("A/B/E/beta", "This is the file 'beta'.");
257: greekWC.addItem("A/B/F", null);
258: greekWC.addItem("A/C", null);
259: greekWC.addItem("A/D", null);
260: greekWC.addItem("A/D/gamma", "This is the file 'gamma'.");
261: greekWC.addItem("A/D/H", null);
262: greekWC.addItem("A/D/H/chi", "This is the file 'chi'.");
263: greekWC.addItem("A/D/H/psi", "This is the file 'psi'.");
264: greekWC.addItem("A/D/H/omega", "This is the file 'omega'.");
265: greekWC.addItem("A/D/G", null);
266: greekWC.addItem("A/D/G/pi", "This is the file 'pi'.");
267: greekWC.addItem("A/D/G/rho", "This is the file 'rho'.");
268: greekWC.addItem("A/D/G/tau", "This is the file 'tau'.");
269: greekWC.materialize(greekFiles);
270: return greekFiles;
271: }
272:
273: /**
274: * Remove a directory with all files and directories it may contain.
275: * @param localTmp
276: */
277: protected void removeDirectoryWithContent(File localTmp) {
278: if (localTmp.isDirectory()) {
279: File[] content = localTmp.listFiles();
280: for (int i = 0; i < content.length; i++)
281: removeDirectoryWithContent(content[i]);
282: }
283: localTmp.delete();
284: }
285:
286: /**
287: * cleanup after one test
288: * @throws Exception
289: */
290: protected void tearDown() throws Exception {
291: // take care of our subversion objects.
292: admin.dispose();
293: client.dispose();
294: // remove the temporary directory
295: removeDirectoryWithContent(localTmp);
296: super .tearDown();
297: }
298:
299: /**
300: * Create the url for the repository to be used for the tests.
301: * @param file the directory of the repository
302: * @return the URL for the repository
303: */
304: protected String makeReposUrl(File file) {
305: // split the common part of the root directory
306: String path = file.getAbsolutePath().substring(
307: rootDirectoryName.length() + 1);
308: // append to the root url
309: return rootUrl + path.replace(File.separatorChar, '/');
310: }
311:
312: /**
313: * add another commit item expected during the callback for the log message.
314: * @param workingCopyPath the path of the of the working
315: * @param baseUrl the url for the repository
316: * @param itemPath the path of the item relative the working copy
317: * @param nodeKind expected node kind (dir or file or none)
318: * @param stateFlags expected commit state flags
319: * (see CommitItemStateFlags)
320: */
321: protected void addExpectedCommitItem(String workingCopyPath,
322: String baseUrl, String itemPath, int nodeKind,
323: int stateFlags) {
324: //determine the full working copy path and the full url of the item.
325: String path = null;
326: if (workingCopyPath != null)
327: if (itemPath != null)
328: path = workingCopyPath.replace(File.separatorChar, '/')
329: + '/' + itemPath;
330: else
331: path = workingCopyPath.replace(File.separatorChar, '/');
332: String url = null;
333: if (baseUrl != null)
334: if (itemPath != null)
335: url = baseUrl + '/' + itemPath;
336: else
337: url = baseUrl;
338:
339: // the key of the item is either the url or the path (if no url)
340: String key;
341: if (url != null)
342: key = url;
343: else
344: key = path;
345: expectedCommitItems.put(key, new MyCommitItem(path, nodeKind,
346: stateFlags, url));
347: }
348:
349: /**
350: * Intended to be called as part of test method execution
351: * (post-{@link #setUp()}). Calls <code>fail()</code> if the
352: * directory name cannot be determined.
353: *
354: * @return The name of the working copy administrative directory.
355: * @since 1.3
356: */
357: protected String getAdminDirectoryName() {
358: String admDirName = null;
359: if (this .client != null) {
360: admDirName = client.getAdminDirectoryName();
361: }
362: if (admDirName == null) {
363: fail("Unable to determine the WC admin directory name");
364: }
365: return admDirName;
366: }
367:
368: /**
369: * internal class which implements the OutputInterface to write the data
370: * to a file.
371: */
372: public class FileOutputer implements OutputInterface {
373: /**
374: * the output file stream
375: */
376: FileOutputStream myStream;
377:
378: /**
379: * create new object
380: * @param outputName the file to write the data to
381: * @throws IOException
382: */
383: public FileOutputer(File outputName) throws IOException {
384: myStream = new FileOutputStream(outputName);
385: }
386:
387: /**
388: * write the bytes in data to java
389: * @param data the data to be writtem
390: * @throws IOException throw in case of problems.
391: */
392: public int write(byte[] data) throws IOException {
393: myStream.write(data);
394: return data.length;
395: }
396:
397: /**
398: * close the output
399: * @throws IOException throw in case of problems.
400: */
401: public void close() throws IOException {
402: myStream.close();
403: }
404: }
405:
406: /**
407: * internal class implements the OutputInterface, but ignores the data
408: */
409: public class IgnoreOutputer implements OutputInterface {
410: /**
411: * write the bytes in data to java
412: * @param data the data to be writtem
413: * @throws IOException throw in case of problems.
414: */
415: public int write(byte[] data) throws IOException {
416: return data.length;
417: }
418:
419: /**
420: * close the output
421: * @throws IOException throw in case of problems.
422: */
423: public void close() throws IOException {
424: }
425: }
426:
427: /**
428: * internal class which implements the InputInterface to read the data
429: * from a file.
430: */
431: public class FileInputer implements InputInterface {
432: /**
433: * input file stream
434: */
435: FileInputStream myStream;
436:
437: /**
438: * create a new object
439: * @param inputName the file from which the data is read
440: * @throws IOException
441: */
442: public FileInputer(File inputName) throws IOException {
443: myStream = new FileInputStream(inputName);
444: }
445:
446: /**
447: * read the number of data.length bytes from input.
448: * @param data array to store the read bytes.
449: * @throws IOException throw in case of problems.
450: */
451: public int read(byte[] data) throws IOException {
452: return myStream.read(data);
453: }
454:
455: /**
456: * close the input
457: * @throws IOException throw in case of problems.
458: */
459: public void close() throws IOException {
460: myStream.close();
461: }
462: }
463:
464: /**
465: * this internal class represent the repository and the working copy for
466: * one test.
467: */
468: protected class OneTest {
469: /**
470: * the file name of repository (used by SVNAdmin)
471: */
472: protected File repository;
473: /**
474: * the file name of the working copy directory
475: */
476: protected File workingCopy;
477: /**
478: * the url of the repository (used by SVNClient)
479: */
480: protected String url;
481: /**
482: * the expected layout of the working copy after the next subversion
483: * command
484: */
485: protected WC wc;
486:
487: /**
488: * build a new test setup with a new repository, a new working and a
489: * new expected working layout
490: * @throws Exception
491: */
492: protected OneTest() throws Exception {
493: String testName = testBaseName + ++testCounter;
494: wc = greekWC.copy();
495: repository = createStartRepository(testName);
496: url = makeReposUrl(repository);
497: workingCopy = createStartWorkingCopy(repository, testName);
498: }
499:
500: /**
501: * Copy the working copy and the expected working copy layout for tests
502: * which need multiple working copy
503: * @param append append to the working copy name of the original
504: * @return second test object.
505: * @throws Exception
506: */
507: protected OneTest copy(String append) throws Exception {
508: return new OneTest(this , append);
509: }
510:
511: /**
512: * constructor for create a copy
513: * @param orig original test
514: * @param append append this to the directory name of the original
515: * test
516: * @throws Exception
517: */
518: private OneTest(OneTest orig, String append) throws Exception {
519: String testName = testBaseName + testCounter + append;
520: repository = orig.getRepository();
521: url = orig.getUrl();
522: wc = orig.wc.copy();
523: workingCopy = createStartWorkingCopy(repository, testName);
524: }
525:
526: /**
527: * Return the directory of the repository
528: * @return the repository directory name
529: */
530: public File getRepository() {
531: return repository;
532: }
533:
534: /**
535: * Return the name of the directory of the repository
536: * @return the name of repository directory
537: */
538: public String getRepositoryPath() {
539: return repository.getAbsolutePath();
540: }
541:
542: /**
543: * Return the working copy directory
544: * @return the working copy directory
545: */
546: public File getWorkingCopy() {
547: return workingCopy;
548: }
549:
550: /**
551: * Return the working copy directory name
552: * @return the name of the working copy directory
553: */
554: public String getWCPath() {
555: return workingCopy.getAbsolutePath();
556: }
557:
558: /**
559: * Returns the url of repository
560: * @return the url
561: */
562: public String getUrl() {
563: return url;
564: }
565:
566: /**
567: * Returns the expected working copy content
568: * @return the expected working copy content
569: */
570: public WC getWc() {
571: return wc;
572: }
573:
574: /**
575: * Create the repository for the beginning of the test
576: * @param testName the name of the test
577: * @return the repository directory
578: * @throws Exception
579: */
580: protected File createStartRepository(String testName)
581: throws Exception {
582: // build a clean repository directory
583: File repos = new File(repositories, testName);
584: removeDirectoryWithContent(repos);
585: // create and load the repository from the default repository dump
586: admin.create(repos.getAbsolutePath(), true, false, conf
587: .getAbsolutePath(), SVNAdmin.BDB);
588: admin.load(repos.getAbsolutePath(), new FileInputer(
589: greekDump), new IgnoreOutputer(), false, false,
590: null);
591: return repos;
592: }
593:
594: /**
595: * Create the working copy for the beginning of the test
596: * @param repos the repository directory
597: * @param testName the name of the test
598: * @return the directory of the working copy
599: * @throws Exception
600: */
601: protected File createStartWorkingCopy(File repos,
602: String testName) throws Exception {
603: // build a clean working directory
604: String uri = makeReposUrl(repos);
605: workingCopy = new File(workingCopies, testName);
606: removeDirectoryWithContent(workingCopy);
607: // checkout the repository
608: client.checkout(uri, workingCopy.getAbsolutePath(), null,
609: true);
610: // sanity check the working with its expected status
611: checkStatus();
612: return workingCopy;
613: }
614:
615: /**
616: * Check if the working copy has the expected status
617: * @throws Exception
618: */
619: public void checkStatus() throws Exception {
620: Status[] states = client.status(workingCopy
621: .getAbsolutePath(), true, false, true, true);
622: wc.check(states, workingCopy.getAbsolutePath());
623: }
624: }
625:
626: /**
627: * internal class to receive the request for the log messages to check if
628: * the expected commit items are presented
629: */
630: class MyCommitMessage implements CommitMessage {
631: /**
632: * Retrieve a commit message from the user based on the items to be commited
633: * @param elementsToBeCommited Array of elements to be commited
634: * @return the log message of the commit.
635: */
636: public String getLogMessage(CommitItem[] elementsToBeCommited) {
637: // check all received CommitItems are expected as received
638: for (int i = 0; i < elementsToBeCommited.length; i++) {
639: CommitItem commitItem = elementsToBeCommited[i];
640: // since imports do not provide a url, the key is either url or
641: // path
642: String key;
643: if (commitItem.getUrl() != null)
644: key = commitItem.getUrl();
645: else
646: key = commitItem.getPath();
647:
648: MyCommitItem myItem = (MyCommitItem) expectedCommitItems
649: .get(key);
650: // check commit item is expected and has the expected data
651: assertNotNull("commit item for " + key
652: + " not expected", myItem);
653: myItem.test(commitItem, key);
654: }
655:
656: // all remaining expected commit items are missing
657: for (Iterator iterator = expectedCommitItems.keySet()
658: .iterator(); iterator.hasNext();) {
659: String str = (String) iterator.next();
660: fail("commit item for " + str + " not found");
661: }
662: return logMessage;
663: }
664: }
665:
666: /**
667: * internal class to describe an expected commit item
668: */
669: class MyCommitItem {
670: /**
671: * the path of the item
672: */
673: String myPath;
674: /**
675: * the kind of node (file, directory or none, see NodeKind)
676: */
677: int myNodeKind;
678: /**
679: * the reason why this item is commited (see CommitItemStateFlag)
680: */
681: int myStateFlags;
682: /**
683: * the url of the item
684: */
685: String myUrl;
686:
687: /**
688: * build one expected commit item
689: * @param path the expected path
690: * @param nodeKind the expected node kind
691: * @param stateFlags the expected state flags
692: * @param url the expected url
693: */
694: private MyCommitItem(String path, int nodeKind, int stateFlags,
695: String url) {
696: myPath = path;
697: myNodeKind = nodeKind;
698: myStateFlags = stateFlags;
699: myUrl = url;
700: }
701:
702: /**
703: * Check if the commit item has the expected data
704: * @param ci the commit item to check
705: * @param key the key of the item
706: */
707: private void test(CommitItem ci, String key) {
708: assertEquals("commit item path", ci.getPath(), myPath);
709: assertEquals("commit item node kind", ci.getNodeKind(),
710: myNodeKind);
711: assertEquals("commit item state flags", ci.getStateFlags(),
712: myStateFlags);
713: assertEquals("commit item url", ci.getUrl(), myUrl);
714: // after the test, remove the item from the expected map
715: expectedCommitItems.remove(key);
716: }
717: }
718:
719: class MyNotifier implements Notify2 {
720:
721: /**
722: * Handler for Subversion notifications.
723: * <p/>
724: * Override this function to allow Subversion to send notifications
725: *
726: * @param info everything to know about this event
727: */
728: public void onNotify(NotifyInformation info) {
729: }
730: }
731: }
|