/*
* Copyright (c) 2011-2013 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.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.tmatesoft.hg.core.HgIterateDirection.NewToOld;
import static org.tmatesoft.hg.core.HgIterateDirection.OldToNew;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import org.junit.Assert;
import org.junit.Rule;
import org.junit.Test;
import org.tmatesoft.hg.core.HgCallbackTargetException;
import org.tmatesoft.hg.core.HgChangeset;
import org.tmatesoft.hg.core.HgChangesetHandler;
import org.tmatesoft.hg.core.HgChangesetTreeHandler;
import org.tmatesoft.hg.core.HgFileRenameHandlerMixin;
import org.tmatesoft.hg.core.HgFileRevision;
import org.tmatesoft.hg.core.HgIterateDirection;
import org.tmatesoft.hg.core.HgLogCommand;
import org.tmatesoft.hg.core.HgLogCommand.CollectHandler;
import org.tmatesoft.hg.core.Nodeid;
import org.tmatesoft.hg.internal.AdapterPlug;
import org.tmatesoft.hg.repo.HgLookup;
import org.tmatesoft.hg.repo.HgRepository;
import org.tmatesoft.hg.repo.HgRuntimeException;
import org.tmatesoft.hg.test.LogOutputParser.Record;
import org.tmatesoft.hg.util.Adaptable;
import org.tmatesoft.hg.util.CancelSupport;
import org.tmatesoft.hg.util.CancelledException;
import org.tmatesoft.hg.util.Pair;
import org.tmatesoft.hg.util.Path;
/**
*
* @author Artem Tikhomirov
* @author TMate Software Ltd.
*/
public class TestHistory {
@Rule
public ErrorCollectorExt errorCollector = new ErrorCollectorExt();
private HgRepository repo;
private final ExecHelper eh;
private LogOutputParser changelogParser;
public static void main(String[] args) throws Throwable {
TestHistory th = new TestHistory(new HgLookup().detectFromWorkingDir());
th.testCompleteLog();
th.testFollowHistory();
th.errorCollector.verify();
// th.testPerformance();
th.testOriginalTestLogRepo();
th.testUsernames();
th.testBranches();
//
th.errorCollector.verify();
}
public TestHistory() {
eh = new ExecHelper(changelogParser = new LogOutputParser(true), null);
}
private TestHistory(HgRepository hgRepo) {
this();
repo = hgRepo;
eh.cwd(repo.getWorkingDir());
}
@Test
public void testCompleteLog() throws Exception {
if (repo == null) {
repo = Configuration.get().own();
eh.cwd(repo.getWorkingDir());
}
changelogParser.reset();
eh.run("hg", "log", "--debug");
List<HgChangeset> r = new HgLogCommand(repo).execute();
report("hg log - COMPLETE REPO HISTORY", r, true);
r = new HgLogCommand(repo).order(NewToOld).execute();
report("hg log - COMPLETE REPO HISTORY, FROM NEW TO OLD", r, false);
}
@Test
public void testFollowHistory() throws Exception {
if (repo == null) {
repo = Configuration.get().own();
eh.cwd(repo.getWorkingDir());
}
final Path f = Path.create("cmdline/org/tmatesoft/hg/console/Remote.java");
assertTrue(repo.getFileNode(f).exists());
changelogParser.reset();
eh.run("hg", "log", "--debug", "--follow", f.toString());
CollectWithRenameHandler h = new CollectWithRenameHandler();
new HgLogCommand(repo).file(f, true).execute(h);
errorCollector.assertEquals(1, h.rh.renames.size());
HgFileRevision from = h.rh.renames.get(0).first();
boolean fromMatched = "src/com/tmate/hgkit/console/Remote.java".equals(from.getPath().toString());
String what = "hg log - FOLLOW FILE HISTORY";
errorCollector.checkThat(what + "#copyReported ", h.rh.copyReported, is(true));
errorCollector.checkThat(what + "#copyFromMatched", fromMatched, is(true));
//
// cmdline always gives in changesets in order from newest (bigger rev number) to oldest.
// LogCommand does other way round, from oldest to newest, follewed by revisions of copy source, if any
// (apparently older than oldest of the copy target). Hence need to sort Java results according to rev numbers
final LinkedList<HgChangeset> sorted = new LinkedList<HgChangeset>(h.getChanges());
Collections.sort(sorted, new Comparator<HgChangeset>() {
public int compare(HgChangeset cs1, HgChangeset cs2) {
return cs1.getRevisionIndex() < cs2.getRevisionIndex() ? 1 : -1;
}
});
report(what, sorted, false);
}
@Test
public void testChangesetTree() throws Exception {
repo = Configuration.get().find("branches-1");
final String fname = "file1";
assertTrue("[sanity]", repo.getFileNode(fname).exists());
eh.run("hg", "log", "--debug", fname, "--cwd", repo.getLocation());
TreeCollectHandler h = new TreeCollectHandler(false);
new HgLogCommand(repo).file(fname, false).execute(h);
// since we use TreeCollectHandler with natural order (older to newer), shall reverse console result in report()
report("execute with HgChangesetTreeHandler(follow == false)", h.getResult(), true);
}
/**
* Few tests to check newly introduced followAncestry parameter to HgLogCommand:
* followRename: true, followAncestry: false
* followRename: false, followAncestry: true
* followRename: true, followAncestry: true
* Perhaps, shall be merged with {@link #testFollowHistory()}
*/
@Test
public void testFollowRenamesNotAncestry() throws Exception {
repo = Configuration.get().find("log-follow");
final String fname1 = "file1_a";
final String fname2 = "file1_b";
assertTrue("[sanity]", repo.getFileNode(fname2).exists());
// no --follow, but two names we know have been the same file (fname1 renamed to fname2)
// sequentially gives follow rename semantics without ancestry
eh.run("hg", "log", "--debug", fname2, fname1, "--cwd", repo.getLocation());
CollectWithRenameHandler h = new CollectWithRenameHandler();
new HgLogCommand(repo).file(fname2, true, false).execute(h);
errorCollector.assertEquals(1, h.rh.renames.size());
Pair<HgFileRevision, HgFileRevision> rename = h.rh.renames.get(0);
errorCollector.assertEquals(fname1, rename.first().getPath().toString());
errorCollector.assertEquals(fname2, rename.second().getPath().toString());
// Ensure rename info came in the right moment
errorCollector.assertEquals(1, h.lastChangesetReportedAtRename.size());
// Command iterates old to new, rename comes after last fname1 revision. Since we don't follow
// ancestry, it's the very last revision in fname1 history
String lastRevOfFname1 = "369c0882d477c11424a62eb4b791e86d1d4b6769";
errorCollector.assertEquals(lastRevOfFname1, h.lastChangesetReportedAtRename.get(0).getNodeid().toString());
report("HgChangesetHandler(renames: true, ancestry:false)", h.getChanges(), true);
//
// Direction
h = new CollectWithRenameHandler();
new HgLogCommand(repo).file(fname2, true, false).order(NewToOld).execute(h);
// Identical rename shall be reported, at the same moment
errorCollector.assertEquals(1, h.rh.renames.size());
rename = h.rh.renames.get(0);
errorCollector.assertEquals(fname1, rename.first().getPath().toString());
errorCollector.assertEquals(fname2, rename.second().getPath().toString());
errorCollector.assertEquals(1, h.lastChangesetReportedAtRename.size());
// new to old, recently reported would be the very first revision fname2 pops up
String firstRevOfFname2 = "27e7a69373b74d42e75f3211e56510ff17d01370";
errorCollector.assertEquals(firstRevOfFname2, h.lastChangesetReportedAtRename.get(0).getNodeid().toString());
report("HgChangesetHandler(renames: true, ancestry:false)", h.getChanges(), false);
//
// TreeChangeHandler - in #testChangesetTreeFollowRenamesNotAncestry
}
@Test
public void testChangesetTreeFollowRenamesNotAncestry() throws Exception {
repo = Configuration.get().find("log-follow");
final String fname1 = "file1_a";
final String fname2 = "file1_b";
assertTrue("[sanity]", repo.getFileNode(fname2).exists());
// no --follow, but two names we know have been the same file (fname1 renamed to fname2)
// sequentially gives follow rename semantics without ancestry
eh.run("hg", "log", "--debug", fname2, fname1, "--cwd", repo.getLocation());
TreeCollectHandler h = new TreeCollectHandler(true);
RenameCollector rh = new RenameCollector(h);
// can't check that prev revision is in parent because there are forks in
// file history (e.g. rev2 and rev3 (that comes next) both have rev0 as their parent
// and followAncestry is false
// h.checkPrevInParents = true;
new HgLogCommand(repo).file(fname2, true, false).execute(h);
errorCollector.assertEquals(1, rh.renames.size());
Pair<HgFileRevision, HgFileRevision> rename = rh.renames.get(0);
errorCollector.assertEquals(fname1, rename.first().getPath().toString());
errorCollector.assertEquals(fname2, rename.second().getPath().toString());
report("HgChangesetTreeHandler(renames: true, ancestry:false)", h.getResult(), false);
// Direction
h = new TreeCollectHandler(false);
rh = new RenameCollector(h);
// h.checkPrevInChildren = true; see above
new HgLogCommand(repo).file(fname2, true, false).order(NewToOld).execute(h);
errorCollector.assertEquals(1, rh.renames.size());
rename = rh.renames.get(0);
errorCollector.assertEquals(fname1, rename.first().getPath().toString());
errorCollector.assertEquals(fname2, rename.second().getPath().toString());
report("HgChangesetTreeHandler(renames: true, ancestry:false)", h.getResult(), false);
}
@Test
public void testFollowAncestryNotRenames() throws Exception {
repo = Configuration.get().find("log-follow");
final String fname2 = "file1_b";
assertTrue("[sanity]", repo.getFileNode(fname2).exists());
final List<Record> fname2Follow = getAncestryWithoutRenamesFromCmdline(fname2);
CollectWithRenameHandler h = new CollectWithRenameHandler();
new HgLogCommand(repo).file(fname2, false, true).execute(h);
// renames are reported regardless of followRenames parameter, but
// solely based on HgFileRenameHandlerMixin
errorCollector.assertEquals(1, h.rh.renames.size());
report("HgChangesetHandler(renames: false, ancestry:true)", h.getChanges(), fname2Follow, true, errorCollector);
//
// Direction
h = new CollectWithRenameHandler();
new HgLogCommand(repo).file(fname2, false, true).order(NewToOld).execute(h);
report("HgChangesetHandler(renames: false, ancestry:true)", h.getChanges(), fname2Follow, false/*!!!*/, errorCollector);
//
// TreeChangeHandler - in #testChangesetTreeFollowAncestryNotRenames
}
@Test
public void testChangesetTreeFollowAncestryNotRenames() throws Exception {
repo = Configuration.get().find("log-follow");
final String fname2 = "file1_b";
final List<Record> fname2Follow = getAncestryWithoutRenamesFromCmdline(fname2);
TreeCollectHandler h = new TreeCollectHandler(false);
h.checkPrevInParents = true;
new HgLogCommand(repo).file(fname2, false, true).execute(h);
report("HgChangesetTreeHandler(renames: false, ancestry:true)", h.getResult(), fname2Follow, true, errorCollector);
// Direction
h = new TreeCollectHandler(false);
h.checkPrevInChildren = true;
new HgLogCommand(repo).file(fname2, false, true).order(NewToOld).execute(h);
report("HgChangesetTreeHandler(renames: false, ancestry:true)", h.getResult(), fname2Follow, false, errorCollector);
}
private List<Record> getAncestryWithoutRenamesFromCmdline(String fname2) throws Exception {
// to get "followed" history of fname2 only (without fname1 origin),
// get the complete history and keep there only elements that match fname2 own history
eh.run("hg", "log", "--debug", "--follow", fname2, "--cwd", repo.getLocation());
final List<Record> fname2Follow = new LinkedList<LogOutputParser.Record>(changelogParser.getResult());
changelogParser.reset();
eh.run("hg", "log", "--debug", fname2, "--cwd", repo.getLocation());
// fname2Follow.retainAll(changelogParser.getResult());
for (Iterator<Record> it = fname2Follow.iterator(); it.hasNext();) {
Record r = it.next();
boolean belongsToSoleFname2History = false;
for (Record d : changelogParser.getResult()) {
if (d.changesetIndex == r.changesetIndex) {
assert d.changesetNodeid.equals(r.changesetNodeid) : "[sanity]";
belongsToSoleFname2History = true;
break;
}
}
if (!belongsToSoleFname2History) {
it.remove();
}
}
return fname2Follow;
}
/**
* output identical to that of "hg log --follow"
*/
@Test
public void testFollowBothRenameAndAncestry() throws Exception {
repo = Configuration.get().find("log-follow");
final String fname1 = "file1_a";
final String fname2 = "file1_b";
assertTrue("[sanity]", repo.getFileNode(fname2).exists());
eh.run("hg", "log", "--debug", "--follow", fname2, "--cwd", repo.getLocation());
CollectWithRenameHandler h = new CollectWithRenameHandler();
new HgLogCommand(repo).file(fname2, true, true).execute(h);
errorCollector.assertEquals(1, h.rh.renames.size());
Pair<HgFileRevision, HgFileRevision> rename = h.rh.renames.get(0);
errorCollector.assertEquals(fname1, rename.first().getPath().toString());
errorCollector.assertEquals(fname2, rename.second().getPath().toString());
// Ensure rename info came in the right moment
errorCollector.assertEquals(1, h.lastChangesetReportedAtRename.size());
String fname1BranchRevision = "6e668ff2940acb250c8627843f8116166fe5d5cd";
errorCollector.assertEquals(fname1BranchRevision, h.lastChangesetReportedAtRename.get(0).getNodeid().toString());
// finally, match output
report("HgChangesetHandler(renames: true, ancestry:true)", h.getChanges(), true);
//
// Switch direction and compare, order shall match that from console
h = new CollectWithRenameHandler();
new HgLogCommand(repo).file(fname2, true, true).order(NewToOld).execute(h);
// Identical rename event shall be reported
errorCollector.assertEquals(1, h.rh.renames.size());
rename = h.rh.renames.get(0);
errorCollector.assertEquals(fname1, rename.first().getPath().toString());
errorCollector.assertEquals(fname2, rename.second().getPath().toString());
// new to old, recently reported would be the very first revision fname2 pops up
String firstRevOfFname2 = "27e7a69373b74d42e75f3211e56510ff17d01370";
errorCollector.assertEquals(firstRevOfFname2, h.lastChangesetReportedAtRename.get(0).getNodeid().toString());
report("HgChangesetHandler(renames: true, ancestry:true)", h.getChanges(), false /*do not reorder console results !!!*/);
//
// TreeChangeHandler in #testChangesetTreeFollowRenameAndAncestry
}
@Test
public void testChangesetTreeFollowRenameAndAncestry() throws Exception {
repo = Configuration.get().find("log-follow");
final String fname = "file1_b";
assertTrue("[sanity]", repo.getFileNode(fname).exists());
eh.run("hg", "log", "--debug", "--follow", fname, "--cwd", repo.getLocation());
TreeCollectHandler h = new TreeCollectHandler(true);
RenameCollector rh = new RenameCollector(h);
h.checkPrevInParents = true;
new HgLogCommand(repo).file(fname, true, true).execute(h);
assertEquals(1, h.getAdapterUse(HgFileRenameHandlerMixin.class));
report("execute with HgChangesetTreeHandler(follow == true)", h.getResult(), false);
assertEquals(1, rh.renames.size());
assertEquals(Path.create(fname), rh.renames.get(0).second().getPath());
}
/**
* Ensure {@link HgFileRenameHandlerMixin} is always notified, even
* if followRename is false.
* Shall check:
* both {@link HgLogCommand#execute(HgChangesetHandler)} and {@link HgLogCommand#execute(HgChangesetTreeHandler)}
* and for both iteration directions in each case
*/
@Test
public void testRenameHandlerNotifiedEvenIfNotFollowRename() throws Exception {
repo = Configuration.get().find("log-follow");
final String fname1 = "file1_a";
final String fname2 = "file1_b";
final String fnameNoRename = "file2";
assertTrue("[sanity]", repo.getFileNode(fnameNoRename).exists());
// first, check that file without renames doesn't report any accidentally
CollectWithRenameHandler h1 = new CollectWithRenameHandler();
HgLogCommand cmd = new HgLogCommand(repo).file(fnameNoRename, false, false);
cmd.execute(h1);
errorCollector.assertEquals(0, h1.rh.renames.size());
TreeCollectHandler h2 = new TreeCollectHandler(false);
RenameCollector rh = new RenameCollector(h2);
cmd.execute(h2);
errorCollector.assertEquals(0, rh.renames.size());
// check default iterate direction
cmd = new HgLogCommand(repo).file(fname2, false, false);
cmd.execute(h1 = new CollectWithRenameHandler());
errorCollector.assertEquals(1, h1.rh.renames.size());
assertRename(fname1, fname2, h1.rh.renames.get(0));
h2 = new TreeCollectHandler(false);
rh = new RenameCollector(h2);
cmd.execute(h2);
errorCollector.assertEquals(1, rh.renames.size());
assertRename(fname1, fname2, rh.renames.get(0));
eh.run("hg", "log", "--debug", fname2, "--cwd", repo.getLocation());
report("HgChangesetHandler+RenameHandler with followRenames = false, default iteration order", h1.getChanges(), true);
report("HgChangesetTreeHandler+RenameHandler with followRenames = false, default iteration order", h2.getResult(), true);
//
// Now, check that iteration in opposite direction (new to old)
// still reports renames (and correct revisions, too)
cmd.order(HgIterateDirection.NewToOld);
cmd.execute(h1 = new CollectWithRenameHandler());
errorCollector.assertEquals(1, h1.rh.renames.size());
assertRename(fname1, fname2, h1.rh.renames.get(0));
h2 = new TreeCollectHandler(false);
rh = new RenameCollector(h2);
cmd.execute(h2);
errorCollector.assertEquals(1, rh.renames.size());
assertRename(fname1, fname2, rh.renames.get(0));
report("HgChangesetHandler+RenameHandler with followRenames = false, new2old iteration order", h1.getChanges(), false);
report("HgChangesetTreeHandler+RenameHandler with followRenames = false, new2old iteration order", h2.getResult(), false);
}
@Test
public void testFollowMultipleRenames() throws Exception {
repo = Configuration.get().find("log-renames");
String fname = "a";
eh.run("hg", "log", "--debug", "--follow", fname, "--cwd", repo.getLocation());
HgLogCommand cmd = new HgLogCommand(repo);
cmd.file(fname, true, true);
CollectWithRenameHandler h1;
//
cmd.order(OldToNew).execute(h1 = new CollectWithRenameHandler());
errorCollector.assertEquals(2, h1.rh.renames.size());
report("Follow a->c->b, old2new:", h1.getChanges(), true);
//
cmd.order(NewToOld).execute(h1 = new CollectWithRenameHandler());
errorCollector.assertEquals(2, h1.rh.renames.size());
report("Follow a->c->b, new2old:", h1.getChanges(), false);
//
//
TreeCollectHandler h2 = new TreeCollectHandler(false);
RenameCollector rh = new RenameCollector(h2);
cmd.order(OldToNew).execute(h2);
errorCollector.assertEquals(2, rh.renames.size());
report("Tree. Follow a->c->b, old2new:", h2.getResult(), true);
//
h2 = new TreeCollectHandler(false);
rh = new RenameCollector(h2);
cmd.order(NewToOld).execute(h2);
errorCollector.assertEquals(2, rh.renames.size());
report("Tree. Follow a->c->b, new2old:", h2.getResult(), false);
}
private void assertRename(String fnameFrom, String fnameTo, Pair<HgFileRevision, HgFileRevision> rename) {
errorCollector.assertEquals(fnameFrom, rename.first().getPath().toString());
errorCollector.assertEquals(fnameTo, rename.second().getPath().toString());
}
/**
* @see TestAuxUtilities#testChangelogCancelSupport()
*/
@Test
public void testLogCommandCancelSupport() throws Exception {
repo = Configuration.get().find("branches-1"); // any repo with more revisions
class BaseCancel extends TestAuxUtilities.CancelAtValue implements HgChangesetHandler {
BaseCancel(int limit) {
super(limit);
}
public void cset(HgChangeset changeset) throws HgCallbackTargetException {
nextValue(changeset.getRevisionIndex());
}
};
class ImplementsCancel extends BaseCancel implements CancelSupport {
ImplementsCancel(int limit) {
super(limit);
}
public void checkCancelled() throws CancelledException {
cancelImpl.checkCancelled();
}
};
class AdaptsToCancel extends BaseCancel implements Adaptable {
AdaptsToCancel(int limit) {
super(limit);
}
public <T> T getAdapter(Class<T> adapterClass) {
if (adapterClass == CancelSupport.class) {
return adapterClass.cast(cancelImpl);
}
return null;
}
}
BaseCancel insp = new ImplementsCancel(3);
try {
new HgLogCommand(repo).execute(insp);
errorCollector.fail("CancelSupport as implemented iface");
} catch (CancelledException ex) {
errorCollector.assertEquals("CancelSupport as implemented iface", insp.stopValue, insp.lastSeen);
}
insp = new AdaptsToCancel(5);
try {
new HgLogCommand(repo).execute(insp);
errorCollector.fail("Adaptable to CancelSupport");
} catch (CancelledException ex) {
errorCollector.assertEquals("Adaptable to CancelSupport", insp.stopValue, insp.lastSeen);
}
insp = new BaseCancel(9);
try {
new HgLogCommand(repo).set(insp.cancelImpl).execute(insp);
errorCollector.fail("cmd#set(CancelSupport)");
} catch (CancelledException e) {
errorCollector.assertEquals("cmd#set(CancelSupport)", insp.stopValue, insp.lastSeen);
}
}
private void report(String what, List<HgChangeset> r, boolean reverseConsoleResult) {
final List<Record> consoleResult = changelogParser.getResult();
report(what, r, consoleResult, reverseConsoleResult, errorCollector);
}
static void report(String what, List<HgChangeset> hg4jResult, List<Record> consoleResult, boolean reverseConsoleResult, ErrorCollectorExt errorCollector) {
consoleResult = new ArrayList<Record>(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", hg4jResult.size(), equalTo(consoleResult.size()));
Iterator<Record> consoleResultItr = consoleResult.iterator();
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();
// flags, not separate checkThat() because when lists are large, and do not match,
// number of failures may slow down test process significantly
int x = cs.getRevisionIndex() == cr.changesetIndex ? 0x1 : 0;
x |= cs.getDate().toString().equals(cr.date) ? 0x2 : 0;
x |= cs.getNodeid().toString().equals(cr.changesetNodeid) ? 0x4 : 0;
x |= cs.getUser().equals(cr.user) ? 0x8 : 0;
// 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.getRevisionIndex(), cr.changesetIndex), x, equalTo(0x1f));
consoleResultItr.remove();
}
errorCollector.checkThat(what + ". Unprocessed results in console left (insufficient from hg4j)", consoleResultItr.hasNext(), equalTo(false));
}
public void testPerformance() throws Exception {
final int runs = 10;
final long start1 = System.currentTimeMillis();
for (int i = 0; i < runs; i++) {
changelogParser.reset();
eh.run("hg", "log", "--debug");
}
final long start2 = System.currentTimeMillis();
for (int i = 0; i < runs; i++) {
new HgLogCommand(repo).execute();
}
final long end = System.currentTimeMillis();
System.out.printf("'hg log --debug', %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);
}
@Test
public void testOriginalTestLogRepo() throws Exception {
// tests fro mercurial distribution, test-log.t
repo = Configuration.get().find("log-1");
HgLogCommand cmd = new HgLogCommand(repo);
// funny enough, but hg log -vf a -R c:\temp\hg\test-log\a doesn't work, while --cwd <same> works fine
//
changelogParser.reset();
eh.run("hg", "log", "--debug", "a", "--cwd", repo.getLocation());
report("log a", cmd.file("a", false).execute(), true);
//
changelogParser.reset();
// fails with Mercurial 2.2.1, @see http://selenic.com/pipermail/mercurial-devel/2012-February/038249.html
// and http://www.selenic.com/hg/rev/60101427d618?rev=
// fix for the test (replacement) is available below
// eh.run("hg", "log", "--debug", "-f", "a", "--cwd", repo.getLocation());
// List<HgChangeset> r = cmd.file("a", true).execute();
// report("log -f a", r, true);
//
changelogParser.reset();
eh.run("hg", "log", "--debug", "-f", "e", "--cwd", repo.getLocation());
report("log -f e", cmd.file("e", true).execute(), true);
//
changelogParser.reset();
eh.run("hg", "log", "--debug", "dir/b", "--cwd", repo.getLocation());
report("log dir/b", cmd.file("dir/b", false).execute(), true);
//
changelogParser.reset();
//
// Commented out for the same reason as above hg log -f a - newly introduced error message in Mercurial 2.2
// when files are not part of the parent revision
// eh.run("hg", "log", "--debug", "-f", "dir/b", "--cwd", repo.getLocation());
// report("log -f dir/b", cmd.file("dir/b", true).execute(), false /*#1, below*/);
/*
* #1: false works because presently commands dispatches history of the queried file, and then history
* of it's origin. With history comprising of renames only, this effectively gives reversed (newest to oldest)
* order of revisions.
*/
// commented tests from above updated to work in 2.2 - update repo to revision where files are present
eh.run("hg", "update", "-q", "-r", "2", "--cwd", repo.getLocation());
changelogParser.reset();
eh.run("hg", "log", "--debug", "-f", "a", "--cwd", repo.getLocation());
List<HgChangeset> r = cmd.file("a", true).execute();
report("log -f a", r, true);
changelogParser.reset();
eh.run("hg", "log", "--debug", "-f", "dir/b", "--cwd", repo.getLocation());
report("log -f dir/b", cmd.file("dir/b", true).execute(), true);
//
// get repo back into clear state, up to the tip
eh.run("hg", "update", "-q", "--cwd", repo.getLocation());
}
@Test
public void testUsernames() throws Exception {
repo = Configuration.get().find("log-users");
final String user1 = "User One <user1@example.org>";
//
changelogParser.reset();
eh.run("hg", "log", "--debug", "-u", user1, "--cwd", repo.getLocation());
report("log -u " + user1, new HgLogCommand(repo).user(user1).execute(), true);
//
changelogParser.reset();
eh.run("hg", "log", "--debug", "-u", "user1", "-u", "user2", "--cwd", repo.getLocation());
report("log -u user1 -u user2", new HgLogCommand(repo).user("user1").user("user2").execute(), true);
//
changelogParser.reset();
eh.run("hg", "log", "--debug", "-u", "user3", "--cwd", repo.getLocation());
report("log -u user3", new HgLogCommand(repo).user("user3").execute(), true);
}
@Test
public void testBranches() throws Exception {
repo = Configuration.get().find("log-branches");
changelogParser.reset();
eh.run("hg", "log", "--debug", "-b", "default", "--cwd", repo.getLocation());
report("log -b default" , new HgLogCommand(repo).branch("default").execute(), true);
//
changelogParser.reset();
eh.run("hg", "log", "--debug", "-b", "test", "--cwd", repo.getLocation());
report("log -b test" , new HgLogCommand(repo).branch("test").execute(), true);
//
assertTrue("log -b dummy shall yeild empty result", new HgLogCommand(repo).branch("dummy").execute().isEmpty());
//
changelogParser.reset();
eh.run("hg", "log", "--debug", "-b", "default", "-b", "test", "--cwd", repo.getLocation());
report("log -b default -b test" , new HgLogCommand(repo).branch("default").branch("test").execute(), true);
}
////
private final class TreeCollectHandler extends AdapterPlug implements HgChangesetTreeHandler {
private final LinkedList<HgChangeset> cmdResult = new LinkedList<HgChangeset>();
private final boolean reverseResult;
boolean checkPrevInChildren = false; // true when iterating new to old
boolean checkPrevInParents = false; // true when iterating old to new
public TreeCollectHandler(boolean _reverseResult) {
this.reverseResult = _reverseResult;
}
public List<HgChangeset> getResult() {
return cmdResult;
}
public void treeElement(TreeElement entry) throws HgCallbackTargetException, HgRuntimeException {
// check consistency
Nodeid cset = entry.changeset().getNodeid();
errorCollector.assertEquals(entry.changesetRevision(), cset);
Pair<HgChangeset, HgChangeset> p = entry.parents();
Pair<HgChangeset, HgChangeset> parents_a = p;
Pair<Nodeid, Nodeid> parents_b = entry.parentRevisions();
if (parents_b.first().isNull()) {
errorCollector.assertTrue(parents_a.first() == null);
} else {
errorCollector.assertEquals(parents_b.first(), parents_a.first().getNodeid());
}
if (parents_b.second().isNull()) {
errorCollector.assertTrue(parents_a.second() == null);
} else {
errorCollector.assertEquals(parents_b.second(), parents_a.second().getNodeid());
}
//
if (checkPrevInChildren && !cmdResult.isEmpty()) {
HgChangeset prevChangeset = reverseResult ? cmdResult.getFirst() : cmdResult.getLast();
String msg = String.format("No parent-child bind between revisions %d and %d", prevChangeset.getRevisionIndex(), entry.changeset().getRevisionIndex());
errorCollector.assertTrue(msg, entry.children().contains(prevChangeset));
}
if (checkPrevInParents && !cmdResult.isEmpty()) {
HgChangeset prevChangeset = reverseResult ? cmdResult.getFirst() : cmdResult.getLast();
String msg = String.format("No parent-child bind between revisions %d and %d", prevChangeset.getRevisionIndex(), entry.changeset().getRevisionIndex());
errorCollector.assertTrue(msg, p.first() == prevChangeset || p.second() == prevChangeset);
}
//
if (reverseResult) {
cmdResult.addFirst(entry.changeset());
} else {
cmdResult.addLast(entry.changeset());
}
}
}
private static class CollectWithRenameHandler extends CollectHandler implements HgChangesetHandler.WithCopyHistory {
public final RenameCollector rh = new RenameCollector();
public List<HgChangeset> lastChangesetReportedAtRename = new LinkedList<HgChangeset>();
public void copy(HgFileRevision from, HgFileRevision to) throws HgCallbackTargetException {
Assert.assertTrue("Renames couldn't be reported prior to any change", getChanges().size() > 0);
HgChangeset lastKnown = getChanges().get(getChanges().size() - 1);
lastChangesetReportedAtRename.add(lastKnown);
rh.copy(from, to);
}
};
private static class RenameCollector implements HgFileRenameHandlerMixin {
public boolean copyReported = false;
public List<Pair<HgFileRevision, HgFileRevision>> renames = new LinkedList<Pair<HgFileRevision,HgFileRevision>>();
public RenameCollector() {
}
public RenameCollector(AdapterPlug ap) {
ap.attachAdapter(HgFileRenameHandlerMixin.class, this);
}
public void copy(HgFileRevision from, HgFileRevision to) {
copyReported = true;
renames.add(new Pair<HgFileRevision, HgFileRevision>(from, to));
}
}
}