# Source code for _pytest.skipping

    """ support for skip/xfail functions and markers. """
    import os
    import sys
    import traceback

    import py
    import pytest

    def pytest_addoption(parser):
        group = parser.getgroup("general")
        group.addoption('--runxfail',
               action="store_true", dest="runxfail", default=False,
               help="run tests even if they are marked xfail")

    def pytest_configure(config):
        if config.option.runxfail:
            old = pytest.xfail
            config._cleanup.append(lambda: setattr(pytest, "xfail", old))
            def nop(*args, **kwargs):
                pass
            nop.Exception = XFailed
            setattr(pytest, "xfail", nop)

        config.addinivalue_line("markers",
            "skipif(condition): skip the given test function if eval(condition) "
            "results in a True value.  Evaluation happens within the "
            "module global context. Example: skipif('sys.platform == \"win32\"') "
            "skips the test if we are on the win32 platform. see "
            "http://pytest.org/latest/skipping.html"
        )
        config.addinivalue_line("markers",
            "xfail(condition, reason=None, run=True, raises=None): mark the the test function "
            "as an expected failure if eval(condition) has a True value. "
            "Optionally specify a reason for better reporting and run=False if "
            "you don't even want to execute the test function. If only specific "
            "exception(s) are expected, you can list them in raises, and if the test fails "
            "in other ways, it will be reported as a true failure. "
            "See http://pytest.org/latest/skipping.html"
        )

    def pytest_namespace():
        return dict(xfail=xfail)

    class XFailed(pytest.fail.Exception):
        """ raised from an explicit call to pytest.xfail() """

    def xfail(reason=""):
        """ xfail an executing test or setup functions with the given reason."""
        __tracebackhide__ = True
        raise XFailed(reason)
    xfail.Exception = XFailed

    class MarkEvaluator:
        def __init__(self, item, name):
            self.item = item
            self.name = name

        @property
        def holder(self):
            return self.item.keywords.get(self.name)

        def __bool__(self):
            return bool(self.holder)
        __nonzero__ = __bool__

        def wasvalid(self):
            return not hasattr(self, 'exc')

        def invalidraise(self, exc):
            raises = self.get('raises')
            if not raises:
                return
            return not isinstance(exc, raises)

        def istrue(self):
            try:
                return self._istrue()
            except Exception:
                self.exc = sys.exc_info()
                if isinstance(self.exc[1], SyntaxError):
                    msg = [" " * (self.exc[1].offset + 4) + "^",]
                    msg.append("SyntaxError: invalid syntax")
                else:
                    msg = traceback.format_exception_only(*self.exc[:2])
                pytest.fail("Error evaluating %r expression\n"
                            "    %s\n"
                            "%s"
                            %(self.name, self.expr, "\n".join(msg)),
                            pytrace=False)

        def _getglobals(self):
            d = {'os': os, 'sys': sys, 'config': self.item.config}
            func = self.item.obj
            try:
                d.update(func.__globals__)
            except AttributeError:
                d.update(func.func_globals)
            return d

        def _istrue(self):
            if hasattr(self, 'result'):
                return self.result
            if self.holder:
                d = self._getglobals()
                if self.holder.args:
                    self.result = False
                    # "holder" might be a MarkInfo or a MarkDecorator; only
                    # MarkInfo keeps track of all parameters it received in an
                    # _arglist attribute
                    if hasattr(self.holder, '_arglist'):
                        arglist = self.holder._arglist
                    else:
                        arglist = [(self.holder.args, self.holder.kwargs)]
                    for args, kwargs in arglist:
                        for expr in args:
                            self.expr = expr
                            if isinstance(expr, py.builtin._basestring):
                                result = cached_eval(self.item.config, expr, d)
                            else:
                                if "reason" not in kwargs:
                                    # XXX better be checked at collection time
                                    msg = "you need to specify reason=STRING " \
                                          "when using booleans as conditions."
                                    pytest.fail(msg)
                                result = bool(expr)
                            if result:
                                self.result = True
                                self.reason = kwargs.get('reason', None)
                                self.expr = expr
                                return self.result
                else:
                    self.result = True
            return getattr(self, 'result', False)

        def get(self, attr, default=None):
            return self.holder.kwargs.get(attr, default)

        def getexplanation(self):
            expl = getattr(self, 'reason', None) or self.get('reason', None)
            if not expl:
                if not hasattr(self, 'expr'):
                    return ""
                else:
                    return "condition: " + str(self.expr)
            return expl

    @pytest.hookimpl(tryfirst=True)
    def pytest_runtest_setup(item):
        evalskip = MarkEvaluator(item, 'skipif')
        if evalskip.istrue():
            item._evalskip = evalskip
            pytest.skip(evalskip.getexplanation())
        item._evalxfail = MarkEvaluator(item, 'xfail')
        check_xfail_no_run(item)

    def pytest_pyfunc_call(pyfuncitem):
        check_xfail_no_run(pyfuncitem)

    def check_xfail_no_run(item):
        if not item.config.option.runxfail:
            evalxfail = item._evalxfail
            if evalxfail.istrue():
                if not evalxfail.get('run', True):
                    pytest.xfail("[NOTRUN] " + evalxfail.getexplanation())

    @pytest.hookimpl(hookwrapper=True)
    def pytest_runtest_makereport(item, call):
        outcome = yield
        rep = outcome.get_result()
        evalxfail = getattr(item, '_evalxfail', None)
        evalskip = getattr(item, '_evalskip', None)
        # unitttest special case, see setting of _unexpectedsuccess
        if hasattr(item, '_unexpectedsuccess') and rep.when == "call":
            # we need to translate into how pytest encodes xpass
            rep.wasxfail = "reason: " + repr(item._unexpectedsuccess)
            rep.outcome = "failed"
        elif item.config.option.runxfail:
            pass   # don't interefere
        elif call.excinfo and call.excinfo.errisinstance(pytest.xfail.Exception):
            rep.wasxfail = "reason: " + call.excinfo.value.msg
            rep.outcome = "skipped"
        elif evalxfail and not rep.skipped and evalxfail.wasvalid() and \
            evalxfail.istrue():
            if call.excinfo:
                if evalxfail.invalidraise(call.excinfo.value):
                    rep.outcome = "failed"
                else:
                    rep.outcome = "skipped"
                    rep.wasxfail = evalxfail.getexplanation()
            elif call.when == "call":
                rep.outcome = "failed"  # xpass outcome
                rep.wasxfail = evalxfail.getexplanation()
        elif evalskip is not None and rep.skipped and type(rep.longrepr) is tuple:
            # skipped by mark.skipif; change the location of the failure
            # to point to the item definition, otherwise it will display
            # the location of where the skip exception was raised within pytest
            filename, line, reason = rep.longrepr
            filename, line = item.location[:2]
            rep.longrepr = filename, line, reason

    # called by terminalreporter progress reporting
    def pytest_report_teststatus(report):
        if hasattr(report, "wasxfail"):
            if report.skipped:
                return "xfailed", "x", "xfail"
            elif report.failed:
                return "xpassed", "X", ("XPASS", {'yellow': True})

    # called by the terminalreporter instance/plugin
    def pytest_terminal_summary(terminalreporter):
        tr = terminalreporter
        if not tr.reportchars:
            #for name in "xfailed skipped failed xpassed":
            #    if not tr.stats.get(name, 0):
            #        tr.write_line("HINT: use '-r' option to see extra "
            #              "summary info about tests")
            #        break
            return

        lines = []
        for char in tr.reportchars:
            if char == "x":
                show_xfailed(terminalreporter, lines)
            elif char == "X":
                show_xpassed(terminalreporter, lines)
            elif char in "fF":
                show_simple(terminalreporter, lines, 'failed', "FAIL %s")
            elif char in "sS":
                show_skipped(terminalreporter, lines)
            elif char == "E":
                show_simple(terminalreporter, lines, 'error', "ERROR %s")
        if lines:
            tr._tw.sep("=", "short test summary info")
            for line in lines:
                tr._tw.line(line)

    def show_simple(terminalreporter, lines, stat, format):
        failed = terminalreporter.stats.get(stat)
        if failed:
            for rep in failed:
                pos = terminalreporter.config.cwd_relative_nodeid(rep.nodeid)
                lines.append(format %(pos,))

    def show_xfailed(terminalreporter, lines):
        xfailed = terminalreporter.stats.get("xfailed")
        if xfailed:
            for rep in xfailed:
                pos = terminalreporter.config.cwd_relative_nodeid(rep.nodeid)
                reason = rep.wasxfail
                lines.append("XFAIL %s" % (pos,))
                if reason:
                    lines.append("  " + str(reason))

    def show_xpassed(terminalreporter, lines):
        xpassed = terminalreporter.stats.get("xpassed")
        if xpassed:
            for rep in xpassed:
                pos = terminalreporter.config.cwd_relative_nodeid(rep.nodeid)
                reason = rep.wasxfail
                lines.append("XPASS %s %s" %(pos, reason))

    def cached_eval(config, expr, d):
        if not hasattr(config, '_evalcache'):
            config._evalcache = {}
        try:
            return config._evalcache[expr]
        except KeyError:
            #import sys
            #print >>sys.stderr, ("cache-miss: %r" % expr)
            exprcode = py.code.compile(expr, mode="eval")
            config._evalcache[expr] = x = eval(exprcode, d)
            return x

    def folded_skips(skipped):
        d = {}
        for event in skipped:
            key = event.longrepr
            assert len(key) == 3, (event, key)
            d.setdefault(key, []).append(event)
        l = []
        for key, events in d.items():
            l.append((len(events),) + key)
        return l

    def show_skipped(terminalreporter, lines):
        tr = terminalreporter
        skipped = tr.stats.get('skipped', [])
        if skipped:
            #if not tr.hasopt('skipped'):
            #    tr.write_line(
            #        "%d skipped tests, specify -rs for more info" %
            #        len(skipped))
            #    return
            fskips = folded_skips(skipped)
            if fskips:
                #tr.write_sep("_", "skipped test summary")
                for num, fspath, lineno, reason in fskips:
                    if reason.startswith("Skipped: "):
                        reason = reason[9:]
                    lines.append("SKIP [%d] %s:%d: %s" %
                        (num, fspath, lineno, reason))