# HG changeset patch # User Artem Tikhomirov # Date 1303779006 -7200 # Node ID 706bcc7cfee4ad66af4abf8e9c8cc210c7ded49a # Parent a736f42ed75bb60f475334df76415af0ab1c03bd Basic test for HgIncomingCommand. Fix RepositoryComparator for cases when whole repository is unknown. Respect freshly initialized (empty) repositories in general. diff -r a736f42ed75b -r 706bcc7cfee4 design.txt --- a/design.txt Thu Apr 21 19:16:45 2011 +0200 +++ b/design.txt Tue Apr 26 02:50:06 2011 +0200 @@ -118,3 +118,5 @@ ExecHelper('cmd', OutputParser()).run(). StatusOutputParser, LogOutputParser extends OutputParser. construct java result similar to that of cmd, compare results +Need better MethodRule than ErrorCollector for tests run as java app (to print not only MultipleFailureException, but distinct errors) +Also consider using ExternalResource and TemporaryFolder rules. diff -r a736f42ed75b -r 706bcc7cfee4 src/org/tmatesoft/hg/core/HgCloneCommand.java --- a/src/org/tmatesoft/hg/core/HgCloneCommand.java Thu Apr 21 19:16:45 2011 +0200 +++ b/src/org/tmatesoft/hg/core/HgCloneCommand.java Tue Apr 26 02:50:06 2011 +0200 @@ -123,20 +123,17 @@ private final ArrayList revisionSequence = new ArrayList(); // last visited nodes first private final LinkedList fncacheFiles = new LinkedList(); + private Internals implHelper; public WriteDownMate(File destDir) { hgDir = new File(destDir, ".hg"); - Internals i = new Internals(); - i.setStorageConfig(1, STORE | FNCACHE | DOTENCODE); - storagePathHelper = i.buildDataFilesHelper(); + implHelper = new Internals(); + implHelper.setStorageConfig(1, STORE | FNCACHE | DOTENCODE); + storagePathHelper = implHelper.buildDataFilesHelper(); } public void initEmptyRepository() throws IOException { - hgDir.mkdir(); - FileOutputStream requiresFile = new FileOutputStream(new File(hgDir, "requires")); - requiresFile.write("revlogv1\nstore\nfncache\ndotencode\n".getBytes()); - requiresFile.close(); - new File(hgDir, "store").mkdir(); // with that, hg verify says ok. + implHelper.initEmptyRepository(hgDir); } public void complete() throws IOException { diff -r a736f42ed75b -r 706bcc7cfee4 src/org/tmatesoft/hg/core/HgIncomingCommand.java --- a/src/org/tmatesoft/hg/core/HgIncomingCommand.java Thu Apr 21 19:16:45 2011 +0200 +++ b/src/org/tmatesoft/hg/core/HgIncomingCommand.java Tue Apr 26 02:50:06 2011 +0200 @@ -18,6 +18,8 @@ import java.io.IOException; import java.util.ArrayList; +import java.util.HashSet; +import java.util.Iterator; import java.util.LinkedHashSet; import java.util.LinkedList; import java.util.List; @@ -43,6 +45,7 @@ private final HgRepository localRepo; private HgRemoteRepository remoteRepo; + @SuppressWarnings("unused") private boolean includeSubrepo; private RepositoryComparator comparator; private List missingBranches; @@ -105,8 +108,10 @@ RepositoryComparator repoCompare = getComparator(context); for (BranchChain bc : getMissingBranches(context)) { List missing = repoCompare.visitBranches(bc); - assert bc.branchRoot.equals(missing.get(0)); - missing.remove(0); + HashSet common = new HashSet(); // ordering is irrelevant + repoCompare.collectKnownRoots(bc, common); + // missing could only start with common elements. Once non-common, rest is just distinct branch revision trails. + for (Iterator it = missing.iterator(); it.hasNext() && common.contains(it.next()); it.remove()) ; result.addAll(missing); } ArrayList rv = new ArrayList(result); @@ -124,10 +129,10 @@ throw new IllegalArgumentException("Delegate can't be null"); } final List common = getCommon(handler); - HgBundle changegroup = remoteRepo.getChanges(new LinkedList(common)); + HgBundle changegroup = remoteRepo.getChanges(common); try { changegroup.changes(localRepo, new HgChangelog.Inspector() { - private int localIndex; + private int localIndex = -1; // in case we start with empty repo and localIndex would not get initialized in regular way private final HgChangelog.ParentWalker parentHelper; private final ChangesetTransformer transformer; private final HgChangelog changelog; @@ -161,7 +166,7 @@ } if (comparator == null) { comparator = new RepositoryComparator(getParentHelper(), remoteRepo); - comparator.compare(context); +// comparator.compare(context); // XXX meanwhile we use distinct path to calculate common } return comparator; } @@ -182,11 +187,13 @@ } private List getCommon(Object context) throws HgException, CancelledException { +// return getComparator(context).getCommon(); final LinkedHashSet common = new LinkedHashSet(); // XXX common can be obtained from repoCompare, but at the moment it would almost duplicate work of calculateMissingBranches // once I refactor latter, common shall be taken from repoCompare. + RepositoryComparator repoCompare = getComparator(context); for (BranchChain bc : getMissingBranches(context)) { - common.add(bc.branchRoot); + repoCompare.collectKnownRoots(bc, common); } return new LinkedList(common); } diff -r a736f42ed75b -r 706bcc7cfee4 src/org/tmatesoft/hg/internal/Internals.java --- a/src/org/tmatesoft/hg/internal/Internals.java Thu Apr 21 19:16:45 2011 +0200 +++ b/src/org/tmatesoft/hg/internal/Internals.java Tue Apr 26 02:50:06 2011 +0200 @@ -18,6 +18,9 @@ import static org.tmatesoft.hg.internal.RequiresFile.*; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; import java.util.ArrayList; import java.util.List; @@ -85,4 +88,24 @@ } return filterFactories; } + + public void initEmptyRepository(File hgDir) throws IOException { + hgDir.mkdir(); + FileOutputStream requiresFile = new FileOutputStream(new File(hgDir, "requires")); + StringBuilder sb = new StringBuilder(40); + sb.append("revlogv1\n"); + if ((requiresFlags & STORE) != 0) { + sb.append("store\n"); + } + if ((requiresFlags & FNCACHE) != 0) { + sb.append("fncache\n"); + } + if ((requiresFlags & DOTENCODE) != 0) { + sb.append("dotencode\n"); + } + requiresFile.write(sb.toString().getBytes()); + requiresFile.close(); + new File(hgDir, "store").mkdir(); // with that, hg verify says ok. + } + } diff -r a736f42ed75b -r 706bcc7cfee4 src/org/tmatesoft/hg/internal/RepositoryComparator.java --- a/src/org/tmatesoft/hg/internal/RepositoryComparator.java Thu Apr 21 19:16:45 2011 +0200 +++ b/src/org/tmatesoft/hg/internal/RepositoryComparator.java Tue Apr 26 02:50:06 2011 +0200 @@ -27,6 +27,7 @@ import java.util.ListIterator; import java.util.Map; import java.util.Map.Entry; +import java.util.Set; import org.tmatesoft.hg.core.HgBadStateException; import org.tmatesoft.hg.core.HgException; @@ -237,14 +238,23 @@ } else { chainElement.branchRoot = rb.root; // dig deeper in the history, if necessary - if (!NULL.equals(rb.p1) && !localRepo.knownNode(rb.p1)) { + boolean hasP1 = !NULL.equals(rb.p1), hasP2 = !NULL.equals(rb.p2); + if (hasP1 && !localRepo.knownNode(rb.p1)) { toQuery.add(rb.p1); head2chain.put(rb.p1, chainElement.p1 = new BranchChain(rb.p1)); } - if (!NULL.equals(rb.p2) && !localRepo.knownNode(rb.p2)) { + if (hasP2 && !localRepo.knownNode(rb.p2)) { toQuery.add(rb.p2); head2chain.put(rb.p2, chainElement.p2 = new BranchChain(rb.p2)); } + if (!hasP1 && !hasP2) { + // special case, when we do incoming against blank repository, chainElement.branchRoot + // is first unknown element (revision 0). We need to add another fake BranchChain + // to fill the promise that terminal BranchChain has branchRoot that is known both locally and remotely + BranchChain fake = new BranchChain(NULL); + fake.branchRoot = NULL; + chainElement.p1 = chainElement.p2 = fake; + } } } } @@ -291,6 +301,11 @@ return branches2load; } + // root and head (and all between) are unknown for each chain element but last (terminal), which has known root (revision + // known to be locally and at remote server + // alternative would be to keep only unknown elements (so that promise of calculateMissingBranches would be 100% true), but that + // seems to complicate the method, while being useful only for the case when we ask incoming for an empty repository (i.e. + // where branch chain return all nodes, -1..tip. public static final class BranchChain { // when we construct a chain, we know head which is missing locally, hence init it right away. // as for root (branch unknown start), we might happen to have one locally, and need further digging to find out right branch start @@ -307,9 +322,15 @@ branchHead = head; } public boolean isTerminal() { - return p1 == null || p2 == null; + return p1 == null && p2 == null; // either can be null, see comment above. Terminal is only when no way to descent } + // true when this BranchChain is a branch that spans up to very start of the repository + // Thus, the only common revision is NULL, recorded in a fake BranchChain object shared between p1 and p2 + /*package-local*/ boolean isRepoStart() { + return p1 == p2 && p1 != null && p1.branchHead == p1.branchRoot && NULL.equals(p1.branchHead); + } + @Override public String toString() { return String.format("BranchChain [%s, %s]", branchRoot, branchHead); @@ -460,7 +481,7 @@ return Collections.emptyList(); } List mine = completeBranch(bc.branchRoot, bc.branchHead); - if (bc.isTerminal()) { + if (bc.isTerminal() || bc.isRepoStart()) { return mine; } List parentBranch1 = visitBranches(bc.p1); @@ -495,4 +516,18 @@ return rv; } + public void collectKnownRoots(BranchChain bc, Set result) { + if (bc == null) { + return; + } + if (bc.isTerminal()) { + result.add(bc.branchRoot); + return; + } + if (bc.isRepoStart()) { + return; + } + collectKnownRoots(bc.p1, result); + collectKnownRoots(bc.p2, result); + } } diff -r a736f42ed75b -r 706bcc7cfee4 src/org/tmatesoft/hg/internal/RevlogStream.java --- a/src/org/tmatesoft/hg/internal/RevlogStream.java Thu Apr 21 19:16:45 2011 +0200 +++ b/src/org/tmatesoft/hg/internal/RevlogStream.java Tue Apr 26 02:50:06 2011 +0200 @@ -319,6 +319,11 @@ ArrayList resOffsets = new ArrayList(); DataAccess da = getIndexStream(); try { + if (da.isEmpty()) { + // do not fail with exception if stream is empty, it's likely intentional + baseRevisions = new int[0]; + return; + } int versionField = da.readInt(); da.readInt(); // just to skip next 4 bytes of offset + flags final int INLINEDATA = 1 << 16; diff -r a736f42ed75b -r 706bcc7cfee4 src/org/tmatesoft/hg/repo/HgRemoteRepository.java --- a/src/org/tmatesoft/hg/repo/HgRemoteRepository.java Thu Apr 21 19:16:45 2011 +0200 +++ b/src/org/tmatesoft/hg/repo/HgRemoteRepository.java Tue Apr 26 02:50:06 2011 +0200 @@ -303,6 +303,9 @@ } /* + * XXX need to describe behavior when roots arg is empty; our RepositoryComparator code currently returns empty lists when + * no common elements found, which in turn means we need to query changes starting with NULL nodeid. + * * WireProtocol wiki: roots = a list of the latest nodes on every service side changeset branch that both the client and server know about. * * Perhaps, shall be named 'changegroup' @@ -316,9 +319,10 @@ * as one may expect according to http://mercurial.selenic.com/wiki/BundleFormat) */ public HgBundle getChanges(List roots) throws HgException { - StringBuilder sb = new StringBuilder(20 + roots.size() * 41); + List _roots = roots.isEmpty() ? Collections.singletonList(Nodeid.NULL) : roots; + StringBuilder sb = new StringBuilder(20 + _roots.size() * 41); sb.append("roots="); - for (Nodeid n : roots) { + for (Nodeid n : _roots) { sb.append(n.toString()); sb.append('+'); } diff -r a736f42ed75b -r 706bcc7cfee4 src/org/tmatesoft/hg/repo/HgRepository.java --- a/src/org/tmatesoft/hg/repo/HgRepository.java Thu Apr 21 19:16:45 2011 +0200 +++ b/src/org/tmatesoft/hg/repo/HgRepository.java Tue Apr 26 02:50:06 2011 +0200 @@ -124,7 +124,7 @@ public HgChangelog getChangelog() { if (this.changelog == null) { String storagePath = repoPathHelper.rewrite("00changelog.i"); - RevlogStream content = resolve(Path.create(storagePath)); + RevlogStream content = resolve(Path.create(storagePath), true); this.changelog = new HgChangelog(this, content); } return this.changelog; @@ -132,7 +132,7 @@ public HgManifest getManifest() { if (this.manifest == null) { - RevlogStream content = resolve(Path.create(repoPathHelper.rewrite("00manifest.i"))); + RevlogStream content = resolve(Path.create(repoPathHelper.rewrite("00manifest.i")), true); this.manifest = new HgManifest(this, content); } return this.manifest; @@ -154,7 +154,7 @@ public HgDataFile getFileNode(String path) { String nPath = normalizePath.rewrite(path); String storagePath = dataPathHelper.rewrite(nPath); - RevlogStream content = resolve(Path.create(storagePath)); + RevlogStream content = resolve(Path.create(storagePath), false); Path p = Path.create(nPath); if (content == null) { return new HgDataFile(this, p); @@ -164,7 +164,7 @@ public HgDataFile getFileNode(Path path) { String storagePath = dataPathHelper.rewrite(path.toString()); - RevlogStream content = resolve(Path.create(storagePath)); + RevlogStream content = resolve(Path.create(storagePath), false); // XXX no content when no file? or HgDataFile.exists() to detect that? if (content == null) { return new HgDataFile(this, path); @@ -220,7 +220,7 @@ * Perhaps, should be separate interface, like ContentLookup * path - repository storage path (i.e. one usually with .i or .d) */ - /*package-local*/ RevlogStream resolve(Path path) { + /*package-local*/ RevlogStream resolve(Path path, boolean shallFakeNonExistent) { final SoftReference ref = streamsCache.get(path); RevlogStream cached = ref == null ? null : ref.get(); if (cached != null) { @@ -231,6 +231,16 @@ RevlogStream s = new RevlogStream(dataAccess, f); streamsCache.put(path, new SoftReference(s)); return s; + } else { + if (shallFakeNonExistent) { + try { + File fake = File.createTempFile(f.getName(), null); + fake.deleteOnExit(); + return new RevlogStream(dataAccess, fake); + } catch (IOException ex) { + ex.printStackTrace(); // FIXME report in debug + } + } } return null; // XXX empty stream instead? } diff -r a736f42ed75b -r 706bcc7cfee4 test/org/tmatesoft/hg/test/Configuration.java --- a/test/org/tmatesoft/hg/test/Configuration.java Thu Apr 21 19:16:45 2011 +0200 +++ b/test/org/tmatesoft/hg/test/Configuration.java Tue Apr 26 02:50:06 2011 +0200 @@ -19,6 +19,8 @@ import static org.junit.Assert.*; import java.io.File; +import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -36,6 +38,8 @@ private static Configuration inst; private final File root; private final HgLookup lookup; + private File tempDir; + private List remoteServers; private Configuration(File reposRoot) { root = reposRoot; @@ -65,7 +69,27 @@ return rv; } + // easy override for manual test runs + public void remoteServers(String... keys) { + remoteServers = Arrays.asList(keys); + } + public List allRemote() throws Exception { - return Collections.singletonList(lookup.detectRemote("hg4j-gc", null)); + if (remoteServers == null) { + remoteServers = Collections.singletonList("hg4j-gc"); // just a default + } + ArrayList rv = new ArrayList(remoteServers.size()); + for (String key : remoteServers) { + rv.add(lookup.detectRemote(key, null)); + } + return rv; + } + + public File getTempDir() { + if (tempDir == null) { + String td = System.getProperty("hg4j.tests.tmpdir", System.getProperty("java.io.tmpdir")); + tempDir = new File(td); + } + return tempDir; } } diff -r a736f42ed75b -r 706bcc7cfee4 test/org/tmatesoft/hg/test/OutputParser.java --- a/test/org/tmatesoft/hg/test/OutputParser.java Thu Apr 21 19:16:45 2011 +0200 +++ b/test/org/tmatesoft/hg/test/OutputParser.java Tue Apr 26 02:50:06 2011 +0200 @@ -24,4 +24,10 @@ public interface OutputParser { public void parse(CharSequence seq); + + public class Stub implements OutputParser { + public void parse(CharSequence seq) { + // no-op + } + } } diff -r a736f42ed75b -r 706bcc7cfee4 test/org/tmatesoft/hg/test/TestClone.java --- a/test/org/tmatesoft/hg/test/TestClone.java Thu Apr 21 19:16:45 2011 +0200 +++ b/test/org/tmatesoft/hg/test/TestClone.java Tue Apr 26 02:50:06 2011 +0200 @@ -22,8 +22,7 @@ import java.util.LinkedList; import java.util.List; -import junit.framework.Assert; - +import org.hamcrest.CoreMatchers; import org.junit.Rule; import org.tmatesoft.hg.core.HgCloneCommand; import org.tmatesoft.hg.repo.HgRemoteRepository; @@ -38,9 +37,10 @@ @Rule public ErrorCollectorExt errorCollector = new ErrorCollectorExt(); - public static void main(String[] args) throws Exception { + public static void main(String[] args) throws Throwable { TestClone t = new TestClone(); t.testSimpleClone(); + t.errorCollector.verify(); } public TestClone() { @@ -48,7 +48,7 @@ public void testSimpleClone() throws Exception { int x = 0; - final File tempDir = new File(System.getProperty("java.io.tmpdir")); + final File tempDir = Configuration.get().getTempDir(); for (HgRemoteRepository hgRemote : Configuration.get().allRemote()) { HgCloneCommand cmd = new HgCloneCommand(); cmd.source(hgRemote); @@ -63,21 +63,16 @@ } private void verify(HgRemoteRepository hgRemote, File dest) throws Exception { - OutputParser noop = new OutputParser() { - public void parse(CharSequence seq) { - // no-op - } - }; - ExecHelper eh = new ExecHelper(noop, dest); + ExecHelper eh = new ExecHelper(new OutputParser.Stub(), dest); eh.run("hg", "verify"); - Assert.assertEquals(0, eh.getExitValue()); + errorCollector.checkThat("Verify", eh.getExitValue(), CoreMatchers.equalTo(0)); eh.run("hg", "out", hgRemote.getLocation()); - Assert.assertEquals(1, eh.getExitValue()); + errorCollector.checkThat("Outgoing", eh.getExitValue(), CoreMatchers.equalTo(1)); eh.run("hg", "in", hgRemote.getLocation()); - Assert.assertEquals(1, eh.getExitValue()); + errorCollector.checkThat("Incoming", eh.getExitValue(), CoreMatchers.equalTo(1)); } - private static void rmdir(File dest) throws IOException { + static void rmdir(File dest) throws IOException { LinkedList queue = new LinkedList(); queue.addAll(Arrays.asList(dest.listFiles())); while (!queue.isEmpty()) { diff -r a736f42ed75b -r 706bcc7cfee4 test/org/tmatesoft/hg/test/TestHistory.java --- a/test/org/tmatesoft/hg/test/TestHistory.java Thu Apr 21 19:16:45 2011 +0200 +++ b/test/org/tmatesoft/hg/test/TestHistory.java Tue Apr 26 02:50:06 2011 +0200 @@ -20,6 +20,7 @@ import static org.hamcrest.CoreMatchers.is; import static org.junit.Assert.assertTrue; +import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.Iterator; @@ -122,23 +123,35 @@ } } - private void report(String what, List r, boolean reverseConsoleResults) { + private void report(String what, List r, boolean reverseConsoleResult) { final List consoleResult = changelogParser.getResult(); - if (reverseConsoleResults) { + report(what, r, consoleResult, reverseConsoleResult, errorCollector); + } + + static void report(String what, List hg4jResult, List consoleResult, boolean reverseConsoleResult, ErrorCollectorExt errorCollector) { + consoleResult = new ArrayList(consoleResult); // need a copy in case callee would use result again + if (reverseConsoleResult) { Collections.reverse(consoleResult); } + errorCollector.checkThat(what + ". Number of changeset reported didn't match", consoleResult.size(), equalTo(hg4jResult.size())); Iterator consoleResultItr = consoleResult.iterator(); - for (HgChangeset cs : r) { + for (HgChangeset cs : hg4jResult) { + if (!consoleResultItr.hasNext()) { + errorCollector.addError(new AssertionError("Ran out of console results while there are still hg4j results")); + break; + } Record cr = consoleResultItr.next(); int x = cs.getRevision() == cr.changesetIndex ? 0x1 : 0; x |= cs.getDate().equals(cr.date) ? 0x2 : 0; x |= cs.getNodeid().toString().equals(cr.changesetNodeid) ? 0x4 : 0; x |= cs.getUser().equals(cr.user) ? 0x8 : 0; - x |= cs.getComment().equals(cr.description) ? 0x10 : 0; - errorCollector.checkThat(String.format(what + ". Error in %d hg4j rev comparing to %d cmdline's.", cs.getRevision(), cr.changesetIndex), x, equalTo(0x1f)); + // need to do trim() on comment because command-line template does, and there are + // repositories that have couple of newlines in the end of the comment (e.g. hello sample repo from the book) + x |= cs.getComment().trim().equals(cr.description) ? 0x10 : 0; + errorCollector.checkThat(String.format(what + ". Mismatch (0x%x) in %d hg4j rev comparing to %d cmdline's.", x, cs.getRevision(), cr.changesetIndex), x, equalTo(0x1f)); consoleResultItr.remove(); } - errorCollector.checkThat(what + ". Insufficient results from Java ", consoleResultItr.hasNext(), equalTo(false)); + errorCollector.checkThat(what + ". Unprocessed results in console left (insufficient from hg4j)", consoleResultItr.hasNext(), equalTo(false)); } public void testPerformance() throws Exception { diff -r a736f42ed75b -r 706bcc7cfee4 test/org/tmatesoft/hg/test/TestIncoming.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/org/tmatesoft/hg/test/TestIncoming.java Tue Apr 26 02:50:06 2011 +0200 @@ -0,0 +1,137 @@ +/* + * Copyright (c) 2011 TMate Software Ltd + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * For information on how to redistribute this software under + * the terms of a license other than GNU General Public License + * contact TMate Software at support@hg4j.com + */ +package org.tmatesoft.hg.test; + +import static org.hamcrest.CoreMatchers.equalTo; +import static org.tmatesoft.hg.internal.RequiresFile.*; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; + +import org.junit.Assert; +import org.junit.Rule; +import org.junit.Test; +import org.tmatesoft.hg.core.HgChangeset; +import org.tmatesoft.hg.core.HgIncomingCommand; +import org.tmatesoft.hg.core.HgLogCommand; +import org.tmatesoft.hg.core.Nodeid; +import org.tmatesoft.hg.internal.Internals; +import org.tmatesoft.hg.repo.HgLookup; +import org.tmatesoft.hg.repo.HgRemoteRepository; +import org.tmatesoft.hg.repo.HgRepository; + +/** + * + * @author Artem Tikhomirov + * @author TMate Software Ltd. + */ +public class TestIncoming { + + @Rule + public ErrorCollectorExt errorCollector = new ErrorCollectorExt(); + + public static void main(String[] args) throws Throwable { + Configuration.get().remoteServers("http://localhost:8000/"); + TestIncoming t = new TestIncoming(); + t.testSimple(); + t.errorCollector.verify(); + } + + public TestIncoming() { +// Configuration.get().remoteServers("http://localhost:8000/"); + } + + @Test + public void testSimple() throws Exception { + int x = 0; + HgLookup lookup = new HgLookup(); + for (HgRemoteRepository hgRemote : Configuration.get().allRemote()) { + File dest = initEmptyTempRepo("test-incoming-" + x++); + HgRepository localRepo = lookup.detect(dest); + // Idea: + // hg in, hg4j in, compare + // hg pull total/2 + // hg in, hg4j in, compare + List incoming = runAndCompareIncoming(localRepo, hgRemote); + Assert.assertTrue("Need remote repository of reasonable size to test incoming command for partially filled case", incoming.size() > 5); + // + Nodeid median = incoming.get(incoming.size() / 2); + System.out.println("About to pull up to revision " + median.shortNotation()); + new ExecHelper(new OutputParser.Stub(), dest).run("hg", "pull", "-r", median.toString(), hgRemote.getLocation()); + // + // shall re-read repository to pull up new changes + localRepo = lookup.detect(dest); + runAndCompareIncoming(localRepo, hgRemote); + } + } + + private List runAndCompareIncoming(HgRepository localRepo, HgRemoteRepository hgRemote) throws Exception { + // need new command instance as subsequence exec[Lite|Full] on the same command would yield same result, + // regardless of the pull in between. + HgIncomingCommand cmd = new HgIncomingCommand(localRepo); + cmd.against(hgRemote); + HgLogCommand.CollectHandler collector = new HgLogCommand.CollectHandler(); + LogOutputParser outParser = new LogOutputParser(true); + ExecHelper eh = new ExecHelper(outParser, new File(localRepo.getLocation())); + cmd.executeFull(collector); + eh.run("hg", "incoming", "--debug", hgRemote.getLocation()); + List liteResult = cmd.executeLite(null); + report(collector, outParser, liteResult); + return liteResult; + } + + private void report(HgLogCommand.CollectHandler collector, LogOutputParser outParser, List liteResult) { + TestHistory.report("hg in - against blank repo", collector.getChanges(), outParser.getResult(), false, errorCollector); + // + ArrayList expected = new ArrayList(outParser.getResult().size()); + for (LogOutputParser.Record r : outParser.getResult()) { + Nodeid nid = Nodeid.fromAscii(r.changesetNodeid); + expected.add(nid); + } + checkNodeids("hg vs execLite:", liteResult, expected, errorCollector); + // + expected = new ArrayList(outParser.getResult().size()); + for (HgChangeset cs : collector.getChanges()) { + expected.add(cs.getNodeid()); + } + checkNodeids("execFull vs execLite:", liteResult, expected, errorCollector); + } + + static void checkNodeids(String what, List liteResult, List expected, ErrorCollectorExt errorCollector) { + HashSet set = new HashSet(liteResult); + for (Nodeid nid : expected) { + boolean removed = set.remove(nid); + errorCollector.checkThat(what + " Missing " + nid.shortNotation() + " in HgIncomingCommand.execLite result", removed, equalTo(true)); + } + errorCollector.checkThat(what + " Superfluous cset reported by HgIncomingCommand.execLite", set.isEmpty(), equalTo(true)); + } + + static File initEmptyTempRepo(String dirName) throws IOException { + File dest = new File(Configuration.get().getTempDir(), dirName); + if (dest.exists()) { + TestClone.rmdir(dest); + } + dest.mkdirs(); + Internals implHelper = new Internals(); + implHelper.setStorageConfig(1, STORE | FNCACHE | DOTENCODE); + implHelper.initEmptyRepository(new File(dest, ".hg")); + return dest; + } +}