##// END OF EJS Templates
py3: make test-ancestors.py pass on Python 3 with C extensions...
Yuya Nishihara -
r36644:6754d0c5 default
parent child Browse files
Show More
@@ -1,275 +1,275
1 from __future__ import absolute_import, print_function
1 from __future__ import absolute_import, print_function
2
2
3 import binascii
3 import binascii
4 import getopt
4 import getopt
5 import math
5 import math
6 import os
6 import os
7 import random
7 import random
8 import sys
8 import sys
9 import time
9 import time
10
10
11 from mercurial.node import nullrev
11 from mercurial.node import nullrev
12 from mercurial import (
12 from mercurial import (
13 ancestor,
13 ancestor,
14 debugcommands,
14 debugcommands,
15 hg,
15 hg,
16 pycompat,
16 pycompat,
17 ui as uimod,
17 ui as uimod,
18 util,
18 util,
19 )
19 )
20
20
21 if pycompat.ispy3:
21 if pycompat.ispy3:
22 long = int
22 long = int
23 xrange = range
23 xrange = range
24
24
25 def buildgraph(rng, nodes=100, rootprob=0.05, mergeprob=0.2, prevprob=0.7):
25 def buildgraph(rng, nodes=100, rootprob=0.05, mergeprob=0.2, prevprob=0.7):
26 '''nodes: total number of nodes in the graph
26 '''nodes: total number of nodes in the graph
27 rootprob: probability that a new node (not 0) will be a root
27 rootprob: probability that a new node (not 0) will be a root
28 mergeprob: probability that, excluding a root a node will be a merge
28 mergeprob: probability that, excluding a root a node will be a merge
29 prevprob: probability that p1 will be the previous node
29 prevprob: probability that p1 will be the previous node
30
30
31 return value is a graph represented as an adjacency list.
31 return value is a graph represented as an adjacency list.
32 '''
32 '''
33 graph = [None] * nodes
33 graph = [None] * nodes
34 for i in xrange(nodes):
34 for i in xrange(nodes):
35 if i == 0 or rng.random() < rootprob:
35 if i == 0 or rng.random() < rootprob:
36 graph[i] = [nullrev]
36 graph[i] = [nullrev]
37 elif i == 1:
37 elif i == 1:
38 graph[i] = [0]
38 graph[i] = [0]
39 elif rng.random() < mergeprob:
39 elif rng.random() < mergeprob:
40 if i == 2 or rng.random() < prevprob:
40 if i == 2 or rng.random() < prevprob:
41 # p1 is prev
41 # p1 is prev
42 p1 = i - 1
42 p1 = i - 1
43 else:
43 else:
44 p1 = rng.randrange(i - 1)
44 p1 = rng.randrange(i - 1)
45 p2 = rng.choice(list(range(0, p1)) + list(range(p1 + 1, i)))
45 p2 = rng.choice(list(range(0, p1)) + list(range(p1 + 1, i)))
46 graph[i] = [p1, p2]
46 graph[i] = [p1, p2]
47 elif rng.random() < prevprob:
47 elif rng.random() < prevprob:
48 graph[i] = [i - 1]
48 graph[i] = [i - 1]
49 else:
49 else:
50 graph[i] = [rng.randrange(i - 1)]
50 graph[i] = [rng.randrange(i - 1)]
51
51
52 return graph
52 return graph
53
53
54 def buildancestorsets(graph):
54 def buildancestorsets(graph):
55 ancs = [None] * len(graph)
55 ancs = [None] * len(graph)
56 for i in xrange(len(graph)):
56 for i in xrange(len(graph)):
57 ancs[i] = {i}
57 ancs[i] = {i}
58 if graph[i] == [nullrev]:
58 if graph[i] == [nullrev]:
59 continue
59 continue
60 for p in graph[i]:
60 for p in graph[i]:
61 ancs[i].update(ancs[p])
61 ancs[i].update(ancs[p])
62 return ancs
62 return ancs
63
63
64 class naiveincrementalmissingancestors(object):
64 class naiveincrementalmissingancestors(object):
65 def __init__(self, ancs, bases):
65 def __init__(self, ancs, bases):
66 self.ancs = ancs
66 self.ancs = ancs
67 self.bases = set(bases)
67 self.bases = set(bases)
68 def addbases(self, newbases):
68 def addbases(self, newbases):
69 self.bases.update(newbases)
69 self.bases.update(newbases)
70 def removeancestorsfrom(self, revs):
70 def removeancestorsfrom(self, revs):
71 for base in self.bases:
71 for base in self.bases:
72 if base != nullrev:
72 if base != nullrev:
73 revs.difference_update(self.ancs[base])
73 revs.difference_update(self.ancs[base])
74 revs.discard(nullrev)
74 revs.discard(nullrev)
75 def missingancestors(self, revs):
75 def missingancestors(self, revs):
76 res = set()
76 res = set()
77 for rev in revs:
77 for rev in revs:
78 if rev != nullrev:
78 if rev != nullrev:
79 res.update(self.ancs[rev])
79 res.update(self.ancs[rev])
80 for base in self.bases:
80 for base in self.bases:
81 if base != nullrev:
81 if base != nullrev:
82 res.difference_update(self.ancs[base])
82 res.difference_update(self.ancs[base])
83 return sorted(res)
83 return sorted(res)
84
84
85 def test_missingancestors(seed, rng):
85 def test_missingancestors(seed, rng):
86 # empirically observed to take around 1 second
86 # empirically observed to take around 1 second
87 graphcount = 100
87 graphcount = 100
88 testcount = 10
88 testcount = 10
89 inccount = 10
89 inccount = 10
90 nerrs = [0]
90 nerrs = [0]
91 # the default mu and sigma give us a nice distribution of mostly
91 # the default mu and sigma give us a nice distribution of mostly
92 # single-digit counts (including 0) with some higher ones
92 # single-digit counts (including 0) with some higher ones
93 def lognormrandom(mu, sigma):
93 def lognormrandom(mu, sigma):
94 return int(math.floor(rng.lognormvariate(mu, sigma)))
94 return int(math.floor(rng.lognormvariate(mu, sigma)))
95
95
96 def samplerevs(nodes, mu=1.1, sigma=0.8):
96 def samplerevs(nodes, mu=1.1, sigma=0.8):
97 count = min(lognormrandom(mu, sigma), len(nodes))
97 count = min(lognormrandom(mu, sigma), len(nodes))
98 return rng.sample(nodes, count)
98 return rng.sample(nodes, count)
99
99
100 def err(seed, graph, bases, seq, output, expected):
100 def err(seed, graph, bases, seq, output, expected):
101 if nerrs[0] == 0:
101 if nerrs[0] == 0:
102 print('seed:', hex(seed)[:-1], file=sys.stderr)
102 print('seed:', hex(seed)[:-1], file=sys.stderr)
103 if gerrs[0] == 0:
103 if gerrs[0] == 0:
104 print('graph:', graph, file=sys.stderr)
104 print('graph:', graph, file=sys.stderr)
105 print('* bases:', bases, file=sys.stderr)
105 print('* bases:', bases, file=sys.stderr)
106 print('* seq: ', seq, file=sys.stderr)
106 print('* seq: ', seq, file=sys.stderr)
107 print('* output: ', output, file=sys.stderr)
107 print('* output: ', output, file=sys.stderr)
108 print('* expected:', expected, file=sys.stderr)
108 print('* expected:', expected, file=sys.stderr)
109 nerrs[0] += 1
109 nerrs[0] += 1
110 gerrs[0] += 1
110 gerrs[0] += 1
111
111
112 for g in xrange(graphcount):
112 for g in xrange(graphcount):
113 graph = buildgraph(rng)
113 graph = buildgraph(rng)
114 ancs = buildancestorsets(graph)
114 ancs = buildancestorsets(graph)
115 gerrs = [0]
115 gerrs = [0]
116 for _ in xrange(testcount):
116 for _ in xrange(testcount):
117 # start from nullrev to include it as a possibility
117 # start from nullrev to include it as a possibility
118 graphnodes = range(nullrev, len(graph))
118 graphnodes = range(nullrev, len(graph))
119 bases = samplerevs(graphnodes)
119 bases = samplerevs(graphnodes)
120
120
121 # fast algorithm
121 # fast algorithm
122 inc = ancestor.incrementalmissingancestors(graph.__getitem__, bases)
122 inc = ancestor.incrementalmissingancestors(graph.__getitem__, bases)
123 # reference slow algorithm
123 # reference slow algorithm
124 naiveinc = naiveincrementalmissingancestors(ancs, bases)
124 naiveinc = naiveincrementalmissingancestors(ancs, bases)
125 seq = []
125 seq = []
126 revs = []
126 revs = []
127 for _ in xrange(inccount):
127 for _ in xrange(inccount):
128 if rng.random() < 0.2:
128 if rng.random() < 0.2:
129 newbases = samplerevs(graphnodes)
129 newbases = samplerevs(graphnodes)
130 seq.append(('addbases', newbases))
130 seq.append(('addbases', newbases))
131 inc.addbases(newbases)
131 inc.addbases(newbases)
132 naiveinc.addbases(newbases)
132 naiveinc.addbases(newbases)
133 if rng.random() < 0.4:
133 if rng.random() < 0.4:
134 # larger set so that there are more revs to remove from
134 # larger set so that there are more revs to remove from
135 revs = samplerevs(graphnodes, mu=1.5)
135 revs = samplerevs(graphnodes, mu=1.5)
136 seq.append(('removeancestorsfrom', revs))
136 seq.append(('removeancestorsfrom', revs))
137 hrevs = set(revs)
137 hrevs = set(revs)
138 rrevs = set(revs)
138 rrevs = set(revs)
139 inc.removeancestorsfrom(hrevs)
139 inc.removeancestorsfrom(hrevs)
140 naiveinc.removeancestorsfrom(rrevs)
140 naiveinc.removeancestorsfrom(rrevs)
141 if hrevs != rrevs:
141 if hrevs != rrevs:
142 err(seed, graph, bases, seq, sorted(hrevs),
142 err(seed, graph, bases, seq, sorted(hrevs),
143 sorted(rrevs))
143 sorted(rrevs))
144 else:
144 else:
145 revs = samplerevs(graphnodes)
145 revs = samplerevs(graphnodes)
146 seq.append(('missingancestors', revs))
146 seq.append(('missingancestors', revs))
147 h = inc.missingancestors(revs)
147 h = inc.missingancestors(revs)
148 r = naiveinc.missingancestors(revs)
148 r = naiveinc.missingancestors(revs)
149 if h != r:
149 if h != r:
150 err(seed, graph, bases, seq, h, r)
150 err(seed, graph, bases, seq, h, r)
151
151
152 # graph is a dict of child->parent adjacency lists for this graph:
152 # graph is a dict of child->parent adjacency lists for this graph:
153 # o 13
153 # o 13
154 # |
154 # |
155 # | o 12
155 # | o 12
156 # | |
156 # | |
157 # | | o 11
157 # | | o 11
158 # | | |\
158 # | | |\
159 # | | | | o 10
159 # | | | | o 10
160 # | | | | |
160 # | | | | |
161 # | o---+ | 9
161 # | o---+ | 9
162 # | | | | |
162 # | | | | |
163 # o | | | | 8
163 # o | | | | 8
164 # / / / /
164 # / / / /
165 # | | o | 7
165 # | | o | 7
166 # | | | |
166 # | | | |
167 # o---+ | 6
167 # o---+ | 6
168 # / / /
168 # / / /
169 # | | o 5
169 # | | o 5
170 # | |/
170 # | |/
171 # | o 4
171 # | o 4
172 # | |
172 # | |
173 # o | 3
173 # o | 3
174 # | |
174 # | |
175 # | o 2
175 # | o 2
176 # |/
176 # |/
177 # o 1
177 # o 1
178 # |
178 # |
179 # o 0
179 # o 0
180
180
181 graph = {0: [-1], 1: [0], 2: [1], 3: [1], 4: [2], 5: [4], 6: [4],
181 graph = {0: [-1], 1: [0], 2: [1], 3: [1], 4: [2], 5: [4], 6: [4],
182 7: [4], 8: [-1], 9: [6, 7], 10: [5], 11: [3, 7], 12: [9],
182 7: [4], 8: [-1], 9: [6, 7], 10: [5], 11: [3, 7], 12: [9],
183 13: [8]}
183 13: [8]}
184
184
185 def genlazyancestors(revs, stoprev=0, inclusive=False):
185 def genlazyancestors(revs, stoprev=0, inclusive=False):
186 print(("%% lazy ancestor set for %s, stoprev = %s, inclusive = %s" %
186 print(("%% lazy ancestor set for %s, stoprev = %s, inclusive = %s" %
187 (revs, stoprev, inclusive)))
187 (revs, stoprev, inclusive)))
188 return ancestor.lazyancestors(graph.get, revs, stoprev=stoprev,
188 return ancestor.lazyancestors(graph.get, revs, stoprev=stoprev,
189 inclusive=inclusive)
189 inclusive=inclusive)
190
190
191 def printlazyancestors(s, l):
191 def printlazyancestors(s, l):
192 print('membership: %r' % [n for n in l if n in s])
192 print('membership: %r' % [n for n in l if n in s])
193 print('iteration: %r' % list(s))
193 print('iteration: %r' % list(s))
194
194
195 def test_lazyancestors():
195 def test_lazyancestors():
196 # Empty revs
196 # Empty revs
197 s = genlazyancestors([])
197 s = genlazyancestors([])
198 printlazyancestors(s, [3, 0, -1])
198 printlazyancestors(s, [3, 0, -1])
199
199
200 # Standard example
200 # Standard example
201 s = genlazyancestors([11, 13])
201 s = genlazyancestors([11, 13])
202 printlazyancestors(s, [11, 13, 7, 9, 8, 3, 6, 4, 1, -1, 0])
202 printlazyancestors(s, [11, 13, 7, 9, 8, 3, 6, 4, 1, -1, 0])
203
203
204 # Standard with ancestry in the initial set (1 is ancestor of 3)
204 # Standard with ancestry in the initial set (1 is ancestor of 3)
205 s = genlazyancestors([1, 3])
205 s = genlazyancestors([1, 3])
206 printlazyancestors(s, [1, -1, 0])
206 printlazyancestors(s, [1, -1, 0])
207
207
208 # Including revs
208 # Including revs
209 s = genlazyancestors([11, 13], inclusive=True)
209 s = genlazyancestors([11, 13], inclusive=True)
210 printlazyancestors(s, [11, 13, 7, 9, 8, 3, 6, 4, 1, -1, 0])
210 printlazyancestors(s, [11, 13, 7, 9, 8, 3, 6, 4, 1, -1, 0])
211
211
212 # Test with stoprev
212 # Test with stoprev
213 s = genlazyancestors([11, 13], stoprev=6)
213 s = genlazyancestors([11, 13], stoprev=6)
214 printlazyancestors(s, [11, 13, 7, 9, 8, 3, 6, 4, 1, -1, 0])
214 printlazyancestors(s, [11, 13, 7, 9, 8, 3, 6, 4, 1, -1, 0])
215 s = genlazyancestors([11, 13], stoprev=6, inclusive=True)
215 s = genlazyancestors([11, 13], stoprev=6, inclusive=True)
216 printlazyancestors(s, [11, 13, 7, 9, 8, 3, 6, 4, 1, -1, 0])
216 printlazyancestors(s, [11, 13, 7, 9, 8, 3, 6, 4, 1, -1, 0])
217
217
218
218
219 # The C gca algorithm requires a real repo. These are textual descriptions of
219 # The C gca algorithm requires a real repo. These are textual descriptions of
220 # DAGs that have been known to be problematic, and, optionally, known pairs
220 # DAGs that have been known to be problematic, and, optionally, known pairs
221 # of revisions and their expected ancestor list.
221 # of revisions and their expected ancestor list.
222 dagtests = [
222 dagtests = [
223 ('+2*2*2/*3/2', {}),
223 (b'+2*2*2/*3/2', {}),
224 ('+3*3/*2*2/*4*4/*4/2*4/2*2', {}),
224 (b'+3*3/*2*2/*4*4/*4/2*4/2*2', {}),
225 ('+2*2*/2*4*/4*/3*2/4', {(6, 7): [3, 5]}),
225 (b'+2*2*/2*4*/4*/3*2/4', {(6, 7): [3, 5]}),
226 ]
226 ]
227 def test_gca():
227 def test_gca():
228 u = uimod.ui.load()
228 u = uimod.ui.load()
229 for i, (dag, tests) in enumerate(dagtests):
229 for i, (dag, tests) in enumerate(dagtests):
230 repo = hg.repository(u, b'gca%d' % i, create=1)
230 repo = hg.repository(u, b'gca%d' % i, create=1)
231 cl = repo.changelog
231 cl = repo.changelog
232 if not util.safehasattr(cl.index, 'ancestors'):
232 if not util.safehasattr(cl.index, 'ancestors'):
233 # C version not available
233 # C version not available
234 return
234 return
235
235
236 debugcommands.debugbuilddag(u, repo, dag)
236 debugcommands.debugbuilddag(u, repo, dag)
237 # Compare the results of the Python and C versions. This does not
237 # Compare the results of the Python and C versions. This does not
238 # include choosing a winner when more than one gca exists -- we make
238 # include choosing a winner when more than one gca exists -- we make
239 # sure both return exactly the same set of gcas.
239 # sure both return exactly the same set of gcas.
240 # Also compare against expected results, if available.
240 # Also compare against expected results, if available.
241 for a in cl:
241 for a in cl:
242 for b in cl:
242 for b in cl:
243 cgcas = sorted(cl.index.ancestors(a, b))
243 cgcas = sorted(cl.index.ancestors(a, b))
244 pygcas = sorted(ancestor.ancestors(cl.parentrevs, a, b))
244 pygcas = sorted(ancestor.ancestors(cl.parentrevs, a, b))
245 expected = None
245 expected = None
246 if (a, b) in tests:
246 if (a, b) in tests:
247 expected = tests[(a, b)]
247 expected = tests[(a, b)]
248 if cgcas != pygcas or (expected and cgcas != expected):
248 if cgcas != pygcas or (expected and cgcas != expected):
249 print("test_gca: for dag %s, gcas for %d, %d:"
249 print("test_gca: for dag %s, gcas for %d, %d:"
250 % (dag, a, b))
250 % (dag, a, b))
251 print(" C returned: %s" % cgcas)
251 print(" C returned: %s" % cgcas)
252 print(" Python returned: %s" % pygcas)
252 print(" Python returned: %s" % pygcas)
253 if expected:
253 if expected:
254 print(" expected: %s" % expected)
254 print(" expected: %s" % expected)
255
255
256 def main():
256 def main():
257 seed = None
257 seed = None
258 opts, args = getopt.getopt(sys.argv[1:], 's:', ['seed='])
258 opts, args = getopt.getopt(sys.argv[1:], 's:', ['seed='])
259 for o, a in opts:
259 for o, a in opts:
260 if o in ('-s', '--seed'):
260 if o in ('-s', '--seed'):
261 seed = long(a, base=0) # accepts base 10 or 16 strings
261 seed = long(a, base=0) # accepts base 10 or 16 strings
262
262
263 if seed is None:
263 if seed is None:
264 try:
264 try:
265 seed = long(binascii.hexlify(os.urandom(16)), 16)
265 seed = long(binascii.hexlify(os.urandom(16)), 16)
266 except AttributeError:
266 except AttributeError:
267 seed = long(time.time() * 1000)
267 seed = long(time.time() * 1000)
268
268
269 rng = random.Random(seed)
269 rng = random.Random(seed)
270 test_missingancestors(seed, rng)
270 test_missingancestors(seed, rng)
271 test_lazyancestors()
271 test_lazyancestors()
272 test_gca()
272 test_gca()
273
273
274 if __name__ == '__main__':
274 if __name__ == '__main__':
275 main()
275 main()
General Comments 0
You need to be logged in to leave comments. Login now