comparison test/org/tmatesoft/hg/test/TestStatus.java @ 442:6865eb742883

Tests for subrepo API, refactor status tests for reuse, better subrepos API
author Artem Tikhomirov <tikhomirov.artem@gmail.com>
date Fri, 27 Apr 2012 20:57:20 +0200
parents cd658b24a620
children 36fd1fd06492
comparison
equal deleted inserted replaced
441:2a08466838d3 442:6865eb742883
48 import org.tmatesoft.hg.repo.HgStatusCollector; 48 import org.tmatesoft.hg.repo.HgStatusCollector;
49 import org.tmatesoft.hg.repo.HgWorkingCopyStatusCollector; 49 import org.tmatesoft.hg.repo.HgWorkingCopyStatusCollector;
50 import org.tmatesoft.hg.util.Path; 50 import org.tmatesoft.hg.util.Path;
51 import org.tmatesoft.hg.util.Status; 51 import org.tmatesoft.hg.util.Status;
52 52
53
54 /** 53 /**
55 * 54 *
56 * @author Artem Tikhomirov 55 * @author Artem Tikhomirov
57 * @author TMate Software Ltd. 56 * @author TMate Software Ltd.
58 */ 57 */
62 public ErrorCollectorExt errorCollector = new ErrorCollectorExt(); 61 public ErrorCollectorExt errorCollector = new ErrorCollectorExt();
63 62
64 private HgRepository repo; 63 private HgRepository repo;
65 private StatusOutputParser statusParser; 64 private StatusOutputParser statusParser;
66 private ExecHelper eh; 65 private ExecHelper eh;
66 private StatusReporter sr;
67 67
68 public static void main(String[] args) throws Throwable { 68 public static void main(String[] args) throws Throwable {
69 TestStatus test = new TestStatus(); 69 TestStatus test = new TestStatus();
70 test.testLowLevel(); 70 test.testLowLevel();
71 test.testStatusCommand(); 71 test.testStatusCommand();
77 t2.errorCollector.verify(); 77 t2.errorCollector.verify();
78 TestStatus t3 = new TestStatus(new HgLookup().detect("/temp/hg/cpython")); 78 TestStatus t3 = new TestStatus(new HgLookup().detect("/temp/hg/cpython"));
79 t3.testDirstateParentOtherThanTipNoUpdate(); 79 t3.testDirstateParentOtherThanTipNoUpdate();
80 t3.errorCollector.verify(); 80 t3.errorCollector.verify();
81 } 81 }
82 82
83 public TestStatus() throws Exception { 83 public TestStatus() throws Exception {
84 this(new HgLookup().detectFromWorkingDir()); 84 this(new HgLookup().detectFromWorkingDir());
85 } 85 }
86 86
87 private TestStatus(HgRepository hgRepo) { 87 private TestStatus(HgRepository hgRepo) {
88 repo = hgRepo; 88 repo = hgRepo;
89 Assume.assumeTrue(!repo.isInvalid()); 89 Assume.assumeTrue(!repo.isInvalid());
90 statusParser = new StatusOutputParser(); 90 statusParser = new StatusOutputParser();
91 eh = new ExecHelper(statusParser, hgRepo.getWorkingDir()); 91 eh = new ExecHelper(statusParser, hgRepo.getWorkingDir());
92 } 92 sr = new StatusReporter(errorCollector, statusParser);
93 93 }
94
94 @Test 95 @Test
95 public void testLowLevel() throws Exception { 96 public void testLowLevel() throws Exception {
96 final HgWorkingCopyStatusCollector wcc = new HgWorkingCopyStatusCollector(repo); 97 final HgWorkingCopyStatusCollector wcc = new HgWorkingCopyStatusCollector(repo);
97 statusParser.reset(); 98 statusParser.reset();
98 eh.run("hg", "status", "-A"); 99 eh.run("hg", "status", "-A");
99 HgStatusCollector.Record r = wcc.status(HgRepository.TIP); 100 HgStatusCollector.Record r = wcc.status(HgRepository.TIP);
100 report("hg status -A", r, statusParser); 101 sr.report("hg status -A", r);
101 // 102 //
102 statusParser.reset(); 103 statusParser.reset();
103 int revision = 3; 104 int revision = 3;
104 eh.run("hg", "status", "-A", "--rev", String.valueOf(revision)); 105 eh.run("hg", "status", "-A", "--rev", String.valueOf(revision));
105 r = wcc.status(revision); 106 r = wcc.status(revision);
106 report("status -A --rev " + revision, r, statusParser); 107 sr.report("status -A --rev " + revision, r);
107 // 108 //
108 statusParser.reset(); 109 statusParser.reset();
109 eh.run("hg", "status", "-A", "--change", String.valueOf(revision)); 110 eh.run("hg", "status", "-A", "--change", String.valueOf(revision));
110 r = new HgStatusCollector.Record(); 111 r = new HgStatusCollector.Record();
111 new HgStatusCollector(repo).change(revision, r); 112 new HgStatusCollector(repo).change(revision, r);
112 report("status -A --change " + revision, r, statusParser); 113 sr.report("status -A --change " + revision, r);
113 // 114 //
114 statusParser.reset(); 115 statusParser.reset();
115 int rev2 = 80; 116 int rev2 = 80;
116 final String range = String.valueOf(revision) + ":" + String.valueOf(rev2); 117 final String range = String.valueOf(revision) + ":" + String.valueOf(rev2);
117 eh.run("hg", "status", "-A", "--rev", range); 118 eh.run("hg", "status", "-A", "--rev", range);
118 r = new HgStatusCollector(repo).status(revision, rev2); 119 r = new HgStatusCollector(repo).status(revision, rev2);
119 report("Status -A -rev " + range, r, statusParser); 120 sr.report("Status -A -rev " + range, r);
120 } 121 }
121 122
122 /** 123 /**
123 * hg up --rev <earlier rev>; hg status 124 * hg up --rev <earlier rev>; hg status
124 * 125 *
125 * To check if HgWorkingCopyStatusCollector respects actual working copy parent (takes from dirstate) 126 * To check if HgWorkingCopyStatusCollector respects actual working copy parent (takes from dirstate)
126 * and if status is calculated correctly 127 * and if status is calculated correctly
127 */ 128 */
128 @Test 129 @Test
129 @Ignore("modifies test repository, needs careful configuration") 130 @Ignore("modifies test repository, needs careful configuration")
130 public void testDirstateParentOtherThanTipWithUpdate() throws Exception { 131 public void testDirstateParentOtherThanTipWithUpdate() throws Exception {
131 int revToUpdate = 238; 132 int revToUpdate = 238;
143 final HgWorkingCopyStatusCollector wcc = new HgWorkingCopyStatusCollector(repo); 144 final HgWorkingCopyStatusCollector wcc = new HgWorkingCopyStatusCollector(repo);
144 statusParser.reset(); 145 statusParser.reset();
145 // 146 //
146 eh.run("hg", "status", "-A"); 147 eh.run("hg", "status", "-A");
147 HgStatusCollector.Record r = wcc.status(HgRepository.TIP); 148 HgStatusCollector.Record r = wcc.status(HgRepository.TIP);
148 report("hg status -A", r, statusParser); 149 sr.report("hg status -A", r);
149 // 150 //
150 statusParser.reset(); 151 statusParser.reset();
151 int revision = 3; 152 int revision = 3;
152 eh.run("hg", "status", "-A", "--rev", String.valueOf(revision)); 153 eh.run("hg", "status", "-A", "--rev", String.valueOf(revision));
153 r = wcc.status(revision); 154 r = wcc.status(revision);
154 report("status -A --rev " + revision, r, statusParser); 155 sr.report("status -A --rev " + revision, r);
155 } 156 }
156 157
157
158 @Test 158 @Test
159 public void testStatusCommand() throws Exception { 159 public void testStatusCommand() throws Exception {
160 final HgStatusCommand sc = new HgStatusCommand(repo).all(); 160 final HgStatusCommand sc = new HgStatusCommand(repo).all();
161 StatusCollector r; 161 StatusCollector r;
162 statusParser.reset(); 162 statusParser.reset();
163 eh.run("hg", "status", "-A"); 163 eh.run("hg", "status", "-A");
164 sc.execute(r = new StatusCollector()); 164 sc.execute(r = new StatusCollector());
165 report("hg status -A", r); 165 sr.report("hg status -A", r);
166 // 166 //
167 statusParser.reset(); 167 statusParser.reset();
168 int revision = 3; 168 int revision = 3;
169 eh.run("hg", "status", "-A", "--rev", String.valueOf(revision)); 169 eh.run("hg", "status", "-A", "--rev", String.valueOf(revision));
170 sc.base(revision).execute(r = new StatusCollector()); 170 sc.base(revision).execute(r = new StatusCollector());
171 report("status -A --rev " + revision, r); 171 sr.report("status -A --rev " + revision, r);
172 // 172 //
173 statusParser.reset(); 173 statusParser.reset();
174 eh.run("hg", "status", "-A", "--change", String.valueOf(revision)); 174 eh.run("hg", "status", "-A", "--change", String.valueOf(revision));
175 sc.base(TIP).revision(revision).execute(r = new StatusCollector()); 175 sc.base(TIP).revision(revision).execute(r = new StatusCollector());
176 report("status -A --change " + revision, r); 176 sr.report("status -A --change " + revision, r);
177 177
178 // TODO check not -A, but defaults()/custom set of modifications 178 // TODO check not -A, but defaults()/custom set of modifications
179 } 179 }
180 180
181 private static class StatusCollector implements HgStatusHandler { 181 static class StatusCollector implements HgStatusHandler {
182 private final Map<Kind, List<Path>> kind2names = new TreeMap<Kind, List<Path>>(); 182 private final Map<Kind, List<Path>> kind2names = new TreeMap<Kind, List<Path>>();
183 private final Map<Path, List<Kind>> name2kinds = new TreeMap<Path, List<Kind>>(); 183 private final Map<Path, List<Kind>> name2kinds = new TreeMap<Path, List<Kind>>();
184 private final Map<Path, Status> name2error = new LinkedHashMap<Path, Status>(); 184 private final Map<Path, Status> name2error = new LinkedHashMap<Path, Status>();
185 private final Map<Path, Path> new2oldName = new LinkedHashMap<Path, Path>();
185 186
186 public void status(HgStatus s) { 187 public void status(HgStatus s) {
187 List<Path> l = kind2names.get(s.getKind()); 188 List<Path> l = kind2names.get(s.getKind());
188 if (l == null) { 189 if (l == null) {
189 kind2names.put(s.getKind(), l = new LinkedList<Path>()); 190 kind2names.put(s.getKind(), l = new LinkedList<Path>());
190 } 191 }
191 l.add(s.getPath()); 192 l.add(s.getPath());
193 if (s.isCopy()) {
194 new2oldName.put(s.getPath(), s.getOriginalPath());
195 }
192 // 196 //
193 List<Kind> k = name2kinds.get(s.getPath()); 197 List<Kind> k = name2kinds.get(s.getPath());
194 if (k == null) { 198 if (k == null) {
195 name2kinds.put(s.getPath(), k = new LinkedList<Kind>()); 199 name2kinds.put(s.getPath(), k = new LinkedList<Kind>());
196 } 200 }
197 k.add(s.getKind()); 201 k.add(s.getKind());
198 } 202 }
199 203
200 public void error(Path file, Status s) { 204 public void error(Path file, Status s) {
201 name2error.put(file, s); 205 name2error.put(file, s);
202 } 206 }
203 207
204 public List<Path> get(Kind k) { 208 public List<Path> get(Kind k) {
205 List<Path> rv = kind2names.get(k); 209 List<Path> rv = kind2names.get(k);
206 return rv == null ? Collections.<Path>emptyList() : rv; 210 return rv == null ? Collections.<Path> emptyList() : rv;
207 } 211 }
208 212
209 public List<Kind> get(Path p) { 213 public List<Kind> get(Path p) {
210 List<Kind> rv = name2kinds.get(p); 214 List<Kind> rv = name2kinds.get(p);
211 return rv == null ? Collections.<Kind>emptyList() : rv; 215 return rv == null ? Collections.<Kind> emptyList() : rv;
212 } 216 }
213 217
214 public Map<Path, Status> getErrors() { 218 public Map<Path, Status> getErrors() {
215 return name2error; 219 return name2error;
220 }
221
222 public HgStatusCollector.Record asStatusRecord() {
223 HgStatusCollector.Record rv = new HgStatusCollector.Record();
224 for (Path p : get(Modified)) {
225 rv.modified(p);
226 }
227 for (Path p : get(Added)) {
228 if (!new2oldName.containsKey(p)) {
229 // new files that are result of a copy get reported separately, below
230 rv.added(p);
231 }
232 }
233 for (Path p : get(Removed)) {
234 rv.removed(p);
235 }
236 for (Path p : get(Clean)) {
237 rv.clean(p);
238 }
239 for (Path p : get(Ignored)) {
240 rv.ignored(p);
241 }
242 for (Path p : get(Missing)) {
243 rv.missing(p);
244 }
245 for (Path p : get(Unknown)) {
246 rv.unknown(p);
247 }
248 for (Map.Entry<Path, Path> e : new2oldName.entrySet()) {
249 rv.copied(e.getValue(), e.getKey());
250 }
251 return rv;
216 } 252 }
217 } 253 }
218 254
219 /* 255 /*
220 * status-1/dir/file5 was added in rev 8, scheduled (hg remove file5) for removal, but not yet committed 256 * status-1/dir/file5 was added in rev 8, scheduled (hg remove file5) for removal, but not yet committed
230 assertTrue(sc.getErrors().isEmpty()); 266 assertTrue(sc.getErrors().isEmpty());
231 Path file5 = Path.create("dir/file5"); 267 Path file5 = Path.create("dir/file5");
232 // shall not be listed at all 268 // shall not be listed at all
233 assertTrue(sc.get(file5).isEmpty()); 269 assertTrue(sc.get(file5).isEmpty());
234 } 270 }
235 271
236 /* 272 /*
237 * status-1/file2 is tracked, but later .hgignore got entry to ignore it, file2 got modified 273 * status-1/file2 is tracked, but later .hgignore got entry to ignore it, file2 got modified
238 * HG doesn't respect .hgignore for tracked files. 274 * HG doesn't respect .hgignore for tracked files.
239 * Now reported as ignored and missing(?!). 275 * Now reported as ignored and missing(?!).
240 * Shall be reported as modified. 276 * Shall be reported as modified.
251 assertTrue(sc.get(file2).size() == 1); 287 assertTrue(sc.get(file2).size() == 1);
252 } 288 }
253 289
254 /* 290 /*
255 * status/dir/file4, added in rev 3, has been scheduled for removal (hg remove -Af file4), but still there in the WC. 291 * status/dir/file4, added in rev 3, has been scheduled for removal (hg remove -Af file4), but still there in the WC.
256 * Shall be reported as Removed, when comparing against rev 3 292 * Shall be reported as Removed, when comparing against rev 3
257 * (despite both rev 3 and WC's parent has file4, there are different paths in the code for wc against parent and wc against rev) 293 * (despite both rev 3 and WC's parent has file4, there are different paths in the code for wc against parent and wc against rev)
258 */ 294 */
259 @Test 295 @Test
260 public void testMarkedRemovedButStillInWC() throws Exception { 296 public void testMarkedRemovedButStillInWC() throws Exception {
261 repo = Configuration.get().find("status-1"); 297 repo = Configuration.get().find("status-1");
262 HgStatusCommand cmd = new HgStatusCommand(repo); 298 HgStatusCommand cmd = new HgStatusCommand(repo);
278 assertTrue(sc.getErrors().isEmpty()); 314 assertTrue(sc.getErrors().isEmpty());
279 assertTrue(sc.get(file4).isEmpty()); 315 assertTrue(sc.get(file4).isEmpty());
280 } 316 }
281 317
282 /* 318 /*
283 * status-1/dir/file3 tracked, listed in .hgignore since rev 4, removed (hg remove file3) from repo and WC 319 * status-1/dir/file3 tracked, listed in .hgignore since rev 4, removed (hg remove file3) from repo and WC
284 * (but entry in .hgignore left) in revision 5, and new file3 got created in WC. 320 * (but entry in .hgignore left) in revision 5, and new file3 got created in WC.
285 * Shall be reported as ignored when comparing against WC's parent, 321 * Shall be reported as ignored when comparing against WC's parent,
286 * and both ignored and removed when comparing against revision 3 322 * and both ignored and removed when comparing against revision 3
287 */ 323 */
288 @Test 324 @Test
289 public void testRemovedIgnoredInWC() throws Exception { 325 public void testRemovedIgnoredInWC() throws Exception {
290 // check branch !known, ignored 326 // check branch !known, ignored
291 repo = Configuration.get().find("status-1"); 327 repo = Configuration.get().find("status-1");
315 351
316 } 352 }
317 353
318 /* 354 /*
319 * status/file1 was removed in cset 2. New file with the same name in the WC. 355 * status/file1 was removed in cset 2. New file with the same name in the WC.
320 * Shall report 2 statuses (as cmdline hg does): unknown and removed when comparing against that revision. 356 * Shall report 2 statuses (as cmdline hg does): unknown and removed when comparing against that revision.
321 */ 357 */
322 @Test 358 @Test
323 public void testNewFileWithSameNameAsDeletedOld() throws Exception { 359 public void testNewFileWithSameNameAsDeletedOld() throws Exception {
324 // check branch !known, !ignored (=> unknown) 360 // check branch !known, !ignored (=> unknown)
325 repo = Configuration.get().find("status-1"); 361 repo = Configuration.get().find("status-1");
337 cmd.base(2).execute(sc = new StatusCollector()); 373 cmd.base(2).execute(sc = new StatusCollector());
338 assertTrue(sc.getErrors().isEmpty()); 374 assertTrue(sc.getErrors().isEmpty());
339 assertTrue(sc.get(file1).contains(Unknown)); 375 assertTrue(sc.get(file1).contains(Unknown));
340 assertTrue(sc.get(file1).size() == 1); 376 assertTrue(sc.get(file1).size() == 1);
341 } 377 }
342 378
343 @Test 379 @Test
344 public void testSubTreeStatus() throws Exception { 380 public void testSubTreeStatus() throws Exception {
345 repo = Configuration.get().find("status-1"); 381 repo = Configuration.get().find("status-1");
346 HgStatusCommand cmd = new HgStatusCommand(repo); 382 HgStatusCommand cmd = new HgStatusCommand(repo);
347 StatusCollector sc = new StatusCollector(); 383 StatusCollector sc = new StatusCollector();
371 assertTrue(sc.get(Modified).isEmpty()); 407 assertTrue(sc.get(Modified).isEmpty());
372 assertTrue(sc.get(Added).isEmpty()); 408 assertTrue(sc.get(Added).isEmpty());
373 assertTrue(sc.get(Ignored).size() == 1); 409 assertTrue(sc.get(Ignored).size() == 1);
374 assertTrue(sc.get(Removed).size() == 2); 410 assertTrue(sc.get(Removed).size() == 2);
375 } 411 }
376 412
377
378 @Test 413 @Test
379 public void testSpecificFileStatus() throws Exception { 414 public void testSpecificFileStatus() throws Exception {
380 repo = Configuration.get().find("status-1"); 415 repo = Configuration.get().find("status-1");
381 // files only 416 // files only
382 final Path file2 = Path.create("file2"); 417 final Path file2 = Path.create("file2");
411 assertTrue(r.getCopied().isEmpty()); 446 assertTrue(r.getCopied().isEmpty());
412 assertTrue(r.getIgnored().contains(file3)); 447 assertTrue(r.getIgnored().contains(file3));
413 assertTrue(r.getIgnored().size() == 1); 448 assertTrue(r.getIgnored().size() == 1);
414 assertTrue(r.getModified().isEmpty()); 449 assertTrue(r.getModified().isEmpty());
415 } 450 }
416 451
417 @Test 452 @Test
418 public void testSameResultDirectPathVsMatcher() throws Exception { 453 public void testSameResultDirectPathVsMatcher() throws Exception {
419 repo = Configuration.get().find("status-1"); 454 repo = Configuration.get().find("status-1");
420 final Path file3 = Path.create("dir/file3"); 455 final Path file3 = Path.create("dir/file3");
421 final Path file5 = Path.create("dir/file5"); 456 final Path file5 = Path.create("dir/file5");
422 457
423 HgWorkingCopyStatusCollector sc = HgWorkingCopyStatusCollector.create(repo, file3, file5); 458 HgWorkingCopyStatusCollector sc = HgWorkingCopyStatusCollector.create(repo, file3, file5);
424 HgStatusCollector.Record r; 459 HgStatusCollector.Record r;
425 sc.walk(WORKING_COPY, r = new HgStatusCollector.Record()); 460 sc.walk(WORKING_COPY, r = new HgStatusCollector.Record());
426 assertTrue(r.getRemoved().contains(file5)); 461 assertTrue(r.getRemoved().contains(file5));
427 assertTrue(r.getIgnored().contains(file3)); 462 assertTrue(r.getIgnored().contains(file3));
430 sc = HgWorkingCopyStatusCollector.create(repo, new PathGlobMatcher(file3.toString(), file5.toString())); 465 sc = HgWorkingCopyStatusCollector.create(repo, new PathGlobMatcher(file3.toString(), file5.toString()));
431 sc.walk(WORKING_COPY, r = new HgStatusCollector.Record()); 466 sc.walk(WORKING_COPY, r = new HgStatusCollector.Record());
432 assertTrue(r.getRemoved().contains(file5)); 467 assertTrue(r.getRemoved().contains(file5));
433 assertTrue(r.getIgnored().contains(file3)); 468 assertTrue(r.getIgnored().contains(file3));
434 } 469 }
435 470
436 @Test 471 @Test
437 public void testScopeInHistoricalStatus() throws Exception { 472 public void testScopeInHistoricalStatus() throws Exception {
438 repo = Configuration.get().find("status-1"); 473 repo = Configuration.get().find("status-1");
439 HgStatusCommand cmd = new HgStatusCommand(repo); 474 HgStatusCommand cmd = new HgStatusCommand(repo);
440 cmd.base(3).revision(8).all(); 475 cmd.base(3).revision(8).all();
457 assertTrue(sc.get(file5).contains(Added)); 492 assertTrue(sc.get(file5).contains(Added));
458 assertTrue(sc.get(file5).size() == 1); 493 assertTrue(sc.get(file5).size() == 1);
459 assertTrue(sc.get(Added).size() == 1); 494 assertTrue(sc.get(Added).size() == 1);
460 495
461 } 496 }
462 497
463 /** 498 /**
464 * Issue 22 499 * Issue 22
465 */ 500 */
466 @Test 501 @Test
467 public void testOnEmptyRepositoryWithAllFilesDeleted() throws Exception { 502 public void testOnEmptyRepositoryWithAllFilesDeleted() throws Exception {
471 StatusCollector sc = new StatusCollector(); 506 StatusCollector sc = new StatusCollector();
472 cmd.execute(sc); 507 cmd.execute(sc);
473 // shall pass without exception 508 // shall pass without exception
474 assertTrue(sc.getErrors().isEmpty()); 509 assertTrue(sc.getErrors().isEmpty());
475 for (HgStatus.Kind k : HgStatus.Kind.values()) { 510 for (HgStatus.Kind k : HgStatus.Kind.values()) {
476 assertTrue("Kind " + k.name() + " shall be empty",sc.get(k).isEmpty()); 511 assertTrue("Kind " + k.name() + " shall be empty", sc.get(k).isEmpty());
477 } 512 }
478 } 513 }
479 514
480 /** 515 /**
481 * Issue 22, two subsequent commits that remove all repository files, each in a different branch. 516 * Issue 22, two subsequent commits that remove all repository files, each in a different branch.
482 * Here's excerpt from my RevlogWriter utility: 517 * Here's excerpt from my RevlogWriter utility:
518 *
483 * <pre> 519 * <pre>
484 * final List<String> filesList = Collections.singletonList("file1"); 520 * final List<String> filesList = Collections.singletonList("file1");
485 * // 521 * //
486 * file1.writeUncompressed(-1, -1, 0, 0, "garbage".getBytes()); 522 * file1.writeUncompressed(-1, -1, 0, 0, "garbage".getBytes());
487 * // 523 * //
488 * ManifestBuilder mb = new ManifestBuilder(); 524 * ManifestBuilder mb = new ManifestBuilder();
489 * mb.reset().add("file1", file1.getRevision(0)); 525 * mb.reset().add("file1", file1.getRevision(0));
490 * manifest.writeUncompressed(-1, -1, 0, 0, mb.build()); // manifest revision 0 526 * manifest.writeUncompressed(-1, -1, 0, 0, mb.build()); // manifest revision 0
491 * final byte[] cset1 = buildChangelogEntry(manifest.getRevision(0), Collections.<String, String>emptyMap(), filesList, "Add a file"); 527 * final byte[] cset1 = buildChangelogEntry(manifest.getRevision(0), Collections.<String, String>emptyMap(), filesList, "Add a file");
492 * changelog.writeUncompressed(-1, -1, 0, 0, cset1); 528 * changelog.writeUncompressed(-1, -1, 0, 0, cset1);
493 * // 529 * //
494 * // pretend we delete all files in a branch 1 530 * // pretend we delete all files in a branch 1
495 * manifest.writeUncompressed(0, -1, 1, 1, new byte[0]); // manifest revision 1 531 * manifest.writeUncompressed(0, -1, 1, 1, new byte[0]); // manifest revision 1
496 * final byte[] cset2 = buildChangelogEntry(manifest.getRevision(1), Collections.singletonMap("branch", "delete-all-1"), filesList, "Delete all files in a first branch"); 532 * final byte[] cset2 = buildChangelogEntry(manifest.getRevision(1), Collections.singletonMap("branch", "delete-all-1"), filesList, "Delete all files in a first branch");
497 * changelog.writeUncompressed(0, -1, 1, 1, cset2); 533 * changelog.writeUncompressed(0, -1, 1, 1, cset2);
498 * // 534 * //
499 * // pretend we delete all files in a branch 2 (which is based on revision 0, same as branch 1) 535 * // pretend we delete all files in a branch 2 (which is based on revision 0, same as branch 1)
500 * manifest.writeUncompressed(1, -1, 1 /*!!! here comes baseRevision != index * /, 2, new byte[0]); // manifest revision 2 536 * manifest.writeUncompressed(1, -1, 1 /*!!! here comes baseRevision != index * /, 2, new byte[0]); // manifest revision 2
501 * final byte[] cset3 = buildChangelogEntry(manifest.getRevision(2), Collections.singletonMap("branch", "delete-all-2"), filesList, "Again delete all files but in another branch"); 537 * final byte[] cset3 = buildChangelogEntry(manifest.getRevision(2), Collections.singletonMap("branch", "delete-all-2"), filesList, "Again delete all files but in another branch");
502 * changelog.writeUncompressed(0, -1, 2, 2, cset3); 538 * changelog.writeUncompressed(0, -1, 2, 2, cset3);
503 * </pre> 539 * </pre>
504 */ 540 */
505 @Test 541 @Test
506 public void testOnEmptyRepositoryWithAllFilesDeletedInBranch() throws Exception { 542 public void testOnEmptyRepositoryWithAllFilesDeletedInBranch() throws Exception {
507 repo = Configuration.get().find("status-3"); 543 repo = Configuration.get().find("status-3");
508 HgStatusCommand cmd = new HgStatusCommand(repo); 544 HgStatusCommand cmd = new HgStatusCommand(repo);
510 StatusCollector sc = new StatusCollector(); 546 StatusCollector sc = new StatusCollector();
511 cmd.execute(sc); 547 cmd.execute(sc);
512 // shall pass without exception 548 // shall pass without exception
513 assertTrue(sc.getErrors().isEmpty()); 549 assertTrue(sc.getErrors().isEmpty());
514 for (HgStatus.Kind k : HgStatus.Kind.values()) { 550 for (HgStatus.Kind k : HgStatus.Kind.values()) {
515 assertTrue("Kind " + k.name() + " shall be empty",sc.get(k).isEmpty()); 551 assertTrue("Kind " + k.name() + " shall be empty", sc.get(k).isEmpty());
516 } 552 }
517 } 553 }
518 554
519 /** 555 /**
520 * Issue 23: HgInvalidRevisionException for svn imported repository (changeset 0 references nullid manifest) 556 * Issue 23: HgInvalidRevisionException for svn imported repository (changeset 0 references nullid manifest)
521 */ 557 */
522 @Test 558 @Test
523 public void testImportedRepoWithOddManifestRevisions() throws Exception { 559 public void testImportedRepoWithOddManifestRevisions() throws Exception {
524 repo = Configuration.get().find("status-4"); 560 repo = Configuration.get().find("status-4");
525 HgStatusCommand cmd = new HgStatusCommand(repo); 561 HgStatusCommand cmd = new HgStatusCommand(repo);
527 StatusCollector sc = new StatusCollector(); 563 StatusCollector sc = new StatusCollector();
528 cmd.execute(sc); 564 cmd.execute(sc);
529 // shall pass without exception 565 // shall pass without exception
530 assertTrue(sc.getErrors().isEmpty()); 566 assertTrue(sc.getErrors().isEmpty());
531 } 567 }
532 568
533 /** 569 /**
534 * Issue 24: IllegalArgumentException in FilterDataAccess 570 * Issue 24: IllegalArgumentException in FilterDataAccess
535 * There were two related defects in RevlogStream 571 * There were two related defects in RevlogStream
536 * a) for compressedLen == 0, a byte was read and FilterDataAccess (of length 0, but it didn't help too much) was created - first byte happen to be 0. 572 * a) for compressedLen == 0, a byte was read and FilterDataAccess (of length 0, but it didn't help too much) was created - first byte happen to be 0.
537 * Patch was not applied (userDataAccess.isEmpty() check thanks to Issue 22) 573 * Patch was not applied (userDataAccess.isEmpty() check thanks to Issue 22)
538 * b) That FilterDataAccess (with 0 size represents patch more or less relevantly, but didn't represent actual revision) get successfully 574 * b) That FilterDataAccess (with 0 size represents patch more or less relevantly, but didn't represent actual revision) get successfully
539 * reassigned as lastUserData for the next iteration. And at the next step attempt to apply patch recorded in the next revision failed 575 * reassigned as lastUserData for the next iteration. And at the next step attempt to apply patch recorded in the next revision failed
540 * because baseRevisionData is 0 length FilterDataAccess 576 * because baseRevisionData is 0 length FilterDataAccess
541 * 577 *
542 * Same applies for 578 * Same applies for
543 * Issue 25: IOException: Underflow. Rewind past end of the slice in InflaterDataAccess 579 * Issue 25: IOException: Underflow. Rewind past end of the slice in InflaterDataAccess
544 * with the difference in separate .i and .d (thus not 0 but 'x' first byte was read) 580 * with the difference in separate .i and .d (thus not 0 but 'x' first byte was read)
545 * 581 *
546 * Sample: 582 * Sample:
547 * status-5/file1 has 3 revisions, second is zero-length patch: 583 * status-5/file1 has 3 revisions, second is zero-length patch:
548 * Index Offset Packed Actual Base Rev 584 * Index Offset Packed Actual Base Rev
549 * 0: 0 8 7 0 585 * 0: 0 8 7 0
550 * DATA 586 * DATA
551 * 1: 8 0 7 0 587 * 1: 8 0 7 0
552 * NO DATA 588 * NO DATA
553 * 2: 8 14 6 0 589 * 2: 8 14 6 0
554 * PATCH 590 * PATCH
555 */ 591 */
556 @Test 592 @Test
557 public void testZeroLengthPatchAgainstNonEmptyBaseRev() throws Exception{ 593 public void testZeroLengthPatchAgainstNonEmptyBaseRev() throws Exception {
558 repo = Configuration.get().find("status-5"); 594 repo = Configuration.get().find("status-5");
559 // pretend we modified files in the working copy 595 // pretend we modified files in the working copy
560 // for HgWorkingCopyStatusCollector to go and retrieve its content from repository 596 // for HgWorkingCopyStatusCollector to go and retrieve its content from repository
561 File f1 = new File(repo.getWorkingDir(), "file1"); 597 File f1 = new File(repo.getWorkingDir(), "file1");
562 f1.setLastModified(System.currentTimeMillis()); 598 f1.setLastModified(System.currentTimeMillis());
567 cmd.all(); 603 cmd.all();
568 StatusCollector sc = new StatusCollector(); 604 StatusCollector sc = new StatusCollector();
569 cmd.execute(sc); 605 cmd.execute(sc);
570 // shall pass without exception 606 // shall pass without exception
571 // 607 //
572 for (Map.Entry<Path,Status> e : sc.getErrors().entrySet()) { 608 for (Map.Entry<Path, Status> e : sc.getErrors().entrySet()) {
573 System.out.printf("%s : (%s %s)\n", e.getKey(), e.getValue().getKind(), e.getValue().getMessage()); 609 System.out.printf("%s : (%s %s)\n", e.getKey(), e.getValue().getKind(), e.getValue().getMessage());
574 } 610 }
575 assertTrue(sc.getErrors().isEmpty()); 611 assertTrue(sc.getErrors().isEmpty());
576 } 612 }
577 613
578 /** 614 /**
579 * Issue 26: UnsupportedOperationException when patching empty base revision 615 * Issue 26: UnsupportedOperationException when patching empty base revision
580 * 616 *
581 * Sample: 617 * Sample:
582 * status-5/file2 has 3 revisions, second is patch (complete revision content in a form of the patch) for empty base revision: 618 * status-5/file2 has 3 revisions, second is patch (complete revision content in a form of the patch) for empty base revision:
583 * Index Offset Packed Actual Base Rev 619 * Index Offset Packed Actual Base Rev
584 * 0: 0 0 0 0 620 * 0: 0 0 0 0
585 * NO DATA 621 * NO DATA
586 * 1: 0 20 7 0 622 * 1: 0 20 7 0
587 * PATCH: 0..0, 7:garbage 623 * PATCH: 0..0, 7:garbage
588 * 2: 20 16 7 0 624 * 2: 20 16 7 0
589 */ 625 */
590 @Test 626 @Test
591 public void testPatchZeroLengthBaseRevision() throws Exception { 627 public void testPatchZeroLengthBaseRevision() throws Exception {
592 repo = Configuration.get().find("status-5"); 628 repo = Configuration.get().find("status-5");
593 // touch the file to force content retrieval 629 // touch the file to force content retrieval
598 cmd.all(); 634 cmd.all();
599 StatusCollector sc = new StatusCollector(); 635 StatusCollector sc = new StatusCollector();
600 cmd.execute(sc); 636 cmd.execute(sc);
601 // shall pass without exception 637 // shall pass without exception
602 // 638 //
603 for (Map.Entry<Path,Status> e : sc.getErrors().entrySet()) { 639 for (Map.Entry<Path, Status> e : sc.getErrors().entrySet()) {
604 System.out.printf("%s : (%s %s)\n", e.getKey(), e.getValue().getKind(), e.getValue().getMessage()); 640 System.out.printf("%s : (%s %s)\n", e.getKey(), e.getValue().getKind(), e.getValue().getMessage());
605 } 641 }
606 assertTrue(sc.getErrors().isEmpty()); 642 assertTrue(sc.getErrors().isEmpty());
607 } 643 }
608 644
609
610 /* 645 /*
611 * With warm-up of previous tests, 10 runs, time in milliseconds 646 * With warm-up of previous tests, 10 runs, time in milliseconds
612 * 'hg status -A': Native client total 953 (95 per run), Java client 94 (9) 647 * 'hg status -A': Native client total 953 (95 per run), Java client 94 (9)
613 * 'hg status -A --rev 3:80': Native client total 1828 (182 per run), Java client 235 (23) 648 * 'hg status -A --rev 3:80': Native client total 1828 (182 per run), Java client 235 (23)
614 * 'hg log --debug', 10 runs: Native client total 1766 (176 per run), Java client 78 (7) 649 * 'hg log --debug', 10 runs: Native client total 1766 (176 per run), Java client 78 (7)
615 * 650 *
616 * 18.02.2011 651 * 18.02.2011
617 * 'hg status -A --rev 3:80', 10 runs: Native client total 2000 (200 per run), Java client 250 (25) 652 * 'hg status -A --rev 3:80', 10 runs: Native client total 2000 (200 per run), Java client 250 (25)
618 * 'hg log --debug', 10 runs: Native client total 2297 (229 per run), Java client 125 (12) 653 * 'hg log --debug', 10 runs: Native client total 2297 (229 per run), Java client 125 (12)
619 * 654 *
620 * 9.3.2011 (DataAccess instead of byte[] in ReflogStream.Inspector 655 * 9.3.2011 (DataAccess instead of byte[] in ReflogStream.Inspector
621 * 'hg status -A', 10 runs: Native client total 1516 (151 per run), Java client 219 (21) 656 * 'hg status -A', 10 runs: Native client total 1516 (151 per run), Java client 219 (21)
622 * 'hg status -A --rev 3:80', 10 runs: Native client total 1875 (187 per run), Java client 3187 (318) (!!! ???) 657 * 'hg status -A --rev 3:80', 10 runs: Native client total 1875 (187 per run), Java client 3187 (318) (!!! ???)
623 * 'hg log --debug', 10 runs: Native client total 2484 (248 per run), Java client 344 (34) 658 * 'hg log --debug', 10 runs: Native client total 2484 (248 per run), Java client 344 (34)
624 */ 659 */
625 public void testPerformance() throws Exception { 660 public void testPerformance() throws Exception {
626 final int runs = 10; 661 final int runs = 10;
627 final long start1 = System.currentTimeMillis(); 662 final long start1 = System.currentTimeMillis();
628 for (int i = 0; i < runs; i++) { 663 for (int i = 0; i < runs; i++) {
633 for (int i = 0; i < runs; i++) { 668 for (int i = 0; i < runs; i++) {
634 StatusCollector r = new StatusCollector(); 669 StatusCollector r = new StatusCollector();
635 new HgStatusCommand(repo).all().base(3).revision(80).execute(r); 670 new HgStatusCommand(repo).all().base(3).revision(80).execute(r);
636 } 671 }
637 final long end = System.currentTimeMillis(); 672 final long end = System.currentTimeMillis();
638 System.out.printf("'hg status -A --rev 3:80', %d runs: Native client total %d (%d per run), Java client %d (%d)\n", runs, start2-start1, (start2-start1)/runs, end-start2, (end-start2)/runs); 673 System.out.printf("'hg status -A --rev 3:80', %d runs: Native client total %d (%d per run), Java client %d (%d)\n", runs, start2 - start1, (start2 - start1) / runs, end - start2,
674 (end - start2) / runs);
639 } 675 }
640 676
641 private void report(String what, StatusCollector r) { 677 static class StatusReporter {
642 assertTrue(r.getErrors().isEmpty()); 678 private final StatusOutputParser statusParser;
643 reportNotEqual(what + "#MODIFIED", r.get(Modified), statusParser.getModified()); 679 private final ErrorCollectorExt errorCollector;
644 reportNotEqual(what + "#ADDED", r.get(Added), statusParser.getAdded()); 680
645 reportNotEqual(what + "#REMOVED", r.get(Removed), statusParser.getRemoved()); 681 public StatusReporter(ErrorCollectorExt ec, StatusOutputParser sp) {
646 reportNotEqual(what + "#CLEAN", r.get(Clean), statusParser.getClean()); 682 errorCollector = ec;
647 reportNotEqual(what + "#IGNORED", r.get(Ignored), statusParser.getIgnored()); 683 statusParser = sp;
648 reportNotEqual(what + "#MISSING", r.get(Missing), statusParser.getMissing()); 684 }
649 reportNotEqual(what + "#UNKNOWN", r.get(Unknown), statusParser.getUnknown()); 685
650 // TODO test copies 686 public void report(String what, StatusCollector r) {
651 } 687 errorCollector.assertTrue(what, r.getErrors().isEmpty());
652 688 report(what, r.asStatusRecord());
653 private void report(String what, HgStatusCollector.Record r, StatusOutputParser statusParser) { 689 }
654 reportNotEqual(what + "#MODIFIED", r.getModified(), statusParser.getModified()); 690
655 reportNotEqual(what + "#ADDED", r.getAdded(), statusParser.getAdded()); 691 public void report(String what, HgStatusCollector.Record r) {
656 reportNotEqual(what + "#REMOVED", r.getRemoved(), statusParser.getRemoved()); 692 reportNotEqual(what + "#MODIFIED", r.getModified(), statusParser.getModified());
657 reportNotEqual(what + "#CLEAN", r.getClean(), statusParser.getClean()); 693 reportNotEqual(what + "#ADDED", r.getAdded(), statusParser.getAdded());
658 reportNotEqual(what + "#IGNORED", r.getIgnored(), statusParser.getIgnored()); 694 reportNotEqual(what + "#REMOVED", r.getRemoved(), statusParser.getRemoved());
659 reportNotEqual(what + "#MISSING", r.getMissing(), statusParser.getMissing()); 695 reportNotEqual(what + "#CLEAN", r.getClean(), statusParser.getClean());
660 reportNotEqual(what + "#UNKNOWN", r.getUnknown(), statusParser.getUnknown()); 696 reportNotEqual(what + "#IGNORED", r.getIgnored(), statusParser.getIgnored());
661 List<Path> copiedKeyDiff = difference(r.getCopied().keySet(), statusParser.getCopied().keySet()); 697 reportNotEqual(what + "#MISSING", r.getMissing(), statusParser.getMissing());
662 HashMap<Path, String> copyDiff = new HashMap<Path,String>(); 698 reportNotEqual(what + "#UNKNOWN", r.getUnknown(), statusParser.getUnknown());
663 if (copiedKeyDiff.isEmpty()) { 699 List<Path> copiedKeyDiff = difference(r.getCopied().keySet(), statusParser.getCopied().keySet());
664 for (Path jk : r.getCopied().keySet()) { 700 HashMap<Path, String> copyDiff = new HashMap<Path, String>();
665 Path jv = r.getCopied().get(jk); 701 if (copiedKeyDiff.isEmpty()) {
666 if (statusParser.getCopied().containsKey(jk)) { 702 for (Path jk : r.getCopied().keySet()) {
667 Path cmdv = statusParser.getCopied().get(jk); 703 Path jv = r.getCopied().get(jk);
668 if (!jv.equals(cmdv)) { 704 if (statusParser.getCopied().containsKey(jk)) {
669 copyDiff.put(jk, jv + " instead of " + cmdv); 705 Path cmdv = statusParser.getCopied().get(jk);
706 if (!jv.equals(cmdv)) {
707 copyDiff.put(jk, jv + " instead of " + cmdv);
708 }
709 } else {
710 copyDiff.put(jk, "ERRONEOUSLY REPORTED IN JAVA");
670 } 711 }
712 }
713 }
714 errorCollector.checkThat(what + "#Non-matching 'copied' keys: ", copiedKeyDiff, equalTo(Collections.<Path> emptyList()));
715 errorCollector.checkThat(what + "#COPIED", copyDiff, equalTo(Collections.<Path, String> emptyMap()));
716 }
717
718 private <T extends Comparable<? super T>> void reportNotEqual(String what, Collection<T> l1, Collection<T> l2) {
719 // List<T> diff = difference(l1, l2);
720 // errorCollector.checkThat(what, diff, equalTo(Collections.<T>emptyList()));
721 ArrayList<T> sl1 = new ArrayList<T>(l1);
722 Collections.sort(sl1);
723 ArrayList<T> sl2 = new ArrayList<T>(l2);
724 Collections.sort(sl2);
725 errorCollector.checkThat(what, sl1, equalTo(sl2));
726 }
727
728 public static <T> List<T> difference(Collection<T> l1, Collection<T> l2) {
729 LinkedList<T> result = new LinkedList<T>(l2);
730 for (T t : l1) {
731 if (l2.contains(t)) {
732 result.remove(t);
671 } else { 733 } else {
672 copyDiff.put(jk, "ERRONEOUSLY REPORTED IN JAVA"); 734 result.add(t);
673 } 735 }
674 } 736 }
675 } 737 return result;
676 errorCollector.checkThat(what + "#Non-matching 'copied' keys: ", copiedKeyDiff, equalTo(Collections.<Path>emptyList())); 738 }
677 errorCollector.checkThat(what + "#COPIED", copyDiff, equalTo(Collections.<Path,String>emptyMap()));
678 }
679
680 private <T extends Comparable<? super T>> void reportNotEqual(String what, Collection<T> l1, Collection<T> l2) {
681 // List<T> diff = difference(l1, l2);
682 // errorCollector.checkThat(what, diff, equalTo(Collections.<T>emptyList()));
683 ArrayList<T> sl1 = new ArrayList<T>(l1);
684 Collections.sort(sl1);
685 ArrayList<T> sl2 = new ArrayList<T>(l2);
686 Collections.sort(sl2);
687 errorCollector.checkThat(what, sl1, equalTo(sl2));
688 }
689
690 private static <T> List<T> difference(Collection<T> l1, Collection<T> l2) {
691 LinkedList<T> result = new LinkedList<T>(l2);
692 for (T t : l1) {
693 if (l2.contains(t)) {
694 result.remove(t);
695 } else {
696 result.add(t);
697 }
698 }
699 return result;
700 } 739 }
701 } 740 }