chiark / gitweb /
.gdbinit: Delete this obsolete file.
[catacomb-python] / t / testutils.py
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
30 import catacomb as C
31 import sys as SYS
32 if SYS.version_info >= (3,): import builtins as B
33 else: import __builtin__ as B
34 import unittest as U
35
36 ###--------------------------------------------------------------------------
37 ### Main code.
38
39 ## Some compatibility hacks.
40 import itertools as I
41 def bin(x): return x
42 range = xrange
43 long = long
44 imap = I.imap
45 def byteseq(seq): return "".join(map(chr, seq))
46 def iterkeys(m): return m.iterkeys()
47 def itervalues(m): return m.itervalues()
48 def iteritems(m): return m.iteritems()
49 from cStringIO import StringIO
50 MAXFIXNUM = SYS.maxint
51
52 DEBUGP = hasattr(SYS, "gettotalrefcount")
53
54 FULLSPAN = byteseq(range(256))
55 def span(n):
56   """A string `00 01 .. NN'."""
57   return (n >> 8)*FULLSPAN + FULLSPAN[:n&255]
58
59 def 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
68 def 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
86 Z64 = C.ByteString.zero(8)
87 def 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
91 class 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
112 class 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
149 class 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
208 class Explosion (Exception): pass
209
210 class 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.
254 neg = lambda x: -x
255 pos = lambda x: +x
256 add = lambda x, y: x + y
257 sub = lambda x, y: x - y
258 mul = lambda x, y: x*y
259 div = lambda x, y: x/y
260 mod = lambda x, y: x%y
261 floordiv = lambda x, y: x//y
262 bitand = lambda x, y: x&y
263 bitor = lambda x, y: x | y
264 bitxor = lambda x, y: x ^ y
265 bitnot = lambda x: ~x
266 lsl = lambda x, y: x << y
267 lsr = lambda x, y: x >> y
268 eq = lambda x, y: x == y
269 ne = lambda x, y: x != y
270 lt = lambda x, y: x < y
271 le = lambda x, y: x <= y
272 ge = lambda x, y: x >= y
273 gt = lambda x, y: x > y
274
275 ###----- That's all, folks --------------------------------------------------