blob: e57d55a956627f21e1c88c4d1067de61c9607479 [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 Calderonef6745b32013-03-01 15:08:46 -080037 def setUp(self):
38 super(TestCase, self).setUp()
Jean-Paul Calderone68a6f8f2013-03-01 17:56:22 -080039 self._before = set(memdbg.heap)
Jean-Paul Calderonef6745b32013-03-01 15:08:46 -080040
Jean-Paul Calderone40732ff2013-03-01 20:53:50 -080041
Jean-Paul Calderone0ef63ed2009-07-05 13:05:45 -040042 def tearDown(self):
43 """
Jonathan Ballet648875f2011-07-16 14:14:58 +090044 Clean up any files or directories created using :py:meth:`TestCase.mktemp`.
Jean-Paul Calderone0ef63ed2009-07-05 13:05:45 -040045 Subclasses must invoke this method if they override it or the
46 cleanup will not occur.
47 """
Jean-Paul Calderonef6745b32013-03-01 15:08:46 -080048 import gc
49 gc.collect(); gc.collect(); gc.collect()
50
Jean-Paul Calderone68a6f8f2013-03-01 17:56:22 -080051 def format_leak(p):
52 stacks = memdbg.heap[p]
53 # Eventually look at multiple stacks for the realloc() case. For
54 # now just look at the original allocation location.
Jean-Paul Calderonec2e8b412013-03-02 16:27:55 -080055 (size, python_stack, c_stack) = stacks[0]
Jean-Paul Calderonef6745b32013-03-01 15:08:46 -080056
Jean-Paul Calderone68a6f8f2013-03-01 17:56:22 -080057 stack = traceback.format_list(python_stack)[:-1]
Jean-Paul Calderonef6745b32013-03-01 15:08:46 -080058
Jean-Paul Calderone68a6f8f2013-03-01 17:56:22 -080059 # c_stack looks something like this (interesting parts indicated
60 # with inserted arrows not part of the data):
61 #
62 # /home/exarkun/Projects/pyOpenSSL/branches/use-opentls/__pycache__/_cffi__x89095113xb9185b9b.so(+0x12cf) [0x7fe2e20582cf]
63 # /home/exarkun/Projects/cpython/2.7/python(PyCFunction_Call+0x8b) [0x56265a]
64 # /home/exarkun/Projects/cpython/2.7/python() [0x4d5f52]
65 # /home/exarkun/Projects/cpython/2.7/python(PyEval_EvalFrameEx+0x753b) [0x4d0e1e]
66 # /home/exarkun/Projects/cpython/2.7/python() [0x4d6419]
67 # /home/exarkun/Projects/cpython/2.7/python() [0x4d6129]
68 # /home/exarkun/Projects/cpython/2.7/python(PyEval_EvalFrameEx+0x753b) [0x4d0e1e]
69 # /home/exarkun/Projects/cpython/2.7/python(PyEval_EvalCodeEx+0x1043) [0x4d3726]
70 # /home/exarkun/Projects/cpython/2.7/python() [0x55fd51]
71 # /home/exarkun/Projects/cpython/2.7/python(PyObject_Call+0x7e) [0x420ee6]
72 # /home/exarkun/Projects/cpython/2.7/python(PyEval_CallObjectWithKeywords+0x158) [0x4d56ec]
73 # /home/exarkun/.local/lib/python2.7/site-packages/cffi-0.5-py2.7-linux-x86_64.egg/_cffi_backend.so(+0xe96e) [0x7fe2e38be96e]
74 # /usr/lib/x86_64-linux-gnu/libffi.so.6(ffi_closure_unix64_inner+0x1b9) [0x7fe2e36ad819]
75 # /usr/lib/x86_64-linux-gnu/libffi.so.6(ffi_closure_unix64+0x46) [0x7fe2e36adb7c]
76 # /lib/x86_64-linux-gnu/libcrypto.so.1.0.0(CRYPTO_malloc+0x64) [0x7fe2e1cef784] <------ end interesting
77 # /lib/x86_64-linux-gnu/libcrypto.so.1.0.0(lh_insert+0x16b) [0x7fe2e1d6a24b] .
78 # /lib/x86_64-linux-gnu/libcrypto.so.1.0.0(+0x61c18) [0x7fe2e1cf0c18] .
79 # /lib/x86_64-linux-gnu/libcrypto.so.1.0.0(+0x625ec) [0x7fe2e1cf15ec] .
80 # /lib/x86_64-linux-gnu/libcrypto.so.1.0.0(DSA_new_method+0xe6) [0x7fe2e1d524d6] .
81 # /lib/x86_64-linux-gnu/libcrypto.so.1.0.0(DSA_generate_parameters+0x3a) [0x7fe2e1d5364a] <------ begin interesting
82 # /home/exarkun/Projects/opentls/trunk/tls/c/__pycache__/_cffi__x305d4698xb539baaa.so(+0x1f397) [0x7fe2df84d397]
83 # /home/exarkun/Projects/cpython/2.7/python(PyCFunction_Call+0x8b) [0x56265a]
84 # /home/exarkun/Projects/cpython/2.7/python() [0x4d5f52]
85 # /home/exarkun/Projects/cpython/2.7/python(PyEval_EvalFrameEx+0x753b) [0x4d0e1e]
86 # /home/exarkun/Projects/cpython/2.7/python() [0x4d6419]
87 # ...
88 #
89 # Notice the stack is upside down compared to a Python traceback.
90 # Identify the start and end of interesting bits and stuff it into the stack we report.
Jean-Paul Calderonef6745b32013-03-01 15:08:46 -080091
Jean-Paul Calderonec2e8b412013-03-02 16:27:55 -080092 saved = list(c_stack)
93
Jean-Paul Calderone68a6f8f2013-03-01 17:56:22 -080094 # Figure the first interesting frame will be after a the cffi-compiled module
Jean-Paul Calderonec2e8b412013-03-02 16:27:55 -080095 while c_stack and '/__pycache__/_cffi__' not in c_stack[-1]:
Jean-Paul Calderone68a6f8f2013-03-01 17:56:22 -080096 c_stack.pop()
Jean-Paul Calderonef6745b32013-03-01 15:08:46 -080097
Jean-Paul Calderone68a6f8f2013-03-01 17:56:22 -080098 # Figure the last interesting frame will always be CRYPTO_malloc,
99 # since that's where we hooked in to things.
Jean-Paul Calderonec2e8b412013-03-02 16:27:55 -0800100 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 -0800101 c_stack.pop(0)
102
Jean-Paul Calderonec2e8b412013-03-02 16:27:55 -0800103 if c_stack:
104 c_stack.reverse()
105 else:
106 c_stack = saved[::-1]
Jean-Paul Calderone68a6f8f2013-03-01 17:56:22 -0800107 stack.extend([frame + "\n" for frame in c_stack])
108
109 # XXX :(
110 ptr = int(str(p).split()[-1][:-1], 16)
Jean-Paul Calderonec2e8b412013-03-02 16:27:55 -0800111 stack.insert(0, "Leaked %d bytes (0x%x) at:\n" % (size, ptr))
Jean-Paul Calderone68a6f8f2013-03-01 17:56:22 -0800112 return "".join(stack)
113
Jean-Paul Calderone40732ff2013-03-01 20:53:50 -0800114 # Clean up some long-lived allocations so they won't be reported as
115 # memory leaks.
116 api.CRYPTO_cleanup_all_ex_data()
Jean-Paul Calderonec2e8b412013-03-02 16:27:55 -0800117 api.ERR_remove_thread_state(api.NULL)
Jean-Paul Calderone40732ff2013-03-01 20:53:50 -0800118
Jean-Paul Calderone68a6f8f2013-03-01 17:56:22 -0800119 after = set(memdbg.heap)
120 leak = after - self._before
121 if leak:
Jean-Paul Calderonec2e8b412013-03-02 16:27:55 -0800122 total = 0
123 reasons = [""]
Jean-Paul Calderone68a6f8f2013-03-01 17:56:22 -0800124 for p in leak:
Jean-Paul Calderonec2e8b412013-03-02 16:27:55 -0800125 total += memdbg.heap[p][-1][0]
Jean-Paul Calderone68a6f8f2013-03-01 17:56:22 -0800126 reasons.append(format_leak(p))
Jean-Paul Calderonec2e8b412013-03-02 16:27:55 -0800127 memdbg.free(p)
128 reasons.append("\nLeaked %d bytes in %d pointers\n" % (total, len(leak)))
Jean-Paul Calderone68a6f8f2013-03-01 17:56:22 -0800129 self.fail('\n'.join(reasons))
Jean-Paul Calderonef6745b32013-03-01 15:08:46 -0800130
Jean-Paul Calderonebf37f0f2010-07-31 14:56:20 -0400131 if False and self._temporaryFiles is not None:
Jean-Paul Calderone0ef63ed2009-07-05 13:05:45 -0400132 for temp in self._temporaryFiles:
133 if os.path.isdir(temp):
134 shutil.rmtree(temp)
135 elif os.path.exists(temp):
136 os.unlink(temp)
Jean-Paul Calderone1206daf2009-07-16 16:07:42 -0400137 try:
138 _exception_from_error_queue()
Jean-Paul Calderone24b64592010-08-12 10:43:09 -0400139 except Error:
140 e = sys.exc_info()[1]
Jean-Paul Calderone1206daf2009-07-16 16:07:42 -0400141 if e.args != ([],):
142 self.fail("Left over errors in OpenSSL error queue: " + repr(e))
143
Jean-Paul Calderone0ef63ed2009-07-05 13:05:45 -0400144
Jean-Paul Calderoneabfbab62013-02-09 21:25:02 -0800145
Jean-Paul Calderone060a57e2011-05-04 18:02:49 -0400146 def failUnlessIn(self, containee, container, msg=None):
147 """
Jonathan Ballet648875f2011-07-16 14:14:58 +0900148 Fail the test if :py:data:`containee` is not found in :py:data:`container`.
Jean-Paul Calderone060a57e2011-05-04 18:02:49 -0400149
Jonathan Ballet648875f2011-07-16 14:14:58 +0900150 :param containee: the value that should be in :py:class:`container`
Jonathan Ballet78b92a22011-07-16 08:07:26 +0900151 :param container: a sequence type, or in the case of a mapping type,
Jean-Paul Calderone060a57e2011-05-04 18:02:49 -0400152 will follow semantics of 'if key in dict.keys()'
Jonathan Ballet78b92a22011-07-16 08:07:26 +0900153 :param msg: if msg is None, then the failure message will be
Jean-Paul Calderone060a57e2011-05-04 18:02:49 -0400154 '%r not in %r' % (first, second)
155 """
156 if containee not in container:
157 raise self.failureException(msg or "%r not in %r"
158 % (containee, container))
159 return containee
160 assertIn = failUnlessIn
161
Jean-Paul Calderone0ef63ed2009-07-05 13:05:45 -0400162 def failUnlessIdentical(self, first, second, msg=None):
163 """
Jonathan Ballet648875f2011-07-16 14:14:58 +0900164 Fail the test if :py:data:`first` is not :py:data:`second`. This is an
Jean-Paul Calderone0ef63ed2009-07-05 13:05:45 -0400165 obect-identity-equality test, not an object equality
Jonathan Ballet648875f2011-07-16 14:14:58 +0900166 (i.e. :py:func:`__eq__`) test.
Jean-Paul Calderone0ef63ed2009-07-05 13:05:45 -0400167
Jonathan Ballet78b92a22011-07-16 08:07:26 +0900168 :param msg: if msg is None, then the failure message will be
Jean-Paul Calderone0ef63ed2009-07-05 13:05:45 -0400169 '%r is not %r' % (first, second)
170 """
171 if first is not second:
172 raise self.failureException(msg or '%r is not %r' % (first, second))
173 return first
174 assertIdentical = failUnlessIdentical
175
176
177 def failIfIdentical(self, first, second, msg=None):
178 """
Jonathan Ballet648875f2011-07-16 14:14:58 +0900179 Fail the test if :py:data:`first` is :py:data:`second`. This is an
Jean-Paul Calderone0ef63ed2009-07-05 13:05:45 -0400180 obect-identity-equality test, not an object equality
Jonathan Ballet648875f2011-07-16 14:14:58 +0900181 (i.e. :py:func:`__eq__`) test.
Jean-Paul Calderone0ef63ed2009-07-05 13:05:45 -0400182
Jonathan Ballet78b92a22011-07-16 08:07:26 +0900183 :param msg: if msg is None, then the failure message will be
Jean-Paul Calderone0ef63ed2009-07-05 13:05:45 -0400184 '%r is %r' % (first, second)
185 """
186 if first is second:
187 raise self.failureException(msg or '%r is %r' % (first, second))
188 return first
189 assertNotIdentical = failIfIdentical
190
191
192 def failUnlessRaises(self, exception, f, *args, **kwargs):
193 """
Jonathan Ballet648875f2011-07-16 14:14:58 +0900194 Fail the test unless calling the function :py:data:`f` with the given
195 :py:data:`args` and :py:data:`kwargs` raises :py:data:`exception`. The
196 failure will report the traceback and call stack of the unexpected
197 exception.
Jean-Paul Calderone0ef63ed2009-07-05 13:05:45 -0400198
Jonathan Ballet78b92a22011-07-16 08:07:26 +0900199 :param exception: exception type that is to be expected
200 :param f: the function to call
Jean-Paul Calderone0ef63ed2009-07-05 13:05:45 -0400201
Jonathan Ballet78b92a22011-07-16 08:07:26 +0900202 :return: The raised exception instance, if it is of the given type.
203 :raise self.failureException: Raised if the function call does
Jean-Paul Calderone0ef63ed2009-07-05 13:05:45 -0400204 not raise an exception or if it raises an exception of a
205 different type.
206 """
207 try:
208 result = f(*args, **kwargs)
Jean-Paul Calderone24b64592010-08-12 10:43:09 -0400209 except exception:
210 inst = sys.exc_info()[1]
Jean-Paul Calderone0ef63ed2009-07-05 13:05:45 -0400211 return inst
212 except:
Rick Dean47262da2009-07-08 16:17:17 -0500213 raise self.failureException('%s raised instead of %s'
Jean-Paul Calderone0ef63ed2009-07-05 13:05:45 -0400214 % (sys.exc_info()[0],
215 exception.__name__,
Rick Dean47262da2009-07-08 16:17:17 -0500216 ))
Jean-Paul Calderone0ef63ed2009-07-05 13:05:45 -0400217 else:
218 raise self.failureException('%s not raised (%r returned)'
219 % (exception.__name__, result))
220 assertRaises = failUnlessRaises
221
222
223 _temporaryFiles = None
224 def mktemp(self):
225 """
226 Pathetic substitute for twisted.trial.unittest.TestCase.mktemp.
227 """
228 if self._temporaryFiles is None:
229 self._temporaryFiles = []
230 temp = mktemp(dir=".")
231 self._temporaryFiles.append(temp)
232 return temp
233
234
235 # Python 2.3 compatibility.
236 def assertTrue(self, *a, **kw):
237 return self.failUnless(*a, **kw)
238
239
240 def assertFalse(self, *a, **kw):
241 return self.failIf(*a, **kw)
Jean-Paul Calderone68649052009-07-17 21:14:27 -0400242
243
244 # Other stuff
245 def assertConsistentType(self, theType, name, *constructionArgs):
246 """
Jonathan Ballet648875f2011-07-16 14:14:58 +0900247 Perform various assertions about :py:data:`theType` to ensure that it is a
Jean-Paul Calderone68649052009-07-17 21:14:27 -0400248 well-defined type. This is useful for extension types, where it's
249 pretty easy to do something wacky. If something about the type is
250 unusual, an exception will be raised.
251
Jonathan Ballet78b92a22011-07-16 08:07:26 +0900252 :param theType: The type object about which to make assertions.
253 :param name: A string giving the name of the type.
Jonathan Ballet648875f2011-07-16 14:14:58 +0900254 :param constructionArgs: Positional arguments to use with :py:data:`theType` to
Jean-Paul Calderone68649052009-07-17 21:14:27 -0400255 create an instance of it.
256 """
257 self.assertEqual(theType.__name__, name)
258 self.assertTrue(isinstance(theType, type))
259 instance = theType(*constructionArgs)
260 self.assertIdentical(type(instance), theType)