class DeprecationTestCase(DebugOnlyTestCase, TestCase): """Test cases for deprecation function in the tools module.""" _generic_match = re.compile(r'.* is deprecated(; use .* instead)?\.') skip_list = [ unittest.case._AssertRaisesContext, TestCase.assertRaises, TestCase.assertRaisesRegex, TestCase.assertRaisesRegexp, ] # Require no instead string NO_INSTEAD = object() # Require an instead string INSTEAD = object() # Python 3 component in the call stack of _AssertRaisesContext if hasattr(unittest.case, '_AssertRaisesBaseContext'): skip_list.append(unittest.case._AssertRaisesBaseContext) def __init__(self, *args, **kwargs): """Constructor.""" super(DeprecationTestCase, self).__init__(*args, **kwargs) self.warning_log = [] self.expect_warning_filename = inspect.getfile(self.__class__) if self.expect_warning_filename.endswith((".pyc", ".pyo")): self.expect_warning_filename = self.expect_warning_filename[:-1] self._do_test_warning_filename = True self._ignore_unknown_warning_packages = False self.context_manager = WarningSourceSkipContextManager(self.skip_list) def _reset_messages(self): """Reset captured deprecation warnings.""" self._do_test_warning_filename = True del self.warning_log[:] @property def deprecation_messages(self): """Return captured deprecation warnings.""" messages = [str(item.message) for item in self.warning_log] return messages @classmethod def _build_message(cls, deprecated, instead): if deprecated is None: if instead is None: msg = None elif instead is True: msg = cls.INSTEAD else: assert instead is False msg = cls.NO_INSTEAD else: msg = '{0} is deprecated'.format(deprecated) if instead: msg += '; use {0} instead'.format(instead) msg += '.' return msg def assertDeprecationParts(self, deprecated=None, instead=None): """ Assert that a deprecation warning happened. To simplify deprecation tests it just requires the to separated parts and forwards the result to L{assertDeprecation}. @param deprecated: The deprecated string. If None it uses a generic match depending on instead. @type deprecated: str or None @param instead: The instead string unless deprecated is None. If it's None it allows any generic deprecation string, on True only those where instead string is present and on False only those where it's missing. If the deprecation string is not None, no instead string is expected when instead evaluates to False. @type instead: str or None or True or False """ self.assertDeprecation(self._build_message(deprecated, instead)) def assertDeprecation(self, msg=None): """ Assert that a deprecation warning happened. @param msg: Either the specific message or None to allow any generic message. When set to C{INSTEAD} it only counts those supplying an alternative and when C{NO_INSTEAD} only those not supplying one. @type msg: string or None or INSTEAD or NO_INSTEAD """ if msg is None or msg is self.INSTEAD or msg is self.NO_INSTEAD: deprecation_messages = self.deprecation_messages for deprecation_message in deprecation_messages: match = self._generic_match.match(deprecation_message) if (match and bool(match.group(1)) == (msg is self.INSTEAD) or msg is None): break else: self.fail('No generic deprecation message match found in ' '{0}'.format(deprecation_messages)) else: self.assertIn(msg, self.deprecation_messages) if self._do_test_warning_filename: self.assertDeprecationFile(self.expect_warning_filename) def assertOneDeprecationParts(self, deprecated=None, instead=None, count=1): """ Assert that exactly one deprecation message happened and reset. It uses the same arguments as L{assertDeprecationParts}. """ self.assertOneDeprecation(self._build_message(deprecated, instead), count) def assertOneDeprecation(self, msg=None, count=1): """Assert that exactly one deprecation message happened and reset.""" self.assertDeprecation(msg) # This is doing such a weird structure, so that it shows any other # deprecation message from the set. self.assertCountEqual(set(self.deprecation_messages), [self.deprecation_messages[0]]) self.assertEqual(len(self.deprecation_messages), count) self._reset_messages() def assertNoDeprecation(self, msg=None): """Assert that no deprecation warning happened.""" if msg: self.assertNotIn(msg, self.deprecation_messages) else: self.assertEqual([], self.deprecation_messages) def assertDeprecationClass(self, cls): """Assert that all deprecation warning are of one class.""" self.assertTrue(all(isinstance(item.message, cls) for item in self.warning_log)) def assertDeprecationFile(self, filename): """Assert that all deprecation warning are of one filename.""" for item in self.warning_log: if (self._ignore_unknown_warning_packages and 'pywikibot' not in item.filename): continue if item.filename != filename: self.fail( 'expected warning filename %s; warning item: %s' % (filename, item)) def setUp(self): """Set up unit test.""" super(DeprecationTestCase, self).setUp() self.warning_log = self.context_manager.__enter__() warnings.simplefilter("always") self._reset_messages() def tearDown(self): """Tear down unit test.""" self.context_manager.__exit__() super(DeprecationTestCase, self).tearDown()