From ff5c676315eea2c8edf4f665afcfa5ea76f83b04 Mon Sep 17 00:00:00 2001 From: Liam DeVoe Date: Fri, 26 Apr 2024 16:34:13 -0400 Subject: [PATCH 1/8] add defaults for check_invariants and short_circuit --- .../src/hypothesis/internal/conjecture/shrinking/common.py | 3 +-- .../src/hypothesis/internal/conjecture/shrinking/lexical.py | 5 ----- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/hypothesis-python/src/hypothesis/internal/conjecture/shrinking/common.py b/hypothesis-python/src/hypothesis/internal/conjecture/shrinking/common.py index 5acdf85a8d..9f6f49e6b0 100644 --- a/hypothesis-python/src/hypothesis/internal/conjecture/shrinking/common.py +++ b/hypothesis-python/src/hypothesis/internal/conjecture/shrinking/common.py @@ -155,7 +155,6 @@ def check_invariants(self, value): Does nothing by default. """ - raise NotImplementedError def short_circuit(self): """Possibly attempt to do some shrinking. @@ -163,7 +162,7 @@ def short_circuit(self): If this returns True, the ``run`` method will terminate early without doing any more work. """ - raise NotImplementedError + return False def left_is_better(self, left, right): """Returns True if the left is strictly simpler than the right diff --git a/hypothesis-python/src/hypothesis/internal/conjecture/shrinking/lexical.py b/hypothesis-python/src/hypothesis/internal/conjecture/shrinking/lexical.py index 569561c4ed..cf134ebbd2 100644 --- a/hypothesis-python/src/hypothesis/internal/conjecture/shrinking/lexical.py +++ b/hypothesis-python/src/hypothesis/internal/conjecture/shrinking/lexical.py @@ -49,11 +49,6 @@ def minimize_as_integer(self): def partial_sort(self): Ordering.shrink(self.current, self.consider, random=self.random) - def short_circuit(self): - """This is just an assemblage of other shrinkers, so we rely on their - short circuiting.""" - return False - def run_step(self): self.minimize_as_integer() self.partial_sort() From cb5dd52e7aaca211a545b8d56b6265dfd3a3e6c9 Mon Sep 17 00:00:00 2001 From: Liam DeVoe Date: Fri, 26 Apr 2024 16:35:04 -0400 Subject: [PATCH 2/8] track index instead of modifying/popping ir_tree_nodes --- .../src/hypothesis/internal/conjecture/data.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/hypothesis-python/src/hypothesis/internal/conjecture/data.py b/hypothesis-python/src/hypothesis/internal/conjecture/data.py index 93f7758ba2..d3b7ba9a8a 100644 --- a/hypothesis-python/src/hypothesis/internal/conjecture/data.py +++ b/hypothesis-python/src/hypothesis/internal/conjecture/data.py @@ -1977,6 +1977,7 @@ def __init__( self.extra_information = ExtraInformation() self.ir_tree_nodes = ir_tree_prefix + self._node_index = 0 self.start_example(TOP_LABEL) def __repr__(self): @@ -2274,10 +2275,11 @@ def _pooled_kwargs(self, ir_type, kwargs): def _pop_ir_tree_node(self, ir_type: IRTypeName, kwargs: IRKWargsType) -> IRNode: assert self.ir_tree_nodes is not None - if self.ir_tree_nodes == []: + if self._node_index == len(self.ir_tree_nodes): self.mark_overrun() - node = self.ir_tree_nodes.pop(0) + node = self.ir_tree_nodes[self._node_index] + self._node_index += 1 # If we're trying to draw a different ir type at the same location, then # this ir tree has become badly misaligned. We don't have many good/simple # options here for realigning beyond giving up. From 43977e43c7c06099f9b32baed68bef8e8c0a6c94 Mon Sep 17 00:00:00 2001 From: Liam DeVoe Date: Fri, 26 Apr 2024 16:36:38 -0400 Subject: [PATCH 3/8] remove unused random= parameter from shrinkers --- .../internal/conjecture/shrinker.py | 6 +---- .../internal/conjecture/shrinking/common.py | 4 +-- .../internal/conjecture/shrinking/lexical.py | 3 +-- .../tests/conjecture/test_minimizer.py | 26 +++++++------------ .../tests/conjecture/test_order_shrinking.py | 16 +++--------- .../tests/cover/test_shrink_budgeting.py | 3 +-- 6 files changed, 18 insertions(+), 40 deletions(-) diff --git a/hypothesis-python/src/hypothesis/internal/conjecture/shrinker.py b/hypothesis-python/src/hypothesis/internal/conjecture/shrinker.py index ae28fcd741..04bbe079a3 100644 --- a/hypothesis-python/src/hypothesis/internal/conjecture/shrinker.py +++ b/hypothesis-python/src/hypothesis/internal/conjecture/shrinker.py @@ -922,7 +922,7 @@ def reoffset(o): new_blocks[i] = int_to_bytes(v + o, len(blocked[i])) return self.incorporate_new_buffer(b"".join(new_blocks)) - Integer.shrink(offset, reoffset, random=self.random) + Integer.shrink(offset, reoffset) self.clear_change_tracking() def clear_change_tracking(self): @@ -1193,7 +1193,6 @@ def minimize_duplicated_blocks(self, chooser): Lexical.shrink( block, lambda b: self.try_shrinking_blocks(targets, b), - random=self.random, ) @defines_shrink_pass() @@ -1236,7 +1235,6 @@ def minimize_floats(self, chooser): + [node.copy(with_value=sign * val)] + self.nodes[node.index + 1 :] ), - random=self.random, node=node, ) @@ -1362,7 +1360,6 @@ def minimize_individual_blocks(self, chooser): Lexical.shrink( self.shrink_target.buffer[u:v], lambda b: self.try_shrinking_blocks((i,), b), - random=self.random, ) if self.shrink_target is not initial: @@ -1459,7 +1456,6 @@ def test_not_equal(x, y): ], ) ), - random=self.random, key=lambda i: st.buffer[examples[i].start : examples[i].end], ) diff --git a/hypothesis-python/src/hypothesis/internal/conjecture/shrinking/common.py b/hypothesis-python/src/hypothesis/internal/conjecture/shrinking/common.py index 9f6f49e6b0..1de89bd18b 100644 --- a/hypothesis-python/src/hypothesis/internal/conjecture/shrinking/common.py +++ b/hypothesis-python/src/hypothesis/internal/conjecture/shrinking/common.py @@ -20,7 +20,6 @@ def __init__( self, initial, predicate, - random, *, full=False, debug=False, @@ -30,7 +29,6 @@ def __init__( self.setup(**kwargs) self.current = self.make_immutable(initial) self.initial = self.current - self.random = random self.full = full self.changes = 0 self.name = name @@ -75,7 +73,7 @@ def call_shrinker(self, other_class, initial, predicate, **kwargs): Note we explicitly do not pass through full. """ - return other_class.shrink(initial, predicate, random=self.random, **kwargs) + return other_class.shrink(initial, predicate, **kwargs) def debug(self, *args): if self.debugging_enabled: diff --git a/hypothesis-python/src/hypothesis/internal/conjecture/shrinking/lexical.py b/hypothesis-python/src/hypothesis/internal/conjecture/shrinking/lexical.py index cf134ebbd2..2f69f1fee3 100644 --- a/hypothesis-python/src/hypothesis/internal/conjecture/shrinking/lexical.py +++ b/hypothesis-python/src/hypothesis/internal/conjecture/shrinking/lexical.py @@ -43,11 +43,10 @@ def minimize_as_integer(self): Integer.shrink( self.current_int, lambda c: c == self.current_int or self.incorporate_int(c), - random=self.random, ) def partial_sort(self): - Ordering.shrink(self.current, self.consider, random=self.random) + Ordering.shrink(self.current, self.consider) def run_step(self): self.minimize_as_integer() diff --git a/hypothesis-python/tests/conjecture/test_minimizer.py b/hypothesis-python/tests/conjecture/test_minimizer.py index 64513dda88..5dee836158 100644 --- a/hypothesis-python/tests/conjecture/test_minimizer.py +++ b/hypothesis-python/tests/conjecture/test_minimizer.py @@ -15,34 +15,30 @@ def test_shrink_to_zero(): - assert Lexical.shrink(bytes([255] * 8), lambda x: True, random=Random(0)) == bytes( - 8 - ) + assert Lexical.shrink(bytes([255] * 8), lambda x: True) == bytes(8) def test_shrink_to_smallest(): - assert Lexical.shrink( - bytes([255] * 8), lambda x: sum(x) > 10, random=Random(0) - ) == bytes([0] * 7 + [11]) + assert Lexical.shrink(bytes([255] * 8), lambda x: sum(x) > 10) == bytes( + [0] * 7 + [11] + ) def test_float_hack_fails(): - assert Lexical.shrink( - bytes([255] * 8), lambda x: x[0] >> 7, random=Random(0) - ) == bytes([128] + [0] * 7) + assert Lexical.shrink(bytes([255] * 8), lambda x: x[0] >> 7) == bytes( + [128] + [0] * 7 + ) def test_can_sort_bytes_by_reordering(): start = bytes([5, 4, 3, 2, 1, 0]) - finish = Lexical.shrink(start, lambda x: set(x) == set(start), random=Random(0)) + finish = Lexical.shrink(start, lambda x: set(x) == set(start)) assert finish == bytes([0, 1, 2, 3, 4, 5]) def test_can_sort_bytes_by_reordering_partially(): start = bytes([5, 4, 3, 2, 1, 0]) - finish = Lexical.shrink( - start, lambda x: set(x) == set(start) and x[0] > x[-1], random=Random(0) - ) + finish = Lexical.shrink(start, lambda x: set(x) == set(start) and x[0] > x[-1]) assert finish == bytes([1, 2, 3, 4, 5, 0]) @@ -59,7 +55,5 @@ def test_can_sort_bytes_by_reordering_partially2(): def test_can_sort_bytes_by_reordering_partially_not_cross_stationary_element(): start = bytes([5, 3, 0, 2, 1, 4]) - finish = Lexical.shrink( - start, lambda x: set(x) == set(start) and x[3] == 2, random=Random(0) - ) + finish = Lexical.shrink(start, lambda x: set(x) == set(start) and x[3] == 2) assert finish <= bytes([0, 3, 5, 2, 1, 4]) diff --git a/hypothesis-python/tests/conjecture/test_order_shrinking.py b/hypothesis-python/tests/conjecture/test_order_shrinking.py index 42046007fa..129595ff89 100644 --- a/hypothesis-python/tests/conjecture/test_order_shrinking.py +++ b/hypothesis-python/tests/conjecture/test_order_shrinking.py @@ -8,8 +8,6 @@ # v. 2.0. If a copy of the MPL was not distributed with this file, You can # obtain one at https://mozilla.org/MPL/2.0/. -from random import Random - from hypothesis import example, given, strategies as st from hypothesis.internal.conjecture.shrinking import Ordering @@ -23,22 +21,18 @@ def test_shrinks_down_to_sorted_the_slow_way(ls): # automatically, but here we test that a single run_step could put the # list in sorted order anyway if it had to, and that that is just an # optimisation. - shrinker = Ordering(ls, lambda ls: True, random=Random(0), full=False) + shrinker = Ordering(ls, lambda ls: True, full=False) shrinker.run_step() assert list(shrinker.current) == sorted(ls) def test_can_partially_sort_a_list(): - finish = Ordering.shrink( - [5, 4, 3, 2, 1, 0], lambda x: x[0] > x[-1], random=Random(0) - ) + finish = Ordering.shrink([5, 4, 3, 2, 1, 0], lambda x: x[0] > x[-1]) assert finish == (1, 2, 3, 4, 5, 0) def test_can_partially_sort_a_list_2(): - finish = Ordering.shrink( - [5, 4, 3, 2, 1, 0], lambda x: x[0] > x[2], random=Random(0), full=True - ) + finish = Ordering.shrink([5, 4, 3, 2, 1, 0], lambda x: x[0] > x[2], full=True) assert finish <= (1, 2, 0, 3, 4, 5) @@ -49,9 +43,7 @@ def test_adaptively_shrinks_around_hole(): intended_result = sorted(initial) intended_result.insert(500, intended_result.pop()) - shrinker = Ordering( - initial, lambda ls: ls[500] == 2000, random=Random(0), full=True - ) + shrinker = Ordering(initial, lambda ls: ls[500] == 2000, full=True) shrinker.run() assert shrinker.current[500] == 2000 diff --git a/hypothesis-python/tests/cover/test_shrink_budgeting.py b/hypothesis-python/tests/cover/test_shrink_budgeting.py index b67e2e2d91..87f2ad6ed5 100644 --- a/hypothesis-python/tests/cover/test_shrink_budgeting.py +++ b/hypothesis-python/tests/cover/test_shrink_budgeting.py @@ -10,7 +10,6 @@ import math import sys -from random import Random import pytest @@ -18,7 +17,7 @@ def measure_baseline(cls, value, **kwargs): - shrinker = cls(value, lambda x: x == value, random=Random(0), **kwargs) + shrinker = cls(value, lambda x: x == value, **kwargs) shrinker.run() return shrinker.calls From cf0fed24416c5be0feb41cba86d8c117465d6a5c Mon Sep 17 00:00:00 2001 From: Liam DeVoe Date: Fri, 26 Apr 2024 16:38:49 -0400 Subject: [PATCH 4/8] remove unecessary lambda x: True params --- .../tests/cover/test_complex_numbers.py | 10 ++++------ .../tests/cover/test_composite.py | 6 +++--- .../tests/cover/test_direct_strategies.py | 2 +- .../tests/cover/test_simple_characters.py | 4 ++-- .../tests/cover/test_simple_collections.py | 19 +++++++++---------- hypothesis-python/tests/nocover/test_find.py | 2 +- .../tests/nocover/test_simple_numbers.py | 14 +++++++------- 7 files changed, 27 insertions(+), 30 deletions(-) diff --git a/hypothesis-python/tests/cover/test_complex_numbers.py b/hypothesis-python/tests/cover/test_complex_numbers.py index 4af329d98e..b878b50cd3 100644 --- a/hypothesis-python/tests/cover/test_complex_numbers.py +++ b/hypothesis-python/tests/cover/test_complex_numbers.py @@ -26,7 +26,7 @@ def test_minimal(): - assert minimal(complex_numbers(), lambda x: True) == 0 + assert minimal(complex_numbers()) == 0 def test_minimal_nonzero_real(): @@ -80,17 +80,15 @@ def test_min_magnitude_respected(data, mag): def test_minimal_min_magnitude_zero(): - assert minimal(complex_numbers(min_magnitude=0), lambda x: True) == 0 + assert minimal(complex_numbers(min_magnitude=0)) == 0 def test_minimal_min_magnitude_positive(): - assert minimal(complex_numbers(min_magnitude=0.5), lambda x: True) in (0.5, 1) + assert minimal(complex_numbers(min_magnitude=0.5)) in (0.5, 1) def test_minimal_minmax_magnitude(): - assert minimal( - complex_numbers(min_magnitude=0.5, max_magnitude=1.5), lambda x: True - ) in (0.5, 1) + assert minimal(complex_numbers(min_magnitude=0.5, max_magnitude=1.5)) in (0.5, 1) @given(st.data(), st.floats(0, 10e300, allow_infinity=False, allow_nan=False)) diff --git a/hypothesis-python/tests/cover/test_composite.py b/hypothesis-python/tests/cover/test_composite.py index 43e823aeab..472515126f 100644 --- a/hypothesis-python/tests/cover/test_composite.py +++ b/hypothesis-python/tests/cover/test_composite.py @@ -34,8 +34,8 @@ def test_simplify_draws(): def test_can_pass_through_arguments(): - assert minimal(badly_draw_lists(5), lambda x: True) == [0] * 5 - assert minimal(badly_draw_lists(m=6), lambda x: True) == [0] * 6 + assert minimal(badly_draw_lists(5)) == [0] * 5 + assert minimal(badly_draw_lists(m=6)) == [0] * 6 @st.composite @@ -95,7 +95,7 @@ def test_can_use_pure_args(): def stuff(*args): return args[0](st.sampled_from(args[1:])) - assert minimal(stuff(1, 2, 3, 4, 5), lambda x: True) == 1 + assert minimal(stuff(1, 2, 3, 4, 5)) == 1 def test_composite_of_lists(): diff --git a/hypothesis-python/tests/cover/test_direct_strategies.py b/hypothesis-python/tests/cover/test_direct_strategies.py index be90ada4c2..05ef29078d 100644 --- a/hypothesis-python/tests/cover/test_direct_strategies.py +++ b/hypothesis-python/tests/cover/test_direct_strategies.py @@ -479,7 +479,7 @@ def test_iterables_are_exhaustible(it): def test_minimal_iterable(): - assert list(minimal(ds.iterables(ds.integers()), lambda x: True)) == [] + assert list(minimal(ds.iterables(ds.integers()))) == [] @pytest.mark.parametrize("parameter_name", ["min_value", "max_value"]) diff --git a/hypothesis-python/tests/cover/test_simple_characters.py b/hypothesis-python/tests/cover/test_simple_characters.py index 1410006a75..f6ee079b54 100644 --- a/hypothesis-python/tests/cover/test_simple_characters.py +++ b/hypothesis-python/tests/cover/test_simple_characters.py @@ -85,7 +85,7 @@ def test_exclude_characters_of_major_categories(): def test_find_one(): - char = minimal(characters(min_codepoint=48, max_codepoint=48), lambda _: True) + char = minimal(characters(min_codepoint=48, max_codepoint=48)) assert char == "0" @@ -138,7 +138,7 @@ def test_blacklisted_characters(): min_codepoint=ord("0"), max_codepoint=ord("9"), exclude_characters=bad_chars ) - assert "1" == minimal(st, lambda c: True) + assert "1" == minimal(st) assert_no_examples(st, lambda c: c in bad_chars) diff --git a/hypothesis-python/tests/cover/test_simple_collections.py b/hypothesis-python/tests/cover/test_simple_collections.py index b811f50c8c..f672a72eca 100644 --- a/hypothesis-python/tests/cover/test_simple_collections.py +++ b/hypothesis-python/tests/cover/test_simple_collections.py @@ -50,7 +50,7 @@ ], ) def test_find_empty_collection_gives_empty(col, strat): - assert minimal(strat, lambda x: True) == col + assert minimal(strat) == col @pytest.mark.parametrize( @@ -64,7 +64,7 @@ def test_find_non_empty_collection_gives_single_zero(coltype, strat): ("coltype", "strat"), [(list, lists), (set, sets), (frozenset, frozensets)] ) def test_minimizes_to_empty(coltype, strat): - assert minimal(strat(integers()), lambda x: True) == coltype() + assert minimal(strat(integers())) == coltype() def test_minimizes_list_of_lists(): @@ -96,12 +96,12 @@ def test_fixed_dictionaries_with_optional_and_empty_keys(d): @pytest.mark.parametrize("n", range(10)) def test_lists_of_fixed_length(n): - assert minimal(lists(integers(), min_size=n, max_size=n), lambda x: True) == [0] * n + assert minimal(lists(integers(), min_size=n, max_size=n)) == [0] * n @pytest.mark.parametrize("n", range(10)) def test_sets_of_fixed_length(n): - x = minimal(sets(integers(), min_size=n, max_size=n), lambda x: True) + x = minimal(sets(integers(), min_size=n, max_size=n)) assert len(x) == n if not n: @@ -113,9 +113,7 @@ def test_sets_of_fixed_length(n): @pytest.mark.parametrize("n", range(10)) def test_dictionaries_of_fixed_length(n): x = set( - minimal( - dictionaries(integers(), booleans(), min_size=n, max_size=n), lambda x: True - ).keys() + minimal(dictionaries(integers(), booleans(), min_size=n, max_size=n)).keys() ) if not n: @@ -168,9 +166,10 @@ def test_small_sized_sets(x): def test_minimize_dicts_with_incompatible_keys(): - assert minimal( - fixed_dictionaries({1: booleans(), "hi": lists(booleans())}), lambda x: True - ) == {1: False, "hi": []} + assert minimal(fixed_dictionaries({1: booleans(), "hi": lists(booleans())})) == { + 1: False, + "hi": [], + } @given( diff --git a/hypothesis-python/tests/nocover/test_find.py b/hypothesis-python/tests/nocover/test_find.py index 2fd6c9c3fb..2f566d44f8 100644 --- a/hypothesis-python/tests/nocover/test_find.py +++ b/hypothesis-python/tests/nocover/test_find.py @@ -20,7 +20,7 @@ def test_can_find_an_int(): - assert minimal(integers(), lambda x: True) == 0 + assert minimal(integers()) == 0 assert minimal(integers(), lambda x: x >= 13) == 13 diff --git a/hypothesis-python/tests/nocover/test_simple_numbers.py b/hypothesis-python/tests/nocover/test_simple_numbers.py index ee7d56f165..5bcc394975 100644 --- a/hypothesis-python/tests/nocover/test_simple_numbers.py +++ b/hypothesis-python/tests/nocover/test_simple_numbers.py @@ -58,7 +58,7 @@ def is_good(x): assert minimal(integers(min_value=boundary - 10), is_good) == boundary - assert minimal(integers(min_value=boundary), lambda x: True) == boundary + assert minimal(integers(min_value=boundary)) == boundary def test_minimizes_negative_integer_range_upwards(): @@ -67,11 +67,11 @@ def test_minimizes_negative_integer_range_upwards(): @boundaries def test_minimizes_integer_range_to_boundary(boundary): - assert minimal(integers(boundary, boundary + 100), lambda x: True) == boundary + assert minimal(integers(boundary, boundary + 100)) == boundary def test_single_integer_range_is_range(): - assert minimal(integers(1, 1), lambda x: True) == 1 + assert minimal(integers(1, 1)) == 1 def test_minimal_small_number_in_large_range(): @@ -97,11 +97,11 @@ def test_minimal_non_boundary_float(): def test_minimal_float_is_zero(): - assert minimal(floats(), lambda x: True) == 0.0 + assert minimal(floats()) == 0.0 def test_minimal_asymetric_bounded_float(): - assert minimal(floats(min_value=1.1, max_value=1.6), lambda x: True) == 1.5 + assert minimal(floats(min_value=1.1, max_value=1.6)) == 1.5 def test_negative_floats_simplify_to_zero(): @@ -176,8 +176,8 @@ def test_in_range(r): def test_bounds_are_respected(): - assert minimal(floats(min_value=1.0), lambda x: True) == 1.0 - assert minimal(floats(max_value=-1.0), lambda x: True) == -1.0 + assert minimal(floats(min_value=1.0)) == 1.0 + assert minimal(floats(max_value=-1.0)) == -1.0 @pytest.mark.parametrize("k", range(10)) From 3cb1284635842956f39978ca39debfbd69d1ba75 Mon Sep 17 00:00:00 2001 From: Liam DeVoe Date: Fri, 26 Apr 2024 17:16:18 -0400 Subject: [PATCH 5/8] add pretty printers for DataTree --- .../internal/conjecture/datatree.py | 55 +++++++++++++++++++ .../tests/conjecture/test_data_tree.py | 42 ++++++++++++++ 2 files changed, 97 insertions(+) diff --git a/hypothesis-python/src/hypothesis/internal/conjecture/datatree.py b/hypothesis-python/src/hypothesis/internal/conjecture/datatree.py index c22cd0b294..ac79b42e6e 100644 --- a/hypothesis-python/src/hypothesis/internal/conjecture/datatree.py +++ b/hypothesis-python/src/hypothesis/internal/conjecture/datatree.py @@ -63,6 +63,15 @@ class Killed: next_node = attr.ib() + def _repr_pretty_(self, p, cycle): + assert cycle is False + p.text("Killed") + + +def _node_pretty(ir_type, value, kwargs, *, forced): + forced_marker = " [forced]" if forced else "" + return f"{ir_type} {value}{forced_marker} {kwargs}" + @attr.s(slots=True) class Branch: @@ -79,6 +88,16 @@ def max_children(self): assert max_children > 0 return max_children + def _repr_pretty_(self, p, cycle): + assert cycle is False + for i, (value, child) in enumerate(self.children.items()): + if i > 0: + p.break_() + p.text(_node_pretty(self.ir_type, value, self.kwargs, forced=False)) + with p.indent(2): + p.break_() + p.pretty(child) + @attr.s(slots=True, frozen=True) class Conclusion: @@ -87,6 +106,15 @@ class Conclusion: status: Status = attr.ib() interesting_origin: Optional[InterestingOrigin] = attr.ib() + def _repr_pretty_(self, p, cycle): + assert cycle is False + o = self.interesting_origin + # avoid str(o), which can include multiple lines of context + origin = ( + "" if o is None else f", {o.exc_type.__name__} at {o.filename}:{o.lineno}" + ) + p.text(f"Conclusion ({self.status!r}{origin})") + # The number of max children where, beyond this, it is practically impossible # for hypothesis to saturate / explore all children nodes in a reasonable time @@ -493,6 +521,29 @@ def check_exhausted(self): ) return self.is_exhausted + def _repr_pretty_(self, p, cycle): + assert cycle is False + indent = 0 + for i, (ir_type, kwargs, value) in enumerate( + zip(self.ir_types, self.kwargs, self.values) + ): + with p.indent(indent): + if i > 0: + p.break_() + p.text(_node_pretty(ir_type, value, kwargs, forced=i in self.forced)) + indent += 2 + + if isinstance(self.transition, Branch): + if len(self.values) > 0: + p.break_() + p.pretty(self.transition) + + if isinstance(self.transition, (Killed, Conclusion)): + with p.indent(indent): + if len(self.values) > 0: + p.break_() + p.pretty(self.transition) + class DataTree: """ @@ -889,6 +940,10 @@ def _reject_child(self, ir_type, kwargs, *, child, key): if child in children: children.remove(child) + def _repr_pretty_(self, p, cycle): + assert cycle is False + return p.pretty(self.root) + class TreeRecordingObserver(DataObserver): def __init__(self, tree): diff --git a/hypothesis-python/tests/conjecture/test_data_tree.py b/hypothesis-python/tests/conjecture/test_data_tree.py index d0b62e4752..212ee19c70 100644 --- a/hypothesis-python/tests/conjecture/test_data_tree.py +++ b/hypothesis-python/tests/conjecture/test_data_tree.py @@ -8,6 +8,7 @@ # v. 2.0. If a copy of the MPL was not distributed with this file, You can # obtain one at https://mozilla.org/MPL/2.0/. +import textwrap from random import Random import pytest @@ -22,7 +23,9 @@ ) from hypothesis.internal.conjecture.engine import ConjectureRunner from hypothesis.internal.conjecture.floats import float_to_int +from hypothesis.internal.escalation import InterestingOrigin from hypothesis.internal.floats import next_up +from hypothesis.vendor import pretty from tests.conjecture.common import ( draw_boolean_kwargs, @@ -567,3 +570,42 @@ def buf(data): prefix = tree.generate_novel_prefix(Random()) data = ConjectureData.for_buffer(prefix) assert data.draw_float(min_value, max_value, allow_nan=False) == expected_value + + +@given(draw_boolean_kwargs(), draw_integer_kwargs()) +def test_datatree_repr(bool_kwargs, int_kwargs): + tree = DataTree() + + try: + int("not an int") + except ValueError as e: + origin = InterestingOrigin.from_exception(e) + + observer = tree.new_observer() + observer.draw_boolean(True, was_forced=False, kwargs=bool_kwargs) + observer.conclude_test(Status.INVALID, interesting_origin=None) + + observer = tree.new_observer() + observer.draw_boolean(False, was_forced=False, kwargs=bool_kwargs) + observer.draw_integer(42, was_forced=False, kwargs=int_kwargs) + observer.conclude_test(Status.VALID, interesting_origin=None) + + observer = tree.new_observer() + observer.draw_boolean(False, was_forced=False, kwargs=bool_kwargs) + observer.draw_integer(0, was_forced=False, kwargs=int_kwargs) + observer.conclude_test(Status.INTERESTING, interesting_origin=origin) + + assert ( + pretty.pretty(tree) + == textwrap.dedent( + f""" + boolean True {bool_kwargs} + Conclusion (Status.INVALID) + boolean False {bool_kwargs} + integer 42 {int_kwargs} + Conclusion (Status.VALID) + integer 0 {int_kwargs} + Conclusion (Status.INTERESTING, {origin}) + """ + ).strip() + ) From a3c9b0a9300e1efd50144b0466f48b15f4c5dbac Mon Sep 17 00:00:00 2001 From: Liam DeVoe Date: Fri, 26 Apr 2024 19:52:03 -0400 Subject: [PATCH 6/8] formatting --- .../tests/cover/test_simple_collections.py | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/hypothesis-python/tests/cover/test_simple_collections.py b/hypothesis-python/tests/cover/test_simple_collections.py index f672a72eca..0d29d54153 100644 --- a/hypothesis-python/tests/cover/test_simple_collections.py +++ b/hypothesis-python/tests/cover/test_simple_collections.py @@ -112,9 +112,7 @@ def test_sets_of_fixed_length(n): @pytest.mark.parametrize("n", range(10)) def test_dictionaries_of_fixed_length(n): - x = set( - minimal(dictionaries(integers(), booleans(), min_size=n, max_size=n)).keys() - ) + x = set(minimal(dictionaries(integers(), booleans(), min_size=n, max_size=n))) if not n: assert x == set() @@ -166,10 +164,8 @@ def test_small_sized_sets(x): def test_minimize_dicts_with_incompatible_keys(): - assert minimal(fixed_dictionaries({1: booleans(), "hi": lists(booleans())})) == { - 1: False, - "hi": [], - } + strat = fixed_dictionaries({1: booleans(), "hi": lists(booleans())}) + assert minimal(strat) == {1: False, "hi": []} @given( From 4041696009696cb0f6c9f35c296883d2de0c0367 Mon Sep 17 00:00:00 2001 From: Liam DeVoe Date: Sat, 27 Apr 2024 16:48:20 -0400 Subject: [PATCH 7/8] add release notes --- hypothesis-python/src/RELEASE.rst | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 hypothesis-python/src/RELEASE.rst diff --git a/hypothesis-python/src/RELEASE.rst b/hypothesis-python/src/RELEASE.rst new file mode 100644 index 0000000000..373954606d --- /dev/null +++ b/hypothesis-python/src/RELEASE.rst @@ -0,0 +1,3 @@ +RELEASE_TYPE: patch + +This patch cleans up some internal code. From 7e36ca8415bec4dbd7d8977307fbfe54a371f535 Mon Sep 17 00:00:00 2001 From: Liam DeVoe Date: Sat, 27 Apr 2024 17:00:06 -0400 Subject: [PATCH 8/8] add forced draw in test --- hypothesis-python/tests/conjecture/test_data_tree.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/hypothesis-python/tests/conjecture/test_data_tree.py b/hypothesis-python/tests/conjecture/test_data_tree.py index 212ee19c70..34190e540a 100644 --- a/hypothesis-python/tests/conjecture/test_data_tree.py +++ b/hypothesis-python/tests/conjecture/test_data_tree.py @@ -593,6 +593,7 @@ def test_datatree_repr(bool_kwargs, int_kwargs): observer = tree.new_observer() observer.draw_boolean(False, was_forced=False, kwargs=bool_kwargs) observer.draw_integer(0, was_forced=False, kwargs=int_kwargs) + observer.draw_boolean(False, was_forced=True, kwargs=bool_kwargs) observer.conclude_test(Status.INTERESTING, interesting_origin=origin) assert ( @@ -605,7 +606,8 @@ def test_datatree_repr(bool_kwargs, int_kwargs): integer 42 {int_kwargs} Conclusion (Status.VALID) integer 0 {int_kwargs} - Conclusion (Status.INTERESTING, {origin}) + boolean False [forced] {bool_kwargs} + Conclusion (Status.INTERESTING, {origin}) """ ).strip() )