Repository: HypothesisWorks/hypothesis

Issue:
stateful _print_step doesn't display invariant
minimal example:
```python
from hypothesis.stateful import *

class StateMachine(RuleBasedStateMachine):
    @invariant()
    def invariant1(self):
        assert False

    @rule()
    def rule1(self):
        assert True

run_state_machine_as_test(StateMachine)
```

Running this code gives:
```
Falsifying example:
state = StateMachine()
state.teardown()
Traceback (most recent call last):
  File "test42.py", line 33, in <module>
    run_state_machine_as_test(StateMachine)
  File "/home/emmanuel/projects/hypothesis-trio/venv/lib/python3.8/site-packages/hypothesis/stateful.py", line 75, in run_state_machine_as_test
    def run_state_machine_as_test(state_machine_factory, *, settings=None):
  File "/home/emmanuel/projects/hypothesis-trio/venv/lib/python3.8/site-packages/hypothesis/internal/reflection.py", line 655, in accept
    return func(*bound.args, **bound.kwargs)
  File "/home/emmanuel/projects/hypothesis-trio/venv/lib/python3.8/site-packages/hypothesis/stateful.py", line 200, in run_state_machine_as_test
    run_state_machine(state_machine_factory)
  File "/home/emmanuel/projects/hypothesis-trio/venv/lib/python3.8/site-packages/hypothesis/stateful.py", line 92, in run_state_machine
    @given(st.data())
  File "/home/emmanuel/projects/hypothesis-trio/venv/lib/python3.8/site-packages/hypothesis/core.py", line 1164, in wrapped_test
    raise the_error_hypothesis_found
  File "test42.py", line 27, in invariant1
    assert False
AssertionError
```

However the falsifying example displayed doesn't trigger the error.
The falsifying should be:
```python
state = StateMachine()
state.invariant1()
state.teardown()
```


Discussion:

Resolving patch:
diff --git a/hypothesis-python/src/hypothesis/stateful.py b/hypothesis-python/src/hypothesis/stateful.py
--- a/hypothesis-python/src/hypothesis/stateful.py
+++ b/hypothesis-python/src/hypothesis/stateful.py
@@ -371,6 +371,11 @@ def check_invariants(self, settings):
                 continue
             if not all(precond(self) for precond in invar.preconditions):
                 continue
+            if (
+                current_build_context().is_final
+                or settings.verbosity >= Verbosity.debug
+            ):
+                report(f"state.{invar.function.__name__}()")
             result = invar.function(self)
             if result is not None:
                 fail_health_check(


Test patch:
diff --git a/hypothesis-python/tests/cover/test_stateful.py b/hypothesis-python/tests/cover/test_stateful.py
--- a/hypothesis-python/tests/cover/test_stateful.py
+++ b/hypothesis-python/tests/cover/test_stateful.py
@@ -597,6 +597,92 @@ def test_foo(self):
         run_state_machine_as_test(BadPrecondition)
 
 
+def test_invariant_failling_present_in_falsifying_example():
+    @Settings(print_blob=False)
+    class BadInvariant(RuleBasedStateMachine):
+        @initialize()
+        def initialize_1(self):
+            pass
+
+        @invariant()
+        def invariant_1(self):
+            raise ValueError()
+
+        @rule()
+        def rule_1(self):
+            pass
+
+    with capture_out() as o:
+        with pytest.raises(ValueError):
+            run_state_machine_as_test(BadInvariant)
+
+    result = o.getvalue()
+    assert (
+        result
+        == """\
+Falsifying example:
+state = BadInvariant()
+state.initialize_1()
+state.invariant_1()
+state.teardown()
+"""
+    )
+
+
+def test_invariant_present_in_falsifying_example():
+    @Settings(print_blob=False)
+    class BadRuleWithGoodInvariants(RuleBasedStateMachine):
+        def __init__(self):
+            super().__init__()
+            self.num = 0
+
+        @initialize()
+        def initialize_1(self):
+            pass
+
+        @invariant(check_during_init=True)
+        def invariant_1(self):
+            pass
+
+        @invariant(check_during_init=False)
+        def invariant_2(self):
+            pass
+
+        @precondition(lambda self: self.num > 0)
+        @invariant()
+        def invariant_3(self):
+            pass
+
+        @rule()
+        def rule_1(self):
+            self.num += 1
+            if self.num == 2:
+                raise ValueError()
+
+    with capture_out() as o:
+        with pytest.raises(ValueError):
+            run_state_machine_as_test(BadRuleWithGoodInvariants)
+
+    result = o.getvalue()
+    assert (
+        result
+        == """\
+Falsifying example:
+state = BadRuleWithGoodInvariants()
+state.invariant_1()
+state.initialize_1()
+state.invariant_1()
+state.invariant_2()
+state.rule_1()
+state.invariant_1()
+state.invariant_2()
+state.invariant_3()
+state.rule_1()
+state.teardown()
+"""
+    )
+
+
 def test_always_runs_at_least_one_step():
     class CountSteps(RuleBasedStateMachine):
         def __init__(self):