Coverage for /usr/local/lib/python3.7/site-packages/_pytest/assertion/__init__.py : 13%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1"""
2support for presenting detailed information in failing assertions.
3"""
4import sys
5from typing import Optional
7from _pytest.assertion import rewrite
8from _pytest.assertion import truncate
9from _pytest.assertion import util
12def pytest_addoption(parser):
13 group = parser.getgroup("debugconfig")
14 group.addoption(
15 "--assert",
16 action="store",
17 dest="assertmode",
18 choices=("rewrite", "plain"),
19 default="rewrite",
20 metavar="MODE",
21 help="""Control assertion debugging tools. 'plain'
22 performs no assertion debugging. 'rewrite'
23 (the default) rewrites assert statements in
24 test modules on import to provide assert
25 expression information.""",
26 )
27 parser.addini(
28 "enable_assertion_pass_hook",
29 type="bool",
30 default=False,
31 help="Enables the pytest_assertion_pass hook."
32 "Make sure to delete any previously generated pyc cache files.",
33 )
36def register_assert_rewrite(*names) -> None:
37 """Register one or more module names to be rewritten on import.
39 This function will make sure that this module or all modules inside
40 the package will get their assert statements rewritten.
41 Thus you should make sure to call this before the module is
42 actually imported, usually in your __init__.py if you are a plugin
43 using a package.
45 :raise TypeError: if the given module names are not strings.
46 """
47 for name in names:
48 if not isinstance(name, str):
49 msg = "expected module names as *args, got {0} instead"
50 raise TypeError(msg.format(repr(names)))
51 for hook in sys.meta_path:
52 if isinstance(hook, rewrite.AssertionRewritingHook):
53 importhook = hook
54 break
55 else:
56 # TODO(typing): Add a protocol for mark_rewrite() and use it
57 # for importhook and for PytestPluginManager.rewrite_hook.
58 importhook = DummyRewriteHook() # type: ignore
59 importhook.mark_rewrite(*names)
62class DummyRewriteHook:
63 """A no-op import hook for when rewriting is disabled."""
65 def mark_rewrite(self, *names):
66 pass
69class AssertionState:
70 """State for the assertion plugin."""
72 def __init__(self, config, mode):
73 self.mode = mode
74 self.trace = config.trace.root.get("assertion")
75 self.hook = None # type: Optional[rewrite.AssertionRewritingHook]
78def install_importhook(config):
79 """Try to install the rewrite hook, raise SystemError if it fails."""
80 config._assertstate = AssertionState(config, "rewrite")
81 config._assertstate.hook = hook = rewrite.AssertionRewritingHook(config)
82 sys.meta_path.insert(0, hook)
83 config._assertstate.trace("installed rewrite import hook")
85 def undo():
86 hook = config._assertstate.hook
87 if hook is not None and hook in sys.meta_path:
88 sys.meta_path.remove(hook)
90 config.add_cleanup(undo)
91 return hook
94def pytest_collection(session):
95 # this hook is only called when test modules are collected
96 # so for example not in the master process of pytest-xdist
97 # (which does not collect test modules)
98 assertstate = getattr(session.config, "_assertstate", None)
99 if assertstate:
100 if assertstate.hook is not None:
101 assertstate.hook.set_session(session)
104def pytest_runtest_setup(item):
105 """Setup the pytest_assertrepr_compare and pytest_assertion_pass hooks
107 The newinterpret and rewrite modules will use util._reprcompare if
108 it exists to use custom reporting via the
109 pytest_assertrepr_compare hook. This sets up this custom
110 comparison for the test.
111 """
113 def callbinrepr(op, left, right):
114 # type: (str, object, object) -> Optional[str]
115 """Call the pytest_assertrepr_compare hook and prepare the result
117 This uses the first result from the hook and then ensures the
118 following:
119 * Overly verbose explanations are truncated unless configured otherwise
120 (eg. if running in verbose mode).
121 * Embedded newlines are escaped to help util.format_explanation()
122 later.
123 * If the rewrite mode is used embedded %-characters are replaced
124 to protect later % formatting.
126 The result can be formatted by util.format_explanation() for
127 pretty printing.
128 """
129 hook_result = item.ihook.pytest_assertrepr_compare(
130 config=item.config, op=op, left=left, right=right
131 )
132 for new_expl in hook_result:
133 if new_expl:
134 new_expl = truncate.truncate_if_required(new_expl, item)
135 new_expl = [line.replace("\n", "\\n") for line in new_expl]
136 res = "\n~".join(new_expl)
137 if item.config.getvalue("assertmode") == "rewrite":
138 res = res.replace("%", "%%")
139 return res
140 return None
142 util._reprcompare = callbinrepr
144 if item.ihook.pytest_assertion_pass.get_hookimpls():
146 def call_assertion_pass_hook(lineno, orig, expl):
147 item.ihook.pytest_assertion_pass(
148 item=item, lineno=lineno, orig=orig, expl=expl
149 )
151 util._assertion_pass = call_assertion_pass_hook
154def pytest_runtest_teardown(item):
155 util._reprcompare = None
156 util._assertion_pass = None
159def pytest_sessionfinish(session):
160 assertstate = getattr(session.config, "_assertstate", None)
161 if assertstate:
162 if assertstate.hook is not None:
163 assertstate.hook.set_session(None)
166def pytest_assertrepr_compare(config, op, left, right):
167 return util.assertrepr_compare(config=config, op=op, left=left, right=right)