commit
						37fb50a3ed
					
				|  | @ -0,0 +1,4 @@ | |||
| New `pytest_assertion_pass <https://docs.pytest.org/en/latest/reference.html#_pytest.hookspec.pytest_assertion_pass>`__ | ||||
| hook, called with context information when an assertion *passes*. | ||||
| 
 | ||||
| This hook is still **experimental** so use it with caution. | ||||
|  | @ -0,0 +1 @@ | |||
| pytest now also depends on the `astor <https://pypi.org/project/astor/>`__ package. | ||||
|  | @ -665,15 +665,14 @@ Session related reporting hooks: | |||
| .. autofunction:: pytest_fixture_post_finalizer | ||||
| .. autofunction:: pytest_warning_captured | ||||
| 
 | ||||
| And here is the central hook for reporting about | ||||
| test execution: | ||||
| Central hook for reporting about test execution: | ||||
| 
 | ||||
| .. autofunction:: pytest_runtest_logreport | ||||
| 
 | ||||
| You can also use this hook to customize assertion representation for some | ||||
| types: | ||||
| Assertion related hooks: | ||||
| 
 | ||||
| .. autofunction:: pytest_assertrepr_compare | ||||
| .. autofunction:: pytest_assertion_pass | ||||
| 
 | ||||
| 
 | ||||
| Debugging/Interaction hooks | ||||
|  |  | |||
							
								
								
									
										1
									
								
								setup.py
								
								
								
								
							
							
						
						
									
										1
									
								
								setup.py
								
								
								
								
							|  | @ -13,6 +13,7 @@ INSTALL_REQUIRES = [ | |||
|     "pluggy>=0.12,<1.0", | ||||
|     "importlib-metadata>=0.12", | ||||
|     "wcwidth", | ||||
|     "astor", | ||||
| ] | ||||
| 
 | ||||
| 
 | ||||
|  |  | |||
|  | @ -23,6 +23,13 @@ def pytest_addoption(parser): | |||
|                             test modules on import to provide assert | ||||
|                             expression information.""", | ||||
|     ) | ||||
|     parser.addini( | ||||
|         "enable_assertion_pass_hook", | ||||
|         type="bool", | ||||
|         default=False, | ||||
|         help="Enables the pytest_assertion_pass hook." | ||||
|         "Make sure to delete any previously generated pyc cache files.", | ||||
|     ) | ||||
| 
 | ||||
| 
 | ||||
| def register_assert_rewrite(*names): | ||||
|  | @ -92,7 +99,7 @@ def pytest_collection(session): | |||
| 
 | ||||
| 
 | ||||
| def pytest_runtest_setup(item): | ||||
|     """Setup the pytest_assertrepr_compare hook | ||||
|     """Setup the pytest_assertrepr_compare and pytest_assertion_pass hooks | ||||
| 
 | ||||
|     The newinterpret and rewrite modules will use util._reprcompare if | ||||
|     it exists to use custom reporting via the | ||||
|  | @ -129,9 +136,19 @@ def pytest_runtest_setup(item): | |||
| 
 | ||||
|     util._reprcompare = callbinrepr | ||||
| 
 | ||||
|     if item.ihook.pytest_assertion_pass.get_hookimpls(): | ||||
| 
 | ||||
|         def call_assertion_pass_hook(lineno, expl, orig): | ||||
|             item.ihook.pytest_assertion_pass( | ||||
|                 item=item, lineno=lineno, orig=orig, expl=expl | ||||
|             ) | ||||
| 
 | ||||
|         util._assertion_pass = call_assertion_pass_hook | ||||
| 
 | ||||
| 
 | ||||
| def pytest_runtest_teardown(item): | ||||
|     util._reprcompare = None | ||||
|     util._assertion_pass = None | ||||
| 
 | ||||
| 
 | ||||
| def pytest_sessionfinish(session): | ||||
|  |  | |||
|  | @ -10,6 +10,7 @@ import struct | |||
| import sys | ||||
| import types | ||||
| 
 | ||||
| import astor | ||||
| import atomicwrites | ||||
| 
 | ||||
| from _pytest._io.saferepr import saferepr | ||||
|  | @ -134,7 +135,7 @@ class AssertionRewritingHook: | |||
|         co = _read_pyc(fn, pyc, state.trace) | ||||
|         if co is None: | ||||
|             state.trace("rewriting {!r}".format(fn)) | ||||
|             source_stat, co = _rewrite_test(fn) | ||||
|             source_stat, co = _rewrite_test(fn, self.config) | ||||
|             if write: | ||||
|                 self._writing_pyc = True | ||||
|                 try: | ||||
|  | @ -278,13 +279,13 @@ def _write_pyc(state, co, source_stat, pyc): | |||
|     return True | ||||
| 
 | ||||
| 
 | ||||
| def _rewrite_test(fn): | ||||
| def _rewrite_test(fn, config): | ||||
|     """read and rewrite *fn* and return the code object.""" | ||||
|     stat = os.stat(fn) | ||||
|     with open(fn, "rb") as f: | ||||
|         source = f.read() | ||||
|     tree = ast.parse(source, filename=fn) | ||||
|     rewrite_asserts(tree, fn) | ||||
|     rewrite_asserts(tree, fn, config) | ||||
|     co = compile(tree, fn, "exec", dont_inherit=True) | ||||
|     return stat, co | ||||
| 
 | ||||
|  | @ -326,9 +327,9 @@ def _read_pyc(source, pyc, trace=lambda x: None): | |||
|         return co | ||||
| 
 | ||||
| 
 | ||||
| def rewrite_asserts(mod, module_path=None): | ||||
| def rewrite_asserts(mod, module_path=None, config=None): | ||||
|     """Rewrite the assert statements in mod.""" | ||||
|     AssertionRewriter(module_path).run(mod) | ||||
|     AssertionRewriter(module_path, config).run(mod) | ||||
| 
 | ||||
| 
 | ||||
| def _saferepr(obj): | ||||
|  | @ -401,6 +402,17 @@ def _call_reprcompare(ops, results, expls, each_obj): | |||
|     return expl | ||||
| 
 | ||||
| 
 | ||||
| def _call_assertion_pass(lineno, orig, expl): | ||||
|     if util._assertion_pass is not None: | ||||
|         util._assertion_pass(lineno=lineno, orig=orig, expl=expl) | ||||
| 
 | ||||
| 
 | ||||
| def _check_if_assertion_pass_impl(): | ||||
|     """Checks if any plugins implement the pytest_assertion_pass hook | ||||
|     in order not to generate explanation unecessarily (might be expensive)""" | ||||
|     return True if util._assertion_pass else False | ||||
| 
 | ||||
| 
 | ||||
| unary_map = {ast.Not: "not %s", ast.Invert: "~%s", ast.USub: "-%s", ast.UAdd: "+%s"} | ||||
| 
 | ||||
| binop_map = { | ||||
|  | @ -473,7 +485,8 @@ class AssertionRewriter(ast.NodeVisitor): | |||
|     original assert statement: it rewrites the test of an assertion | ||||
|     to provide intermediate values and replace it with an if statement | ||||
|     which raises an assertion error with a detailed explanation in | ||||
|     case the expression is false. | ||||
|     case the expression is false and calls pytest_assertion_pass hook | ||||
|     if expression is true. | ||||
| 
 | ||||
|     For this .visit_Assert() uses the visitor pattern to visit all the | ||||
|     AST nodes of the ast.Assert.test field, each visit call returning | ||||
|  | @ -491,9 +504,10 @@ class AssertionRewriter(ast.NodeVisitor): | |||
|        by statements.  Variables are created using .variable() and | ||||
|        have the form of "@py_assert0". | ||||
| 
 | ||||
|     :on_failure: The AST statements which will be executed if the | ||||
|        assertion test fails.  This is the code which will construct | ||||
|        the failure message and raises the AssertionError. | ||||
|     :expl_stmts: The AST statements which will be executed to get | ||||
|        data from the assertion.  This is the code which will construct | ||||
|        the detailed assertion message that is used in the AssertionError | ||||
|        or for the pytest_assertion_pass hook. | ||||
| 
 | ||||
|     :explanation_specifiers: A dict filled by .explanation_param() | ||||
|        with %-formatting placeholders and their corresponding | ||||
|  | @ -509,9 +523,16 @@ class AssertionRewriter(ast.NodeVisitor): | |||
| 
 | ||||
|     """ | ||||
| 
 | ||||
|     def __init__(self, module_path): | ||||
|     def __init__(self, module_path, config): | ||||
|         super().__init__() | ||||
|         self.module_path = module_path | ||||
|         self.config = config | ||||
|         if config is not None: | ||||
|             self.enable_assertion_pass_hook = config.getini( | ||||
|                 "enable_assertion_pass_hook" | ||||
|             ) | ||||
|         else: | ||||
|             self.enable_assertion_pass_hook = False | ||||
| 
 | ||||
|     def run(self, mod): | ||||
|         """Find all assert statements in *mod* and rewrite them.""" | ||||
|  | @ -642,7 +663,7 @@ class AssertionRewriter(ast.NodeVisitor): | |||
| 
 | ||||
|         The expl_expr should be an ast.Str instance constructed from | ||||
|         the %-placeholders created by .explanation_param().  This will | ||||
|         add the required code to format said string to .on_failure and | ||||
|         add the required code to format said string to .expl_stmts and | ||||
|         return the ast.Name instance of the formatted string. | ||||
| 
 | ||||
|         """ | ||||
|  | @ -653,7 +674,9 @@ class AssertionRewriter(ast.NodeVisitor): | |||
|         format_dict = ast.Dict(keys, list(current.values())) | ||||
|         form = ast.BinOp(expl_expr, ast.Mod(), format_dict) | ||||
|         name = "@py_format" + str(next(self.variable_counter)) | ||||
|         self.on_failure.append(ast.Assign([ast.Name(name, ast.Store())], form)) | ||||
|         if self.enable_assertion_pass_hook: | ||||
|             self.format_variables.append(name) | ||||
|         self.expl_stmts.append(ast.Assign([ast.Name(name, ast.Store())], form)) | ||||
|         return ast.Name(name, ast.Load()) | ||||
| 
 | ||||
|     def generic_visit(self, node): | ||||
|  | @ -687,8 +710,12 @@ class AssertionRewriter(ast.NodeVisitor): | |||
|         self.statements = [] | ||||
|         self.variables = [] | ||||
|         self.variable_counter = itertools.count() | ||||
| 
 | ||||
|         if self.enable_assertion_pass_hook: | ||||
|             self.format_variables = [] | ||||
| 
 | ||||
|         self.stack = [] | ||||
|         self.on_failure = [] | ||||
|         self.expl_stmts = [] | ||||
|         self.push_format_context() | ||||
|         # Rewrite assert into a bunch of statements. | ||||
|         top_condition, explanation = self.visit(assert_.test) | ||||
|  | @ -699,24 +726,77 @@ class AssertionRewriter(ast.NodeVisitor): | |||
|                     top_condition, module_path=self.module_path, lineno=assert_.lineno | ||||
|                 ) | ||||
|             ) | ||||
|         # Create failure message. | ||||
|         body = self.on_failure | ||||
|         negation = ast.UnaryOp(ast.Not(), top_condition) | ||||
|         self.statements.append(ast.If(negation, body, [])) | ||||
|         if assert_.msg: | ||||
|             assertmsg = self.helper("_format_assertmsg", assert_.msg) | ||||
|             explanation = "\n>assert " + explanation | ||||
|         else: | ||||
|             assertmsg = ast.Str("") | ||||
|             explanation = "assert " + explanation | ||||
|         template = ast.BinOp(assertmsg, ast.Add(), ast.Str(explanation)) | ||||
|         msg = self.pop_format_context(template) | ||||
|         fmt = self.helper("_format_explanation", msg) | ||||
|         err_name = ast.Name("AssertionError", ast.Load()) | ||||
|         exc = ast.Call(err_name, [fmt], []) | ||||
|         raise_ = ast.Raise(exc, None) | ||||
| 
 | ||||
|         body.append(raise_) | ||||
|         if self.enable_assertion_pass_hook:  # Experimental pytest_assertion_pass hook | ||||
|             negation = ast.UnaryOp(ast.Not(), top_condition) | ||||
|             msg = self.pop_format_context(ast.Str(explanation)) | ||||
| 
 | ||||
|             # Failed | ||||
|             if assert_.msg: | ||||
|                 assertmsg = self.helper("_format_assertmsg", assert_.msg) | ||||
|                 gluestr = "\n>assert " | ||||
|             else: | ||||
|                 assertmsg = ast.Str("") | ||||
|                 gluestr = "assert " | ||||
|             err_explanation = ast.BinOp(ast.Str(gluestr), ast.Add(), msg) | ||||
|             err_msg = ast.BinOp(assertmsg, ast.Add(), err_explanation) | ||||
|             err_name = ast.Name("AssertionError", ast.Load()) | ||||
|             fmt = self.helper("_format_explanation", err_msg) | ||||
|             exc = ast.Call(err_name, [fmt], []) | ||||
|             raise_ = ast.Raise(exc, None) | ||||
|             statements_fail = [] | ||||
|             statements_fail.extend(self.expl_stmts) | ||||
|             statements_fail.append(raise_) | ||||
| 
 | ||||
|             # Passed | ||||
|             fmt_pass = self.helper("_format_explanation", msg) | ||||
|             orig = astor.to_source(assert_.test).rstrip("\n").lstrip("(").rstrip(")") | ||||
|             hook_call_pass = ast.Expr( | ||||
|                 self.helper( | ||||
|                     "_call_assertion_pass", | ||||
|                     ast.Num(assert_.lineno), | ||||
|                     ast.Str(orig), | ||||
|                     fmt_pass, | ||||
|                 ) | ||||
|             ) | ||||
|             # If any hooks implement assert_pass hook | ||||
|             hook_impl_test = ast.If( | ||||
|                 self.helper("_check_if_assertion_pass_impl"), | ||||
|                 self.expl_stmts + [hook_call_pass], | ||||
|                 [], | ||||
|             ) | ||||
|             statements_pass = [hook_impl_test] | ||||
| 
 | ||||
|             # Test for assertion condition | ||||
|             main_test = ast.If(negation, statements_fail, statements_pass) | ||||
|             self.statements.append(main_test) | ||||
|             if self.format_variables: | ||||
|                 variables = [ | ||||
|                     ast.Name(name, ast.Store()) for name in self.format_variables | ||||
|                 ] | ||||
|                 clear_format = ast.Assign(variables, _NameConstant(None)) | ||||
|                 self.statements.append(clear_format) | ||||
| 
 | ||||
|         else:  # Original assertion rewriting | ||||
|             # Create failure message. | ||||
|             body = self.expl_stmts | ||||
|             negation = ast.UnaryOp(ast.Not(), top_condition) | ||||
|             self.statements.append(ast.If(negation, body, [])) | ||||
|             if assert_.msg: | ||||
|                 assertmsg = self.helper("_format_assertmsg", assert_.msg) | ||||
|                 explanation = "\n>assert " + explanation | ||||
|             else: | ||||
|                 assertmsg = ast.Str("") | ||||
|                 explanation = "assert " + explanation | ||||
|             template = ast.BinOp(assertmsg, ast.Add(), ast.Str(explanation)) | ||||
|             msg = self.pop_format_context(template) | ||||
|             fmt = self.helper("_format_explanation", msg) | ||||
|             err_name = ast.Name("AssertionError", ast.Load()) | ||||
|             exc = ast.Call(err_name, [fmt], []) | ||||
|             raise_ = ast.Raise(exc, None) | ||||
| 
 | ||||
|             body.append(raise_) | ||||
| 
 | ||||
|         # Clear temporary variables by setting them to None. | ||||
|         if self.variables: | ||||
|             variables = [ast.Name(name, ast.Store()) for name in self.variables] | ||||
|  | @ -770,7 +850,7 @@ warn_explicit( | |||
|         app = ast.Attribute(expl_list, "append", ast.Load()) | ||||
|         is_or = int(isinstance(boolop.op, ast.Or)) | ||||
|         body = save = self.statements | ||||
|         fail_save = self.on_failure | ||||
|         fail_save = self.expl_stmts | ||||
|         levels = len(boolop.values) - 1 | ||||
|         self.push_format_context() | ||||
|         # Process each operand, short-circuiting if needed. | ||||
|  | @ -778,14 +858,14 @@ warn_explicit( | |||
|             if i: | ||||
|                 fail_inner = [] | ||||
|                 # cond is set in a prior loop iteration below | ||||
|                 self.on_failure.append(ast.If(cond, fail_inner, []))  # noqa | ||||
|                 self.on_failure = fail_inner | ||||
|                 self.expl_stmts.append(ast.If(cond, fail_inner, []))  # noqa | ||||
|                 self.expl_stmts = fail_inner | ||||
|             self.push_format_context() | ||||
|             res, expl = self.visit(v) | ||||
|             body.append(ast.Assign([ast.Name(res_var, ast.Store())], res)) | ||||
|             expl_format = self.pop_format_context(ast.Str(expl)) | ||||
|             call = ast.Call(app, [expl_format], []) | ||||
|             self.on_failure.append(ast.Expr(call)) | ||||
|             self.expl_stmts.append(ast.Expr(call)) | ||||
|             if i < levels: | ||||
|                 cond = res | ||||
|                 if is_or: | ||||
|  | @ -794,7 +874,7 @@ warn_explicit( | |||
|                 self.statements.append(ast.If(cond, inner, [])) | ||||
|                 self.statements = body = inner | ||||
|         self.statements = save | ||||
|         self.on_failure = fail_save | ||||
|         self.expl_stmts = fail_save | ||||
|         expl_template = self.helper("_format_boolop", expl_list, ast.Num(is_or)) | ||||
|         expl = self.pop_format_context(expl_template) | ||||
|         return ast.Name(res_var, ast.Load()), self.explanation_param(expl) | ||||
|  |  | |||
|  | @ -12,6 +12,10 @@ from _pytest._io.saferepr import saferepr | |||
| # DebugInterpreter. | ||||
| _reprcompare = None | ||||
| 
 | ||||
| # Works similarly as _reprcompare attribute. Is populated with the hook call | ||||
| # when pytest_runtest_setup is called. | ||||
| _assertion_pass = None | ||||
| 
 | ||||
| 
 | ||||
| def format_explanation(explanation): | ||||
|     """This formats an explanation | ||||
|  |  | |||
|  | @ -485,6 +485,42 @@ def pytest_assertrepr_compare(config, op, left, right): | |||
|     """ | ||||
| 
 | ||||
| 
 | ||||
| def pytest_assertion_pass(item, lineno, orig, expl): | ||||
|     """ | ||||
|     **(Experimental)** | ||||
| 
 | ||||
|     Hook called whenever an assertion *passes*. | ||||
| 
 | ||||
|     Use this hook to do some processing after a passing assertion. | ||||
|     The original assertion information is available in the `orig` string | ||||
|     and the pytest introspected assertion information is available in the | ||||
|     `expl` string. | ||||
| 
 | ||||
|     This hook must be explicitly enabled by the ``enable_assertion_pass_hook`` | ||||
|     ini-file option: | ||||
| 
 | ||||
|     .. code-block:: ini | ||||
| 
 | ||||
|         [pytest] | ||||
|         enable_assertion_pass_hook=true | ||||
| 
 | ||||
|     You need to **clean the .pyc** files in your project directory and interpreter libraries | ||||
|     when enabling this option, as assertions will require to be re-written. | ||||
| 
 | ||||
|     :param _pytest.nodes.Item item: pytest item object of current test | ||||
|     :param int lineno: line number of the assert statement | ||||
|     :param string orig: string with original assertion | ||||
|     :param string expl: string with assert explanation | ||||
| 
 | ||||
|     .. note:: | ||||
| 
 | ||||
|         This hook is **experimental**, so its parameters or even the hook itself might | ||||
|         be changed/removed without warning in any future pytest release. | ||||
| 
 | ||||
|         If you find this hook useful, please share your feedback opening an issue. | ||||
|     """ | ||||
| 
 | ||||
| 
 | ||||
| # ------------------------------------------------------------------------- | ||||
| # hooks for influencing reporting (invoked from _pytest_terminal) | ||||
| # ------------------------------------------------------------------------- | ||||
|  |  | |||
|  | @ -1101,7 +1101,10 @@ def test_fixture_values_leak(testdir): | |||
|             assert fix_of_test1_ref() is None | ||||
|     """ | ||||
|     ) | ||||
|     result = testdir.runpytest() | ||||
|     # Running on subprocess does not activate the HookRecorder | ||||
|     # which holds itself a reference to objects in case of the | ||||
|     # pytest_assert_reprcompare hook | ||||
|     result = testdir.runpytest_subprocess() | ||||
|     result.stdout.fnmatch_lines(["* 2 passed *"]) | ||||
| 
 | ||||
| 
 | ||||
|  |  | |||
|  | @ -202,6 +202,9 @@ class TestRaises: | |||
|         assert sys.exc_info() == (None, None, None) | ||||
| 
 | ||||
|         del t | ||||
|         # Make sure this does get updated in locals dict | ||||
|         # otherwise it could keep a reference | ||||
|         locals() | ||||
| 
 | ||||
|         # ensure the t instance is not stuck in a cyclic reference | ||||
|         for o in gc.get_objects(): | ||||
|  |  | |||
|  | @ -1332,3 +1332,115 @@ class TestEarlyRewriteBailout: | |||
|         ) | ||||
|         result = testdir.runpytest() | ||||
|         result.stdout.fnmatch_lines(["* 1 passed in *"]) | ||||
| 
 | ||||
| 
 | ||||
| class TestAssertionPass: | ||||
|     def test_option_default(self, testdir): | ||||
|         config = testdir.parseconfig() | ||||
|         assert config.getini("enable_assertion_pass_hook") is False | ||||
| 
 | ||||
|     def test_hook_call(self, testdir): | ||||
|         testdir.makeconftest( | ||||
|             """ | ||||
|             def pytest_assertion_pass(item, lineno, orig, expl): | ||||
|                 raise Exception("Assertion Passed: {} {} at line {}".format(orig, expl, lineno)) | ||||
|             """ | ||||
|         ) | ||||
| 
 | ||||
|         testdir.makeini( | ||||
|             """ | ||||
|         [pytest] | ||||
|         enable_assertion_pass_hook = True | ||||
|         """ | ||||
|         ) | ||||
| 
 | ||||
|         testdir.makepyfile( | ||||
|             """ | ||||
|             def test_simple(): | ||||
|                 a=1 | ||||
|                 b=2 | ||||
|                 c=3 | ||||
|                 d=0 | ||||
| 
 | ||||
|                 assert a+b == c+d | ||||
| 
 | ||||
|             # cover failing assertions with a message | ||||
|             def test_fails(): | ||||
|                 assert False, "assert with message" | ||||
|             """ | ||||
|         ) | ||||
|         result = testdir.runpytest() | ||||
|         result.stdout.fnmatch_lines( | ||||
|             "*Assertion Passed: a + b == c + d (1 + 2) == (3 + 0) at line 7*" | ||||
|         ) | ||||
| 
 | ||||
|     def test_hook_not_called_without_hookimpl(self, testdir, monkeypatch): | ||||
|         """Assertion pass should not be called (and hence formatting should | ||||
|         not occur) if there is no hook declared for pytest_assertion_pass""" | ||||
| 
 | ||||
|         def raise_on_assertionpass(*_, **__): | ||||
|             raise Exception("Assertion passed called when it shouldn't!") | ||||
| 
 | ||||
|         monkeypatch.setattr( | ||||
|             _pytest.assertion.rewrite, "_call_assertion_pass", raise_on_assertionpass | ||||
|         ) | ||||
| 
 | ||||
|         testdir.makeini( | ||||
|             """ | ||||
|         [pytest] | ||||
|         enable_assertion_pass_hook = True | ||||
|         """ | ||||
|         ) | ||||
| 
 | ||||
|         testdir.makepyfile( | ||||
|             """ | ||||
|             def test_simple(): | ||||
|                 a=1 | ||||
|                 b=2 | ||||
|                 c=3 | ||||
|                 d=0 | ||||
| 
 | ||||
|                 assert a+b == c+d | ||||
|             """ | ||||
|         ) | ||||
|         result = testdir.runpytest() | ||||
|         result.assert_outcomes(passed=1) | ||||
| 
 | ||||
|     def test_hook_not_called_without_cmd_option(self, testdir, monkeypatch): | ||||
|         """Assertion pass should not be called (and hence formatting should | ||||
|         not occur) if there is no hook declared for pytest_assertion_pass""" | ||||
| 
 | ||||
|         def raise_on_assertionpass(*_, **__): | ||||
|             raise Exception("Assertion passed called when it shouldn't!") | ||||
| 
 | ||||
|         monkeypatch.setattr( | ||||
|             _pytest.assertion.rewrite, "_call_assertion_pass", raise_on_assertionpass | ||||
|         ) | ||||
| 
 | ||||
|         testdir.makeconftest( | ||||
|             """ | ||||
|             def pytest_assertion_pass(item, lineno, orig, expl): | ||||
|                 raise Exception("Assertion Passed: {} {} at line {}".format(orig, expl, lineno)) | ||||
|             """ | ||||
|         ) | ||||
| 
 | ||||
|         testdir.makeini( | ||||
|             """ | ||||
|         [pytest] | ||||
|         enable_assertion_pass_hook = False | ||||
|         """ | ||||
|         ) | ||||
| 
 | ||||
|         testdir.makepyfile( | ||||
|             """ | ||||
|             def test_simple(): | ||||
|                 a=1 | ||||
|                 b=2 | ||||
|                 c=3 | ||||
|                 d=0 | ||||
| 
 | ||||
|                 assert a+b == c+d | ||||
|             """ | ||||
|         ) | ||||
|         result = testdir.runpytest() | ||||
|         result.assert_outcomes(passed=1) | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue