Repository: HypothesisWorks/hypothesis

Issue:
Stateful printing referring to undefined variable
Run this example with pytest (hypothesis 5.4.1, but also seen with 4.57.1):
```python
import hypothesis.strategies as st
from hypothesis.stateful import RuleBasedStateMachine, Bundle, initialize, rule


class MyStateMachine(RuleBasedStateMachine):
    data = Bundle('data')

    @initialize(target=data, value=st.integers())
    def init_data(self, value):
        return value

    @rule(d=data)
    def mostly_fails(self, d):
        assert d == 42


Test = MyStateMachine.TestCase
```

It reports this falsifying example:
```
Falsifying example:
state = MyStateMachine()
v1 = state.init_data(value=v1)
state.mostly_fails(d=v1)
state.teardown()
```

But v1 is never initially defined.


Discussion:
Blocked on #2332, because I don't want to try to deal with merge conflicts between a bugfix and the big refactoring.  It's a high priority after that though!

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
@@ -141,7 +141,15 @@ def run_state_machine(factory, data):
                     machine._initialize_rules_to_run.remove(rule)
                 else:
                     rule, data = cd.draw(machine._rules_strategy)
-                data_to_print = dict(data)
+
+                # Pretty-print the values this rule was called with *before* calling
+                # _add_result_to_targets, to avoid printing arguments which are also
+                # a return value using the variable name they are assigned to.
+                # See https://github.com/HypothesisWorks/hypothesis/issues/2341
+                if print_steps:
+                    data_to_print = {
+                        k: machine._pretty_print(v) for k, v in data.items()
+                    }
 
                 # Assign 'result' here in case executing the rule fails below
                 result = multiple()
@@ -228,7 +236,7 @@ def __init__(self):
         self._initialize_rules_to_run = copy(self.initialize_rules())
         self._rules_strategy = RuleStrategy(self)
 
-    def __pretty(self, value):
+    def _pretty_print(self, value):
         if isinstance(value, VarReference):
             return value.name
         self.__stream.seek(0)
@@ -326,9 +334,6 @@ def define_rule(cls, targets, function, arguments, precondition=None):
         return target.append(Rule(targets, function, converted_arguments, precondition))
 
     def _print_step(self, rule, data, result):
-        data_repr = {}
-        for k, v in data.items():
-            data_repr[k] = self.__pretty(v)
         self.step_count = getattr(self, "step_count", 0) + 1
         # If the step has target bundles, and the result is a MultipleResults
         # then we want to assign to multiple variables.
@@ -346,7 +351,7 @@ def _print_step(self, rule, data, result):
             % (
                 output_assignment,
                 rule.function.__name__,
-                ", ".join("%s=%s" % kv for kv in data_repr.items()),
+                ", ".join("%s=%s" % kv for kv in data.items()),
             )
         )
 


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
@@ -1159,3 +1159,24 @@ def ok(self):
 def test_cannot_have_zero_steps():
     with pytest.raises(InvalidArgument):
         Settings(stateful_step_count=0)
+
+
+def test_arguments_do_not_use_names_of_return_values():
+    # See https://github.com/HypothesisWorks/hypothesis/issues/2341
+    class TrickyPrintingMachine(RuleBasedStateMachine):
+        data = Bundle("data")
+
+        @initialize(target=data, value=integers())
+        def init_data(self, value):
+            return value
+
+        @rule(d=data)
+        def mostly_fails(self, d):
+            assert d == 42
+
+    with capture_out() as o:
+        with pytest.raises(AssertionError):
+            run_state_machine_as_test(TrickyPrintingMachine)
+    output = o.getvalue()
+    assert "v1 = state.init_data(value=0)" in output
+    assert "v1 = state.init_data(value=v1)" not in output