blob: 11bdcb03303846358ddb2f5bfb2b216bff85a010 [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
126 # XXX :(
Jean-Paul Calderone855331d2013-03-03 10:21:43 -0800127 # ptr = int(str(p).split()[-1][:-1], 16)
128 stack.insert(0, "Leaked %d bytes at:\n" % (size,))
Jean-Paul Calderone68a6f8f2013-03-01 17:56:22 -0800129 return "".join(stack)
130
Jean-Paul Calderone855331d2013-03-03 10:21:43 -0800131 if leaks:
Jean-Paul Calderonec2e8b412013-03-02 16:27:55 -0800132 total = 0
Jean-Paul Calderone855331d2013-03-03 10:21:43 -0800133 for p in leaks:
Jean-Paul Calderonec2e8b412013-03-02 16:27:55 -0800134 total += memdbg.heap[p][-1][0]
Jean-Paul Calderone855331d2013-03-03 10:21:43 -0800135 result.addError(
136 self,
137 (None, Exception(format_leak(p)), None))
Jean-Paul Calderonec2e8b412013-03-02 16:27:55 -0800138 memdbg.free(p)
Jean-Paul Calderonef6745b32013-03-01 15:08:46 -0800139
Jean-Paul Calderone855331d2013-03-03 10:21:43 -0800140
141 def tearDown(self):
142 """
143 Clean up any files or directories created using :py:meth:`TestCase.mktemp`.
144 Subclasses must invoke this method if they override it or the
145 cleanup will not occur.
146 """
Jean-Paul Calderonebf37f0f2010-07-31 14:56:20 -0400147 if False and self._temporaryFiles is not None:
Jean-Paul Calderone0ef63ed2009-07-05 13:05:45 -0400148 for temp in self._temporaryFiles:
149 if os.path.isdir(temp):
150 shutil.rmtree(temp)
151 elif os.path.exists(temp):
152 os.unlink(temp)
Jean-Paul Calderone1206daf2009-07-16 16:07:42 -0400153 try:
154 _exception_from_error_queue()
Jean-Paul Calderone24b64592010-08-12 10:43:09 -0400155 except Error:
156 e = sys.exc_info()[1]
Jean-Paul Calderone1206daf2009-07-16 16:07:42 -0400157 if e.args != ([],):
158 self.fail("Left over errors in OpenSSL error queue: " + repr(e))
159
Jean-Paul Calderone0ef63ed2009-07-05 13:05:45 -0400160
Jean-Paul Calderoneabfbab62013-02-09 21:25:02 -0800161
Jean-Paul Calderone060a57e2011-05-04 18:02:49 -0400162 def failUnlessIn(self, containee, container, msg=None):
163 """
Jonathan Ballet648875f2011-07-16 14:14:58 +0900164 Fail the test if :py:data:`containee` is not found in :py:data:`container`.
Jean-Paul Calderone060a57e2011-05-04 18:02:49 -0400165
Jonathan Ballet648875f2011-07-16 14:14:58 +0900166 :param containee: the value that should be in :py:class:`container`
Jonathan Ballet78b92a22011-07-16 08:07:26 +0900167 :param container: a sequence type, or in the case of a mapping type,
Jean-Paul Calderone060a57e2011-05-04 18:02:49 -0400168 will follow semantics of 'if key in dict.keys()'
Jonathan Ballet78b92a22011-07-16 08:07:26 +0900169 :param msg: if msg is None, then the failure message will be
Jean-Paul Calderone060a57e2011-05-04 18:02:49 -0400170 '%r not in %r' % (first, second)
171 """
172 if containee not in container:
173 raise self.failureException(msg or "%r not in %r"
174 % (containee, container))
175 return containee
176 assertIn = failUnlessIn
177
Jean-Paul Calderone0ef63ed2009-07-05 13:05:45 -0400178 def failUnlessIdentical(self, first, second, msg=None):
179 """
Jonathan Ballet648875f2011-07-16 14:14:58 +0900180 Fail the test if :py:data:`first` is not :py:data:`second`. This is an
Jean-Paul Calderone0ef63ed2009-07-05 13:05:45 -0400181 obect-identity-equality test, not an object equality
Jonathan Ballet648875f2011-07-16 14:14:58 +0900182 (i.e. :py:func:`__eq__`) test.
Jean-Paul Calderone0ef63ed2009-07-05 13:05:45 -0400183
Jonathan Ballet78b92a22011-07-16 08:07:26 +0900184 :param msg: if msg is None, then the failure message will be
Jean-Paul Calderone0ef63ed2009-07-05 13:05:45 -0400185 '%r is not %r' % (first, second)
186 """
187 if first is not second:
188 raise self.failureException(msg or '%r is not %r' % (first, second))
189 return first
190 assertIdentical = failUnlessIdentical
191
192
193 def failIfIdentical(self, first, second, msg=None):
194 """
Jonathan Ballet648875f2011-07-16 14:14:58 +0900195 Fail the test if :py:data:`first` is :py:data:`second`. This is an
Jean-Paul Calderone0ef63ed2009-07-05 13:05:45 -0400196 obect-identity-equality test, not an object equality
Jonathan Ballet648875f2011-07-16 14:14:58 +0900197 (i.e. :py:func:`__eq__`) test.
Jean-Paul Calderone0ef63ed2009-07-05 13:05:45 -0400198
Jonathan Ballet78b92a22011-07-16 08:07:26 +0900199 :param msg: if msg is None, then the failure message will be
Jean-Paul Calderone0ef63ed2009-07-05 13:05:45 -0400200 '%r is %r' % (first, second)
201 """
202 if first is second:
203 raise self.failureException(msg or '%r is %r' % (first, second))
204 return first
205 assertNotIdentical = failIfIdentical
206
207
208 def failUnlessRaises(self, exception, f, *args, **kwargs):
209 """
Jonathan Ballet648875f2011-07-16 14:14:58 +0900210 Fail the test unless calling the function :py:data:`f` with the given
211 :py:data:`args` and :py:data:`kwargs` raises :py:data:`exception`. The
212 failure will report the traceback and call stack of the unexpected
213 exception.
Jean-Paul Calderone0ef63ed2009-07-05 13:05:45 -0400214
Jonathan Ballet78b92a22011-07-16 08:07:26 +0900215 :param exception: exception type that is to be expected
216 :param f: the function to call
Jean-Paul Calderone0ef63ed2009-07-05 13:05:45 -0400217
Jonathan Ballet78b92a22011-07-16 08:07:26 +0900218 :return: The raised exception instance, if it is of the given type.
219 :raise self.failureException: Raised if the function call does
Jean-Paul Calderone0ef63ed2009-07-05 13:05:45 -0400220 not raise an exception or if it raises an exception of a
221 different type.
222 """
223 try:
224 result = f(*args, **kwargs)
Jean-Paul Calderone24b64592010-08-12 10:43:09 -0400225 except exception:
226 inst = sys.exc_info()[1]
Jean-Paul Calderone0ef63ed2009-07-05 13:05:45 -0400227 return inst
228 except:
Rick Dean47262da2009-07-08 16:17:17 -0500229 raise self.failureException('%s raised instead of %s'
Jean-Paul Calderone0ef63ed2009-07-05 13:05:45 -0400230 % (sys.exc_info()[0],
231 exception.__name__,
Rick Dean47262da2009-07-08 16:17:17 -0500232 ))
Jean-Paul Calderone0ef63ed2009-07-05 13:05:45 -0400233 else:
234 raise self.failureException('%s not raised (%r returned)'
235 % (exception.__name__, result))
236 assertRaises = failUnlessRaises
237
238
239 _temporaryFiles = None
240 def mktemp(self):
241 """
242 Pathetic substitute for twisted.trial.unittest.TestCase.mktemp.
243 """
244 if self._temporaryFiles is None:
245 self._temporaryFiles = []
246 temp = mktemp(dir=".")
247 self._temporaryFiles.append(temp)
248 return temp
249
250
251 # Python 2.3 compatibility.
252 def assertTrue(self, *a, **kw):
253 return self.failUnless(*a, **kw)
254
255
256 def assertFalse(self, *a, **kw):
257 return self.failIf(*a, **kw)
Jean-Paul Calderone68649052009-07-17 21:14:27 -0400258
259
260 # Other stuff
261 def assertConsistentType(self, theType, name, *constructionArgs):
262 """
Jonathan Ballet648875f2011-07-16 14:14:58 +0900263 Perform various assertions about :py:data:`theType` to ensure that it is a
Jean-Paul Calderone68649052009-07-17 21:14:27 -0400264 well-defined type. This is useful for extension types, where it's
265 pretty easy to do something wacky. If something about the type is
266 unusual, an exception will be raised.
267
Jonathan Ballet78b92a22011-07-16 08:07:26 +0900268 :param theType: The type object about which to make assertions.
269 :param name: A string giving the name of the type.
Jonathan Ballet648875f2011-07-16 14:14:58 +0900270 :param constructionArgs: Positional arguments to use with :py:data:`theType` to
Jean-Paul Calderone68649052009-07-17 21:14:27 -0400271 create an instance of it.
272 """
273 self.assertEqual(theType.__name__, name)
274 self.assertTrue(isinstance(theType, type))
275 instance = theType(*constructionArgs)
276 self.assertIdentical(type(instance), theType)