Пример #1
0
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
Пример #2
0
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
Пример #3
0
    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)
Пример #4
0
    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)
Пример #5
0
    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)