1 ### -*- mode: python, coding: utf-8 -*-
5 ### (c) 2019 Straylight/Edgeware
8 ###----- Licensing notice ---------------------------------------------------
10 ### This file is part of the Python interface to Catacomb.
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.
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.
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,
27 ###--------------------------------------------------------------------------
32 if SYS
.version_info
>= (3,): import builtins
as B
33 else: import __builtin__
as B
36 ###--------------------------------------------------------------------------
39 ## Some compatibility hacks.
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
52 DEBUGP
= hasattr(SYS
, "gettotalrefcount")
54 FULLSPAN
= byteseq(range(256))
56 """A string `00 01 .. NN'."""
57 return (n
>> 8)*FULLSPAN
+ FULLSPAN
[:n
&255]
59 def bytes_as_int(w
, bigendp
):
60 """Convert the byte-sequence `01 02 ... WW' to an integer."""
63 for i
in range(w
): x
= x
<< 8 | i
+ 1
65 for i
in range(w
): x |
= i
+ 1 << 8*i
68 def prep_lenseq(w
, n
, bigendp
, goodp
):
70 Return a reference buffer containing `00 LL .. LL 00 01 02 .. NN ff'.
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.
77 else: l
= n
+ (1 << 8*w
- 1)
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
)]) + \
86 Z64
= C
.ByteString
.zero(8)
88 """Return a fast deterministic random generator with the given SEED."""
89 return C
.chacha8rand(C
.sha256().hash(bin(seed
)).done(), Z64
)
91 class GenericTestMixin (U
.TestCase
):
93 A mixin class to generate test-case functions for all similar things.
97 def generate_testcases(cls
, things
):
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
)
112 class ImmutableMappingTextMixin (U
.TestCase
):
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
)
120 def check_immutable_mapping(me
, map, model
):
125 me
.assertEqual(len(map), len(model
))
126 for k
, v
in iteritems(model
):
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
)))
149 class MutableMappingTestMixin (ImmutableMappingTextMixin
):
152 def _mkvalue(me
, i
): return "v#%d" % i
154 def check_mapping(me
, emptymapfn
):
157 me
.assertEqual(len(map), 0)
160 me
.check_immutable_mapping(map, model
)
162 model
= { 1: 101, 2: 202, 4: 404 }
163 for k
, v
in iteritems(model
): map[me
._mkkey(k
)] = me
._mkvalue(v
)
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) })
173 map[me
._mkkey(9)] = me
._mkvalue(909)
177 map[me
._mkkey(9)] = me
._mkvalue(919)
180 map.setdefault(me
._mkkey(9), me
._mkvalue(929))
184 map.setdefault(me
._mkkey(8), me
._mkvalue(808))
187 me
.assertRaises(KeyError, map.pop
, me
._mkkey(5))
189 me
.assertEqual(map.pop(me
._mkkey(5), obj
), obj
)
190 me
.assertEqual(me
._getvalue(map.pop(me
._mkkey(8))), 808)
195 del map[me
._mkkey(9)]
199 mk
, mv
= me
._getkey(k
), me
._getvalue(v
)
200 me
.assertEqual(model
[mk
], mv
)
208 class Explosion (Exception): pass
210 class EventRecorder (C
.PrimeGenEventHandler
):
211 def __init__(me
, parent
= None, explode_after
= None, *args
, **kw
):
212 super(EventRecorder
, me
).__init__(*args
, **kw
)
216 me
._countdown
= explode_after
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
229 if me
._op
is not None: me
._buf
.write("%s%d/" %
(me
._op
, me
._streak
))
232 def pg_begin(me
, ev
):
234 me
._buf
.write("[%s:" % ev
.name
)
245 me
._put(None); me
._buf
.write("D]")
246 def pg_abort(me
, ev
):
248 me
._put(None); me
._buf
.write("A]")
251 return me
._buf
.getvalue()
253 ## Functions for operators.
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
275 ###----- That's all, folks --------------------------------------------------