Mercurial > hg4j
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 } |