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