| """ |
| support for presenting detailed information in failing assertions. |
| """ |
| from __future__ import absolute_import, division, print_function |
| import sys |
| import six |
| |
| from _pytest.assertion import util |
| from _pytest.assertion import rewrite |
| from _pytest.assertion import truncate |
| |
| |
| def pytest_addoption(parser): |
| group = parser.getgroup("debugconfig") |
| group.addoption('--assert', |
| action="store", |
| dest="assertmode", |
| choices=("rewrite", "plain",), |
| default="rewrite", |
| metavar="MODE", |
| help="""Control assertion debugging tools. 'plain' |
| performs no assertion debugging. 'rewrite' |
| (the default) rewrites assert statements in |
| test modules on import to provide assert |
| expression information.""") |
| |
| |
| def register_assert_rewrite(*names): |
| """Register one or more module names to be rewritten on import. |
| |
| This function will make sure that this module or all modules inside |
| the package will get their assert statements rewritten. |
| Thus you should make sure to call this before the module is |
| actually imported, usually in your __init__.py if you are a plugin |
| using a package. |
| |
| :raise TypeError: if the given module names are not strings. |
| """ |
| for name in names: |
| if not isinstance(name, str): |
| msg = 'expected module names as *args, got {0} instead' |
| raise TypeError(msg.format(repr(names))) |
| for hook in sys.meta_path: |
| if isinstance(hook, rewrite.AssertionRewritingHook): |
| importhook = hook |
| break |
| else: |
| importhook = DummyRewriteHook() |
| importhook.mark_rewrite(*names) |
| |
| |
| class DummyRewriteHook(object): |
| """A no-op import hook for when rewriting is disabled.""" |
| |
| def mark_rewrite(self, *names): |
| pass |
| |
| |
| class AssertionState: |
| """State for the assertion plugin.""" |
| |
| def __init__(self, config, mode): |
| self.mode = mode |
| self.trace = config.trace.root.get("assertion") |
| self.hook = None |
| |
| |
| def install_importhook(config): |
| """Try to install the rewrite hook, raise SystemError if it fails.""" |
| # Jython has an AST bug that make the assertion rewriting hook malfunction. |
| if (sys.platform.startswith('java')): |
| raise SystemError('rewrite not supported') |
| |
| config._assertstate = AssertionState(config, 'rewrite') |
| config._assertstate.hook = hook = rewrite.AssertionRewritingHook(config) |
| sys.meta_path.insert(0, hook) |
| config._assertstate.trace('installed rewrite import hook') |
| |
| def undo(): |
| hook = config._assertstate.hook |
| if hook is not None and hook in sys.meta_path: |
| sys.meta_path.remove(hook) |
| |
| config.add_cleanup(undo) |
| return hook |
| |
| |
| def pytest_collection(session): |
| # this hook is only called when test modules are collected |
| # so for example not in the master process of pytest-xdist |
| # (which does not collect test modules) |
| assertstate = getattr(session.config, '_assertstate', None) |
| if assertstate: |
| if assertstate.hook is not None: |
| assertstate.hook.set_session(session) |
| |
| |
| def pytest_runtest_setup(item): |
| """Setup the pytest_assertrepr_compare hook |
| |
| The newinterpret and rewrite modules will use util._reprcompare if |
| it exists to use custom reporting via the |
| pytest_assertrepr_compare hook. This sets up this custom |
| comparison for the test. |
| """ |
| def callbinrepr(op, left, right): |
| """Call the pytest_assertrepr_compare hook and prepare the result |
| |
| This uses the first result from the hook and then ensures the |
| following: |
| * Overly verbose explanations are truncated unless configured otherwise |
| (eg. if running in verbose mode). |
| * Embedded newlines are escaped to help util.format_explanation() |
| later. |
| * If the rewrite mode is used embedded %-characters are replaced |
| to protect later % formatting. |
| |
| The result can be formatted by util.format_explanation() for |
| pretty printing. |
| """ |
| hook_result = item.ihook.pytest_assertrepr_compare( |
| config=item.config, op=op, left=left, right=right) |
| for new_expl in hook_result: |
| if new_expl: |
| new_expl = truncate.truncate_if_required(new_expl, item) |
| new_expl = [line.replace("\n", "\\n") for line in new_expl] |
| res = six.text_type("\n~").join(new_expl) |
| if item.config.getvalue("assertmode") == "rewrite": |
| res = res.replace("%", "%%") |
| return res |
| util._reprcompare = callbinrepr |
| |
| |
| def pytest_runtest_teardown(item): |
| util._reprcompare = None |
| |
| |
| def pytest_sessionfinish(session): |
| assertstate = getattr(session.config, '_assertstate', None) |
| if assertstate: |
| if assertstate.hook is not None: |
| assertstate.hook.set_session(None) |
| |
| |
| # Expose this plugin's implementation for the pytest_assertrepr_compare hook |
| pytest_assertrepr_compare = util.assertrepr_compare |