1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
|
"""helpers for bps unittests"""
#=========================================================
#imports
#=========================================================
#core
import warnings
import os
import atexit
import tempfile
import unittest
import sys
from logging import getLogger; log = getLogger(__name__)
from cStringIO import StringIO
import logging
#pkg
from bps.types import BaseClass
from bps.fs import filepath
from bps.meta import Params, is_oseq, is_iter
from bps.logs import config_logging
from bps.logs.handlers import purge_handlers
#local
__all__ = [
'ak'
'TestCase',
'get_tmp_path', 'enable_suite',
'catch_warnings', 'capture_logger',
]
ak = Params
#=========================================================
#custom test base
#=========================================================
class TestCase(unittest.TestCase, BaseClass):
"bps-specific test case class, mainly contains messaging enhancements"
_prefix = None
def __init__(self, *a, **k):
#set the doc strings to begin w/ prefix
#yes, this is incredibly hacked
prefix = self._prefix
if prefix:
if callable(prefix):
prefix = prefix()
for attr in dir(self):
if not attr.startswith("test_"):
continue
v = getattr(self, attr)
d = v.im_func.__doc__ or v.im_func.__name__
idx = d.find(": ")
if idx > -1:
d = d[idx+1:]
v.im_func.__doc__ = d = "%s: %s" % (prefix, d.lstrip())
assert v.__doc__ == d
unittest.TestCase.__init__(self, *a, **k)
def assertEquals(self, real, correct, msg=None):
#NOTE: overriding this to get msg formatting capability
msg = self._format_msg(msg, "got %r, expected would equal %r", real, correct)
return self.assert_(real == correct, msg)
## assert_equals = assertEquals
def assertEqual(self, *a, **k):
return self.assertEquals(*a, **k)
def assertElementsEqual(self, real, correct, msg=None):
"test that two objects have same set of elements"
real = set(real)
correct = set(correct)
msg = self._format_msg(msg, "got %r, expected would have same elements as %r", sorted(real), sorted(correct))
return self.assert_(real == correct, msg)
assert_sets_equal = assertElementsEqual #deprecated
assert_same_set = assertElementsEqual #preferred
def assert_same_order(self, real, correct, msg=None):
"test that two objects are sequences w/ same elements in same order"
real = list(real)
correct = list(correct)
msg = self._format_msg(msg, "got %r, expected would have same elements, in same order, as %r", real, correct)
return self.assert_(real == correct, msg)
def assertNotEquals(self, real, correct, msg=None):
#NOTE: overriding this to get msg formatting capability
msg = self._format_msg(msg, "got %r, expected would equal %r", real, correct)
return self.assert_(real != correct, msg)
## assert_not_equals = assertNotEquals
def assertNotEqual(self, *a, **k):
return self.assertNotEquals(*a, **k)
def assertIs(self, real, correct, msg=None):
msg = self._format_msg(msg, "got %r, expected would be %r", real, correct)
return self.assert_(real is correct, msg)
def assertIsNot(self, real, correct, msg=None):
msg = self._format_msg(msg, "expected would not be %r", real)
return self.assert_(real is not correct, msg)
def assertIsInstance(self, obj, klass, msg=None):
msg = self._format_msg(msg, "got %r, expected instance of %r", obj, klass)
return self.assert_(isinstance(obj, klass), msg)
def assertRaises(self, type, func, *args, **kwds):
msg = kwds.pop("__msg__", None)
err = None
try:
result = func(*args, **kwds)
except Exception, err:
pass
if err is None:
msg = self._format_msg(msg, "function returned %r, expected it to raise %r", result, type)
raise AssertionError(msg)
elif not isinstance(err, type):
msg = self._format_msg(msg, "function raised %r, expected %r", err, type)
raise AssertionError(msg)
def assertAttrRaises(self, excClass, obj, attr):
#XXX: default msg?
self.assertRaises(excClass, getattr, obj, attr)
def assertWarningEquals(self, warning, **kwds):
"check if WarningMessage instance matches parameters"
for key in ("message", "category", "filename", "lineno", "file", "line"):
if key not in kwds:
continue
real = getattr(warning, key)
if key == "message":
real = str(real) #usually a UserWarning(value), etc
value = kwds[key]
if key == "filename":
if value.endswith(".pyc") or value.endswith(".pyo"):
value = value[:-1]
if real != value:
raise AssertionError("warning %s doesn't match pattern %r" % (warning, kwds))
assert_warning = assertWarningEquals
def check_function_results(self, func, cases):
"helper for running through function call cases"
#cases should be list of ak objects,
#whose first element is the function's return value
for elem in cases:
elem = Params.normalize(elem)
correct = elem.args[0]
result = func(*elem.args[1:], **elem.kwds)
self.assertEqual(result, correct,
"error for case %s: got %r, expected would equal %r" % (elem.render(1), result, correct)
)
def check_function_rtype(self, func, retval=None, rtype=None, ordered=False):
"""helper for testing functions that allow return type to be specified via rtype kwd.
:arg func: function (w/ parameters bound via partial)
:arg retval: expected result (can be set, list, etc)
:arg rtype: default rtype
:param ordered: if order must match retval when rtype is ordered
"""
#NOTE: 'self' should be test case
has_retval = (retval is not None)
#check default rtype is correct
result = func()
if rtype is None:
pass
elif rtype is iter:
self.assert_(is_iter(result))
else:
self.assertIsInstance(result, rtype)
if has_retval:
if ordered and (is_oseq(result) or is_iter(result)):
self.assert_same_order(result, retval)
else:
self.assert_same_set(result, retval)
#check unordered types work
for t in (set, frozenset):
result = func(rtype=t)
self.assertIsInstance(result,t)
if has_retval:
self.assert_same_set(result, retval)
#check ordered types work
for t in (list, tuple):
result = func(rtype=t)
self.assertIsInstance(result,t)
if has_retval:
if ordered:
self.assert_same_order(result, retval)
else:
self.assert_same_set(result, retval)
#check rtype=iter works
result = func(rtype=iter)
self.assert_(is_iter(result))
if has_retval:
if ordered:
self.assert_same_order(result, retval)
else:
self.assert_same_set(result, retval)
def _format_msg(self, msg, template, *args, **kwds):
if msg and not msg.endswith(":"):
return msg
if args:
template %= args
if kwds:
template %= kwds
if msg:
return msg + " " + template
return template
#=========================================================
#helper funcs
#=========================================================
_tmp_files = []
def _tmpfile_cleaner():
for path in _tmp_files:
try:
path.discard()
except:
log.warning("error removing temp file: %r", path, exc_info=True)
atexit.register(_tmpfile_cleaner)
def get_tmp_path():
"returns a temporary path suitable for any use, which will be removed on exit"
fd, path = tempfile.mkstemp(prefix=__name__ + "-")
os.close(fd) #close the descriptor
path = filepath(path)
_tmp_files.append(path) #register it with cleanup routine
path.remove() #remove the file which was there
assert path.ismissing
return path
def enable_suite(name):
"""check if a given test should be included based on the env var.
test flags:
bcrypt enable basic bcrypt tests
slow_bcrypt enable extra check for slow bcrypt implementation
pwgen_dups enable duplication rate checks for pwgen
"""
_flags = [ v.strip()
for v
in os.environ.get("BPS_TEST_SUITE", "").lower().split(",")
]
if 'all' in _flags:
return True
if name in _flags:
return True
return False
#=========================================================
#python backports
#=========================================================
#this was copied from the python 2.6.2 warnings.py file,
#so it would always be available for unit-tests
try:
from warnings import catch_warnings, WarningMessage
except ImportError:
class WarningMessage(object):
"""Holds the result of a single showwarning() call."""
_WARNING_DETAILS = ("message", "category", "filename", "lineno", "file",
"line")
def __init__(self, message, category, filename, lineno, file=None,
line=None):
local_values = locals()
for attr in self._WARNING_DETAILS:
setattr(self, attr, local_values[attr])
self._category_name = category.__name__ if category else None
def __str__(self):
return ("{message : %r, category : %r, filename : %r, lineno : %s, "
"line : %r}" % (self.message, self._category_name,
self.filename, self.lineno, self.line))
class catch_warnings(object):
"""A context manager that copies and restores the warnings filter upon
exiting the context.
The 'record' argument specifies whether warnings should be captured by a
custom implementation of warnings.showwarning() and be appended to a list
returned by the context manager. Otherwise None is returned by the context
manager. The objects appended to the list are arguments whose attributes
mirror the arguments to showwarning().
The 'module' argument is to specify an alternative module to the module
named 'warnings' and imported under that name. This argument is only useful
when testing the warnings module itself.
"""
def __init__(self, record=False, module=None):
"""Specify whether to record warnings and if an alternative module
should be used other than sys.modules['warnings'].
For compatibility with Python 3.0, please consider all arguments to be
keyword-only.
"""
self._record = record
self._module = sys.modules['warnings'] if module is None else module
self._entered = False
def __repr__(self):
args = []
if self._record:
args.append("record=True")
if self._module is not sys.modules['warnings']:
args.append("module=%r" % self._module)
name = type(self).__name__
return "%s(%s)" % (name, ", ".join(args))
def __enter__(self):
if self._entered:
raise RuntimeError("Cannot enter %r twice" % self)
self._entered = True
self._filters = self._module.filters
self._module.filters = self._filters[:]
self._showwarning = self._module.showwarning
if self._record:
log = []
def showwarning(*args, **kwargs):
log.append(WarningMessage(*args, **kwargs))
self._module.showwarning = showwarning
return log
else:
return None
def __exit__(self, *exc_info):
if not self._entered:
raise RuntimeError("Cannot exit %r without entering first" % self)
self._module.filters = self._filters
self._module.showwarning = self._showwarning
class catch_all_warnings(catch_warnings):
"wrap which ensures all warnings are logged to buffer"
def __init__(self):
self.__super = super(catch_all_warnings,self)
self.__super.__init__(record=True)
def __enter__(self):
log = self.__super.__enter__()
warnings.filterwarnings("always")
return log
#=========================================================
#capture logging output
#=========================================================
class capture_logger(object):
"capture output of logger, returning StringIO buffer output is written to"
def __init__(self, name=""):
self.log = getLogger(name)
def __enter__(self):
#remove handlers but don't delete them (we'll restore later)
self.propagate = self.log.propagate
self.handlers = purge_handlers(self.log, close=False)
#create new handler
buffer = StringIO()
handler = logging.StreamHandler(buffer)
handler.formatter = logging.Formatter("%(name)s: %(levelname)s: %(message)s")
self.log.addHandler(handler)
self.log.propagate = False
return buffer
def __exit__(self, *exc_info):
#remove handler we added
purge_handlers(self.log)
#restore original list of handlers
self.log.handlers[:] = self.handlers
self.log.propagate = self.propagate
#=========================================================
#EOF
#=========================================================
|