blob: 31242ce22fdfd3506a11e7e11d951164ba7c9be0 [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 Calderone88f38b22009-07-16 16:25:19 -040017from OpenSSL.crypto import Error, _exception_from_error_queue
18
Jean-Paul Calderone68a6f8f2013-03-01 17:56:22 -080019import memdbg
20
Jean-Paul Calderoneea9c8a32011-04-01 18:26:37 -040021if sys.version_info < (3, 0):
Jean-Paul Calderone9e4eeae2010-08-22 21:32:52 -040022 def b(s):
23 return s
24 bytes = str
25else:
26 def b(s):
Jean-Paul Calderone77769602011-04-06 18:20:10 -040027 return s.encode("charmap")
Jean-Paul Calderoneea9c8a32011-04-01 18:26:37 -040028 bytes = bytes
Jean-Paul Calderone9e4eeae2010-08-22 21:32:52 -040029
Jean-Paul Calderonef6745b32013-03-01 15:08:46 -080030from tls.c import api
Jean-Paul Calderone9e4eeae2010-08-22 21:32:52 -040031
Jean-Paul Calderone0ef63ed2009-07-05 13:05:45 -040032class TestCase(TestCase):
33 """
Jonathan Ballet648875f2011-07-16 14:14:58 +090034 :py:class:`TestCase` adds useful testing functionality beyond what is available
35 from the standard library :py:class:`unittest.TestCase`.
Jean-Paul Calderone0ef63ed2009-07-05 13:05:45 -040036 """
Jean-Paul Calderone855331d2013-03-03 10:21:43 -080037 def run(self, result):
Jean-Paul Calderone68703ed2013-03-04 12:23:44 -080038 run = super(TestCase, self).run
39 if memdbg.heap is None:
40 return run(result)
41
Jean-Paul Calderone855331d2013-03-03 10:21:43 -080042 # Run the test as usual
43 before = set(memdbg.heap)
Jean-Paul Calderone68703ed2013-03-04 12:23:44 -080044 run(result)
Jean-Paul Calderone855331d2013-03-03 10:21:43 -080045
46 # Clean up some long-lived allocations so they won't be reported as
47 # memory leaks.
48 api.CRYPTO_cleanup_all_ex_data()
49 api.ERR_remove_thread_state(api.NULL)
50 after = set(memdbg.heap)
51
52 if not after - before:
53 # No leaks, fast succeed
54 return
55
56 if result.wasSuccessful():
57 # If it passed, run it again with memory debugging
58 before = set(memdbg.heap)
Jean-Paul Calderone68703ed2013-03-04 12:23:44 -080059 run(result)
Jean-Paul Calderone855331d2013-03-03 10:21:43 -080060
61 # Clean up some long-lived allocations so they won't be reported as
62 # memory leaks.
63 api.CRYPTO_cleanup_all_ex_data()
64 api.ERR_remove_thread_state(api.NULL)
65
66 after = set(memdbg.heap)
67
68 self._reportLeaks(after - before, result)
Jean-Paul Calderonef6745b32013-03-01 15:08:46 -080069
Jean-Paul Calderone40732ff2013-03-01 20:53:50 -080070
Jean-Paul Calderone855331d2013-03-03 10:21:43 -080071 def _reportLeaks(self, leaks, result):
Jean-Paul Calderone68a6f8f2013-03-01 17:56:22 -080072 def format_leak(p):
73 stacks = memdbg.heap[p]
74 # Eventually look at multiple stacks for the realloc() case. For
75 # now just look at the original allocation location.
Jean-Paul Calderonec2e8b412013-03-02 16:27:55 -080076 (size, python_stack, c_stack) = stacks[0]
Jean-Paul Calderonef6745b32013-03-01 15:08:46 -080077
Jean-Paul Calderone68a6f8f2013-03-01 17:56:22 -080078 stack = traceback.format_list(python_stack)[:-1]
Jean-Paul Calderonef6745b32013-03-01 15:08:46 -080079
Jean-Paul Calderone68a6f8f2013-03-01 17:56:22 -080080 # c_stack looks something like this (interesting parts indicated
81 # with inserted arrows not part of the data):
82 #
83 # /home/exarkun/Projects/pyOpenSSL/branches/use-opentls/__pycache__/_cffi__x89095113xb9185b9b.so(+0x12cf) [0x7fe2e20582cf]
84 # /home/exarkun/Projects/cpython/2.7/python(PyCFunction_Call+0x8b) [0x56265a]
85 # /home/exarkun/Projects/cpython/2.7/python() [0x4d5f52]
86 # /home/exarkun/Projects/cpython/2.7/python(PyEval_EvalFrameEx+0x753b) [0x4d0e1e]
87 # /home/exarkun/Projects/cpython/2.7/python() [0x4d6419]
88 # /home/exarkun/Projects/cpython/2.7/python() [0x4d6129]
89 # /home/exarkun/Projects/cpython/2.7/python(PyEval_EvalFrameEx+0x753b) [0x4d0e1e]
90 # /home/exarkun/Projects/cpython/2.7/python(PyEval_EvalCodeEx+0x1043) [0x4d3726]
91 # /home/exarkun/Projects/cpython/2.7/python() [0x55fd51]
92 # /home/exarkun/Projects/cpython/2.7/python(PyObject_Call+0x7e) [0x420ee6]
93 # /home/exarkun/Projects/cpython/2.7/python(PyEval_CallObjectWithKeywords+0x158) [0x4d56ec]
94 # /home/exarkun/.local/lib/python2.7/site-packages/cffi-0.5-py2.7-linux-x86_64.egg/_cffi_backend.so(+0xe96e) [0x7fe2e38be96e]
95 # /usr/lib/x86_64-linux-gnu/libffi.so.6(ffi_closure_unix64_inner+0x1b9) [0x7fe2e36ad819]
96 # /usr/lib/x86_64-linux-gnu/libffi.so.6(ffi_closure_unix64+0x46) [0x7fe2e36adb7c]
97 # /lib/x86_64-linux-gnu/libcrypto.so.1.0.0(CRYPTO_malloc+0x64) [0x7fe2e1cef784] <------ end interesting
98 # /lib/x86_64-linux-gnu/libcrypto.so.1.0.0(lh_insert+0x16b) [0x7fe2e1d6a24b] .
99 # /lib/x86_64-linux-gnu/libcrypto.so.1.0.0(+0x61c18) [0x7fe2e1cf0c18] .
100 # /lib/x86_64-linux-gnu/libcrypto.so.1.0.0(+0x625ec) [0x7fe2e1cf15ec] .
101 # /lib/x86_64-linux-gnu/libcrypto.so.1.0.0(DSA_new_method+0xe6) [0x7fe2e1d524d6] .
102 # /lib/x86_64-linux-gnu/libcrypto.so.1.0.0(DSA_generate_parameters+0x3a) [0x7fe2e1d5364a] <------ begin interesting
103 # /home/exarkun/Projects/opentls/trunk/tls/c/__pycache__/_cffi__x305d4698xb539baaa.so(+0x1f397) [0x7fe2df84d397]
104 # /home/exarkun/Projects/cpython/2.7/python(PyCFunction_Call+0x8b) [0x56265a]
105 # /home/exarkun/Projects/cpython/2.7/python() [0x4d5f52]
106 # /home/exarkun/Projects/cpython/2.7/python(PyEval_EvalFrameEx+0x753b) [0x4d0e1e]
107 # /home/exarkun/Projects/cpython/2.7/python() [0x4d6419]
108 # ...
109 #
110 # Notice the stack is upside down compared to a Python traceback.
111 # Identify the start and end of interesting bits and stuff it into the stack we report.
Jean-Paul Calderonef6745b32013-03-01 15:08:46 -0800112
Jean-Paul Calderonec2e8b412013-03-02 16:27:55 -0800113 saved = list(c_stack)
114
Jean-Paul Calderone68a6f8f2013-03-01 17:56:22 -0800115 # Figure the first interesting frame will be after a the cffi-compiled module
Jean-Paul Calderonec2e8b412013-03-02 16:27:55 -0800116 while c_stack and '/__pycache__/_cffi__' not in c_stack[-1]:
Jean-Paul Calderone68a6f8f2013-03-01 17:56:22 -0800117 c_stack.pop()
Jean-Paul Calderonef6745b32013-03-01 15:08:46 -0800118
Jean-Paul Calderone68a6f8f2013-03-01 17:56:22 -0800119 # Figure the last interesting frame will always be CRYPTO_malloc,
120 # since that's where we hooked in to things.
Jean-Paul Calderonec2e8b412013-03-02 16:27:55 -0800121 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 -0800122 c_stack.pop(0)
123
Jean-Paul Calderonec2e8b412013-03-02 16:27:55 -0800124 if c_stack:
125 c_stack.reverse()
126 else:
127 c_stack = saved[::-1]
Jean-Paul Calderone68a6f8f2013-03-01 17:56:22 -0800128 stack.extend([frame + "\n" for frame in c_stack])
129
Jean-Paul Calderone2beac532013-03-03 17:30:36 -0800130 stack.insert(0, "Leaked (%s) at:\n")
Jean-Paul Calderone68a6f8f2013-03-01 17:56:22 -0800131 return "".join(stack)
132
Jean-Paul Calderone855331d2013-03-03 10:21:43 -0800133 if leaks:
Jean-Paul Calderone2beac532013-03-03 17:30:36 -0800134 unique_leaks = {}
Jean-Paul Calderone855331d2013-03-03 10:21:43 -0800135 for p in leaks:
Jean-Paul Calderone2beac532013-03-03 17:30:36 -0800136 size = memdbg.heap[p][-1][0]
137 new_leak = format_leak(p)
138 if new_leak not in unique_leaks:
139 unique_leaks[new_leak] = [(size, p)]
140 else:
141 unique_leaks[new_leak].append((size, p))
142 memdbg.free(p)
143
144 for (stack, allocs) in unique_leaks.iteritems():
145 allocs_accum = []
146 for (size, pointer) in allocs:
147
148 addr = int(api.ffi.cast('uintptr_t', pointer))
149 allocs_accum.append("%d@0x%x" % (size, addr))
150 allocs_report = ", ".join(sorted(allocs_accum))
151
Jean-Paul Calderone855331d2013-03-03 10:21:43 -0800152 result.addError(
153 self,
Jean-Paul Calderone2beac532013-03-03 17:30:36 -0800154 (None, Exception(stack % (allocs_report,)), None))
Jean-Paul Calderonef6745b32013-03-01 15:08:46 -0800155
Jean-Paul Calderone855331d2013-03-03 10:21:43 -0800156
157 def tearDown(self):
158 """
159 Clean up any files or directories created using :py:meth:`TestCase.mktemp`.
160 Subclasses must invoke this method if they override it or the
161 cleanup will not occur.
162 """
Jean-Paul Calderonebf37f0f2010-07-31 14:56:20 -0400163 if False and self._temporaryFiles is not None:
Jean-Paul Calderone0ef63ed2009-07-05 13:05:45 -0400164 for temp in self._temporaryFiles:
165 if os.path.isdir(temp):
166 shutil.rmtree(temp)
167 elif os.path.exists(temp):
168 os.unlink(temp)
Jean-Paul Calderone1206daf2009-07-16 16:07:42 -0400169 try:
170 _exception_from_error_queue()
Jean-Paul Calderone24b64592010-08-12 10:43:09 -0400171 except Error:
172 e = sys.exc_info()[1]
Jean-Paul Calderone1206daf2009-07-16 16:07:42 -0400173 if e.args != ([],):
174 self.fail("Left over errors in OpenSSL error queue: " + repr(e))
175
Jean-Paul Calderone0ef63ed2009-07-05 13:05:45 -0400176
Jean-Paul Calderoneabfbab62013-02-09 21:25:02 -0800177
Jean-Paul Calderone060a57e2011-05-04 18:02:49 -0400178 def failUnlessIn(self, containee, container, msg=None):
179 """
Jonathan Ballet648875f2011-07-16 14:14:58 +0900180 Fail the test if :py:data:`containee` is not found in :py:data:`container`.
Jean-Paul Calderone060a57e2011-05-04 18:02:49 -0400181
Jonathan Ballet648875f2011-07-16 14:14:58 +0900182 :param containee: the value that should be in :py:class:`container`
Jonathan Ballet78b92a22011-07-16 08:07:26 +0900183 :param container: a sequence type, or in the case of a mapping type,
Jean-Paul Calderone060a57e2011-05-04 18:02:49 -0400184 will follow semantics of 'if key in dict.keys()'
Jonathan Ballet78b92a22011-07-16 08:07:26 +0900185 :param msg: if msg is None, then the failure message will be
Jean-Paul Calderone060a57e2011-05-04 18:02:49 -0400186 '%r not in %r' % (first, second)
187 """
188 if containee not in container:
189 raise self.failureException(msg or "%r not in %r"
190 % (containee, container))
191 return containee
192 assertIn = failUnlessIn
193
Jean-Paul Calderone0ef63ed2009-07-05 13:05:45 -0400194 def failUnlessIdentical(self, first, second, msg=None):
195 """
Jonathan Ballet648875f2011-07-16 14:14:58 +0900196 Fail the test if :py:data:`first` is not :py:data:`second`. This is an
Jean-Paul Calderone0ef63ed2009-07-05 13:05:45 -0400197 obect-identity-equality test, not an object equality
Jonathan Ballet648875f2011-07-16 14:14:58 +0900198 (i.e. :py:func:`__eq__`) test.
Jean-Paul Calderone0ef63ed2009-07-05 13:05:45 -0400199
Jonathan Ballet78b92a22011-07-16 08:07:26 +0900200 :param msg: if msg is None, then the failure message will be
Jean-Paul Calderone0ef63ed2009-07-05 13:05:45 -0400201 '%r is not %r' % (first, second)
202 """
203 if first is not second:
204 raise self.failureException(msg or '%r is not %r' % (first, second))
205 return first
206 assertIdentical = failUnlessIdentical
207
208
209 def failIfIdentical(self, first, second, msg=None):
210 """
Jonathan Ballet648875f2011-07-16 14:14:58 +0900211 Fail the test if :py:data:`first` is :py:data:`second`. This is an
Jean-Paul Calderone0ef63ed2009-07-05 13:05:45 -0400212 obect-identity-equality test, not an object equality
Jonathan Ballet648875f2011-07-16 14:14:58 +0900213 (i.e. :py:func:`__eq__`) test.
Jean-Paul Calderone0ef63ed2009-07-05 13:05:45 -0400214
Jonathan Ballet78b92a22011-07-16 08:07:26 +0900215 :param msg: if msg is None, then the failure message will be
Jean-Paul Calderone0ef63ed2009-07-05 13:05:45 -0400216 '%r is %r' % (first, second)
217 """
218 if first is second:
219 raise self.failureException(msg or '%r is %r' % (first, second))
220 return first
221 assertNotIdentical = failIfIdentical
222
223
224 def failUnlessRaises(self, exception, f, *args, **kwargs):
225 """
Jonathan Ballet648875f2011-07-16 14:14:58 +0900226 Fail the test unless calling the function :py:data:`f` with the given
227 :py:data:`args` and :py:data:`kwargs` raises :py:data:`exception`. The
228 failure will report the traceback and call stack of the unexpected
229 exception.
Jean-Paul Calderone0ef63ed2009-07-05 13:05:45 -0400230
Jonathan Ballet78b92a22011-07-16 08:07:26 +0900231 :param exception: exception type that is to be expected
232 :param f: the function to call
Jean-Paul Calderone0ef63ed2009-07-05 13:05:45 -0400233
Jonathan Ballet78b92a22011-07-16 08:07:26 +0900234 :return: The raised exception instance, if it is of the given type.
235 :raise self.failureException: Raised if the function call does
Jean-Paul Calderone0ef63ed2009-07-05 13:05:45 -0400236 not raise an exception or if it raises an exception of a
237 different type.
238 """
239 try:
240 result = f(*args, **kwargs)
Jean-Paul Calderone24b64592010-08-12 10:43:09 -0400241 except exception:
242 inst = sys.exc_info()[1]
Jean-Paul Calderone0ef63ed2009-07-05 13:05:45 -0400243 return inst
244 except:
Rick Dean47262da2009-07-08 16:17:17 -0500245 raise self.failureException('%s raised instead of %s'
Jean-Paul Calderone0ef63ed2009-07-05 13:05:45 -0400246 % (sys.exc_info()[0],
247 exception.__name__,
Rick Dean47262da2009-07-08 16:17:17 -0500248 ))
Jean-Paul Calderone0ef63ed2009-07-05 13:05:45 -0400249 else:
250 raise self.failureException('%s not raised (%r returned)'
251 % (exception.__name__, result))
252 assertRaises = failUnlessRaises
253
254
255 _temporaryFiles = None
256 def mktemp(self):
257 """
258 Pathetic substitute for twisted.trial.unittest.TestCase.mktemp.
259 """
260 if self._temporaryFiles is None:
261 self._temporaryFiles = []
262 temp = mktemp(dir=".")
263 self._temporaryFiles.append(temp)
264 return temp
265
266
267 # Python 2.3 compatibility.
268 def assertTrue(self, *a, **kw):
269 return self.failUnless(*a, **kw)
270
271
272 def assertFalse(self, *a, **kw):
273 return self.failIf(*a, **kw)
Jean-Paul Calderone68649052009-07-17 21:14:27 -0400274
275
276 # Other stuff
277 def assertConsistentType(self, theType, name, *constructionArgs):
278 """
Jonathan Ballet648875f2011-07-16 14:14:58 +0900279 Perform various assertions about :py:data:`theType` to ensure that it is a
Jean-Paul Calderone68649052009-07-17 21:14:27 -0400280 well-defined type. This is useful for extension types, where it's
281 pretty easy to do something wacky. If something about the type is
282 unusual, an exception will be raised.
283
Jonathan Ballet78b92a22011-07-16 08:07:26 +0900284 :param theType: The type object about which to make assertions.
285 :param name: A string giving the name of the type.
Jonathan Ballet648875f2011-07-16 14:14:58 +0900286 :param constructionArgs: Positional arguments to use with :py:data:`theType` to
Jean-Paul Calderone68649052009-07-17 21:14:27 -0400287 create an instance of it.
288 """
289 self.assertEqual(theType.__name__, name)
290 self.assertTrue(isinstance(theType, type))
291 instance = theType(*constructionArgs)
292 self.assertIdentical(type(instance), theType)