chiark / gitweb /
t/: Add a test suite.
[catacomb-python] / t / testutils.py
CommitLineData
553d59fe
MW
1### -*- mode: python, coding: utf-8 -*-
2###
3### Test utilities
4###
5### (c) 2019 Straylight/Edgeware
6###
7
8###----- Licensing notice ---------------------------------------------------
9###
10### This file is part of the Python interface to Catacomb.
11###
12### Catacomb/Python is free software: you can redistribute it and/or
13### modify it under the terms of the GNU General Public License as
14### published by the Free Software Foundation; either version 2 of the
15### License, or (at your option) any later version.
16###
17### Catacomb/Python is distributed in the hope that it will be useful, but
18### WITHOUT ANY WARRANTY; without even the implied warranty of
19### MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20### General Public License for more details.
21###
22### You should have received a copy of the GNU General Public License
23### along with Catacomb/Python. If not, write to the Free Software
24### Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
25### USA.
26
27###--------------------------------------------------------------------------
28### Imported modules.
29
30import catacomb as C
31import sys as SYS
32if SYS.version_info >= (3,): import builtins as B
33else: import __builtin__ as B
34import unittest as U
35
36###--------------------------------------------------------------------------
37### Main code.
38
39## Some compatibility hacks.
40import itertools as I
41def bin(x): return x
42range = xrange
43long = long
44imap = I.imap
45def byteseq(seq): return "".join(map(chr, seq))
46def iterkeys(m): return m.iterkeys()
47def itervalues(m): return m.itervalues()
48def iteritems(m): return m.iteritems()
49from cStringIO import StringIO
50MAXFIXNUM = SYS.maxint
51
52DEBUGP = hasattr(SYS, "gettotalrefcount")
53
54FULLSPAN = byteseq(range(256))
55def span(n):
56 """A string `00 01 .. NN'."""
57 return (n >> 8)*FULLSPAN + FULLSPAN[:n&255]
58
59def bytes_as_int(w, bigendp):
60 """Convert the byte-sequence `01 02 ... WW' to an integer."""
61 x = 0
62 if bigendp:
63 for i in range(w): x = x << 8 | i + 1
64 else:
65 for i in range(w): x |= i + 1 << 8*i
66 return x
67
68def prep_lenseq(w, n, bigendp, goodp):
69 """
70 Return a reference buffer containing `00 LL .. LL 00 01 02 .. NN ff'.
71
72 Here, LL .. LL is the length of following sequence, not including the final
73 `ff', as a W-byte integer. If GOODP is false, then the most significant
74 bit of LL .. LL is set, to provoke an overflow.
75 """
76 if goodp: l = n
77 else: l = n + (1 << 8*w - 1)
78 lenbyte = bigendp \
79 and (lambda i: (l >> 8*(w - i - 1))&0xff) \
80 or (lambda i: (l >> 8*i)&0xff)
81 return byteseq([0x00]) + \
82 byteseq([lenbyte(i) for i in range(w)]) + \
83 span(n) + \
84 byteseq([0xff])
85
86Z64 = C.ByteString.zero(8)
87def detrand(seed):
88 """Return a fast deterministic random generator with the given SEED."""
89 return C.chacha8rand(C.sha256().hash(bin(seed)).done(), Z64)
90
91class GenericTestMixin (U.TestCase):
92 """
93 A mixin class to generate test-case functions for all similar things.
94 """
95
96 @classmethod
97 def generate_testcases(cls, things):
98 testfns = dict()
99 checkfns = []
100 for k, v in iteritems(cls.__dict__):
101 if k.startswith("_test_"): checkfns.append((k[6:], v))
102 for name, thing in things:
103 for test, checkfn in checkfns:
104 testfn = lambda me, thing = thing: checkfn(me, thing)
105 doc = getattr(checkfn, "__doc__", None)
106 if doc is not None: testfn.__doc__ = doc % name
107 testfns["test_%s%%%s" % (test, name)] = testfn
108 tmpcls = type("_tmp", (cls,), testfns)
109 for k, v in iteritems(tmpcls.__dict__):
110 if k.startswith("test_"): setattr(cls, k, v)
111
112class ImmutableMappingTextMixin (U.TestCase):
113
114 ## Subclass stubs.
115 def _mkkey(me, i): return "k#%d" % i
116 def _getkey(me, k): return int(k[2:])
117 def _getvalue(me, v): return int(v[2:])
118 def _getitem(me, it): k, v = it; return me._getkey(k), me._getvalue(v)
119
120 def check_immutable_mapping(me, map, model):
121
122 ## Lookup.
123 limk = 0
124 any = False
125 me.assertEqual(len(map), len(model))
126 for k, v in iteritems(model):
127 any = True
128 if k >= limk: limk = k + 1
129 me.assertTrue(me._mkkey(k) in map)
130 me.assertTrue(map.has_key(me._mkkey(k)))
131 me.assertEqual(me._getvalue(map[me._mkkey(k)]), v)
132 me.assertEqual(me._getvalue(map.get(me._mkkey(k))), v)
133 if any: me.assertTrue(me._mkkey(k) in map)
134 me.assertFalse(map.has_key(me._mkkey(limk)))
135 me.assertRaises(KeyError, lambda: map[me._mkkey(limk)])
136 me.assertEqual(map.get(me._mkkey(limk)), None)
137 for listfn, getfn in [(lambda x: x.keys(), me._getkey),
138 (lambda x: x.values(), me._getvalue),
139 (lambda x: x.items(), me._getitem)]:
140 rlist, mlist = listfn(map), listfn(model)
141 me.assertEqual(type(rlist), list)
142 rlist = B.map(getfn, rlist)
143 rlist.sort(); mlist.sort(); me.assertEqual(rlist, mlist)
144 for iterfn, getfn in [(lambda x: x.iterkeys(), me._getkey),
145 (lambda x: x.itervalues(), me._getvalue),
146 (lambda x: x.iteritems(), me._getitem)]:
147 me.assertEqual(set(imap(getfn, iterfn(map))), set(iterfn(model)))
148
149class MutableMappingTestMixin (ImmutableMappingTextMixin):
150
151 ## Subclass stubs.
152 def _mkvalue(me, i): return "v#%d" % i
153
154 def check_mapping(me, emptymapfn):
155
156 map = emptymapfn()
157 me.assertEqual(len(map), 0)
158
159 def check_views():
160 me.check_immutable_mapping(map, model)
161
162 model = { 1: 101, 2: 202, 4: 404 }
163 for k, v in iteritems(model): map[me._mkkey(k)] = me._mkvalue(v)
164 check_views()
165
166 model.update({ 2: 212, 6: 606, 7: 707 })
167 map.update({ me._mkkey(2): me._mkvalue(212),
168 me._mkkey(6): me._mkvalue(606),
169 me._mkkey(7): me._mkvalue(707) })
170 check_views()
171
172 model[9] = 909
173 map[me._mkkey(9)] = me._mkvalue(909)
174 check_views()
175
176 model[9] = 919
177 map[me._mkkey(9)] = me._mkvalue(919)
178 check_views()
179
180 map.setdefault(me._mkkey(9), me._mkvalue(929))
181 check_views()
182
183 model[8] = 808
184 map.setdefault(me._mkkey(8), me._mkvalue(808))
185 check_views()
186
187 me.assertRaises(KeyError, map.pop, me._mkkey(5))
188 obj = object()
189 me.assertEqual(map.pop(me._mkkey(5), obj), obj)
190 me.assertEqual(me._getvalue(map.pop(me._mkkey(8))), 808)
191 del model[8]
192 check_views()
193
194 del model[9]
195 del map[me._mkkey(9)]
196 check_views()
197
198 k, v = map.popitem()
199 mk, mv = me._getkey(k), me._getvalue(v)
200 me.assertEqual(model[mk], mv)
201 del model[mk]
202 check_views()
203
204 map.clear()
205 model = {}
206 check_views()
207
208class Explosion (Exception): pass
209
210class EventRecorder (C.PrimeGenEventHandler):
211 def __init__(me, parent = None, explode_after = None, *args, **kw):
212 super(EventRecorder, me).__init__(*args, **kw)
213 me._streak = 0
214 me._op = None
215 me._parent = parent
216 me._countdown = explode_after
217 me.rng = None
218 if parent is None: me._buf = StringIO()
219 else: me._buf = parent._buf
220 def _event_common(me, ev):
221 if me.rng is None: me.rng = ev.rng
222 if me._countdown is None: pass
223 elif me._countdown == 0: raise Explosion()
224 else: me._countdown -= 1
225 def _put(me, op):
226 if op == me._op:
227 me._streak += 1
228 else:
229 if me._op is not None: me._buf.write("%s%d/" % (me._op, me._streak))
230 me._op = op
231 me._streak = 1
232 def pg_begin(me, ev):
233 me._event_common(ev)
234 me._buf.write("[%s:" % ev.name)
235 def pg_try(me, ev):
236 me._event_common(ev)
237 def pg_fail(me, ev):
238 me._event_common(ev)
239 me._put("F")
240 def pg_pass(me, ev):
241 me._event_common(ev)
242 me._put("P")
243 def pg_done(me, ev):
244 me._event_common(ev)
245 me._put(None); me._buf.write("D]")
246 def pg_abort(me, ev):
247 me._event_common(ev)
248 me._put(None); me._buf.write("A]")
249 @property
250 def events(me):
251 return me._buf.getvalue()
252
253## Functions for operators.
254neg = lambda x: -x
255pos = lambda x: +x
256add = lambda x, y: x + y
257sub = lambda x, y: x - y
258mul = lambda x, y: x*y
259div = lambda x, y: x/y
260mod = lambda x, y: x%y
261floordiv = lambda x, y: x//y
262bitand = lambda x, y: x&y
263bitor = lambda x, y: x | y
264bitxor = lambda x, y: x ^ y
265bitnot = lambda x: ~x
266lsl = lambda x, y: x << y
267lsr = lambda x, y: x >> y
268eq = lambda x, y: x == y
269ne = lambda x, y: x != y
270lt = lambda x, y: x < y
271le = lambda x, y: x <= y
272ge = lambda x, y: x >= y
273gt = lambda x, y: x > y
274
275###----- That's all, folks --------------------------------------------------