blob: db33df2bc40ffbe5188a4683342247e880639acc [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):
38 # Run the test as usual
39 before = set(memdbg.heap)
40 super(TestCase, self).run(result)
41
42 # Clean up some long-lived allocations so they won't be reported as
43 # memory leaks.
44 api.CRYPTO_cleanup_all_ex_data()
45 api.ERR_remove_thread_state(api.NULL)
46 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)
55 super(TestCase, self).run(result)
56
57 # Clean up some long-lived allocations so they won't be reported as
58 # memory leaks.
59 api.CRYPTO_cleanup_all_ex_data()
60 api.ERR_remove_thread_state(api.NULL)
61
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
144 addr = int(api.ffi.cast('uintptr_t', pointer))
145 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:
166 _exception_from_error_queue()
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 Calderoneabfbab62013-02-09 21:25:02 -0800173
Jean-Paul Calderone060a57e2011-05-04 18:02:49 -0400174 def failUnlessIn(self, containee, container, msg=None):
175 """
Jonathan Ballet648875f2011-07-16 14:14:58 +0900176 Fail the test if :py:data:`containee` is not found in :py:data:`container`.
Jean-Paul Calderone060a57e2011-05-04 18:02:49 -0400177
Jonathan Ballet648875f2011-07-16 14:14:58 +0900178 :param containee: the value that should be in :py:class:`container`
Jonathan Ballet78b92a22011-07-16 08:07:26 +0900179 :param container: a sequence type, or in the case of a mapping type,
Jean-Paul Calderone060a57e2011-05-04 18:02:49 -0400180 will follow semantics of 'if key in dict.keys()'
Jonathan Ballet78b92a22011-07-16 08:07:26 +0900181 :param msg: if msg is None, then the failure message will be
Jean-Paul Calderone060a57e2011-05-04 18:02:49 -0400182 '%r not in %r' % (first, second)
183 """
184 if containee not in container:
185 raise self.failureException(msg or "%r not in %r"
186 % (containee, container))
187 return containee
188 assertIn = failUnlessIn
189
Jean-Paul Calderone0ef63ed2009-07-05 13:05:45 -0400190 def failUnlessIdentical(self, first, second, msg=None):
191 """
Jonathan Ballet648875f2011-07-16 14:14:58 +0900192 Fail the test if :py:data:`first` is not :py:data:`second`. This is an
Jean-Paul Calderone0ef63ed2009-07-05 13:05:45 -0400193 obect-identity-equality test, not an object equality
Jonathan Ballet648875f2011-07-16 14:14:58 +0900194 (i.e. :py:func:`__eq__`) test.
Jean-Paul Calderone0ef63ed2009-07-05 13:05:45 -0400195
Jonathan Ballet78b92a22011-07-16 08:07:26 +0900196 :param msg: if msg is None, then the failure message will be
Jean-Paul Calderone0ef63ed2009-07-05 13:05:45 -0400197 '%r is not %r' % (first, second)
198 """
199 if first is not second:
200 raise self.failureException(msg or '%r is not %r' % (first, second))
201 return first
202 assertIdentical = failUnlessIdentical
203
204
205 def failIfIdentical(self, first, second, msg=None):
206 """
Jonathan Ballet648875f2011-07-16 14:14:58 +0900207 Fail the test if :py:data:`first` is :py:data:`second`. This is an
Jean-Paul Calderone0ef63ed2009-07-05 13:05:45 -0400208 obect-identity-equality test, not an object equality
Jonathan Ballet648875f2011-07-16 14:14:58 +0900209 (i.e. :py:func:`__eq__`) test.
Jean-Paul Calderone0ef63ed2009-07-05 13:05:45 -0400210
Jonathan Ballet78b92a22011-07-16 08:07:26 +0900211 :param msg: if msg is None, then the failure message will be
Jean-Paul Calderone0ef63ed2009-07-05 13:05:45 -0400212 '%r is %r' % (first, second)
213 """
214 if first is second:
215 raise self.failureException(msg or '%r is %r' % (first, second))
216 return first
217 assertNotIdentical = failIfIdentical
218
219
220 def failUnlessRaises(self, exception, f, *args, **kwargs):
221 """
Jonathan Ballet648875f2011-07-16 14:14:58 +0900222 Fail the test unless calling the function :py:data:`f` with the given
223 :py:data:`args` and :py:data:`kwargs` raises :py:data:`exception`. The
224 failure will report the traceback and call stack of the unexpected
225 exception.
Jean-Paul Calderone0ef63ed2009-07-05 13:05:45 -0400226
Jonathan Ballet78b92a22011-07-16 08:07:26 +0900227 :param exception: exception type that is to be expected
228 :param f: the function to call
Jean-Paul Calderone0ef63ed2009-07-05 13:05:45 -0400229
Jonathan Ballet78b92a22011-07-16 08:07:26 +0900230 :return: The raised exception instance, if it is of the given type.
231 :raise self.failureException: Raised if the function call does
Jean-Paul Calderone0ef63ed2009-07-05 13:05:45 -0400232 not raise an exception or if it raises an exception of a
233 different type.
234 """
235 try:
236 result = f(*args, **kwargs)
Jean-Paul Calderone24b64592010-08-12 10:43:09 -0400237 except exception:
238 inst = sys.exc_info()[1]
Jean-Paul Calderone0ef63ed2009-07-05 13:05:45 -0400239 return inst
240 except:
Rick Dean47262da2009-07-08 16:17:17 -0500241 raise self.failureException('%s raised instead of %s'
Jean-Paul Calderone0ef63ed2009-07-05 13:05:45 -0400242 % (sys.exc_info()[0],
243 exception.__name__,
Rick Dean47262da2009-07-08 16:17:17 -0500244 ))
Jean-Paul Calderone0ef63ed2009-07-05 13:05:45 -0400245 else:
246 raise self.failureException('%s not raised (%r returned)'
247 % (exception.__name__, result))
248 assertRaises = failUnlessRaises
249
250
251 _temporaryFiles = None
252 def mktemp(self):
253 """
254 Pathetic substitute for twisted.trial.unittest.TestCase.mktemp.
255 """
256 if self._temporaryFiles is None:
257 self._temporaryFiles = []
258 temp = mktemp(dir=".")
259 self._temporaryFiles.append(temp)
260 return temp
261
262
263 # Python 2.3 compatibility.
264 def assertTrue(self, *a, **kw):
265 return self.failUnless(*a, **kw)
266
267
268 def assertFalse(self, *a, **kw):
269 return self.failIf(*a, **kw)
Jean-Paul Calderone68649052009-07-17 21:14:27 -0400270
271
272 # Other stuff
273 def assertConsistentType(self, theType, name, *constructionArgs):
274 """
Jonathan Ballet648875f2011-07-16 14:14:58 +0900275 Perform various assertions about :py:data:`theType` to ensure that it is a
Jean-Paul Calderone68649052009-07-17 21:14:27 -0400276 well-defined type. This is useful for extension types, where it's
277 pretty easy to do something wacky. If something about the type is
278 unusual, an exception will be raised.
279
Jonathan Ballet78b92a22011-07-16 08:07:26 +0900280 :param theType: The type object about which to make assertions.
281 :param name: A string giving the name of the type.
Jonathan Ballet648875f2011-07-16 14:14:58 +0900282 :param constructionArgs: Positional arguments to use with :py:data:`theType` to
Jean-Paul Calderone68649052009-07-17 21:14:27 -0400283 create an instance of it.
284 """
285 self.assertEqual(theType.__name__, name)
286 self.assertTrue(isinstance(theType, type))
287 instance = theType(*constructionArgs)
288 self.assertIdentical(type(instance), theType)