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