1 /**
2  * @copyright
3  * ====================================================================
4  *    Licensed to the Apache Software Foundation (ASF) under one
5  *    or more contributor license agreements.  See the NOTICE file
6  *    distributed with this work for additional information
7  *    regarding copyright ownership.  The ASF licenses this file
8  *    to you under the Apache License, Version 2.0 (the
9  *    "License"); you may not use this file except in compliance
10  *    with the License.  You may obtain a copy of the License at
11  *
12  *      http://www.apache.org/licenses/LICENSE-2.0
13  *
14  *    Unless required by applicable law or agreed to in writing,
15  *    software distributed under the License is distributed on an
16  *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17  *    KIND, either express or implied.  See the License for the
18  *    specific language governing permissions and limitations
19  *    under the License.
20  * ====================================================================
21  * @endcopyright
22  */
23 package org.apache.subversion.javahl;
24 
25 /*import org.apache.subversion.javahl.Revision;
26 import org.apache.subversion.javahl.Status;
27 import org.apache.subversion.javahl.NodeKind;
28 import org.apache.subversion.javahl.DirEntry;*/
29 
30 import java.io.*;
31 import java.util.HashMap;
32 import java.util.Map;
33 import java.util.Date;
34 
35 import org.apache.subversion.javahl.types.*;
36 
37 import junit.framework.TestCase;
38 
39 /**
40  * This class describe the expected state of the working copy
41  */
42 public class WC extends TestCase
43 {
44     /**
45      * the map of the items of the working copy. The relative path is the key
46      * for the map
47      */
48     Map<String, Item> items = new HashMap<String, Item>();
49 
50     /**
51      * Generate from the expected state of the working copy a new working copy
52      * @param root      the working copy directory
53      * @throws IOException
54      */
materialize(File root)55     public void materialize(File root) throws IOException
56     {
57         // generate all directories first
58         for (Item item : items.values())
59         {
60             if (item.myContent == null) // is a directory
61             {
62                 File dir = new File(root, item.myPath);
63                 if (!dir.exists())
64                     dir.mkdirs();
65             }
66         }
67         // generate all files with the content in the second run
68         for (Item item : items.values())
69         {
70             if (item.myContent != null) // is a file
71             {
72                 File file = new File(root, item.myPath);
73                 PrintWriter pw = new PrintWriter(new FileOutputStream(file));
74                 pw.print(item.myContent);
75                 pw.close();
76             }
77         }
78     }
79     /**
80      * Add a new item to the working copy
81      * @param path      the path of the item
82      * @param content   the content of the item. A null content signifies a
83      *                  directory
84      * @return          the new Item object
85      */
addItem(String path, String content)86     public Item addItem(String path, String content)
87     {
88         return new Item(path, content);
89     }
90 
91     /**
92      * Returns the item at a path
93      * @param path  the path, where the item is searched
94      * @return  the found item
95      */
getItem(String path)96     public Item getItem(String path)
97     {
98         return items.get(path);
99     }
100 
101     /**
102      * Remove the item at a path
103      * @param path  the path, where the item is removed
104      */
removeItem(String path)105     public void removeItem(String path)
106     {
107         items.remove(path);
108     }
109 
110     /**
111      * Set text (content) status of the item at a path
112      * @param path      the path, where the status is set
113      * @param status    the new text status
114      */
setItemTextStatus(String path, Status.Kind status)115     public void setItemTextStatus(String path, Status.Kind status)
116     {
117         items.get(path).textStatus = status;
118     }
119 
120     /**
121      * Set property status of the item at a path
122      * @param path      the path, where the status is set
123      * @param status    the new property status
124      */
setItemPropStatus(String path, Status.Kind status)125     public void setItemPropStatus(String path, Status.Kind status)
126     {
127         items.get(path).propStatus = status;
128     }
129 
130     /**
131      * Set the depth of the item at a path
132      * @param path      the path, where the status is set
133      * @param depth     the new depth
134      */
setItemDepth(String path, Depth depth)135     public void setItemDepth(String path, Depth depth)
136     {
137         items.get(path).depth = depth;
138     }
139 
140     /**
141      * Set the revision number of the item at a path
142      * @param path      the path, where the revision number is set
143      * @param revision  the new revision number
144      */
setItemWorkingCopyRevision(String path, long revision)145     public void setItemWorkingCopyRevision(String path, long revision)
146     {
147         items.get(path).workingCopyRev = revision;
148     }
149 
150     /**
151      * Set the revision number of all paths in a working copy.
152      * @param revision The revision number to associate with all
153      * paths.
154      */
setRevision(long revision)155     public void setRevision(long revision)
156     {
157         for (Item item : this.items.values())
158         {
159             item.workingCopyRev = revision;
160         }
161     }
162 
163     /**
164      * Returns the file content of the item at a path
165      * @param path  the path, where the content is retrieved
166      * @return  the content of the file
167      */
getItemContent(String path)168     public String getItemContent(String path)
169     {
170         return items.get(path).myContent;
171     }
172 
173     /**
174      * Set the file content of the item at a path
175      * @param path      the path, where the content is set
176      * @param content   the new content
177      */
setItemContent(String path, String content)178     public void setItemContent(String path, String content)
179     {
180         // since having no content signals a directory, changes of removing the
181         // content or setting a former not set content is not allowed. That
182         // would change the type of the item.
183         assertNotNull("cannot unset content", content);
184         Item i = items.get(path);
185         assertNotNull("cannot set content on directory", i.myContent);
186         i.myContent = content;
187     }
188 
189     /**
190      * set the flag to check the content of item at a path during next check.
191      * @param path      the path, where the flag is set
192      * @param check     the flag
193      */
setItemCheckContent(String path, boolean check)194     public void setItemCheckContent(String path, boolean check)
195     {
196         items.get(path).checkContent = check;
197     }
198 
199     /**
200      * Set the expected node kind at a path
201      * @param path      the path, where the node kind is set
202      * @param nodeKind  the expected node kind
203      */
setItemNodeKind(String path, NodeKind nodeKind)204     public void setItemNodeKind(String path, NodeKind nodeKind)
205     {
206         items.get(path).nodeKind = nodeKind;
207     }
208 
209     /**
210      * Set the expected lock state at a path
211      * @param path      the path, where the lock state is set
212      * @param isLocked  the flag
213      */
setItemIsLocked(String path, boolean isLocked)214     public void setItemIsLocked(String path, boolean isLocked)
215     {
216         items.get(path).isLocked = isLocked;
217     }
218 
219     /**
220      * Set the expected switched flag at a path
221      * @param path          the path, where the switch flag is set
222      * @param isSwitched    the flag
223      */
setItemIsSwitched(String path, boolean isSwitched)224     public void setItemIsSwitched(String path, boolean isSwitched)
225     {
226         items.get(path).isSwitched = isSwitched;
227     }
228 
229     /**
230      * Set the youngest committed revision of an out of date item at
231      * <code>path</code>.
232      * @param path The path to set the last revision for.
233      * @param revision The last revision number for <code>path</code>
234      * known to the repository.
235      */
setItemReposLastCmtRevision(String path, long revision)236     public void setItemReposLastCmtRevision(String path, long revision)
237     {
238         items.get(path).reposLastCmtRevision = revision;
239     }
240 
241     /**
242      * Set the youngest committed revision of an out of date item at
243      * <code>path</code>.
244      * @param path The path to set the last author for.
245      * @param revision The last author for <code>path</code> known to
246      * the repository.
247      */
setItemReposLastCmtAuthor(String path, String author)248     public void setItemReposLastCmtAuthor(String path, String author)
249     {
250         items.get(path).reposLastCmtAuthor = author;
251     }
252 
253     /**
254      * Set the youngest committed revision of an out of date item at
255      * <code>path</cod>.
256      * @param path The path to set the last date for.
257      * @param revision The last date for <code>path</code> known to
258      * the repository.
259      */
setItemReposLastCmtDate(String path, long date)260     public void setItemReposLastCmtDate(String path, long date)
261     {
262         items.get(path).reposLastCmtDate = date;
263     }
264 
265     /**
266      * Set the youngest committed node kind of an out of date item at
267      * <code>path</code>.
268      * @param path The path to set the last node kind for.
269      * @param revision The last node kind for <code>path</code> known
270      * to the repository.
271      */
setItemReposKind(String path, NodeKind nodeKind)272     public void setItemReposKind(String path, NodeKind nodeKind)
273     {
274         items.get(path).reposKind = nodeKind;
275     }
276 
277     /**
278      * Set the youngest committed rev, author, date, and node kind of an
279      * out of date item at <code>path</code>.
280      *
281      * @param path The path to set the repos info for.
282      * @param revision The last revision number.
283      * @param revision The last author.
284      * @param date The last date.
285      * @param nodeKind The last node kind.
286      */
setItemOODInfo(String path, long revision, String author, long date, NodeKind nodeKind)287     public void setItemOODInfo(String path, long revision, String author,
288                                long date, NodeKind nodeKind)
289     {
290         this.setItemReposLastCmtRevision(path, revision);
291         this.setItemReposLastCmtAuthor(path, author);
292         this.setItemReposLastCmtDate(path, date);
293         this.setItemReposKind(path, nodeKind);
294     }
295 
296     /**
297      * Copy an expected working copy state
298      * @return the copy of the exiting object
299      */
copy()300     public WC copy()
301     {
302         WC c = new WC();
303         for (Item item : items.values())
304         {
305             item.copy(c);
306         }
307         return c;
308     }
309 
310     /**
311      * Check the result of a single file SVNClient.list call
312      * @param tested            the result array
313      * @param singleFilePath    the path to be checked
314      * @throws Exception
315      */
check(DirEntry[] tested, String singleFilePath)316     void check(DirEntry[] tested, String singleFilePath)
317     {
318         assertEquals("not a single dir entry", 1, tested.length);
319         Item item = items.get(singleFilePath);
320         assertNotNull("not found in working copy", item);
321         assertNotNull("not a file", item.myContent);
322         assertEquals("state says file, working copy not",
323                      tested[0].getNodeKind(),
324                      item.nodeKind == null ? NodeKind.file : item.nodeKind);
325     }
326 
327     /**
328      * Check the result of a directory SVNClient.list call
329      * @param tested        the result array
330      * @param basePath      the path of the directory
331      * @param recursive     the recursive flag of the call
332      * @throws Exception
333      */
check(DirEntry[] tested, String basePath, boolean recursive)334     void check(DirEntry[] tested, String basePath, boolean recursive)
335     {
336         // clear the touched flag of all items
337         for (Item item : items.values())
338         {
339             item.touched = false;
340         }
341 
342         // normalize directory path
343         if (basePath != null && basePath.length() > 0)
344         {
345             basePath += '/';
346         }
347         else
348         {
349             basePath = "";
350         }
351         // check all returned DirEntry's
352         for (DirEntry entry : tested)
353         {
354             String name = basePath + entry.getPath();
355             Item item = items.get(name);
356             assertNotNull("null paths won't be found in working copy", item);
357             if (item.myContent != null)
358             {
359                 assertEquals("Expected '" + entry + "' to be file",
360                              entry.getNodeKind(),
361                              item.nodeKind == null ? NodeKind.file : item.nodeKind);
362             }
363             else
364             {
365                 assertEquals("Expected '" + entry + "' to be dir",
366                              entry.getNodeKind(),
367                              item.nodeKind == null ? NodeKind.dir : item.nodeKind);
368             }
369             item.touched = true;
370         }
371 
372         // all items should have been in items, should had their touched flag
373         // set
374         for (Item item : items.values())
375         {
376             if (!item.touched)
377             {
378                 if (item.myPath.startsWith(basePath) &&
379                         !item.myPath.equals(basePath))
380                 {
381                     // Non-recursive checks will fail here.
382                     assertFalse("Expected path '" + item.myPath +
383                                 "' not found in dir entries",
384                                 recursive);
385 
386                     // Look deeper under the tree.
387                     boolean found = false;
388                     for (DirEntry entry : tested)
389                     {
390                         if (entry.getNodeKind() == NodeKind.dir)
391                         {
392                             if (item.myPath.startsWith(basePath +
393                                                        entry.getPath()))
394                             {
395                                 found = true;
396                                 break;
397                             }
398                         }
399                     }
400                     assertTrue("Expected path '" + item.myPath +
401                                "' not found in dir entries", found);
402                 }
403             }
404         }
405     }
406 
407     /**
408      * Check the result of a SVNClient.status() versus the expected
409      * state.  Does not extract "out of date" information from the
410      * repository.
411      *
412      * @param tested            the result to be tested
413      * @param workingCopyPath   the path of the working copy
414      * @throws IOException If there is a problem finding or reading
415      * the WC.
416      * @see #check(Status[], String, boolean)
417      */
check(Status[] tested, String workingCopyPath)418     void check(Status[] tested, String workingCopyPath)
419         throws IOException
420     {
421         check(tested, workingCopyPath, false);
422     }
423 
424     /**
425      * Check the result of a SVNClient.status() versus the expected state.
426      *
427      * @param tested The result to be tested.
428      * @param workingCopyPath The path of the working copy.
429      * @param checkRepos Whether to compare the "out of date" statii.
430      * @throws IOException If there is a problem finding or reading
431      * the WC.
432      */
check(Status[] tested, String workingCopyPath, boolean checkRepos)433     void check(Status[] tested, String workingCopyPath, boolean checkRepos)
434         throws IOException
435     {
436         // clear the touched flag of all items
437         for (Item item : items.values())
438         {
439             item.touched = false;
440         }
441 
442         String normalizeWCPath =
443                 workingCopyPath.replace(File.separatorChar, '/');
444 
445         // check all result Staus object
446         for (Status status : tested)
447         {
448             String path = status.getPath();
449             assertTrue("status path starts not with working copy path",
450                        path.startsWith(normalizeWCPath));
451 
452             // we calculate the relative path to the working copy root
453             if (path.length() > workingCopyPath.length() + 1)
454             {
455                 assertEquals("missing '/' in status path",
456                              path.charAt(workingCopyPath.length()), '/');
457                 path = path.substring(workingCopyPath.length() + 1);
458             }
459             else
460                 // this is the working copy root itself
461                 path = "";
462 
463             Item item = items.get(path);
464             assertNotNull("status not found in working copy: " + path, item);
465             assertEquals("wrong text status in working copy: " + path,
466                          item.textStatus, status.getTextStatus());
467             if (item.workingCopyRev != -1)
468                 assertEquals("wrong revision number in working copy: " + path,
469                              item.workingCopyRev, status.getRevisionNumber());
470             assertEquals("lock status wrong: " + path,
471                          item.isLocked, status.isLocked());
472             assertEquals("switch status wrong: " + path,
473                          item.isSwitched, status.isSwitched());
474             assertEquals("wrong prop status in working copy: " + path,
475                          item.propStatus, status.getPropStatus());
476             if (item.depth != null)
477                 assertEquals("wrong ambient depth in working copy: " + path,
478                              item.depth, status.getDepth());
479             if (item.myContent != null)
480             {
481                 assertEquals("state says file, working copy not: " + path,
482                              status.getNodeKind(),
483                              item.nodeKind == null ? NodeKind.file : item.nodeKind);
484                 if (status.getTextStatus() == Status.Kind.normal ||
485                         item.checkContent)
486                 {
487                     File input = new File(workingCopyPath, item.myPath);
488                     Reader rd =
489                             new InputStreamReader(new FileInputStream(input));
490                     StringBuffer buffer = new StringBuffer();
491                     int ch;
492                     while ((ch = rd.read()) != -1)
493                     {
494                         buffer.append((char) ch);
495                     }
496                     rd.close();
497                     assertEquals("content mismatch: " + path,
498                                  buffer.toString(), item.myContent);
499                 }
500             }
501             else
502             {
503                 assertEquals("state says dir, working copy not: " + path,
504                              status.getNodeKind(),
505                              item.nodeKind == null ? NodeKind.dir : item.nodeKind);
506             }
507 
508             if (checkRepos)
509             {
510                 assertEquals("Last commit revisions for OOD path '"
511                              + item.myPath + "' don't match:",
512                              item.reposLastCmtRevision,
513                              status.getReposLastCmtRevisionNumber());
514                 assertEquals("Last commit kinds for OOD path '"
515                              + item.myPath + "' don't match:",
516                              item.reposKind, status.getReposKind());
517 
518                 // Only the last committed rev and kind is available for
519                 // paths deleted in the repos.
520                 if (status.getRepositoryTextStatus() != Status.Kind.deleted)
521                 {
522                     long lastCmtTime =
523                         (status.getReposLastCmtDate() == null ?
524                          0 : status.getReposLastCmtDate().getTime());
525                     assertEquals("Last commit dates for OOD path '" +
526                                  item.myPath + "' don't match:",
527                                  new Date(item.reposLastCmtDate),
528                                  new Date(lastCmtTime));
529                     assertEquals("Last commit authors for OOD path '"
530                                  + item.myPath + "' don't match:",
531                                  item.reposLastCmtAuthor,
532                                  status.getReposLastCmtAuthor());
533                     assertNotNull("URL for path " + item.myPath
534                                   + " should not be null",
535                                   status.getUrl());
536                 }
537             }
538             item.touched = true;
539         }
540 
541         // all items which have the touched flag not set, are missing in the
542         // result array
543         for (Item item : items.values())
544         {
545             assertTrue("item '" + item.myPath +
546                        "' in working copy not found in status",
547                        item.touched);
548         }
549     }
550 
551     /**
552      * internal class to discribe a single working copy item
553      */
554     public class Item
555     {
556         /**
557          * the content of a file. A directory has a null content
558          */
559         String myContent;
560 
561         /**
562          * the relative path of the item
563          */
564         String myPath;
565 
566         /**
567          * the text (content) status of the item
568          */
569         Status.Kind textStatus = Status.Kind.normal;
570 
571         /**
572          * the property status of the item.
573          */
574         Status.Kind propStatus = Status.Kind.none;
575 
576         /**
577          * the ambient depth of the item.
578          */
579         Depth depth = null;
580 
581         /**
582          * the expected revision number. -1 means do not check.
583          */
584         long workingCopyRev = -1;
585 
586         /**
587          * flag if item has been touched. To detect missing items.
588          */
589         boolean touched;
590 
591         /**
592          * flag if the content will be checked
593          */
594         boolean checkContent;
595 
596         /**
597          * expected node kind. null means do not check.
598          */
599         NodeKind nodeKind = null;
600 
601         /**
602          * expected locked status
603          */
604         boolean isLocked;
605 
606         /**
607          * expected switched status
608          */
609         boolean isSwitched;
610 
611         /**
612          * youngest committed revision on repos if out of date
613          */
614         long reposLastCmtRevision = Revision.SVN_INVALID_REVNUM;
615 
616         /**
617          * most recent commit date on repos if out of date
618          */
619         long reposLastCmtDate = 0;
620 
621         /**
622          * node kind of the youngest commit if out of date
623          */
624         NodeKind reposKind = NodeKind.none;
625 
626         /**
627          * author of the youngest commit if out of date.
628          */
629         String reposLastCmtAuthor;
630 
631         /**
632          * create a new item
633          * @param path      the path of the item.
634          * @param content   the content of the item. A null signals a directory.
635          */
Item(String path, String content)636         private Item(String path, String content)
637         {
638             myPath = path;
639             myContent = content;
640             items.put(path, this);
641         }
642 
643         /**
644          * copy constructor
645          * @param source    the copy source.
646          * @param owner     the WC of the copy
647          */
Item(Item source, WC owner)648         private Item(Item source, WC owner)
649         {
650             myPath = source.myPath;
651             myContent = source.myContent;
652             textStatus = source.textStatus;
653             propStatus = source.propStatus;
654             depth = source.depth;
655             owner.items.put(myPath, this);
656         }
657 
658         /**
659          * copy this item
660          * @param owner the new WC
661          * @return  the copied item
662          */
copy(WC owner)663         private Item copy(WC owner)
664         {
665             return new Item(this, owner);
666         }
667     }
668 }
669