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