tikhomirov@542: /* tikhomirov@542: * Copyright (c) 2013 TMate Software Ltd tikhomirov@542: * tikhomirov@542: * This program is free software; you can redistribute it and/or modify tikhomirov@542: * it under the terms of the GNU General Public License as published by tikhomirov@542: * the Free Software Foundation; version 2 of the License. tikhomirov@542: * tikhomirov@542: * This program is distributed in the hope that it will be useful, tikhomirov@542: * but WITHOUT ANY WARRANTY; without even the implied warranty of tikhomirov@542: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the tikhomirov@542: * GNU General Public License for more details. tikhomirov@542: * tikhomirov@542: * For information on how to redistribute this software under tikhomirov@542: * the terms of a license other than GNU General Public License tikhomirov@542: * contact TMate Software at support@hg4j.com tikhomirov@542: */ tikhomirov@542: package org.tmatesoft.hg.test; tikhomirov@542: tikhomirov@546: import static org.junit.Assert.assertEquals; tikhomirov@546: import static org.junit.Assert.assertTrue; tikhomirov@545: import static org.tmatesoft.hg.repo.HgRepository.NO_REVISION; tikhomirov@548: import static org.tmatesoft.hg.repo.HgRepository.TIP; tikhomirov@545: tikhomirov@543: import java.io.ByteArrayOutputStream; tikhomirov@543: import java.io.PrintStream; tikhomirov@543: import java.util.Arrays; tikhomirov@553: import java.util.LinkedHashSet; tikhomirov@553: import java.util.LinkedList; tikhomirov@543: import java.util.regex.Pattern; tikhomirov@543: tikhomirov@543: import org.junit.Assert; tikhomirov@549: import org.junit.Rule; tikhomirov@542: import org.junit.Test; tikhomirov@552: import org.tmatesoft.hg.core.HgIterateDirection; tikhomirov@542: import org.tmatesoft.hg.internal.AnnotateFacility; tikhomirov@553: import org.tmatesoft.hg.internal.IntVector; tikhomirov@542: import org.tmatesoft.hg.internal.AnnotateFacility.AddBlock; tikhomirov@549: import org.tmatesoft.hg.internal.AnnotateFacility.Block; tikhomirov@554: import org.tmatesoft.hg.internal.AnnotateFacility.BlockData; tikhomirov@542: import org.tmatesoft.hg.internal.AnnotateFacility.ChangeBlock; tikhomirov@542: import org.tmatesoft.hg.internal.AnnotateFacility.DeleteBlock; tikhomirov@545: import org.tmatesoft.hg.internal.AnnotateFacility.EqualBlock; tikhomirov@546: import org.tmatesoft.hg.internal.AnnotateFacility.LineDescriptor; tikhomirov@542: import org.tmatesoft.hg.repo.HgDataFile; tikhomirov@542: import org.tmatesoft.hg.repo.HgLookup; tikhomirov@542: import org.tmatesoft.hg.repo.HgRepository; tikhomirov@542: tikhomirov@542: /** tikhomirov@542: * tikhomirov@542: * @author Artem Tikhomirov tikhomirov@542: * @author TMate Software Ltd. tikhomirov@542: */ tikhomirov@542: public class TestBlame { tikhomirov@542: tikhomirov@549: @Rule tikhomirov@549: public ErrorCollectorExt errorCollector = new ErrorCollectorExt(); tikhomirov@549: tikhomirov@542: tikhomirov@542: @Test tikhomirov@542: public void testSingleParentBlame() throws Exception { tikhomirov@542: HgRepository repo = new HgLookup().detectFromWorkingDir(); tikhomirov@543: final String fname = "src/org/tmatesoft/hg/internal/PatchGenerator.java"; tikhomirov@543: final int checkChangeset = 539; tikhomirov@543: HgDataFile df = repo.getFileNode(fname); tikhomirov@543: ByteArrayOutputStream bos = new ByteArrayOutputStream(); tikhomirov@544: new AnnotateFacility().annotateChange(df, checkChangeset, new DiffOutInspector(new PrintStream(bos))); tikhomirov@543: LineGrepOutputParser gp = new LineGrepOutputParser("^@@.+"); tikhomirov@543: ExecHelper eh = new ExecHelper(gp, null); tikhomirov@543: eh.run("hg", "diff", "-c", String.valueOf(checkChangeset), "-U", "0", fname); tikhomirov@543: // tikhomirov@543: String[] apiResult = splitLines(bos.toString()); tikhomirov@543: String[] expected = splitLines(gp.result()); tikhomirov@543: Assert.assertArrayEquals(expected, apiResult); tikhomirov@543: } tikhomirov@543: tikhomirov@545: @Test tikhomirov@545: public void testFileAnnotate() throws Exception { tikhomirov@545: HgRepository repo = new HgLookup().detectFromWorkingDir(); tikhomirov@545: final String fname = "src/org/tmatesoft/hg/internal/PatchGenerator.java"; tikhomirov@545: HgDataFile df = repo.getFileNode(fname); tikhomirov@546: OutputParser.Stub op = new OutputParser.Stub(); tikhomirov@546: ExecHelper eh = new ExecHelper(op, null); tikhomirov@546: tikhomirov@552: for (int startChangeset : new int[] { 539, 541 /*, TIP */}) { tikhomirov@548: FileAnnotateInspector fa = new FileAnnotateInspector(); tikhomirov@548: new AnnotateFacility().annotate(df, startChangeset, fa); tikhomirov@548: tikhomirov@548: tikhomirov@548: op.reset(); tikhomirov@548: eh.run("hg", "annotate", "-r", startChangeset == TIP ? "tip" : String.valueOf(startChangeset), fname); tikhomirov@548: tikhomirov@548: String[] hgAnnotateLines = splitLines(op.result()); tikhomirov@548: assertTrue("[sanity]", hgAnnotateLines.length > 0); tikhomirov@548: assertEquals("Number of lines reported by native annotate and our impl", hgAnnotateLines.length, fa.lineRevisions.length); tikhomirov@548: tikhomirov@548: for (int i = 0; i < fa.lineRevisions.length; i++) { tikhomirov@548: int hgAnnotateRevIndex = Integer.parseInt(hgAnnotateLines[i].substring(0, hgAnnotateLines[i].indexOf(':'))); tikhomirov@549: errorCollector.assertEquals(String.format("Revision mismatch for line %d", i+1), hgAnnotateRevIndex, fa.lineRevisions[i]); tikhomirov@548: } tikhomirov@545: } tikhomirov@545: } tikhomirov@545: tikhomirov@553: @Test tikhomirov@553: public void testComplexHistoryAnnotate() throws Exception { tikhomirov@553: HgRepository repo = Configuration.get().find("test-annotate"); tikhomirov@553: HgDataFile df = repo.getFileNode("file1"); tikhomirov@553: AnnotateFacility af = new AnnotateFacility(); tikhomirov@553: ByteArrayOutputStream bos = new ByteArrayOutputStream(); tikhomirov@553: DiffOutInspector dump = new DiffOutInspector(new PrintStream(bos)); tikhomirov@553: af.annotate(df, TIP, dump, HgIterateDirection.OldToNew); tikhomirov@553: LinkedList apiResult = new LinkedList(Arrays.asList(splitLines(bos.toString()))); tikhomirov@553: tikhomirov@553: LineGrepOutputParser gp = new LineGrepOutputParser("^@@.+"); tikhomirov@553: ExecHelper eh = new ExecHelper(gp, repo.getWorkingDir()); tikhomirov@553: System.out.println(Arrays.toString(dump.getReportedTargetRevisions())); tikhomirov@553: for (int cs : dump.getReportedTargetRevisions()) { tikhomirov@553: gp.reset(); tikhomirov@553: eh.run("hg", "diff", "-c", String.valueOf(cs), "-U", "0", df.getPath().toString()); tikhomirov@553: for (String expected : splitLines(gp.result())) { tikhomirov@553: if (!apiResult.remove(expected)) { tikhomirov@553: errorCollector.fail(String.format("Expected diff output '%s' for changes in revision %d", expected, cs)); tikhomirov@553: } tikhomirov@553: } tikhomirov@553: } tikhomirov@553: errorCollector.assertTrue(String.format("Annotate API reported excessive diff: %s ", apiResult.toString()), apiResult.isEmpty()); tikhomirov@553: } tikhomirov@553: tikhomirov@543: private static String[] splitLines(CharSequence seq) { tikhomirov@543: int lineCount = 0; tikhomirov@543: for (int i = 0, x = seq.length(); i < x; i++) { tikhomirov@543: if (seq.charAt(i) == '\n') { tikhomirov@543: lineCount++; tikhomirov@542: } tikhomirov@543: } tikhomirov@543: if (seq.length() > 0 && seq.charAt(seq.length()-1) != '\n') { tikhomirov@543: lineCount++; tikhomirov@543: } tikhomirov@543: String[] rv = new String[lineCount]; tikhomirov@543: int lineStart = 0, lineEnd = 0, ix = 0; tikhomirov@543: do { tikhomirov@543: while (lineEnd < seq.length() && seq.charAt(lineEnd) != '\n') lineEnd++; tikhomirov@543: if (lineEnd == lineStart) { tikhomirov@543: continue; tikhomirov@542: } tikhomirov@543: CharSequence line = seq.subSequence(lineStart, lineEnd); tikhomirov@543: rv[ix++] = line.toString(); tikhomirov@543: lineStart = ++lineEnd; tikhomirov@543: } while (lineStart < seq.length()); tikhomirov@543: assert ix == lineCount; tikhomirov@543: return rv; tikhomirov@543: } tikhomirov@543: tikhomirov@549: tikhomirov@549: private void aaa() throws Exception { tikhomirov@546: HgRepository repo = new HgLookup().detectFromWorkingDir(); tikhomirov@546: final String fname = "src/org/tmatesoft/hg/internal/PatchGenerator.java"; tikhomirov@546: final int checkChangeset = 539; tikhomirov@546: HgDataFile df = repo.getFileNode(fname); tikhomirov@546: AnnotateFacility af = new AnnotateFacility(); tikhomirov@549: DiffOutInspector dump = new DiffOutInspector(System.out); tikhomirov@549: System.out.println("541 -> 543"); tikhomirov@549: af.annotateChange(df, 543, dump); tikhomirov@549: System.out.println("539 -> 541"); tikhomirov@549: af.annotateChange(df, 541, dump); tikhomirov@546: System.out.println("536 -> 539"); tikhomirov@549: af.annotateChange(df, checkChangeset, dump); tikhomirov@546: System.out.println("531 -> 536"); tikhomirov@549: af.annotateChange(df, 536, dump); tikhomirov@546: System.out.println(" -1 -> 531"); tikhomirov@549: af.annotateChange(df, 531, dump); tikhomirov@549: tikhomirov@549: FileAnnotateInspector fai = new FileAnnotateInspector(); tikhomirov@552: af.annotate(df, 541, fai); tikhomirov@549: for (int i = 0; i < fai.lineRevisions.length; i++) { tikhomirov@549: System.out.printf("%3d: LINE %d\n", fai.lineRevisions[i], i+1); tikhomirov@549: } tikhomirov@549: } tikhomirov@546: tikhomirov@549: private void bbb() throws Exception { tikhomirov@549: HgRepository repo = new HgLookup().detectFromWorkingDir(); tikhomirov@549: final String fname = "src/org/tmatesoft/hg/repo/HgManifest.java"; tikhomirov@549: final int checkChangeset = 415; tikhomirov@549: HgDataFile df = repo.getFileNode(fname); tikhomirov@549: AnnotateFacility af = new AnnotateFacility(); tikhomirov@549: DiffOutInspector dump = new DiffOutInspector(System.out); tikhomirov@552: // System.out.println("413 -> 415"); tikhomirov@552: // af.diff(df, 413, 415, dump); tikhomirov@552: // System.out.println("408 -> 415"); tikhomirov@552: // af.diff(df, 408, 415, dump); tikhomirov@552: // System.out.println("Combined (with merge):"); tikhomirov@552: // dump.needRevisions(true); tikhomirov@552: // af.annotateChange(df, checkChangeset, dump); tikhomirov@549: dump.needRevisions(true); tikhomirov@552: af.annotate(df, checkChangeset, dump, HgIterateDirection.OldToNew); tikhomirov@549: } tikhomirov@553: tikhomirov@553: private void ccc() throws Exception { tikhomirov@554: HgRepository repo = new HgLookup().detect("/home/artem/hg/junit-test-repos/test-annotate/"); tikhomirov@553: HgDataFile df = repo.getFileNode("file1"); tikhomirov@553: AnnotateFacility af = new AnnotateFacility(); tikhomirov@553: DiffOutInspector dump = new DiffOutInspector(System.out); tikhomirov@553: dump.needRevisions(true); tikhomirov@553: af.annotate(df, TIP, dump, HgIterateDirection.OldToNew); tikhomirov@553: System.out.println(); tikhomirov@554: af.annotate(df, TIP, new LineDumpInspector(true), HgIterateDirection.NewToOld); tikhomirov@554: System.out.println(); tikhomirov@554: af.annotate(df, TIP, new LineDumpInspector(false), HgIterateDirection.NewToOld); tikhomirov@554: System.out.println(); tikhomirov@553: FileAnnotateInspector fa = new FileAnnotateInspector(); tikhomirov@553: af.annotate(df, TIP, fa); tikhomirov@553: for (int i = 0; i < fa.lineRevisions.length; i++) { tikhomirov@553: System.out.printf("%d: LINE %d\n", fa.lineRevisions[i], i+1); tikhomirov@542: } tikhomirov@542: } tikhomirov@553: tikhomirov@542: public static void main(String[] args) throws Exception { tikhomirov@545: // System.out.println(Arrays.equals(new String[0], splitLines(""))); tikhomirov@545: // System.out.println(Arrays.equals(new String[] { "abc" }, splitLines("abc"))); tikhomirov@545: // System.out.println(Arrays.equals(new String[] { "a", "bc" }, splitLines("a\nbc"))); tikhomirov@545: // System.out.println(Arrays.equals(new String[] { "a", "bc" }, splitLines("a\nbc\n"))); tikhomirov@553: new TestBlame().ccc(); tikhomirov@542: } tikhomirov@543: tikhomirov@554: private static class DiffOutInspector implements AnnotateFacility.BlockInspector { tikhomirov@543: private final PrintStream out; tikhomirov@549: private boolean dumpRevs; tikhomirov@553: private IntVector reportedRevisionPairs = new IntVector(); tikhomirov@543: tikhomirov@543: DiffOutInspector(PrintStream ps) { tikhomirov@543: out = ps; tikhomirov@543: } tikhomirov@543: tikhomirov@554: // Note, true makes output incompatible with 'hg diff' tikhomirov@549: public void needRevisions(boolean dumpRevs) { tikhomirov@549: this.dumpRevs = dumpRevs; tikhomirov@549: } tikhomirov@549: tikhomirov@549: private void printRevs(Block b) { tikhomirov@549: if (dumpRevs) { tikhomirov@549: out.printf("[%3d -> %3d] ", b.originChangesetIndex(), b.targetChangesetIndex()); tikhomirov@549: } tikhomirov@553: reportedRevisionPairs.add(b.originChangesetIndex(), b.targetChangesetIndex()); tikhomirov@553: } tikhomirov@553: tikhomirov@553: int[] getReportedTargetRevisions() { tikhomirov@553: LinkedHashSet rv = new LinkedHashSet(); tikhomirov@553: for (int i = 1; i < reportedRevisionPairs.size(); i += 2) { tikhomirov@553: rv.add(reportedRevisionPairs.get(i)); tikhomirov@553: } tikhomirov@553: int[] x = new int[rv.size()]; tikhomirov@553: int i = 0; tikhomirov@553: for (int v : rv) { tikhomirov@553: x[i++] = v; tikhomirov@553: } tikhomirov@553: return x; tikhomirov@549: } tikhomirov@549: tikhomirov@545: public void same(EqualBlock block) { tikhomirov@543: // nothing tikhomirov@543: } tikhomirov@543: tikhomirov@543: public void deleted(DeleteBlock block) { tikhomirov@549: printRevs(block); tikhomirov@543: out.printf("@@ -%d,%d +%d,0 @@\n", block.firstRemovedLine() + 1, block.totalRemovedLines(), block.removedAt()); tikhomirov@543: } tikhomirov@543: tikhomirov@543: public void changed(ChangeBlock block) { tikhomirov@549: printRevs(block); tikhomirov@543: out.printf("@@ -%d,%d +%d,%d @@\n", block.firstRemovedLine() + 1, block.totalRemovedLines(), block.firstAddedLine() + 1, block.totalAddedLines()); tikhomirov@543: } tikhomirov@543: tikhomirov@543: public void added(AddBlock block) { tikhomirov@549: printRevs(block); tikhomirov@543: out.printf("@@ -%d,0 +%d,%d @@\n", block.insertedAt(), block.firstAddedLine() + 1, block.totalAddedLines()); tikhomirov@543: } tikhomirov@543: } tikhomirov@543: tikhomirov@543: public static class LineGrepOutputParser implements OutputParser { tikhomirov@543: tikhomirov@543: private final Pattern pattern; tikhomirov@543: private final StringBuilder result = new StringBuilder(); tikhomirov@543: tikhomirov@543: public LineGrepOutputParser(String regexp) { tikhomirov@543: pattern = Pattern.compile(regexp); tikhomirov@543: } tikhomirov@543: tikhomirov@553: public void reset() { tikhomirov@553: result.setLength(0); tikhomirov@553: } tikhomirov@553: tikhomirov@543: public CharSequence result() { tikhomirov@543: return result; tikhomirov@543: } tikhomirov@543: tikhomirov@543: public void parse(CharSequence seq) { tikhomirov@543: int lineStart = 0, lineEnd = 0; tikhomirov@543: do { tikhomirov@543: while (lineEnd < seq.length() && seq.charAt(lineEnd) != '\n') lineEnd++; tikhomirov@543: if (lineEnd == lineStart) { tikhomirov@543: continue; tikhomirov@543: } tikhomirov@543: CharSequence line = seq.subSequence(lineStart, lineEnd); tikhomirov@543: if (pattern.matcher(line).matches()) { tikhomirov@543: result.append(line); tikhomirov@543: result.append('\n'); tikhomirov@543: } tikhomirov@543: lineStart = ++lineEnd; tikhomirov@543: } while (lineStart < seq.length()); tikhomirov@543: } tikhomirov@543: } tikhomirov@545: tikhomirov@546: private static class FileAnnotateInspector implements AnnotateFacility.LineInspector { tikhomirov@545: private int[] lineRevisions; tikhomirov@545: tikhomirov@546: FileAnnotateInspector() { tikhomirov@545: } tikhomirov@545: tikhomirov@546: public void line(int lineNumber, int changesetRevIndex, LineDescriptor ld) { tikhomirov@545: if (lineRevisions == null) { tikhomirov@546: lineRevisions = new int [ld.totalLines()]; tikhomirov@545: Arrays.fill(lineRevisions, NO_REVISION); tikhomirov@545: } tikhomirov@546: lineRevisions[lineNumber] = changesetRevIndex; tikhomirov@545: } tikhomirov@545: } tikhomirov@546: tikhomirov@554: private static class LineDumpInspector implements AnnotateFacility.BlockInspector { tikhomirov@554: tikhomirov@554: private final boolean lineByLine; tikhomirov@554: tikhomirov@554: public LineDumpInspector(boolean lineByLine) { tikhomirov@554: this.lineByLine = lineByLine; tikhomirov@554: } tikhomirov@554: tikhomirov@554: public void same(EqualBlock block) { tikhomirov@554: } tikhomirov@554: tikhomirov@554: public void added(AddBlock block) { tikhomirov@554: BlockData lines = block.addedLines(); tikhomirov@554: printBlock(lines, block.targetChangesetIndex(), block.firstAddedLine(), block.totalAddedLines(), "+++"); tikhomirov@554: } tikhomirov@554: tikhomirov@554: public void changed(ChangeBlock block) { tikhomirov@554: deleted(block); tikhomirov@554: added(block); tikhomirov@554: } tikhomirov@554: tikhomirov@554: public void deleted(DeleteBlock block) { tikhomirov@554: BlockData lines = block.removedLines(); tikhomirov@554: assert lines.elementCount() == block.totalRemovedLines(); tikhomirov@554: printBlock(lines, block.originChangesetIndex(), block.firstRemovedLine(), block.totalRemovedLines(), "---"); tikhomirov@554: } tikhomirov@554: tikhomirov@554: private void printBlock(BlockData lines, int cset, int first, int length, String marker) { tikhomirov@554: assert lines.elementCount() == length; tikhomirov@554: if (lineByLine) { tikhomirov@554: for (int i = 0, ln = first; i < length; i++, ln++) { tikhomirov@554: String line = new String(lines.elementAt(i).asArray()); tikhomirov@554: System.out.printf("%3d:%3d:%s:%s", cset, ln, marker, line); tikhomirov@554: } tikhomirov@554: } else { tikhomirov@554: String content = new String(lines.asArray()); tikhomirov@554: System.out.printf("%3d:%s:[%d..%d):\n%s", cset, marker, first, first+length, content); tikhomirov@554: } tikhomirov@554: } tikhomirov@554: } tikhomirov@542: }