import os import unittest import collections import email from email.message import Message from email._policybase import compat32 from test.support import load_package_tests from test.test_email import __file__ as landmark # Load all tests in package def load_tests(*args): return load_package_tests(os.path.dirname(__file__), *args) # helper code used by a number of test modules. def openfile(filename, *args, **kws): path = os.path.join(os.path.dirname(landmark), 'data', filename) return open(path, *args, **kws) # Base test class class TestEmailBase(unittest.TestCase): maxDiff = None # Currently the default policy is compat32. By setting that as the default # here we make minimal changes in the test_email tests compared to their # pre-3.3 state. policy = compat32 # Likewise, the default message object is Message. message = Message def __init__(self, *args, **kw): super().__init__(*args, **kw) self.addTypeEqualityFunc(bytes, self.assertBytesEqual) # Backward compatibility to minimize test_email test changes. ndiffAssertEqual = unittest.TestCase.assertEqual def _msgobj(self, filename): with openfile(filename) as fp: return email.message_from_file(fp, policy=self.policy) def _str_msg(self, string, message=None, policy=None): if policy is None: policy = self.policy if message is None: message = self.message return email.message_from_string(string, message, policy=policy) def _bytes_msg(self, bytestring, message=None, policy=None): if policy is None: policy = self.policy if message is None: message = self.message return email.message_from_bytes(bytestring, message, policy=policy) def _make_message(self): return self.message(policy=self.policy) def _bytes_repr(self, b): return [repr(x) for x in b.splitlines(keepends=True)] def assertBytesEqual(self, first, second, msg): """Our byte strings are really encoded strings; improve diff output""" self.assertEqual(self._bytes_repr(first), self._bytes_repr(second)) def assertDefectsEqual(self, actual, expected): self.assertEqual(len(actual), len(expected), actual) for i in range(len(actual)): self.assertIsInstance(actual[i], expected[i], 'item {}'.format(i)) def parameterize(cls): """A test method parameterization class decorator. Parameters are specified as the value of a class attribute that ends with the string '_params'. Call the portion before '_params' the prefix. Then a method to be parameterized must have the same prefix, the string '_as_', and an arbitrary suffix. The value of the _params attribute may be either a dictionary or a list. The values in the dictionary and the elements of the list may either be single values, or a list. If single values, they are turned into single element tuples. However derived, the resulting sequence is passed via *args to the parameterized test function. In a _params dictionary, the keys become part of the name of the generated tests. In a _params list, the values in the list are converted into a string by joining the string values of the elements of the tuple by '_' and converting any blanks into '_'s, and this become part of the name. The full name of a generated test is a 'test_' prefix, the portion of the test function name after the '_as_' separator, plus an '_', plus the name derived as explained above. For example, if we have: count_params = range(2) def count_as_foo_arg(self, foo): self.assertEqual(foo+1, myfunc(foo)) we will get parameterized test methods named: test_foo_arg_0 test_foo_arg_1 test_foo_arg_2 Or we could have: example_params = {'foo': ('bar', 1), 'bing': ('bang', 2)} def example_as_myfunc_input(self, name, count): self.assertEqual(name+str(count), myfunc(name, count)) and get: test_myfunc_input_foo test_myfunc_input_bing Note: if and only if the generated test name is a valid identifier can it be used to select the test individually from the unittest command line. The values in the params dict can be a single value, a tuple, or a dict. If a single value of a tuple, it is passed to the test function as positional arguments. If a dict, it is a passed via **kw. """ paramdicts = {} testers = collections.defaultdict(list) for name, attr in cls.__dict__.items(): if name.endswith('_params'): if not hasattr(attr, 'keys'): d = {} for x in attr: if not hasattr(x, '__iter__'): x = (x,) n = '_'.join(str(v) for v in x).replace(' ', '_') d[n] = x attr = d paramdicts[name[:-7] + '_as_'] = attr if '_as_' in name: testers[name.split('_as_')[0] + '_as_'].append(name) testfuncs = {} for name in paramdicts: if name not in testers: raise ValueError("No tester found for {}".format(name)) for name in testers: if name not in paramdicts: raise ValueError("No params found for {}".format(name)) for name, attr in cls.__dict__.items(): for paramsname, paramsdict in paramdicts.items(): if name.startswith(paramsname): testnameroot = 'test_' + name[len(paramsname):] for paramname, params in paramsdict.items(): if hasattr(params, 'keys'): test = (lambda self, name=name, params=params: getattr(self, name)(**params)) else: test = (lambda self, name=name, params=params: getattr(self, name)(*params)) testname = testnameroot + '_' + paramname test.__name__ = testname testfuncs[testname] = test for key, value in testfuncs.items(): setattr(cls, key, value) return cls