blob: 4260eb0bfdb9ac7179b988b7de441423c37038dc [file] [log] [blame]
Jean-Paul Calderone8671c852011-03-02 19:26:20 -05001# Copyright (C) Jean-Paul Calderone
2# Copyright (C) Twisted Matrix Laboratories.
Jean-Paul Calderone0ef63ed2009-07-05 13:05:45 -04003# See LICENSE for details.
4
5"""
6Helpers for the OpenSSL test suite, largely copied from
7U{Twisted<http://twistedmatrix.com/>}.
8"""
9
10import shutil
Jean-Paul Calderone68a6f8f2013-03-01 17:56:22 -080011import traceback
Jean-Paul Calderone0ef63ed2009-07-05 13:05:45 -040012import os, os.path
13from tempfile import mktemp
14from unittest import TestCase
Rick Dean47262da2009-07-08 16:17:17 -050015import sys
Jean-Paul Calderone0ef63ed2009-07-05 13:05:45 -040016
Jean-Paul Calderonec86bb7d2013-12-29 10:25:59 -050017from OpenSSL._util import exception_from_error_queue
18from OpenSSL.crypto import Error
Jean-Paul Calderone88f38b22009-07-16 16:25:19 -040019
Konstantinos Koukopoulos410d0422014-01-30 20:15:25 +020020try:
21 import memdbg
22except Exception:
23 class _memdbg(object): heap = None
24 memdbg = _memdbg()
Jean-Paul Calderone68a6f8f2013-03-01 17:56:22 -080025
Jean-Paul Calderone4f0467a2014-01-11 11:58:41 -050026from OpenSSL._util import ffi, lib, byte_string as b
Jean-Paul Calderone9e4eeae2010-08-22 21:32:52 -040027
Jean-Paul Calderone0ef63ed2009-07-05 13:05:45 -040028class TestCase(TestCase):
29 """
Jonathan Ballet648875f2011-07-16 14:14:58 +090030 :py:class:`TestCase` adds useful testing functionality beyond what is available
31 from the standard library :py:class:`unittest.TestCase`.
Jean-Paul Calderone0ef63ed2009-07-05 13:05:45 -040032 """
Jean-Paul Calderone855331d2013-03-03 10:21:43 -080033 def run(self, result):
Jean-Paul Calderone68703ed2013-03-04 12:23:44 -080034 run = super(TestCase, self).run
35 if memdbg.heap is None:
36 return run(result)
37
Jean-Paul Calderone855331d2013-03-03 10:21:43 -080038 # Run the test as usual
39 before = set(memdbg.heap)
Jean-Paul Calderone68703ed2013-03-04 12:23:44 -080040 run(result)
Jean-Paul Calderone855331d2013-03-03 10:21:43 -080041
42 # Clean up some long-lived allocations so they won't be reported as
43 # memory leaks.
Jean-Paul Calderone9227c472013-12-31 13:47:36 -050044 lib.CRYPTO_cleanup_all_ex_data()
45 lib.ERR_remove_thread_state(ffi.NULL)
Jean-Paul Calderone855331d2013-03-03 10:21:43 -080046 after = set(memdbg.heap)
47
48 if not after - before:
49 # No leaks, fast succeed
50 return
51
52 if result.wasSuccessful():
53 # If it passed, run it again with memory debugging
54 before = set(memdbg.heap)
Jean-Paul Calderone68703ed2013-03-04 12:23:44 -080055 run(result)
Jean-Paul Calderone855331d2013-03-03 10:21:43 -080056
57 # Clean up some long-lived allocations so they won't be reported as
58 # memory leaks.
Jean-Paul Calderone3f93d212014-01-01 12:36:53 -050059 lib.CRYPTO_cleanup_all_ex_data()
60 lib.ERR_remove_thread_state(ffi.NULL)
Jean-Paul Calderone855331d2013-03-03 10:21:43 -080061
62 after = set(memdbg.heap)
63
64 self._reportLeaks(after - before, result)
Jean-Paul Calderonef6745b32013-03-01 15:08:46 -080065
Jean-Paul Calderone40732ff2013-03-01 20:53:50 -080066
Jean-Paul Calderone855331d2013-03-03 10:21:43 -080067 def _reportLeaks(self, leaks, result):
Jean-Paul Calderone68a6f8f2013-03-01 17:56:22 -080068 def format_leak(p):
69 stacks = memdbg.heap[p]
70 # Eventually look at multiple stacks for the realloc() case. For
71 # now just look at the original allocation location.
Jean-Paul Calderonec2e8b412013-03-02 16:27:55 -080072 (size, python_stack, c_stack) = stacks[0]
Jean-Paul Calderonef6745b32013-03-01 15:08:46 -080073
Jean-Paul Calderone68a6f8f2013-03-01 17:56:22 -080074 stack = traceback.format_list(python_stack)[:-1]
Jean-Paul Calderonef6745b32013-03-01 15:08:46 -080075
Jean-Paul Calderone68a6f8f2013-03-01 17:56:22 -080076 # c_stack looks something like this (interesting parts indicated
77 # with inserted arrows not part of the data):
78 #
79 # /home/exarkun/Projects/pyOpenSSL/branches/use-opentls/__pycache__/_cffi__x89095113xb9185b9b.so(+0x12cf) [0x7fe2e20582cf]
80 # /home/exarkun/Projects/cpython/2.7/python(PyCFunction_Call+0x8b) [0x56265a]
81 # /home/exarkun/Projects/cpython/2.7/python() [0x4d5f52]
82 # /home/exarkun/Projects/cpython/2.7/python(PyEval_EvalFrameEx+0x753b) [0x4d0e1e]
83 # /home/exarkun/Projects/cpython/2.7/python() [0x4d6419]
84 # /home/exarkun/Projects/cpython/2.7/python() [0x4d6129]
85 # /home/exarkun/Projects/cpython/2.7/python(PyEval_EvalFrameEx+0x753b) [0x4d0e1e]
86 # /home/exarkun/Projects/cpython/2.7/python(PyEval_EvalCodeEx+0x1043) [0x4d3726]
87 # /home/exarkun/Projects/cpython/2.7/python() [0x55fd51]
88 # /home/exarkun/Projects/cpython/2.7/python(PyObject_Call+0x7e) [0x420ee6]
89 # /home/exarkun/Projects/cpython/2.7/python(PyEval_CallObjectWithKeywords+0x158) [0x4d56ec]
90 # /home/exarkun/.local/lib/python2.7/site-packages/cffi-0.5-py2.7-linux-x86_64.egg/_cffi_backend.so(+0xe96e) [0x7fe2e38be96e]
91 # /usr/lib/x86_64-linux-gnu/libffi.so.6(ffi_closure_unix64_inner+0x1b9) [0x7fe2e36ad819]
92 # /usr/lib/x86_64-linux-gnu/libffi.so.6(ffi_closure_unix64+0x46) [0x7fe2e36adb7c]
93 # /lib/x86_64-linux-gnu/libcrypto.so.1.0.0(CRYPTO_malloc+0x64) [0x7fe2e1cef784] <------ end interesting
94 # /lib/x86_64-linux-gnu/libcrypto.so.1.0.0(lh_insert+0x16b) [0x7fe2e1d6a24b] .
95 # /lib/x86_64-linux-gnu/libcrypto.so.1.0.0(+0x61c18) [0x7fe2e1cf0c18] .
96 # /lib/x86_64-linux-gnu/libcrypto.so.1.0.0(+0x625ec) [0x7fe2e1cf15ec] .
97 # /lib/x86_64-linux-gnu/libcrypto.so.1.0.0(DSA_new_method+0xe6) [0x7fe2e1d524d6] .
98 # /lib/x86_64-linux-gnu/libcrypto.so.1.0.0(DSA_generate_parameters+0x3a) [0x7fe2e1d5364a] <------ begin interesting
99 # /home/exarkun/Projects/opentls/trunk/tls/c/__pycache__/_cffi__x305d4698xb539baaa.so(+0x1f397) [0x7fe2df84d397]
100 # /home/exarkun/Projects/cpython/2.7/python(PyCFunction_Call+0x8b) [0x56265a]
101 # /home/exarkun/Projects/cpython/2.7/python() [0x4d5f52]
102 # /home/exarkun/Projects/cpython/2.7/python(PyEval_EvalFrameEx+0x753b) [0x4d0e1e]
103 # /home/exarkun/Projects/cpython/2.7/python() [0x4d6419]
104 # ...
105 #
106 # Notice the stack is upside down compared to a Python traceback.
107 # Identify the start and end of interesting bits and stuff it into the stack we report.
Jean-Paul Calderonef6745b32013-03-01 15:08:46 -0800108
Jean-Paul Calderonec2e8b412013-03-02 16:27:55 -0800109 saved = list(c_stack)
110
Jean-Paul Calderone68a6f8f2013-03-01 17:56:22 -0800111 # Figure the first interesting frame will be after a the cffi-compiled module
Jean-Paul Calderonec2e8b412013-03-02 16:27:55 -0800112 while c_stack and '/__pycache__/_cffi__' not in c_stack[-1]:
Jean-Paul Calderone68a6f8f2013-03-01 17:56:22 -0800113 c_stack.pop()
Jean-Paul Calderonef6745b32013-03-01 15:08:46 -0800114
Jean-Paul Calderone68a6f8f2013-03-01 17:56:22 -0800115 # Figure the last interesting frame will always be CRYPTO_malloc,
116 # since that's where we hooked in to things.
Jean-Paul Calderonec2e8b412013-03-02 16:27:55 -0800117 while c_stack and 'CRYPTO_malloc' not in c_stack[0] and 'CRYPTO_realloc' not in c_stack[0]:
Jean-Paul Calderone68a6f8f2013-03-01 17:56:22 -0800118 c_stack.pop(0)
119
Jean-Paul Calderonec2e8b412013-03-02 16:27:55 -0800120 if c_stack:
121 c_stack.reverse()
122 else:
123 c_stack = saved[::-1]
Jean-Paul Calderone68a6f8f2013-03-01 17:56:22 -0800124 stack.extend([frame + "\n" for frame in c_stack])
125
Jean-Paul Calderone2beac532013-03-03 17:30:36 -0800126 stack.insert(0, "Leaked (%s) at:\n")
Jean-Paul Calderone68a6f8f2013-03-01 17:56:22 -0800127 return "".join(stack)
128
Jean-Paul Calderone855331d2013-03-03 10:21:43 -0800129 if leaks:
Jean-Paul Calderone2beac532013-03-03 17:30:36 -0800130 unique_leaks = {}
Jean-Paul Calderone855331d2013-03-03 10:21:43 -0800131 for p in leaks:
Jean-Paul Calderone2beac532013-03-03 17:30:36 -0800132 size = memdbg.heap[p][-1][0]
133 new_leak = format_leak(p)
134 if new_leak not in unique_leaks:
135 unique_leaks[new_leak] = [(size, p)]
136 else:
137 unique_leaks[new_leak].append((size, p))
138 memdbg.free(p)
139
140 for (stack, allocs) in unique_leaks.iteritems():
141 allocs_accum = []
142 for (size, pointer) in allocs:
143
Jean-Paul Calderone9227c472013-12-31 13:47:36 -0500144 addr = int(ffi.cast('uintptr_t', pointer))
Jean-Paul Calderone2beac532013-03-03 17:30:36 -0800145 allocs_accum.append("%d@0x%x" % (size, addr))
146 allocs_report = ", ".join(sorted(allocs_accum))
147
Jean-Paul Calderone855331d2013-03-03 10:21:43 -0800148 result.addError(
149 self,
Jean-Paul Calderone2beac532013-03-03 17:30:36 -0800150 (None, Exception(stack % (allocs_report,)), None))
Jean-Paul Calderonef6745b32013-03-01 15:08:46 -0800151
Jean-Paul Calderone855331d2013-03-03 10:21:43 -0800152
153 def tearDown(self):
154 """
155 Clean up any files or directories created using :py:meth:`TestCase.mktemp`.
156 Subclasses must invoke this method if they override it or the
157 cleanup will not occur.
158 """
Jean-Paul Calderonebf37f0f2010-07-31 14:56:20 -0400159 if False and self._temporaryFiles is not None:
Jean-Paul Calderone0ef63ed2009-07-05 13:05:45 -0400160 for temp in self._temporaryFiles:
161 if os.path.isdir(temp):
162 shutil.rmtree(temp)
163 elif os.path.exists(temp):
164 os.unlink(temp)
Jean-Paul Calderone1206daf2009-07-16 16:07:42 -0400165 try:
Jean-Paul Calderonec86bb7d2013-12-29 10:25:59 -0500166 exception_from_error_queue(Error)
Jean-Paul Calderone24b64592010-08-12 10:43:09 -0400167 except Error:
168 e = sys.exc_info()[1]
Jean-Paul Calderone1206daf2009-07-16 16:07:42 -0400169 if e.args != ([],):
170 self.fail("Left over errors in OpenSSL error queue: " + repr(e))
171
Jean-Paul Calderone0ef63ed2009-07-05 13:05:45 -0400172
Jean-Paul Calderone8fb53182013-12-30 08:35:49 -0500173 def assertIsInstance(self, instance, classOrTuple, message=None):
174 """
175 Fail if C{instance} is not an instance of the given class or of
176 one of the given classes.
177
178 @param instance: the object to test the type (first argument of the
179 C{isinstance} call).
180 @type instance: any.
181 @param classOrTuple: the class or classes to test against (second
182 argument of the C{isinstance} call).
183 @type classOrTuple: class, type, or tuple.
184
185 @param message: Custom text to include in the exception text if the
186 assertion fails.
187 """
188 if not isinstance(instance, classOrTuple):
189 if message is None:
190 suffix = ""
191 else:
192 suffix = ": " + message
193 self.fail("%r is not an instance of %s%s" % (
194 instance, classOrTuple, suffix))
195
Jean-Paul Calderoneabfbab62013-02-09 21:25:02 -0800196
Jean-Paul Calderone060a57e2011-05-04 18:02:49 -0400197 def failUnlessIn(self, containee, container, msg=None):
198 """
Jonathan Ballet648875f2011-07-16 14:14:58 +0900199 Fail the test if :py:data:`containee` is not found in :py:data:`container`.
Jean-Paul Calderone060a57e2011-05-04 18:02:49 -0400200
Jonathan Ballet648875f2011-07-16 14:14:58 +0900201 :param containee: the value that should be in :py:class:`container`
Jonathan Ballet78b92a22011-07-16 08:07:26 +0900202 :param container: a sequence type, or in the case of a mapping type,
Jean-Paul Calderone060a57e2011-05-04 18:02:49 -0400203 will follow semantics of 'if key in dict.keys()'
Jonathan Ballet78b92a22011-07-16 08:07:26 +0900204 :param msg: if msg is None, then the failure message will be
Jean-Paul Calderone060a57e2011-05-04 18:02:49 -0400205 '%r not in %r' % (first, second)
206 """
207 if containee not in container:
208 raise self.failureException(msg or "%r not in %r"
209 % (containee, container))
210 return containee
211 assertIn = failUnlessIn
212
Jean-Paul Calderone15f36442014-05-01 07:58:02 -0400213 def assertNotIn(self, containee, container, msg=None):
214 """
215 Fail the test if C{containee} is found in C{container}.
216
217 @param containee: the value that should not be in C{container}
218 @param container: a sequence type, or in the case of a mapping type,
219 will follow semantics of 'if key in dict.keys()'
220 @param msg: if msg is None, then the failure message will be
221 '%r in %r' % (first, second)
222 """
223 if containee in container:
224 raise self.failureException(msg or "%r in %r"
225 % (containee, container))
226 return containee
227 failIfIn = assertNotIn
228
229
Jean-Paul Calderone77b3d082014-12-12 20:04:35 -0500230 def assertIs(self, first, second, msg=None):
Jean-Paul Calderone0ef63ed2009-07-05 13:05:45 -0400231 """
Jonathan Ballet648875f2011-07-16 14:14:58 +0900232 Fail the test if :py:data:`first` is not :py:data:`second`. This is an
Jean-Paul Calderone0ef63ed2009-07-05 13:05:45 -0400233 obect-identity-equality test, not an object equality
Jonathan Ballet648875f2011-07-16 14:14:58 +0900234 (i.e. :py:func:`__eq__`) test.
Jean-Paul Calderone0ef63ed2009-07-05 13:05:45 -0400235
Jonathan Ballet78b92a22011-07-16 08:07:26 +0900236 :param msg: if msg is None, then the failure message will be
Jean-Paul Calderone0ef63ed2009-07-05 13:05:45 -0400237 '%r is not %r' % (first, second)
238 """
239 if first is not second:
240 raise self.failureException(msg or '%r is not %r' % (first, second))
241 return first
Jean-Paul Calderone77b3d082014-12-12 20:04:35 -0500242 assertIdentical = failUnlessIdentical = assertIs
Jean-Paul Calderone0ef63ed2009-07-05 13:05:45 -0400243
244
Jean-Paul Calderone77b3d082014-12-12 20:04:35 -0500245 def assertIsNot(self, first, second, msg=None):
Jean-Paul Calderone0ef63ed2009-07-05 13:05:45 -0400246 """
Jonathan Ballet648875f2011-07-16 14:14:58 +0900247 Fail the test if :py:data:`first` is :py:data:`second`. This is an
Jean-Paul Calderone0ef63ed2009-07-05 13:05:45 -0400248 obect-identity-equality test, not an object equality
Jonathan Ballet648875f2011-07-16 14:14:58 +0900249 (i.e. :py:func:`__eq__`) test.
Jean-Paul Calderone0ef63ed2009-07-05 13:05:45 -0400250
Jonathan Ballet78b92a22011-07-16 08:07:26 +0900251 :param msg: if msg is None, then the failure message will be
Jean-Paul Calderone0ef63ed2009-07-05 13:05:45 -0400252 '%r is %r' % (first, second)
253 """
254 if first is second:
255 raise self.failureException(msg or '%r is %r' % (first, second))
256 return first
Jean-Paul Calderone77b3d082014-12-12 20:04:35 -0500257 assertNotIdentical = failIfIdentical = assertIsNot
Jean-Paul Calderone0ef63ed2009-07-05 13:05:45 -0400258
259
260 def failUnlessRaises(self, exception, f, *args, **kwargs):
261 """
Jonathan Ballet648875f2011-07-16 14:14:58 +0900262 Fail the test unless calling the function :py:data:`f` with the given
263 :py:data:`args` and :py:data:`kwargs` raises :py:data:`exception`. The
264 failure will report the traceback and call stack of the unexpected
265 exception.
Jean-Paul Calderone0ef63ed2009-07-05 13:05:45 -0400266
Jonathan Ballet78b92a22011-07-16 08:07:26 +0900267 :param exception: exception type that is to be expected
268 :param f: the function to call
Jean-Paul Calderone0ef63ed2009-07-05 13:05:45 -0400269
Jonathan Ballet78b92a22011-07-16 08:07:26 +0900270 :return: The raised exception instance, if it is of the given type.
271 :raise self.failureException: Raised if the function call does
Jean-Paul Calderone0ef63ed2009-07-05 13:05:45 -0400272 not raise an exception or if it raises an exception of a
273 different type.
274 """
275 try:
276 result = f(*args, **kwargs)
Jean-Paul Calderone24b64592010-08-12 10:43:09 -0400277 except exception:
278 inst = sys.exc_info()[1]
Jean-Paul Calderone0ef63ed2009-07-05 13:05:45 -0400279 return inst
280 except:
Rick Dean47262da2009-07-08 16:17:17 -0500281 raise self.failureException('%s raised instead of %s'
Jean-Paul Calderone0ef63ed2009-07-05 13:05:45 -0400282 % (sys.exc_info()[0],
283 exception.__name__,
Rick Dean47262da2009-07-08 16:17:17 -0500284 ))
Jean-Paul Calderone0ef63ed2009-07-05 13:05:45 -0400285 else:
286 raise self.failureException('%s not raised (%r returned)'
287 % (exception.__name__, result))
288 assertRaises = failUnlessRaises
289
290
291 _temporaryFiles = None
292 def mktemp(self):
293 """
294 Pathetic substitute for twisted.trial.unittest.TestCase.mktemp.
295 """
296 if self._temporaryFiles is None:
297 self._temporaryFiles = []
Jean-Paul Calderonef9fb8922014-01-11 08:35:54 -0500298 temp = b(mktemp(dir="."))
Jean-Paul Calderone0ef63ed2009-07-05 13:05:45 -0400299 self._temporaryFiles.append(temp)
300 return temp
301
302
Jean-Paul Calderone68649052009-07-17 21:14:27 -0400303 # Other stuff
304 def assertConsistentType(self, theType, name, *constructionArgs):
305 """
Jonathan Ballet648875f2011-07-16 14:14:58 +0900306 Perform various assertions about :py:data:`theType` to ensure that it is a
Jean-Paul Calderone68649052009-07-17 21:14:27 -0400307 well-defined type. This is useful for extension types, where it's
308 pretty easy to do something wacky. If something about the type is
309 unusual, an exception will be raised.
310
Jonathan Ballet78b92a22011-07-16 08:07:26 +0900311 :param theType: The type object about which to make assertions.
312 :param name: A string giving the name of the type.
Jonathan Ballet648875f2011-07-16 14:14:58 +0900313 :param constructionArgs: Positional arguments to use with :py:data:`theType` to
Jean-Paul Calderone68649052009-07-17 21:14:27 -0400314 create an instance of it.
315 """
316 self.assertEqual(theType.__name__, name)
317 self.assertTrue(isinstance(theType, type))
318 instance = theType(*constructionArgs)
319 self.assertIdentical(type(instance), theType)
Jean-Paul Calderone9c7f0692014-04-30 18:17:19 -0400320
321
322
323class EqualityTestsMixin(object):
324 """
325 A mixin defining tests for the standard implementation of C{==} and C{!=}.
326 """
327 def anInstance(self):
328 """
329 Return an instance of the class under test. Each call to this method
330 must return a different object. All objects returned must be equal to
331 each other.
332 """
333 raise NotImplementedError()
334
335
336 def anotherInstance(self):
337 """
338 Return an instance of the class under test. Each call to this method
339 must return a different object. The objects must not be equal to the
340 objects returned by C{anInstance}. They may or may not be equal to
341 each other (they will not be compared against each other).
342 """
343 raise NotImplementedError()
344
345
346 def test_identicalEq(self):
347 """
348 An object compares equal to itself using the C{==} operator.
349 """
350 o = self.anInstance()
351 self.assertTrue(o == o)
352
353
354 def test_identicalNe(self):
355 """
356 An object doesn't compare not equal to itself using the C{!=} operator.
357 """
358 o = self.anInstance()
359 self.assertFalse(o != o)
360
361
362 def test_sameEq(self):
363 """
364 Two objects that are equal to each other compare equal to each other
365 using the C{==} operator.
366 """
367 a = self.anInstance()
368 b = self.anInstance()
369 self.assertTrue(a == b)
370
371
372 def test_sameNe(self):
373 """
374 Two objects that are equal to each other do not compare not equal to
375 each other using the C{!=} operator.
376 """
377 a = self.anInstance()
378 b = self.anInstance()
379 self.assertFalse(a != b)
380
381
382 def test_differentEq(self):
383 """
384 Two objects that are not equal to each other do not compare equal to
385 each other using the C{==} operator.
386 """
387 a = self.anInstance()
388 b = self.anotherInstance()
389 self.assertFalse(a == b)
390
391
392 def test_differentNe(self):
393 """
394 Two objects that are not equal to each other compare not equal to each
395 other using the C{!=} operator.
396 """
397 a = self.anInstance()
398 b = self.anotherInstance()
399 self.assertTrue(a != b)
400
401
402 def test_anotherTypeEq(self):
403 """
404 The object does not compare equal to an object of an unrelated type
405 (which does not implement the comparison) using the C{==} operator.
406 """
407 a = self.anInstance()
408 b = object()
409 self.assertFalse(a == b)
410
411
412 def test_anotherTypeNe(self):
413 """
414 The object compares not equal to an object of an unrelated type (which
415 does not implement the comparison) using the C{!=} operator.
416 """
417 a = self.anInstance()
418 b = object()
419 self.assertTrue(a != b)
420
421
422 def test_delegatedEq(self):
423 """
424 The result of comparison using C{==} is delegated to the right-hand
425 operand if it is of an unrelated type.
426 """
427 class Delegate(object):
428 def __eq__(self, other):
429 # Do something crazy and obvious.
430 return [self]
431
432 a = self.anInstance()
433 b = Delegate()
434 self.assertEqual(a == b, [b])
435
436
437 def test_delegateNe(self):
438 """
439 The result of comparison using C{!=} is delegated to the right-hand
440 operand if it is of an unrelated type.
441 """
442 class Delegate(object):
443 def __ne__(self, other):
444 # Do something crazy and obvious.
445 return [self]
446
447 a = self.anInstance()
448 b = Delegate()
449 self.assertEqual(a != b, [b])