def allowed_failure(func): """ Unit test decorator to allow failure. Test runners each have different interpretations of what should be the result of an @expectedFailure test if it succeeds. Some consider it to be a pass; others a failure. This decorator runs the test and, if it is a failure, reports the result and considers it a skipped test. """ def wrapper(*args, **kwargs): try: func(*args, **kwargs) except AssertionError: tb = traceback.extract_tb(sys.exc_info()[2]) for depth, line in enumerate(tb): if re.match('assert[A-Z]', line[2]): break tb = traceback.format_list(tb[:depth]) pywikibot.error('\n' + ''.join(tb)[:-1]) # remove \n at the end raise unittest.SkipTest('Test is allowed to fail.') except Exception: pywikibot.exception(tb=True) raise unittest.SkipTest('Test is allowed to fail.') wrapper.__name__ = func.__name__ if PY2: return unittest.expectedFailure(func) else: return wrapper
def allowed_failure(func): """ Unit test decorator to allow failure. This decorator runs the test and, if it is an Assertion failure, reports the result and considers it a skipped test. This is a similar behaviour like expectedFailure in Python 2. Passing a test will not count as failure (unexpected success) which Python 3 does. This decorator should be used if a test passes or fails due to random test parameters. If tests fails deterministic expectedFailure decorator should be used instead. @note: This decorator does not support subTest content manager. """ @wraps(func) def wrapper(*args, **kwargs): try: func(*args, **kwargs) except AssertionError: pywikibot.exception(tb=False) tb = traceback.extract_tb(sys.exc_info()[2]) for depth, line in enumerate(tb): if re.match('assert[A-Z]', line[2]): break tb = traceback.format_list(tb[:depth]) pywikibot.error('\n' + ''.join(tb)[:-1]) # remove \n at the end raise unittest.SkipTest('Test is allowed to fail.') if PY2: return unittest.expectedFailure(func) else: return wrapper
def __new__(cls, name, bases, dct): """Create the new class.""" def wrap_method(key, sitedata, func): def wrapped_method(self): sitedata = self.sites[key] self.site_key = key self.family = sitedata['family'] self.code = sitedata['code'] self.site = sitedata['site'] func(self, key) sitename = sitedata['family'] + ':' + sitedata['code'] if func.__doc__: if func.__doc__.endswith('.'): wrapped_method.__doc__ = func.__doc__[:-1] else: wrapped_method.__doc__ = func.__doc__ wrapped_method.__doc__ += ' on ' + sitename else: wrapped_method.__doc__ = 'Test ' + sitename return wrapped_method tests = [attr_name for attr_name in dct if attr_name.startswith('test')] dct['abstract_class'] = len(tests) == 0 # Bail out if it is the abstract class. if dct['abstract_class']: return super(MetaTestCaseClass, cls).__new__(cls, name, bases, dct) # Inherit superclass attributes for base in bases: for key in ('pwb', 'net', 'site', 'user', 'sysop', 'write', 'sites', 'family', 'code', 'dry', 'cached', 'cacheinfo', 'wikibase'): if hasattr(base, key) and key not in dct: # print('%s has %s; copying to %s' # % (base.__name__, key, name)) dct[key] = getattr(base, key) if 'net' in dct and dct['net'] is False: dct['site'] = False if 'sites' in dct and 'site' not in dct: dct['site'] = True # If either are specified, assume both should be specified if 'family' in dct or 'code' in dct: dct['site'] = True if (('sites' not in dct or not len(dct['sites'])) and 'family' in dct and 'code' in dct and dct['code'] != '*'): # Add entry to self.sites dct['sites'] = { str(dct['family'] + ':' + dct['code']): { 'code': dct['code'], 'family': dct['family'], } } if 'dry' in dct and dct['dry'] is True: dct['net'] = False if (('sites' not in dct and 'site' not in dct) or ('site' in dct and not dct['site'])): # Prevent use of pywikibot.Site bases = tuple([DisableSiteMixin] + list(bases)) # 'pwb' tests will _usually_ require a site. To ensure the # test class dependencies are declarative, this requires the # test writer explicitly sets 'site=False' so code reviewers # check that the script invoked by pwb will not load a site. if 'pwb' in dct and dct['pwb']: if 'site' not in dct: raise Exception( '%s: Test classes using pwb must set "site"; add ' 'site=False if the test script will not use a site' % name) # If the 'site' attribute is a false value, # remove it so it matches !site in nose. if 'site' in dct: del dct['site'] # If there isn't a site, require declaration of net activity. if 'net' not in dct: raise Exception( '%s: Test classes without a site configured must set "net"' % name) # If the 'net' attribute is a false value, # remove it so it matches !net in nose. if not dct['net']: del dct['net'] return super(MetaTestCaseClass, cls).__new__(cls, name, bases, dct) # The following section is only processed if the test uses sites. if 'dry' in dct and dct['dry']: bases = tuple([DisconnectedSiteMixin] + list(bases)) del dct['net'] else: dct['net'] = True if 'cacheinfo' in dct and dct['cacheinfo']: bases = tuple([CacheInfoMixin] + list(bases)) if 'cached' in dct and dct['cached']: bases = tuple([ForceCacheMixin] + list(bases)) bases = tuple([CheckHostnameMixin] + list(bases)) if 'write' in dct and dct['write']: bases = tuple([SiteWriteMixin] + list(bases)) if ('user' in dct and dct['user']) or ('sysop' in dct and dct['sysop']): bases = tuple([RequireUserMixin] + list(bases)) for test in tests: test_func = dct[test] # method decorated with unittest.expectedFailure has no arguments # so it is assumed to not be a multi-site test method. if test_func.__code__.co_argcount == 0: continue # a normal test method only accepts 'self' if test_func.__code__.co_argcount == 1: continue # a multi-site test method only accepts 'self' and the site-key if test_func.__code__.co_argcount != 2: raise Exception( '%s: Test method %s must accept either 1 or 2 arguments; ' ' %d found' % (name, test, test_func.__code__.co_argcount)) # create test methods processed by unittest for (key, sitedata) in dct['sites'].items(): test_name = test + '_' + key dct[test_name] = wrap_method(key, sitedata, dct[test]) if key in dct.get('expected_failures', []): dct[test_name] = unittest.expectedFailure(dct[test_name]) del dct[test] return super(MetaTestCaseClass, cls).__new__(cls, name, bases, dct)
def __new__(cls, name, bases, dct): """Create the new class.""" def wrap_method(key, sitedata, func): def wrapped_method(self): sitedata = self.sites[key] self.family = sitedata['family'] self.code = sitedata['code'] self.site = sitedata['site'] func(self, key) sitename = sitedata['family'] + ':' + sitedata['code'] if func.__doc__: if func.__doc__.endswith('.'): wrapped_method.__doc__ = func.__doc__[:-1] else: wrapped_method.__doc__ = func.__doc__ wrapped_method.__doc__ += ' on ' + sitename else: wrapped_method.__doc__ = 'Test ' + sitename return wrapped_method tests = [ attr_name for attr_name in dct if attr_name.startswith('test') ] dct['abstract_class'] = len(tests) == 0 # Bail out if it is the abstract class. if dct['abstract_class']: return super(MetaTestCaseClass, cls).__new__(cls, name, bases, dct) # Inherit superclass attributes for base in bases: for key in ('pwb', 'net', 'site', 'user', 'sysop', 'write', 'sites', 'family', 'code', 'dry', 'cached', 'cacheinfo', 'wikibase'): if hasattr(base, key) and key not in dct: # print('%s has %s; copying to %s' # % (base.__name__, key, name)) dct[key] = getattr(base, key) if 'net' in dct and dct['net'] is False: dct['site'] = False if 'sites' in dct and 'site' not in dct: dct['site'] = True # If either are specified, assume both should be specified if 'family' in dct or 'code' in dct: dct['site'] = True if (('sites' not in dct or not len(dct['sites'])) and 'family' in dct and 'code' in dct and dct['code'] != '*'): # Add entry to self.sites dct['sites'] = { str(dct['family'] + ':' + dct['code']): { 'code': dct['code'], 'family': dct['family'], } } if 'dry' in dct and dct['dry'] is True: dct['net'] = False if (('sites' not in dct and 'site' not in dct) or ('site' in dct and not dct['site'])): # Prevent use of pywikibot.Site bases = tuple([DisableSiteMixin] + list(bases)) # 'pwb' tests will _usually_ require a site. To ensure the # test class dependencies are declarative, this requires the # test writer explicitly sets 'site=False' so code reviewers # check that the script invoked by pwb will not load a site. if 'pwb' in dct and dct['pwb']: if 'site' not in dct: raise Exception( '%s: Test classes using pwb must set "site"; add ' 'site=False if the test script will not use a site' % name) # If the 'site' attribute is a false value, # remove it so it matches !site in nose. if 'site' in dct: del dct['site'] # If there isn't a site, require declaration of net activity. if 'net' not in dct: raise Exception( '%s: Test classes without a site configured must set "net"' % name) # If the 'net' attribute is a false value, # remove it so it matches !net in nose. if not dct['net']: del dct['net'] return super(MetaTestCaseClass, cls).__new__(cls, name, bases, dct) # The following section is only processed if the test uses sites. if 'dry' in dct and dct['dry']: bases = tuple([DisconnectedSiteMixin] + list(bases)) del dct['net'] else: dct['net'] = True if 'cacheinfo' in dct and dct['cacheinfo']: bases = tuple([CacheInfoMixin] + list(bases)) if 'cached' in dct and dct['cached']: bases = tuple([ForceCacheMixin] + list(bases)) bases = tuple([CheckHostnameMixin] + list(bases)) if 'write' in dct and dct['write']: bases = tuple([SiteWriteMixin] + list(bases)) if ('user' in dct and dct['user']) or ('sysop' in dct and dct['sysop']): bases = tuple([RequireUserMixin] + list(bases)) for test in tests: test_func = dct[test] # method decorated with unittest.expectedFailure has no arguments # so it is assumed to not be a multi-site test method. if test_func.__code__.co_argcount == 0: continue # a normal test method only accepts 'self' if test_func.__code__.co_argcount == 1: continue # a multi-site test method only accepts 'self' and the site-key if test_func.__code__.co_argcount != 2: raise Exception( '%s: Test method %s must accept either 1 or 2 arguments; ' ' %d found' % (name, test, test_func.__code__.co_argcount)) # create test methods processed by unittest for (key, sitedata) in dct['sites'].items(): test_name = test + '_' + key dct[test_name] = wrap_method(key, sitedata, dct[test]) if key in dct.get('expected_failures', []): dct[test_name] = unittest.expectedFailure(dct[test_name]) del dct[test] return super(MetaTestCaseClass, cls).__new__(cls, name, bases, dct)
def __new__(cls, name, bases, dct): """Create the new class.""" def wrap_method(key, sitedata, func): def wrapped_method(self): sitedata = self.sites[key] self.site_key = key self.family = sitedata["family"] self.code = sitedata["code"] self.site = sitedata["site"] func(self, key) sitename = sitedata["family"] + ":" + sitedata["code"] if func.__doc__: if func.__doc__.endswith("."): wrapped_method.__doc__ = func.__doc__[:-1] else: wrapped_method.__doc__ = func.__doc__ wrapped_method.__doc__ += " on " + sitename else: wrapped_method.__doc__ = "Test " + sitename return wrapped_method tests = [attr_name for attr_name in dct if attr_name.startswith("test")] dct["abstract_class"] = len(tests) == 0 # Bail out if it is the abstract class. if dct["abstract_class"]: return super(MetaTestCaseClass, cls).__new__(cls, name, bases, dct) # Inherit superclass attributes for base in bases: for key in ( "pwb", "net", "site", "user", "sysop", "write", "sites", "family", "code", "dry", "cached", "cacheinfo", "wikibase", ): if hasattr(base, key) and key not in dct: # print('%s has %s; copying to %s' # % (base.__name__, key, name)) dct[key] = getattr(base, key) if "net" in dct and dct["net"] is False: dct["site"] = False if "sites" in dct and "site" not in dct: dct["site"] = True # If either are specified, assume both should be specified if "family" in dct or "code" in dct: dct["site"] = True if ( ("sites" not in dct or not len(dct["sites"])) and "family" in dct and "code" in dct and dct["code"] != "*" ): # Add entry to self.sites dct["sites"] = {str(dct["family"] + ":" + dct["code"]): {"code": dct["code"], "family": dct["family"]}} if "dry" in dct and dct["dry"] is True: dct["net"] = False if ("sites" not in dct and "site" not in dct) or ("site" in dct and not dct["site"]): # Prevent use of pywikibot.Site bases = tuple([DisableSiteMixin] + list(bases)) # 'pwb' tests will _usually_ require a site. To ensure the # test class dependencies are declarative, this requires the # test writer explicitly sets 'site=False' so code reviewers # check that the script invoked by pwb will not load a site. if "pwb" in dct and dct["pwb"]: if "site" not in dct: raise Exception( '%s: Test classes using pwb must set "site"; add ' "site=False if the test script will not use a site" % name ) # If the 'site' attribute is a false value, # remove it so it matches !site in nose. if "site" in dct: del dct["site"] # If there isn't a site, require declaration of net activity. if "net" not in dct: raise Exception('%s: Test classes without a site configured must set "net"' % name) # If the 'net' attribute is a false value, # remove it so it matches !net in nose. if not dct["net"]: del dct["net"] return super(MetaTestCaseClass, cls).__new__(cls, name, bases, dct) # The following section is only processed if the test uses sites. if "dry" in dct and dct["dry"]: bases = tuple([DisconnectedSiteMixin] + list(bases)) del dct["net"] else: dct["net"] = True if "cacheinfo" in dct and dct["cacheinfo"]: bases = tuple([CacheInfoMixin] + list(bases)) if "cached" in dct and dct["cached"]: bases = tuple([ForceCacheMixin] + list(bases)) bases = tuple([CheckHostnameMixin] + list(bases)) if "write" in dct and dct["write"]: if "user" not in dct: dct["user"] = True bases = tuple([SiteWriteMixin] + list(bases)) if ("user" in dct and dct["user"]) or ("sysop" in dct and dct["sysop"]): bases = tuple([RequireUserMixin] + list(bases)) for test in tests: test_func = dct[test] # method decorated with unittest.expectedFailure has no arguments # so it is assumed to not be a multi-site test method. if test_func.__code__.co_argcount == 0: continue # a normal test method only accepts 'self' if test_func.__code__.co_argcount == 1: continue # a multi-site test method only accepts 'self' and the site-key if test_func.__code__.co_argcount != 2: raise Exception( "%s: Test method %s must accept either 1 or 2 arguments; " " %d found" % (name, test, test_func.__code__.co_argcount) ) # create test methods processed by unittest for (key, sitedata) in dct["sites"].items(): test_name = test + "_" + key dct[test_name] = wrap_method(key, sitedata, dct[test]) if key in dct.get("expected_failures", []): dct[test_name] = unittest.expectedFailure(dct[test_name]) del dct[test] return super(MetaTestCaseClass, cls).__new__(cls, name, bases, dct)