Skip to content

Commit

Permalink
Print original bundle variable names in stateful mode
Browse files Browse the repository at this point in the history
  • Loading branch information
Nikita Fomichev committed Feb 28, 2024
1 parent 1be5544 commit 8e90c32
Show file tree
Hide file tree
Showing 4 changed files with 31 additions and 24 deletions.
4 changes: 4 additions & 0 deletions RELEASE.rst
@@ -0,0 +1,4 @@
RELEASE_TYPE: patch

This patch adjusts the printing of bundle values to correspond
with their names when using stateful testing.
28 changes: 15 additions & 13 deletions hypothesis-python/src/hypothesis/stateful.py
Expand Up @@ -15,7 +15,7 @@
Notably, the set of steps available at any point may depend on the
execution to date.
"""

import collections
import inspect
from copy import copy
from functools import lru_cache
Expand Down Expand Up @@ -268,7 +268,8 @@ def __init__(self) -> None:
if not self.rules():
raise InvalidDefinition(f"Type {type(self).__name__} defines no rules")
self.bundles: Dict[str, list] = {}
self.name_counter = 1
self.names_counters: collections.Counter = collections.Counter()
self.names_list: list[str] = []
self.names_to_values: Dict[str, Any] = {}
self.__stream = StringIO()
self.__printer = RepresentationPrinter(
Expand Down Expand Up @@ -301,15 +302,16 @@ def _pretty_print(self, value):
def __repr__(self):
return f"{type(self).__name__}({nicerepr(self.bundles)})"

def _new_name(self):
result = f"v{self.name_counter}"
self.name_counter += 1
def _new_name(self, target):
result = f"{target}_{self.names_counters[target]}"
self.names_counters[target] += 1
self.names_list.append(result)
return result

def _last_names(self, n):
assert self.name_counter > n
count = self.name_counter
return [f"v{i}" for i in range(count - n, count)]
len_ = len(self.names_list)
assert len_ >= n
return self.names_list[len_ - n :]

def bundle(self, name):
return self.bundles.setdefault(name, [])
Expand Down Expand Up @@ -372,12 +374,12 @@ def _repr_step(self, rule, data, result):
return f"{output_assignment}state.{rule.function.__name__}({args})"

def _add_result_to_targets(self, targets, result):
name = self._new_name()
self.__printer.singleton_pprinters.setdefault(
id(result), lambda obj, p, cycle: p.text(name)
)
self.names_to_values[name] = result
for target in targets:
name = self._new_name(target)
self.__printer.singleton_pprinters.setdefault(
id(result), lambda obj, p, cycle: p.text(name)
)
self.names_to_values[name] = result
self.bundles.setdefault(target, []).append(VarReference(name))

def check_invariants(self, settings, output, runtimes):
Expand Down
22 changes: 11 additions & 11 deletions hypothesis-python/tests/cover/test_stateful.py
Expand Up @@ -225,12 +225,12 @@ def fail_fast(self):
assignment_line = err.value.__notes__[2]
# 'populate_bundle()' returns 2 values, so should be
# expanded to 2 variables.
assert assignment_line == "v1, v2 = state.populate_bundle()"
assert assignment_line == "b_0, b_1 = state.populate_bundle()"

# Make sure MultipleResult is iterable so the printed code is valid.
# See https://github.com/HypothesisWorks/hypothesis/issues/2311
state = ProducesMultiple()
v1, v2 = state.populate_bundle()
b_0, b_1 = state.populate_bundle()
with raises(AssertionError):
state.fail_fast()

Expand All @@ -252,7 +252,7 @@ def fail_fast(self, b):
run_state_machine_as_test(ProducesMultiple)

assignment_line = err.value.__notes__[2]
assert assignment_line == "(v1,) = state.populate_bundle()"
assert assignment_line == "(b_0,) = state.populate_bundle()"

state = ProducesMultiple()
(v1,) = state.populate_bundle()
Expand Down Expand Up @@ -797,9 +797,9 @@ def fail(self, source):
result = "\n".join(err.value.__notes__)
for m in ["create", "transfer", "fail"]:
assert result.count("state." + m) == 1
assert "v1 = state.create()" in result
assert "v2 = state.transfer(source=v1)" in result
assert "state.fail(source=v2)" in result
assert "b1_0 = state.create()" in result
assert "b2_0 = state.transfer(source=b1_0)" in result
assert "state.fail(source=b2_0)" in result


def test_initialize_rule():
Expand Down Expand Up @@ -845,7 +845,7 @@ class WithInitializeBundleRules(RuleBasedStateMachine):

@initialize(target=a, dep=just("dep"))
def initialize_a(self, dep):
return f"a v1 with ({dep})"
return f"a a_0 with ({dep})"

@rule(param=a)
def fail_fast(self, param):
Expand All @@ -861,8 +861,8 @@ def fail_fast(self, param):
== """
Falsifying example:
state = WithInitializeBundleRules()
v1 = state.initialize_a(dep='dep')
state.fail_fast(param=v1)
a_0 = state.initialize_a(dep='dep')
state.fail_fast(param=a_0)
state.teardown()
""".strip()
)
Expand Down Expand Up @@ -1087,8 +1087,8 @@ def mostly_fails(self, d):

with pytest.raises(AssertionError) as err:
run_state_machine_as_test(TrickyPrintingMachine)
assert "v1 = state.init_data(value=0)" in err.value.__notes__
assert "v1 = state.init_data(value=v1)" not in err.value.__notes__
assert "data_0 = state.init_data(value=0)" in err.value.__notes__
assert "data_0 = state.init_data(value=data_0)" not in err.value.__notes__


class TrickyInitMachine(RuleBasedStateMachine):
Expand Down
1 change: 1 addition & 0 deletions pyproject.toml
Expand Up @@ -79,5 +79,6 @@ exclude = [
"hypothesis-python/tests/nocover/test_imports.py" = ["F403", "F405"]
"hypothesis-python/tests/numpy/test_randomness.py" = ["NPY002"]
"hypothesis-python/src/hypothesis/internal/conjecture/*" = ["B023"]
"hypothesis-python/src/hypothesis/stateful.py" = ["B023"]
"hypothesis-python/tests/conjecture/test_data_tree.py" = ["B023"]
"hypothesis-python/tests/conjecture/test_test_data.py" = ["FBT001"]

0 comments on commit 8e90c32

Please sign in to comment.