blob: 011e7da39f58cff23c2d4fa8b71917482aba68f0 [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
Jean-Paul Calderone68a6f8f2013-03-01 17:56:22 -080020import memdbg
21
Jean-Paul Calderone4f0467a2014-01-11 11:58:41 -050022from OpenSSL._util import ffi, lib, byte_string as b
Jean-Paul Calderone9e4eeae2010-08-22 21:32:52 -040023
Jean-Paul Calderone0ef63ed2009-07-05 13:05:45 -040024class TestCase(TestCase):
25 """
Jonathan Ballet648875f2011-07-16 14:14:58 +090026 :py:class:`TestCase` adds useful testing functionality beyond what is available
27 from the standard library :py:class:`unittest.TestCase`.
Jean-Paul Calderone0ef63ed2009-07-05 13:05:45 -040028 """
Jean-Paul Calderone855331d2013-03-03 10:21:43 -080029 def run(self, result):
Jean-Paul Calderone68703ed2013-03-04 12:23:44 -080030 run = super(TestCase, self).run
31 if memdbg.heap is None:
32 return run(result)
33
Jean-Paul Calderone855331d2013-03-03 10:21:43 -080034 # Run the test as usual
35 before = set(memdbg.heap)
Jean-Paul Calderone68703ed2013-03-04 12:23:44 -080036 run(result)
Jean-Paul Calderone855331d2013-03-03 10:21:43 -080037
38 # Clean up some long-lived allocations so they won't be reported as
39 # memory leaks.
Jean-Paul Calderone9227c472013-12-31 13:47:36 -050040 lib.CRYPTO_cleanup_all_ex_data()
41 lib.ERR_remove_thread_state(ffi.NULL)
Jean-Paul Calderone855331d2013-03-03 10:21:43 -080042 after = set(memdbg.heap)
43
44 if not after - before:
45 # No leaks, fast succeed
46 return
47
48 if result.wasSuccessful():
49 # If it passed, run it again with memory debugging
50 before = set(memdbg.heap)
Jean-Paul Calderone68703ed2013-03-04 12:23:44 -080051 run(result)
Jean-Paul Calderone855331d2013-03-03 10:21:43 -080052
53 # Clean up some long-lived allocations so they won't be reported as
54 # memory leaks.
Jean-Paul Calderone3f93d212014-01-01 12:36:53 -050055 lib.CRYPTO_cleanup_all_ex_data()
56 lib.ERR_remove_thread_state(ffi.NULL)
Jean-Paul Calderone855331d2013-03-03 10:21:43 -080057
58 after = set(memdbg.heap)
59
60 self._reportLeaks(after - before, result)
Jean-Paul Calderonef6745b32013-03-01 15:08:46 -080061
Jean-Paul Calderone40732ff2013-03-01 20:53:50 -080062
Jean-Paul Calderone855331d2013-03-03 10:21:43 -080063 def _reportLeaks(self, leaks, result):
Jean-Paul Calderone68a6f8f2013-03-01 17:56:22 -080064 def format_leak(p):
65 stacks = memdbg.heap[p]
66 # Eventually look at multiple stacks for the realloc() case. For
67 # now just look at the original allocation location.
Jean-Paul Calderonec2e8b412013-03-02 16:27:55 -080068 (size, python_stack, c_stack) = stacks[0]
Jean-Paul Calderonef6745b32013-03-01 15:08:46 -080069
Jean-Paul Calderone68a6f8f2013-03-01 17:56:22 -080070 stack = traceback.format_list(python_stack)[:-1]
Jean-Paul Calderonef6745b32013-03-01 15:08:46 -080071
Jean-Paul Calderone68a6f8f2013-03-01 17:56:22 -080072 # c_stack looks something like this (interesting parts indicated
73 # with inserted arrows not part of the data):
74 #
75 # /home/exarkun/Projects/pyOpenSSL/branches/use-opentls/__pycache__/_cffi__x89095113xb9185b9b.so(+0x12cf) [0x7fe2e20582cf]
76 # /home/exarkun/Projects/cpython/2.7/python(PyCFunction_Call+0x8b) [0x56265a]
77 # /home/exarkun/Projects/cpython/2.7/python() [0x4d5f52]
78 # /home/exarkun/Projects/cpython/2.7/python(PyEval_EvalFrameEx+0x753b) [0x4d0e1e]
79 # /home/exarkun/Projects/cpython/2.7/python() [0x4d6419]
80 # /home/exarkun/Projects/cpython/2.7/python() [0x4d6129]
81 # /home/exarkun/Projects/cpython/2.7/python(PyEval_EvalFrameEx+0x753b) [0x4d0e1e]
82 # /home/exarkun/Projects/cpython/2.7/python(PyEval_EvalCodeEx+0x1043) [0x4d3726]
83 # /home/exarkun/Projects/cpython/2.7/python() [0x55fd51]
84 # /home/exarkun/Projects/cpython/2.7/python(PyObject_Call+0x7e) [0x420ee6]
85 # /home/exarkun/Projects/cpython/2.7/python(PyEval_CallObjectWithKeywords+0x158) [0x4d56ec]
86 # /home/exarkun/.local/lib/python2.7/site-packages/cffi-0.5-py2.7-linux-x86_64.egg/_cffi_backend.so(+0xe96e) [0x7fe2e38be96e]
87 # /usr/lib/x86_64-linux-gnu/libffi.so.6(ffi_closure_unix64_inner+0x1b9) [0x7fe2e36ad819]
88 # /usr/lib/x86_64-linux-gnu/libffi.so.6(ffi_closure_unix64+0x46) [0x7fe2e36adb7c]
89 # /lib/x86_64-linux-gnu/libcrypto.so.1.0.0(CRYPTO_malloc+0x64) [0x7fe2e1cef784] <------ end interesting
90 # /lib/x86_64-linux-gnu/libcrypto.so.1.0.0(lh_insert+0x16b) [0x7fe2e1d6a24b] .
91 # /lib/x86_64-linux-gnu/libcrypto.so.1.0.0(+0x61c18) [0x7fe2e1cf0c18] .
92 # /lib/x86_64-linux-gnu/libcrypto.so.1.0.0(+0x625ec) [0x7fe2e1cf15ec] .
93 # /lib/x86_64-linux-gnu/libcrypto.so.1.0.0(DSA_new_method+0xe6) [0x7fe2e1d524d6] .
94 # /lib/x86_64-linux-gnu/libcrypto.so.1.0.0(DSA_generate_parameters+0x3a) [0x7fe2e1d5364a] <------ begin interesting
95 # /home/exarkun/Projects/opentls/trunk/tls/c/__pycache__/_cffi__x305d4698xb539baaa.so(+0x1f397) [0x7fe2df84d397]
96 # /home/exarkun/Projects/cpython/2.7/python(PyCFunction_Call+0x8b) [0x56265a]
97 # /home/exarkun/Projects/cpython/2.7/python() [0x4d5f52]
98 # /home/exarkun/Projects/cpython/2.7/python(PyEval_EvalFrameEx+0x753b) [0x4d0e1e]
99 # /home/exarkun/Projects/cpython/2.7/python() [0x4d6419]
100 # ...
101 #
102 # Notice the stack is upside down compared to a Python traceback.
103 # Identify the start and end of interesting bits and stuff it into the stack we report.
Jean-Paul Calderonef6745b32013-03-01 15:08:46 -0800104
Jean-Paul Calderonec2e8b412013-03-02 16:27:55 -0800105 saved = list(c_stack)
106
Jean-Paul Calderone68a6f8f2013-03-01 17:56:22 -0800107 # Figure the first interesting frame will be after a the cffi-compiled module
Jean-Paul Calderonec2e8b412013-03-02 16:27:55 -0800108 while c_stack and '/__pycache__/_cffi__' not in c_stack[-1]:
Jean-Paul Calderone68a6f8f2013-03-01 17:56:22 -0800109 c_stack.pop()
Jean-Paul Calderonef6745b32013-03-01 15:08:46 -0800110
Jean-Paul Calderone68a6f8f2013-03-01 17:56:22 -0800111 # Figure the last interesting frame will always be CRYPTO_malloc,
112 # since that's where we hooked in to things.
Jean-Paul Calderonec2e8b412013-03-02 16:27:55 -0800113 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 -0800114 c_stack.pop(0)
115
Jean-Paul Calderonec2e8b412013-03-02 16:27:55 -0800116 if c_stack:
117 c_stack.reverse()
118 else:
119 c_stack = saved[::-1]
Jean-Paul Calderone68a6f8f2013-03-01 17:56:22 -0800120 stack.extend([frame + "\n" for frame in c_stack])
121
Jean-Paul Calderone2beac532013-03-03 17:30:36 -0800122 stack.insert(0, "Leaked (%s) at:\n")
Jean-Paul Calderone68a6f8f2013-03-01 17:56:22 -0800123 return "".join(stack)
124
Jean-Paul Calderone855331d2013-03-03 10:21:43 -0800125 if leaks:
Jean-Paul Calderone2beac532013-03-03 17:30:36 -0800126 unique_leaks = {}
Jean-Paul Calderone855331d2013-03-03 10:21:43 -0800127 for p in leaks:
Jean-Paul Calderone2beac532013-03-03 17:30:36 -0800128 size = memdbg.heap[p][-1][0]
129 new_leak = format_leak(p)
130 if new_leak not in unique_leaks:
131 unique_leaks[new_leak] = [(size, p)]
132 else:
133 unique_leaks[new_leak].append((size, p))
134 memdbg.free(p)
135
136 for (stack, allocs) in unique_leaks.iteritems():
137 allocs_accum = []
138 for (size, pointer) in allocs:
139
Jean-Paul Calderone9227c472013-12-31 13:47:36 -0500140 addr = int(ffi.cast('uintptr_t', pointer))
Jean-Paul Calderone2beac532013-03-03 17:30:36 -0800141 allocs_accum.append("%d@0x%x" % (size, addr))
142 allocs_report = ", ".join(sorted(allocs_accum))
143
Jean-Paul Calderone855331d2013-03-03 10:21:43 -0800144 result.addError(
145 self,
Jean-Paul Calderone2beac532013-03-03 17:30:36 -0800146 (None, Exception(stack % (allocs_report,)), None))
Jean-Paul Calderonef6745b32013-03-01 15:08:46 -0800147
Jean-Paul Calderone855331d2013-03-03 10:21:43 -0800148
149 def tearDown(self):
150 """
151 Clean up any files or directories created using :py:meth:`TestCase.mktemp`.
152 Subclasses must invoke this method if they override it or the
153 cleanup will not occur.
154 """
Jean-Paul Calderonebf37f0f2010-07-31 14:56:20 -0400155 if False and self._temporaryFiles is not None:
Jean-Paul Calderone0ef63ed2009-07-05 13:05:45 -0400156 for temp in self._temporaryFiles:
157 if os.path.isdir(temp):
158 shutil.rmtree(temp)
159 elif os.path.exists(temp):
160 os.unlink(temp)
Jean-Paul Calderone1206daf2009-07-16 16:07:42 -0400161 try:
Jean-Paul Calderonec86bb7d2013-12-29 10:25:59 -0500162 exception_from_error_queue(Error)
Jean-Paul Calderone24b64592010-08-12 10:43:09 -0400163 except Error:
164 e = sys.exc_info()[1]
Jean-Paul Calderone1206daf2009-07-16 16:07:42 -0400165 if e.args != ([],):
166 self.fail("Left over errors in OpenSSL error queue: " + repr(e))
167
Jean-Paul Calderone0ef63ed2009-07-05 13:05:45 -0400168
Jean-Paul Calderone8fb53182013-12-30 08:35:49 -0500169 def assertIsInstance(self, instance, classOrTuple, message=None):
170 """
171 Fail if C{instance} is not an instance of the given class or of
172 one of the given classes.
173
174 @param instance: the object to test the type (first argument of the
175 C{isinstance} call).
176 @type instance: any.
177 @param classOrTuple: the class or classes to test against (second
178 argument of the C{isinstance} call).
179 @type classOrTuple: class, type, or tuple.
180
181 @param message: Custom text to include in the exception text if the
182 assertion fails.
183 """
184 if not isinstance(instance, classOrTuple):
185 if message is None:
186 suffix = ""
187 else:
188 suffix = ": " + message
189 self.fail("%r is not an instance of %s%s" % (
190 instance, classOrTuple, suffix))
191
Jean-Paul Calderoneabfbab62013-02-09 21:25:02 -0800192
Jean-Paul Calderone060a57e2011-05-04 18:02:49 -0400193 def failUnlessIn(self, containee, container, msg=None):
194 """
Jonathan Ballet648875f2011-07-16 14:14:58 +0900195 Fail the test if :py:data:`containee` is not found in :py:data:`container`.
Jean-Paul Calderone060a57e2011-05-04 18:02:49 -0400196
Jonathan Ballet648875f2011-07-16 14:14:58 +0900197 :param containee: the value that should be in :py:class:`container`
Jonathan Ballet78b92a22011-07-16 08:07:26 +0900198 :param container: a sequence type, or in the case of a mapping type,
Jean-Paul Calderone060a57e2011-05-04 18:02:49 -0400199 will follow semantics of 'if key in dict.keys()'
Jonathan Ballet78b92a22011-07-16 08:07:26 +0900200 :param msg: if msg is None, then the failure message will be
Jean-Paul Calderone060a57e2011-05-04 18:02:49 -0400201 '%r not in %r' % (first, second)
202 """
203 if containee not in container:
204 raise self.failureException(msg or "%r not in %r"
205 % (containee, container))
206 return containee
207 assertIn = failUnlessIn
208
Jean-Paul Calderone0ef63ed2009-07-05 13:05:45 -0400209 def failUnlessIdentical(self, first, second, msg=None):
210 """
Jonathan Ballet648875f2011-07-16 14:14:58 +0900211 Fail the test if :py:data:`first` is not :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 not %r' % (first, second)
217 """
218 if first is not second:
219 raise self.failureException(msg or '%r is not %r' % (first, second))
220 return first
221 assertIdentical = failUnlessIdentical
222
223
224 def failIfIdentical(self, first, second, msg=None):
225 """
Jonathan Ballet648875f2011-07-16 14:14:58 +0900226 Fail the test if :py:data:`first` is :py:data:`second`. This is an
Jean-Paul Calderone0ef63ed2009-07-05 13:05:45 -0400227 obect-identity-equality test, not an object equality
Jonathan Ballet648875f2011-07-16 14:14:58 +0900228 (i.e. :py:func:`__eq__`) test.
Jean-Paul Calderone0ef63ed2009-07-05 13:05:45 -0400229
Jonathan Ballet78b92a22011-07-16 08:07:26 +0900230 :param msg: if msg is None, then the failure message will be
Jean-Paul Calderone0ef63ed2009-07-05 13:05:45 -0400231 '%r is %r' % (first, second)
232 """
233 if first is second:
234 raise self.failureException(msg or '%r is %r' % (first, second))
235 return first
236 assertNotIdentical = failIfIdentical
237
238
239 def failUnlessRaises(self, exception, f, *args, **kwargs):
240 """
Jonathan Ballet648875f2011-07-16 14:14:58 +0900241 Fail the test unless calling the function :py:data:`f` with the given
242 :py:data:`args` and :py:data:`kwargs` raises :py:data:`exception`. The
243 failure will report the traceback and call stack of the unexpected
244 exception.
Jean-Paul Calderone0ef63ed2009-07-05 13:05:45 -0400245
Jonathan Ballet78b92a22011-07-16 08:07:26 +0900246 :param exception: exception type that is to be expected
247 :param f: the function to call
Jean-Paul Calderone0ef63ed2009-07-05 13:05:45 -0400248
Jonathan Ballet78b92a22011-07-16 08:07:26 +0900249 :return: The raised exception instance, if it is of the given type.
250 :raise self.failureException: Raised if the function call does
Jean-Paul Calderone0ef63ed2009-07-05 13:05:45 -0400251 not raise an exception or if it raises an exception of a
252 different type.
253 """
254 try:
255 result = f(*args, **kwargs)
Jean-Paul Calderone24b64592010-08-12 10:43:09 -0400256 except exception:
257 inst = sys.exc_info()[1]
Jean-Paul Calderone0ef63ed2009-07-05 13:05:45 -0400258 return inst
259 except:
Rick Dean47262da2009-07-08 16:17:17 -0500260 raise self.failureException('%s raised instead of %s'
Jean-Paul Calderone0ef63ed2009-07-05 13:05:45 -0400261 % (sys.exc_info()[0],
262 exception.__name__,
Rick Dean47262da2009-07-08 16:17:17 -0500263 ))
Jean-Paul Calderone0ef63ed2009-07-05 13:05:45 -0400264 else:
265 raise self.failureException('%s not raised (%r returned)'
266 % (exception.__name__, result))
267 assertRaises = failUnlessRaises
268
269
270 _temporaryFiles = None
271 def mktemp(self):
272 """
273 Pathetic substitute for twisted.trial.unittest.TestCase.mktemp.
274 """
275 if self._temporaryFiles is None:
276 self._temporaryFiles = []
Jean-Paul Calderonef9fb8922014-01-11 08:35:54 -0500277 temp = b(mktemp(dir="."))
Jean-Paul Calderone0ef63ed2009-07-05 13:05:45 -0400278 self._temporaryFiles.append(temp)
279 return temp
280
281
Jean-Paul Calderone68649052009-07-17 21:14:27 -0400282 # Other stuff
283 def assertConsistentType(self, theType, name, *constructionArgs):
284 """
Jonathan Ballet648875f2011-07-16 14:14:58 +0900285 Perform various assertions about :py:data:`theType` to ensure that it is a
Jean-Paul Calderone68649052009-07-17 21:14:27 -0400286 well-defined type. This is useful for extension types, where it's
287 pretty easy to do something wacky. If something about the type is
288 unusual, an exception will be raised.
289
Jonathan Ballet78b92a22011-07-16 08:07:26 +0900290 :param theType: The type object about which to make assertions.
291 :param name: A string giving the name of the type.
Jonathan Ballet648875f2011-07-16 14:14:58 +0900292 :param constructionArgs: Positional arguments to use with :py:data:`theType` to
Jean-Paul Calderone68649052009-07-17 21:14:27 -0400293 create an instance of it.
294 """
295 self.assertEqual(theType.__name__, name)
296 self.assertTrue(isinstance(theType, type))
297 instance = theType(*constructionArgs)
298 self.assertIdentical(type(instance), theType)