test-ancestor.py
466 lines
| 12.7 KiB
| text/x-python
|
PythonLexer
/ tests / test-ancestor.py
Gregory Szorc
|
r27280 | import binascii | ||
import getopt | ||||
import math | ||||
import os | ||||
import random | ||||
import sys | ||||
import time | ||||
Siddharth Agarwal
|
r23331 | from mercurial.node import nullrev | ||
Gregory Szorc
|
r27280 | from mercurial import ( | ||
ancestor, | ||||
Gregory Szorc
|
r30402 | debugcommands, | ||
Gregory Szorc
|
r27280 | hg, | ||
Yuya Nishihara
|
r28774 | ui as uimod, | ||
Gregory Szorc
|
r27280 | ) | ||
Siddharth Agarwal
|
r23331 | |||
Augie Fackler
|
r43346 | |||
Siddharth Agarwal
|
r23331 | def buildgraph(rng, nodes=100, rootprob=0.05, mergeprob=0.2, prevprob=0.7): | ||
Augie Fackler
|
r46554 | """nodes: total number of nodes in the graph | ||
Siddharth Agarwal
|
r23331 | rootprob: probability that a new node (not 0) will be a root | ||
mergeprob: probability that, excluding a root a node will be a merge | ||||
prevprob: probability that p1 will be the previous node | ||||
return value is a graph represented as an adjacency list. | ||||
Augie Fackler
|
r46554 | """ | ||
Siddharth Agarwal
|
r23331 | graph = [None] * nodes | ||
Manuel Jacob
|
r50180 | for i in range(nodes): | ||
Siddharth Agarwal
|
r23331 | if i == 0 or rng.random() < rootprob: | ||
graph[i] = [nullrev] | ||||
elif i == 1: | ||||
graph[i] = [0] | ||||
elif rng.random() < mergeprob: | ||||
if i == 2 or rng.random() < prevprob: | ||||
# p1 is prev | ||||
p1 = i - 1 | ||||
else: | ||||
p1 = rng.randrange(i - 1) | ||||
Pulkit Goyal
|
r32893 | p2 = rng.choice(list(range(0, p1)) + list(range(p1 + 1, i))) | ||
Siddharth Agarwal
|
r23331 | graph[i] = [p1, p2] | ||
elif rng.random() < prevprob: | ||||
graph[i] = [i - 1] | ||||
else: | ||||
graph[i] = [rng.randrange(i - 1)] | ||||
return graph | ||||
Augie Fackler
|
r43346 | |||
Siddharth Agarwal
|
r23331 | def buildancestorsets(graph): | ||
ancs = [None] * len(graph) | ||||
Manuel Jacob
|
r50180 | for i in range(len(graph)): | ||
Martin von Zweigbergk
|
r32291 | ancs[i] = {i} | ||
Siddharth Agarwal
|
r23331 | if graph[i] == [nullrev]: | ||
continue | ||||
for p in graph[i]: | ||||
ancs[i].update(ancs[p]) | ||||
return ancs | ||||
Augie Fackler
|
r43346 | |||
Gregory Szorc
|
r49801 | class naiveincrementalmissingancestors: | ||
Siddharth Agarwal
|
r23335 | def __init__(self, ancs, bases): | ||
self.ancs = ancs | ||||
self.bases = set(bases) | ||||
Augie Fackler
|
r43346 | |||
Siddharth Agarwal
|
r23341 | def addbases(self, newbases): | ||
self.bases.update(newbases) | ||||
Augie Fackler
|
r43346 | |||
Siddharth Agarwal
|
r23342 | def removeancestorsfrom(self, revs): | ||
for base in self.bases: | ||||
if base != nullrev: | ||||
revs.difference_update(self.ancs[base]) | ||||
revs.discard(nullrev) | ||||
Augie Fackler
|
r43346 | |||
Siddharth Agarwal
|
r23335 | def missingancestors(self, revs): | ||
res = set() | ||||
for rev in revs: | ||||
if rev != nullrev: | ||||
res.update(self.ancs[rev]) | ||||
for base in self.bases: | ||||
if base != nullrev: | ||||
res.difference_update(self.ancs[base]) | ||||
return sorted(res) | ||||
Siddharth Agarwal
|
r23331 | |||
Augie Fackler
|
r43346 | |||
Siddharth Agarwal
|
r23331 | def test_missingancestors(seed, rng): | ||
# empirically observed to take around 1 second | ||||
graphcount = 100 | ||||
Siddharth Agarwal
|
r23336 | testcount = 10 | ||
inccount = 10 | ||||
Siddharth Agarwal
|
r23331 | nerrs = [0] | ||
Raphaël Gomès
|
r52596 | |||
Siddharth Agarwal
|
r23331 | # the default mu and sigma give us a nice distribution of mostly | ||
# single-digit counts (including 0) with some higher ones | ||||
def lognormrandom(mu, sigma): | ||||
return int(math.floor(rng.lognormvariate(mu, sigma))) | ||||
def samplerevs(nodes, mu=1.1, sigma=0.8): | ||||
count = min(lognormrandom(mu, sigma), len(nodes)) | ||||
return rng.sample(nodes, count) | ||||
Siddharth Agarwal
|
r23336 | def err(seed, graph, bases, seq, output, expected): | ||
Siddharth Agarwal
|
r23331 | if nerrs[0] == 0: | ||
Robert Stanca
|
r28723 | print('seed:', hex(seed)[:-1], file=sys.stderr) | ||
Siddharth Agarwal
|
r23331 | if gerrs[0] == 0: | ||
Robert Stanca
|
r28723 | print('graph:', graph, file=sys.stderr) | ||
print('* bases:', bases, file=sys.stderr) | ||||
print('* seq: ', seq, file=sys.stderr) | ||||
print('* output: ', output, file=sys.stderr) | ||||
print('* expected:', expected, file=sys.stderr) | ||||
Siddharth Agarwal
|
r23331 | nerrs[0] += 1 | ||
gerrs[0] += 1 | ||||
Manuel Jacob
|
r50180 | for g in range(graphcount): | ||
Siddharth Agarwal
|
r23331 | graph = buildgraph(rng) | ||
ancs = buildancestorsets(graph) | ||||
gerrs = [0] | ||||
Manuel Jacob
|
r50180 | for _ in range(testcount): | ||
Siddharth Agarwal
|
r23331 | # start from nullrev to include it as a possibility | ||
graphnodes = range(nullrev, len(graph)) | ||||
bases = samplerevs(graphnodes) | ||||
# fast algorithm | ||||
Siddharth Agarwal
|
r23334 | inc = ancestor.incrementalmissingancestors(graph.__getitem__, bases) | ||
Siddharth Agarwal
|
r23331 | # reference slow algorithm | ||
Siddharth Agarwal
|
r23335 | naiveinc = naiveincrementalmissingancestors(ancs, bases) | ||
Siddharth Agarwal
|
r23336 | seq = [] | ||
Manuel Jacob
|
r50180 | for _ in range(inccount): | ||
Siddharth Agarwal
|
r23341 | if rng.random() < 0.2: | ||
newbases = samplerevs(graphnodes) | ||||
seq.append(('addbases', newbases)) | ||||
inc.addbases(newbases) | ||||
naiveinc.addbases(newbases) | ||||
Siddharth Agarwal
|
r23342 | if rng.random() < 0.4: | ||
# larger set so that there are more revs to remove from | ||||
revs = samplerevs(graphnodes, mu=1.5) | ||||
seq.append(('removeancestorsfrom', revs)) | ||||
hrevs = set(revs) | ||||
rrevs = set(revs) | ||||
inc.removeancestorsfrom(hrevs) | ||||
naiveinc.removeancestorsfrom(rrevs) | ||||
if hrevs != rrevs: | ||||
Augie Fackler
|
r43346 | err( | ||
seed, | ||||
graph, | ||||
bases, | ||||
seq, | ||||
sorted(hrevs), | ||||
sorted(rrevs), | ||||
) | ||||
Siddharth Agarwal
|
r23342 | else: | ||
revs = samplerevs(graphnodes) | ||||
seq.append(('missingancestors', revs)) | ||||
h = inc.missingancestors(revs) | ||||
r = naiveinc.missingancestors(revs) | ||||
if h != r: | ||||
err(seed, graph, bases, seq, h, r) | ||||
Siddharth Agarwal
|
r18079 | |||
Augie Fackler
|
r43346 | |||
Siddharth Agarwal
|
r18079 | # graph is a dict of child->parent adjacency lists for this graph: | ||
# o 13 | ||||
# | | ||||
# | o 12 | ||||
# | | | ||||
# | | o 11 | ||||
# | | |\ | ||||
# | | | | o 10 | ||||
# | | | | | | ||||
# | o---+ | 9 | ||||
# | | | | | | ||||
# o | | | | 8 | ||||
# / / / / | ||||
# | | o | 7 | ||||
# | | | | | ||||
# o---+ | 6 | ||||
# / / / | ||||
# | | o 5 | ||||
# | |/ | ||||
# | o 4 | ||||
# | | | ||||
# o | 3 | ||||
# | | | ||||
# | o 2 | ||||
# |/ | ||||
# o 1 | ||||
# | | ||||
# o 0 | ||||
Augie Fackler
|
r43346 | graph = { | ||
0: [-1, -1], | ||||
1: [0, -1], | ||||
2: [1, -1], | ||||
3: [1, -1], | ||||
4: [2, -1], | ||||
5: [4, -1], | ||||
6: [4, -1], | ||||
7: [4, -1], | ||||
8: [-1, -1], | ||||
9: [6, 7], | ||||
10: [5, -1], | ||||
11: [3, 7], | ||||
12: [9, -1], | ||||
13: [8, -1], | ||||
} | ||||
Siddharth Agarwal
|
r18079 | |||
Georges Racinet
|
r40995 | def test_missingancestors_explicit(): | ||
"""A few explicit cases, easier to check for catching errors in refactors. | ||||
The bigger graph at the end has been produced by the random generator | ||||
above, and we have some evidence that the other tests don't cover it. | ||||
""" | ||||
Augie Fackler
|
r43346 | for i, (bases, revs) in enumerate( | ||
( | ||||
Manuel Jacob
|
r50180 | ({1, 2, 3, 4, 7}, set(range(10))), | ||
Augie Fackler
|
r43346 | ({10}, set({11, 12, 13, 14})), | ||
({7}, set({1, 2, 3, 4, 5})), | ||||
) | ||||
): | ||||
Georges Racinet
|
r40995 | print("%% removeancestorsfrom(), example %d" % (i + 1)) | ||
missanc = ancestor.incrementalmissingancestors(graph.get, bases) | ||||
missanc.removeancestorsfrom(revs) | ||||
print("remaining (sorted): %s" % sorted(list(revs))) | ||||
Augie Fackler
|
r43346 | for i, (bases, revs) in enumerate( | ||
Augie Fackler
|
r46554 | ( | ||
({10}, {11}), | ||||
({11}, {10}), | ||||
({7}, {9, 11}), | ||||
) | ||||
Augie Fackler
|
r43346 | ): | ||
Georges Racinet
|
r40995 | print("%% missingancestors(), example %d" % (i + 1)) | ||
missanc = ancestor.incrementalmissingancestors(graph.get, bases) | ||||
print("return %s" % missanc.missingancestors(revs)) | ||||
print("% removeancestorsfrom(), bigger graph") | ||||
vecgraph = [ | ||||
Augie Fackler
|
r43346 | [-1, -1], | ||
[0, -1], | ||||
[1, 0], | ||||
[2, 1], | ||||
[3, -1], | ||||
[4, -1], | ||||
[5, 1], | ||||
[2, -1], | ||||
[7, -1], | ||||
[8, -1], | ||||
[9, -1], | ||||
[10, 1], | ||||
[3, -1], | ||||
[12, -1], | ||||
[13, -1], | ||||
[14, -1], | ||||
[4, -1], | ||||
[16, -1], | ||||
[17, -1], | ||||
[18, -1], | ||||
[19, 11], | ||||
[20, -1], | ||||
[21, -1], | ||||
[22, -1], | ||||
[23, -1], | ||||
[2, -1], | ||||
[3, -1], | ||||
[26, 24], | ||||
[27, -1], | ||||
[28, -1], | ||||
[12, -1], | ||||
[1, -1], | ||||
[1, 9], | ||||
[32, -1], | ||||
[33, -1], | ||||
[34, 31], | ||||
[35, -1], | ||||
[36, 26], | ||||
[37, -1], | ||||
[38, -1], | ||||
[39, -1], | ||||
[40, -1], | ||||
[41, -1], | ||||
[42, 26], | ||||
[0, -1], | ||||
[44, -1], | ||||
[45, 4], | ||||
[40, -1], | ||||
[47, -1], | ||||
[36, 0], | ||||
[49, -1], | ||||
[-1, -1], | ||||
[51, -1], | ||||
[52, -1], | ||||
[53, -1], | ||||
[14, -1], | ||||
[55, -1], | ||||
[15, -1], | ||||
[23, -1], | ||||
[58, -1], | ||||
[59, -1], | ||||
[2, -1], | ||||
[61, 59], | ||||
[62, -1], | ||||
[63, -1], | ||||
[-1, -1], | ||||
[65, -1], | ||||
[66, -1], | ||||
[67, -1], | ||||
[68, -1], | ||||
[37, 28], | ||||
[69, 25], | ||||
[71, -1], | ||||
[72, -1], | ||||
[50, 2], | ||||
[74, -1], | ||||
[12, -1], | ||||
[18, -1], | ||||
[77, -1], | ||||
[78, -1], | ||||
[79, -1], | ||||
[43, 33], | ||||
[81, -1], | ||||
[82, -1], | ||||
[83, -1], | ||||
[84, 45], | ||||
[85, -1], | ||||
[86, -1], | ||||
[-1, -1], | ||||
[88, -1], | ||||
[-1, -1], | ||||
[76, 83], | ||||
[44, -1], | ||||
[92, -1], | ||||
[93, -1], | ||||
[9, -1], | ||||
[95, 67], | ||||
[96, -1], | ||||
[97, -1], | ||||
[-1, -1], | ||||
] | ||||
Georges Racinet
|
r40995 | problem_rev = 28 | ||
problem_base = 70 | ||||
# problem_rev is a parent of problem_base, but a faulty implementation | ||||
# could forget to remove it. | ||||
bases = {60, 26, 70, 3, 96, 19, 98, 49, 97, 47, 1, 6} | ||||
if problem_rev not in vecgraph[problem_base] or problem_base not in bases: | ||||
print("Conditions have changed") | ||||
missanc = ancestor.incrementalmissingancestors(vecgraph.__getitem__, bases) | ||||
revs = {4, 12, 41, 28, 68, 38, 1, 30, 56, 44} | ||||
missanc.removeancestorsfrom(revs) | ||||
if 28 in revs: | ||||
print("Failed!") | ||||
else: | ||||
print("Ok") | ||||
Augie Fackler
|
r43346 | |||
Siddharth Agarwal
|
r18091 | def genlazyancestors(revs, stoprev=0, inclusive=False): | ||
Augie Fackler
|
r43346 | print( | ||
( | ||||
"%% lazy ancestor set for %s, stoprev = %s, inclusive = %s" | ||||
% (revs, stoprev, inclusive) | ||||
) | ||||
) | ||||
return ancestor.lazyancestors( | ||||
graph.get, revs, stoprev=stoprev, inclusive=inclusive | ||||
) | ||||
Siddharth Agarwal
|
r18091 | |||
def printlazyancestors(s, l): | ||||
Robert Stanca
|
r28723 | print('membership: %r' % [n for n in l if n in s]) | ||
print('iteration: %r' % list(s)) | ||||
Siddharth Agarwal
|
r18091 | |||
Augie Fackler
|
r43346 | |||
Siddharth Agarwal
|
r18091 | def test_lazyancestors(): | ||
# Empty revs | ||||
s = genlazyancestors([]) | ||||
printlazyancestors(s, [3, 0, -1]) | ||||
# Standard example | ||||
s = genlazyancestors([11, 13]) | ||||
printlazyancestors(s, [11, 13, 7, 9, 8, 3, 6, 4, 1, -1, 0]) | ||||
Pierre-Yves David
|
r22355 | # Standard with ancestry in the initial set (1 is ancestor of 3) | ||
s = genlazyancestors([1, 3]) | ||||
printlazyancestors(s, [1, -1, 0]) | ||||
Siddharth Agarwal
|
r18091 | # Including revs | ||
s = genlazyancestors([11, 13], inclusive=True) | ||||
printlazyancestors(s, [11, 13, 7, 9, 8, 3, 6, 4, 1, -1, 0]) | ||||
# Test with stoprev | ||||
s = genlazyancestors([11, 13], stoprev=6) | ||||
printlazyancestors(s, [11, 13, 7, 9, 8, 3, 6, 4, 1, -1, 0]) | ||||
s = genlazyancestors([11, 13], stoprev=6, inclusive=True) | ||||
printlazyancestors(s, [11, 13, 7, 9, 8, 3, 6, 4, 1, -1, 0]) | ||||
Yuya Nishihara
|
r39511 | # Test with stoprev >= min(initrevs) | ||
s = genlazyancestors([11, 13], stoprev=11, inclusive=True) | ||||
printlazyancestors(s, [11, 13, 7, 9, 8, 3, 6, 4, 1, -1, 0]) | ||||
s = genlazyancestors([11, 13], stoprev=12, inclusive=True) | ||||
printlazyancestors(s, [11, 13, 7, 9, 8, 3, 6, 4, 1, -1, 0]) | ||||
Siddharth Agarwal
|
r19504 | |||
Yuya Nishihara
|
r39572 | # Contiguous chains: 5->4, 2->1 (where 1 is in seen set), 1->0 | ||
s = genlazyancestors([10, 1], inclusive=True) | ||||
printlazyancestors(s, [2, 10, 4, 5, -1, 0, 1]) | ||||
Augie Fackler
|
r43346 | |||
Siddharth Agarwal
|
r19504 | # The C gca algorithm requires a real repo. These are textual descriptions of | ||
Sune Foldager
|
r33475 | # DAGs that have been known to be problematic, and, optionally, known pairs | ||
# of revisions and their expected ancestor list. | ||||
Siddharth Agarwal
|
r19504 | dagtests = [ | ||
Yuya Nishihara
|
r36644 | (b'+2*2*2/*3/2', {}), | ||
(b'+3*3/*2*2/*4*4/*4/2*4/2*2', {}), | ||||
(b'+2*2*/2*4*/4*/3*2/4', {(6, 7): [3, 5]}), | ||||
Siddharth Agarwal
|
r19504 | ] | ||
Augie Fackler
|
r43346 | |||
Siddharth Agarwal
|
r19504 | def test_gca(): | ||
Yuya Nishihara
|
r30559 | u = uimod.ui.load() | ||
Sune Foldager
|
r33475 | for i, (dag, tests) in enumerate(dagtests): | ||
Pulkit Goyal
|
r32894 | repo = hg.repository(u, b'gca%d' % i, create=1) | ||
Siddharth Agarwal
|
r19504 | cl = repo.changelog | ||
r51821 | if not hasattr(cl.index, 'ancestors'): | |||
Siddharth Agarwal
|
r19504 | # C version not available | ||
return | ||||
Gregory Szorc
|
r30402 | debugcommands.debugbuilddag(u, repo, dag) | ||
Siddharth Agarwal
|
r19504 | # Compare the results of the Python and C versions. This does not | ||
# include choosing a winner when more than one gca exists -- we make | ||||
# sure both return exactly the same set of gcas. | ||||
Sune Foldager
|
r33475 | # Also compare against expected results, if available. | ||
Siddharth Agarwal
|
r19504 | for a in cl: | ||
for b in cl: | ||||
cgcas = sorted(cl.index.ancestors(a, b)) | ||||
pygcas = sorted(ancestor.ancestors(cl.parentrevs, a, b)) | ||||
Sune Foldager
|
r33475 | expected = None | ||
if (a, b) in tests: | ||||
expected = tests[(a, b)] | ||||
if cgcas != pygcas or (expected and cgcas != expected): | ||||
Augie Fackler
|
r43346 | print( | ||
"test_gca: for dag %s, gcas for %d, %d:" % (dag, a, b) | ||||
) | ||||
Robert Stanca
|
r28723 | print(" C returned: %s" % cgcas) | ||
print(" Python returned: %s" % pygcas) | ||||
Sune Foldager
|
r33475 | if expected: | ||
print(" expected: %s" % expected) | ||||
Siddharth Agarwal
|
r19504 | |||
Augie Fackler
|
r43346 | |||
Siddharth Agarwal
|
r23330 | def main(): | ||
Siddharth Agarwal
|
r23331 | seed = None | ||
opts, args = getopt.getopt(sys.argv[1:], 's:', ['seed=']) | ||||
for o, a in opts: | ||||
if o in ('-s', '--seed'): | ||||
Manuel Jacob
|
r50189 | seed = int(a, base=0) # accepts base 10 or 16 strings | ||
Siddharth Agarwal
|
r23331 | |||
if seed is None: | ||||
try: | ||||
Manuel Jacob
|
r50189 | seed = int(binascii.hexlify(os.urandom(16)), 16) | ||
Siddharth Agarwal
|
r23331 | except AttributeError: | ||
Manuel Jacob
|
r50189 | seed = int(time.time() * 1000) | ||
Siddharth Agarwal
|
r23331 | |||
rng = random.Random(seed) | ||||
Georges Racinet
|
r40995 | test_missingancestors_explicit() | ||
Siddharth Agarwal
|
r23331 | test_missingancestors(seed, rng) | ||
Siddharth Agarwal
|
r18091 | test_lazyancestors() | ||
Siddharth Agarwal
|
r19504 | test_gca() | ||
Siddharth Agarwal
|
r23330 | |||
Augie Fackler
|
r43346 | |||
Siddharth Agarwal
|
r23330 | if __name__ == '__main__': | ||
main() | ||||