comparison test/org/tmatesoft/hg/test/TestBlame.java @ 546:cd78e8b9d7bc

File annotate test. Refactored FileAnnotation as standalone class, introduced LineInspector to make line offset calc code shared
author Artem Tikhomirov <tikhomirov.artem@gmail.com>
date Mon, 18 Feb 2013 19:19:48 +0100
parents 15b406c7cd9d
children ab21ac7dd833
comparison
equal deleted inserted replaced
545:15b406c7cd9d 546:cd78e8b9d7bc
14 * the terms of a license other than GNU General Public License 14 * the terms of a license other than GNU General Public License
15 * contact TMate Software at support@hg4j.com 15 * contact TMate Software at support@hg4j.com
16 */ 16 */
17 package org.tmatesoft.hg.test; 17 package org.tmatesoft.hg.test;
18 18
19 import static org.junit.Assert.assertEquals;
20 import static org.junit.Assert.assertTrue;
19 import static org.tmatesoft.hg.repo.HgRepository.NO_REVISION; 21 import static org.tmatesoft.hg.repo.HgRepository.NO_REVISION;
20 22
21 import java.io.ByteArrayOutputStream; 23 import java.io.ByteArrayOutputStream;
22 import java.io.PrintStream; 24 import java.io.PrintStream;
23 import java.util.Arrays; 25 import java.util.Arrays;
24 import java.util.LinkedList;
25 import java.util.regex.Pattern; 26 import java.util.regex.Pattern;
26 27
27 import org.junit.Assert; 28 import org.junit.Assert;
28 import org.junit.Test; 29 import org.junit.Test;
29 import org.tmatesoft.hg.internal.AnnotateFacility; 30 import org.tmatesoft.hg.internal.AnnotateFacility;
30 import org.tmatesoft.hg.internal.AnnotateFacility.AddBlock; 31 import org.tmatesoft.hg.internal.AnnotateFacility.AddBlock;
31 import org.tmatesoft.hg.internal.AnnotateFacility.ChangeBlock; 32 import org.tmatesoft.hg.internal.AnnotateFacility.ChangeBlock;
32 import org.tmatesoft.hg.internal.AnnotateFacility.DeleteBlock; 33 import org.tmatesoft.hg.internal.AnnotateFacility.DeleteBlock;
33 import org.tmatesoft.hg.internal.AnnotateFacility.EqualBlock; 34 import org.tmatesoft.hg.internal.AnnotateFacility.EqualBlock;
35 import org.tmatesoft.hg.internal.AnnotateFacility.LineDescriptor;
34 import org.tmatesoft.hg.internal.IntMap; 36 import org.tmatesoft.hg.internal.IntMap;
35 import org.tmatesoft.hg.internal.IntVector;
36 import org.tmatesoft.hg.repo.HgDataFile; 37 import org.tmatesoft.hg.repo.HgDataFile;
37 import org.tmatesoft.hg.repo.HgLookup; 38 import org.tmatesoft.hg.repo.HgLookup;
38 import org.tmatesoft.hg.repo.HgRepository; 39 import org.tmatesoft.hg.repo.HgRepository;
39 40
40 /** 41 /**
64 65
65 @Test 66 @Test
66 public void testFileAnnotate() throws Exception { 67 public void testFileAnnotate() throws Exception {
67 HgRepository repo = new HgLookup().detectFromWorkingDir(); 68 HgRepository repo = new HgLookup().detectFromWorkingDir();
68 final String fname = "src/org/tmatesoft/hg/internal/PatchGenerator.java"; 69 final String fname = "src/org/tmatesoft/hg/internal/PatchGenerator.java";
70 final int[] checkChangesets = new int[] { 539 , 536, 531 };
71 HgDataFile df = repo.getFileNode(fname);
72 AnnotateFacility af = new AnnotateFacility();
73 FileAnnotateInspector fa = new FileAnnotateInspector();
74 for (int cs : checkChangesets) {
75 af.annotateChange(df, cs, new FileAnnotation(fa));
76 }
77
78 OutputParser.Stub op = new OutputParser.Stub();
79 ExecHelper eh = new ExecHelper(op, null);
80 eh.run("hg", "annotate", "-r", String.valueOf(checkChangesets[0]), fname);
81
82 String[] hgAnnotateLines = splitLines(op.result());
83 assertTrue("[sanity]", hgAnnotateLines.length > 0);
84 assertEquals("Number of lines reported by native annotate and our impl", hgAnnotateLines.length, fa.lineRevisions.length);
85
86 for (int i = 0; i < fa.lineRevisions.length; i++) {
87 int hgAnnotateRevIndex = Integer.parseInt(hgAnnotateLines[i].substring(0, hgAnnotateLines[i].indexOf(':')));
88 assertEquals(String.format("Revision mismatch for line %d", i+1), hgAnnotateRevIndex, fa.lineRevisions[i]);
89 }
90 }
91
92 private static String[] splitLines(CharSequence seq) {
93 int lineCount = 0;
94 for (int i = 0, x = seq.length(); i < x; i++) {
95 if (seq.charAt(i) == '\n') {
96 lineCount++;
97 }
98 }
99 if (seq.length() > 0 && seq.charAt(seq.length()-1) != '\n') {
100 lineCount++;
101 }
102 String[] rv = new String[lineCount];
103 int lineStart = 0, lineEnd = 0, ix = 0;
104 do {
105 while (lineEnd < seq.length() && seq.charAt(lineEnd) != '\n') lineEnd++;
106 if (lineEnd == lineStart) {
107 continue;
108 }
109 CharSequence line = seq.subSequence(lineStart, lineEnd);
110 rv[ix++] = line.toString();
111 lineStart = ++lineEnd;
112 } while (lineStart < seq.length());
113 assert ix == lineCount;
114 return rv;
115 }
116
117 private void leftovers() throws Exception {
118 HgRepository repo = new HgLookup().detectFromWorkingDir();
119 final String fname = "src/org/tmatesoft/hg/internal/PatchGenerator.java";
69 final int checkChangeset = 539; 120 final int checkChangeset = 539;
70 HgDataFile df = repo.getFileNode(fname); 121 HgDataFile df = repo.getFileNode(fname);
71 AnnotateFacility af = new AnnotateFacility(); 122 AnnotateFacility af = new AnnotateFacility();
72 System.out.println("536 -> 539"); 123 System.out.println("536 -> 539");
73 af.annotateChange(df, checkChangeset, new DiffOutInspector(System.out)); 124 af.annotateChange(df, checkChangeset, new DiffOutInspector(System.out));
74 System.out.println("531 -> 536"); 125 System.out.println("531 -> 536");
75 af.annotateChange(df, 536, new DiffOutInspector(System.out)); 126 af.annotateChange(df, 536, new DiffOutInspector(System.out));
76 System.out.println(" -1 -> 531"); 127 System.out.println(" -1 -> 531");
77 af.annotateChange(df, 531, new DiffOutInspector(System.out)); 128 af.annotateChange(df, 531, new DiffOutInspector(System.out));
78 129
79 FileAnnotation fa = new FileAnnotation();
80 af.annotateChange(df, checkChangeset, fa);
81 af.annotateChange(df, 536, fa);
82 af.annotateChange(df, 531, fa);
83 for (int i = 0; i < fa.lineRevisions.length; i++) {
84 System.out.printf("%3d: %d\n", fa.lineRevisions[i], i+1);
85 }
86 }
87
88 private static String[] splitLines(CharSequence seq) {
89 int lineCount = 0;
90 for (int i = 0, x = seq.length(); i < x; i++) {
91 if (seq.charAt(i) == '\n') {
92 lineCount++;
93 }
94 }
95 if (seq.length() > 0 && seq.charAt(seq.length()-1) != '\n') {
96 lineCount++;
97 }
98 String[] rv = new String[lineCount];
99 int lineStart = 0, lineEnd = 0, ix = 0;
100 do {
101 while (lineEnd < seq.length() && seq.charAt(lineEnd) != '\n') lineEnd++;
102 if (lineEnd == lineStart) {
103 continue;
104 }
105 CharSequence line = seq.subSequence(lineStart, lineEnd);
106 rv[ix++] = line.toString();
107 lineStart = ++lineEnd;
108 } while (lineStart < seq.length());
109 assert ix == lineCount;
110 return rv;
111 }
112
113 private void leftovers() {
114 IntMap<String> linesOld = new IntMap<String>(100), linesNew = new IntMap<String>(100); 130 IntMap<String> linesOld = new IntMap<String>(100), linesNew = new IntMap<String>(100);
115 System.out.println("Changes to old revision:"); 131 System.out.println("Changes to old revision:");
116 for (int i = linesOld.firstKey(), x = linesOld.lastKey(); i < x; i++) { 132 for (int i = linesOld.firstKey(), x = linesOld.lastKey(); i < x; i++) {
117 if (linesOld.containsKey(i)) { 133 if (linesOld.containsKey(i)) {
118 System.out.println(linesOld.get(i)); 134 System.out.println(linesOld.get(i));
133 // System.out.println(Arrays.equals(new String[] { "a", "bc" }, splitLines("a\nbc"))); 149 // System.out.println(Arrays.equals(new String[] { "a", "bc" }, splitLines("a\nbc")));
134 // System.out.println(Arrays.equals(new String[] { "a", "bc" }, splitLines("a\nbc\n"))); 150 // System.out.println(Arrays.equals(new String[] { "a", "bc" }, splitLines("a\nbc\n")));
135 new TestBlame().testFileAnnotate(); 151 new TestBlame().testFileAnnotate();
136 } 152 }
137 153
138 static class DiffOutInspector implements AnnotateFacility.Inspector { 154 static class DiffOutInspector implements AnnotateFacility.BlockInspector {
139 private final PrintStream out; 155 private final PrintStream out;
140 156
141 DiffOutInspector(PrintStream ps) { 157 DiffOutInspector(PrintStream ps) {
142 out = ps; 158 out = ps;
143 } 159 }
199 lineStart = ++lineEnd; 215 lineStart = ++lineEnd;
200 } while (lineStart < seq.length()); 216 } while (lineStart < seq.length());
201 } 217 }
202 } 218 }
203 219
204 private static class FileAnnotation implements AnnotateFacility.InspectorEx { 220 private static class FileAnnotateInspector implements AnnotateFacility.LineInspector {
205 private int[] lineRevisions; 221 private int[] lineRevisions;
206 private LinkedList<DeleteBlock> deleted = new LinkedList<DeleteBlock>(); 222
207 private LinkedList<DeleteBlock> newDeleted = new LinkedList<DeleteBlock>(); 223 FileAnnotateInspector() {
208 // keeps <startSeq1, startSeq2, len> of equal blocks 224 }
209 // XXX smth like IntSliceVector to access triples (or slices of any size, in fact) 225
210 // with easy indexing, e.g. #get(sliceIndex, indexWithinSlice) 226 public void line(int lineNumber, int changesetRevIndex, LineDescriptor ld) {
211 // and vect.get(7,2) instead of vect.get(7*SIZEOF_SLICE+2)
212 private IntVector identical = new IntVector(20*3, 2*3);
213 private IntVector newIdentical = new IntVector(20*3, 2*3);
214
215 public FileAnnotation() {
216 }
217
218 public void start(int originLineCount, int targetLineCount) {
219 if (lineRevisions == null) { 227 if (lineRevisions == null) {
220 lineRevisions = new int [targetLineCount]; 228 lineRevisions = new int [ld.totalLines()];
221 Arrays.fill(lineRevisions, NO_REVISION); 229 Arrays.fill(lineRevisions, NO_REVISION);
222 } 230 }
223 } 231 lineRevisions[lineNumber] = changesetRevIndex;
224 232 }
225 // private static void ppp(IntVector v) { 233 }
226 // for (int i = 0; i < v.size(); i+= 3) { 234
227 // int len = v.get(i+2);
228 // System.out.printf("[%d..%d) == [%d..%d); ", v.get(i), v.get(i) + len, v.get(i+1), v.get(i+1) + len);
229 // }
230 // System.out.println();
231 // }
232
233 public void done() {
234 if (identical.size() > 0) {
235 // update line numbers of the intermediate target to point to ultimate target's line numbers
236 IntVector v = new IntVector(identical.size(), 2*3);
237 for (int i = 0; i < newIdentical.size(); i+= 3) {
238 int originLine = newIdentical.get(i);
239 int targetLine = newIdentical.get(i+1);
240 int length = newIdentical.get(i+2);
241 int startTargetLine = -1, startOriginLine = -1, c = 0;
242 for (int j = 0; j < length; j++) {
243 int lnInFinal = mapLineIndex(targetLine + j);
244 if (lnInFinal == -1 || (startTargetLine != -1 && lnInFinal != startTargetLine + c)) {
245 // the line is not among "same" in ultimate origin
246 // or belongs to another/next "same" chunk
247 if (startOriginLine == -1) {
248 continue;
249 }
250 v.add(startOriginLine);
251 v.add(startTargetLine);
252 v.add(c);
253 c = 0;
254 startOriginLine = startTargetLine = -1;
255 // fall-through to check if it's not complete miss but a next chunk
256 }
257 if (lnInFinal != -1) {
258 if (startOriginLine == -1) {
259 startOriginLine = originLine + j;
260 startTargetLine = lnInFinal;
261 c = 1;
262 } else {
263 assert lnInFinal == startTargetLine + c;
264 c++;
265 }
266 }
267 }
268 if (startOriginLine != -1) {
269 assert c > 0;
270 v.add(startOriginLine);
271 v.add(startTargetLine);
272 v.add(c);
273 }
274 }
275 newIdentical.clear();
276 identical = v;
277 } else {
278 IntVector li = newIdentical;
279 newIdentical = identical;
280 identical = li;
281 }
282 LinkedList<DeleteBlock> ld = newDeleted;
283 deleted.clear();
284 newDeleted = deleted;
285 deleted = ld;
286 }
287
288 public void same(EqualBlock block) {
289 newIdentical.add(block.originStart());
290 newIdentical.add(block.targetStart());
291 newIdentical.add(block.length());
292 }
293
294 public void added(AddBlock block) {
295 for (int i = 0, ln = block.firstAddedLine(), x = block.totalAddedLines(); i < x; i++, ln++) {
296 int lnInFinal = mapLineIndex(ln);
297 if (lnInFinal != -1 && historyUnknown(lnInFinal)) {
298 lineRevisions[lnInFinal] = block.targetChangesetIndex();
299 }
300 }
301 }
302
303 public void changed(ChangeBlock block) {
304 deleted(block);
305 added(block);
306 }
307
308 public void deleted(DeleteBlock block) {
309 newDeleted.add(block);
310 }
311
312 private boolean historyUnknown(int lineNumber) {
313 return lineRevisions[lineNumber] == NO_REVISION;
314 }
315
316 private boolean isDeleted(int line) {
317 for (DeleteBlock b : deleted) {
318 if (b.firstRemovedLine() > line) {
319 break;
320 }
321 // line >= b.firstRemovedLine
322 if (b.firstRemovedLine() + b.totalRemovedLines() > line) {
323 return true;
324 }
325 }
326 return false;
327 }
328
329 // map target lines to the lines of the revision being annotated (the one that came first)
330 private int mapLineIndex(int ln) {
331 if (isDeleted(ln)) {
332 return -1;
333 }
334 if (identical.isEmpty()) {
335 return ln;
336 }
337 for (int i = 0; i < identical.size(); i += 3) {
338 final int originStart = identical.get(i);
339 if (originStart > ln) {
340 assert false;
341 return -1;
342 }
343 // ln >= b.originStart
344 final int length = identical.get(i+2);
345 if (originStart + length > ln) {
346 int targetStart = identical.get(i+1);
347 return targetStart + (ln - originStart);
348 }
349 }
350 assert false;
351 return -1;
352 }
353 }
354 } 235 }