blob: 4e4d81240c4b595ecbe949ce4bc343e15761afd8 [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 Calderone0ef63ed2009-07-05 13:05:45 -0400213 def failUnlessIdentical(self, first, second, msg=None):
214 """
Jonathan Ballet648875f2011-07-16 14:14:58 +0900215 Fail the test if :py:data:`first` is not :py:data:`second`. This is an
Jean-Paul Calderone0ef63ed2009-07-05 13:05:45 -0400216 obect-identity-equality test, not an object equality
Jonathan Ballet648875f2011-07-16 14:14:58 +0900217 (i.e. :py:func:`__eq__`) test.
Jean-Paul Calderone0ef63ed2009-07-05 13:05:45 -0400218
Jonathan Ballet78b92a22011-07-16 08:07:26 +0900219 :param msg: if msg is None, then the failure message will be
Jean-Paul Calderone0ef63ed2009-07-05 13:05:45 -0400220 '%r is not %r' % (first, second)
221 """
222 if first is not second:
223 raise self.failureException(msg or '%r is not %r' % (first, second))
224 return first
225 assertIdentical = failUnlessIdentical
226
227
228 def failIfIdentical(self, first, second, msg=None):
229 """
Jonathan Ballet648875f2011-07-16 14:14:58 +0900230 Fail the test if :py:data:`first` is :py:data:`second`. This is an
Jean-Paul Calderone0ef63ed2009-07-05 13:05:45 -0400231 obect-identity-equality test, not an object equality
Jonathan Ballet648875f2011-07-16 14:14:58 +0900232 (i.e. :py:func:`__eq__`) test.
Jean-Paul Calderone0ef63ed2009-07-05 13:05:45 -0400233
Jonathan Ballet78b92a22011-07-16 08:07:26 +0900234 :param msg: if msg is None, then the failure message will be
Jean-Paul Calderone0ef63ed2009-07-05 13:05:45 -0400235 '%r is %r' % (first, second)
236 """
237 if first is second:
238 raise self.failureException(msg or '%r is %r' % (first, second))
239 return first
240 assertNotIdentical = failIfIdentical
241
242
243 def failUnlessRaises(self, exception, f, *args, **kwargs):
244 """
Jonathan Ballet648875f2011-07-16 14:14:58 +0900245 Fail the test unless calling the function :py:data:`f` with the given
246 :py:data:`args` and :py:data:`kwargs` raises :py:data:`exception`. The
247 failure will report the traceback and call stack of the unexpected
248 exception.
Jean-Paul Calderone0ef63ed2009-07-05 13:05:45 -0400249
Jonathan Ballet78b92a22011-07-16 08:07:26 +0900250 :param exception: exception type that is to be expected
251 :param f: the function to call
Jean-Paul Calderone0ef63ed2009-07-05 13:05:45 -0400252
Jonathan Ballet78b92a22011-07-16 08:07:26 +0900253 :return: The raised exception instance, if it is of the given type.
254 :raise self.failureException: Raised if the function call does
Jean-Paul Calderone0ef63ed2009-07-05 13:05:45 -0400255 not raise an exception or if it raises an exception of a
256 different type.
257 """
258 try:
259 result = f(*args, **kwargs)
Jean-Paul Calderone24b64592010-08-12 10:43:09 -0400260 except exception:
261 inst = sys.exc_info()[1]
Jean-Paul Calderone0ef63ed2009-07-05 13:05:45 -0400262 return inst
263 except:
Rick Dean47262da2009-07-08 16:17:17 -0500264 raise self.failureException('%s raised instead of %s'
Jean-Paul Calderone0ef63ed2009-07-05 13:05:45 -0400265 % (sys.exc_info()[0],
266 exception.__name__,
Rick Dean47262da2009-07-08 16:17:17 -0500267 ))
Jean-Paul Calderone0ef63ed2009-07-05 13:05:45 -0400268 else:
269 raise self.failureException('%s not raised (%r returned)'
270 % (exception.__name__, result))
271 assertRaises = failUnlessRaises
272
273
274 _temporaryFiles = None
275 def mktemp(self):
276 """
277 Pathetic substitute for twisted.trial.unittest.TestCase.mktemp.
278 """
279 if self._temporaryFiles is None:
280 self._temporaryFiles = []
Jean-Paul Calderonef9fb8922014-01-11 08:35:54 -0500281 temp = b(mktemp(dir="."))
Jean-Paul Calderone0ef63ed2009-07-05 13:05:45 -0400282 self._temporaryFiles.append(temp)
283 return temp
284
285
Jean-Paul Calderone68649052009-07-17 21:14:27 -0400286 # Other stuff
287 def assertConsistentType(self, theType, name, *constructionArgs):
288 """
Jonathan Ballet648875f2011-07-16 14:14:58 +0900289 Perform various assertions about :py:data:`theType` to ensure that it is a
Jean-Paul Calderone68649052009-07-17 21:14:27 -0400290 well-defined type. This is useful for extension types, where it's
291 pretty easy to do something wacky. If something about the type is
292 unusual, an exception will be raised.
293
Jonathan Ballet78b92a22011-07-16 08:07:26 +0900294 :param theType: The type object about which to make assertions.
295 :param name: A string giving the name of the type.
Jonathan Ballet648875f2011-07-16 14:14:58 +0900296 :param constructionArgs: Positional arguments to use with :py:data:`theType` to
Jean-Paul Calderone68649052009-07-17 21:14:27 -0400297 create an instance of it.
298 """
299 self.assertEqual(theType.__name__, name)
300 self.assertTrue(isinstance(theType, type))
301 instance = theType(*constructionArgs)
302 self.assertIdentical(type(instance), theType)