diff --git a/hypothesis-python/docs/changes.rst b/hypothesis-python/docs/changes.rst index 3d00e9318e..44bb5704da 100644 --- a/hypothesis-python/docs/changes.rst +++ b/hypothesis-python/docs/changes.rst @@ -18,6 +18,44 @@ Hypothesis 6.x .. include:: ../RELEASE.rst +.. _v6.118.0: + +-------------------- +6.118.0 - 2024-11-08 +-------------------- + +The :func:`~hypothesis.provisional.urls` strategy no longer generates +URLs where the port number is 0. + +This change is motivated by the idea that the generated URLs should, at least in +theory, be possible to fetch. The port number 0 is special; if a server binds to +port 0, the kernel will allocate an unused, and non-zero, port instead. That +means that it's not possible for a server to actually be listening on port 0. +This motivation is briefly described in the documentation for +:func:`~hypothesis.provisional.urls`. + +Fixes :issue:`4157`. + +Thanks to @gmacon for this contribution! + +.. _v6.117.0: + +-------------------- +6.117.0 - 2024-11-07 +-------------------- + +This changes the behaviour of settings profiles so that if you reregister the currently loaded profile it will automatically reload it. Previously you would have had to load it again. + +In particular this means that if you register a "ci" profile, it will automatically be used when Hypothesis detects you are running on CI. + +.. _v6.116.0: + +-------------------- +6.116.0 - 2024-11-01 +-------------------- + +Hypothesis now detects if it is running on a CI server and provides better default settings for running on CI in this case. + .. _v6.115.6: -------------------- diff --git a/hypothesis-python/docs/settings.rst b/hypothesis-python/docs/settings.rst index 5b2db744fb..be210d4487 100644 --- a/hypothesis-python/docs/settings.rst +++ b/hypothesis-python/docs/settings.rst @@ -250,6 +250,28 @@ by your conftest you can load one with the command line option ``--hypothesis-pr $ pytest tests --hypothesis-profile +Hypothesis comes with two built-in profiles, ``ci`` and ``default``. +``ci`` is set up to have good defaults for running in a CI environment, so emphasizes determinism, while the +``default`` settings are picked to be more likely to find bugs and to have a good workflow when used for local development. + +Hypothesis will automatically detect certain common CI environments and use the ci profile automatically +when running in them. +In particular, if you wish to use the ``ci`` profile, setting the ``CI`` environment variable will do this. + +This will still be the case if you register your own ci profile. For example, if you wanted to run more examples in CI, you might configure it as follows: + +.. code-block:: python + + settings.register_profile( + "ci", + settings( + settings.get_profile("ci"), + max_examples=1000, + ), + ) + +This will configure your CI to run 1000 examples per test rather than the default of 100, and this change will automatically be picked up when running on a CI server. + .. _healthchecks: ------------- diff --git a/hypothesis-python/src/hypothesis/_settings.py b/hypothesis-python/src/hypothesis/_settings.py index 2f18ff920f..f4c77ca420 100644 --- a/hypothesis-python/src/hypothesis/_settings.py +++ b/hypothesis-python/src/hypothesis/_settings.py @@ -63,6 +63,7 @@ def __get__(self, obj, type=None): from hypothesis.database import ExampleDatabase result = ExampleDatabase(not_set) + assert result is not not_set return result except KeyError: raise AttributeError(self.name) from None @@ -96,7 +97,7 @@ def default(cls): v = default_variable.value if v is not None: return v - if hasattr(settings, "_current_profile"): + if getattr(settings, "_current_profile", None) is not None: settings.load_profile(settings._current_profile) assert default_variable.value is not None return default_variable.value @@ -131,6 +132,7 @@ class settings(metaclass=settingsMeta): __definitions_are_locked = False _profiles: ClassVar[dict[str, "settings"]] = {} __module__ = "hypothesis" + _current_profile = None def __getattr__(self, name): if name in all_settings: @@ -315,9 +317,15 @@ def register_profile( :class:`~hypothesis.settings`: optional ``parent`` settings, and keyword arguments for each setting that will be set differently to parent (or settings.default, if parent is None). + + If you register a profile that has already been defined and that profile + is the currently loaded profile, the new changes will take effect immediately, + and do not require reloading the profile. """ check_type(str, name, "name") settings._profiles[name] = settings(parent=parent, **kwargs) + if settings._current_profile == name: + settings.load_profile(name) @staticmethod def get_profile(name: str) -> "settings": @@ -407,6 +415,8 @@ def _max_examples_validator(x): :ref:`separate settings profiles ` - for example running quick deterministic tests on every commit, and a longer non-deterministic nightly testing run. + +By default when running on CI, this will be set to True. """, ) @@ -682,6 +692,8 @@ def _validate_deadline(x): variability in test run time). Set this to ``None`` to disable this behaviour entirely. + +By default when running on CI, this will be set to None. """, ) @@ -694,13 +706,11 @@ def is_in_ci() -> bool: settings._define_setting( "print_blob", - default=is_in_ci(), - show_default=False, + default=False, options=(True, False), description=""" If set to ``True``, Hypothesis will print code for failing examples that can be used with :func:`@reproduce_failure ` to reproduce the failing example. -The default is ``True`` if the ``CI`` or ``TF_BUILD`` env vars are set, ``False`` otherwise. """, ) @@ -750,6 +760,23 @@ def note_deprecation( settings.register_profile("default", settings()) settings.load_profile("default") + +assert settings.default is not None + +CI = settings( + derandomize=True, + deadline=None, + database=None, + print_blob=True, + suppress_health_check=[HealthCheck.too_slow], +) + +settings.register_profile("ci", CI) + + +if is_in_ci(): + settings.load_profile("ci") + assert settings.default is not None diff --git a/hypothesis-python/src/hypothesis/core.py b/hypothesis-python/src/hypothesis/core.py index 9a1a19c1e2..188fb6d1a4 100644 --- a/hypothesis-python/src/hypothesis/core.py +++ b/hypothesis-python/src/hypothesis/core.py @@ -1564,23 +1564,23 @@ def wrapped_test(*arguments, **kwargs): "to ensure that each example is run in a separate " "database transaction." ) - if settings.database is not None: - nonlocal prev_self - # Check selfy really is self (not e.g. a mock) before we health-check - cur_self = ( - stuff.selfy - if getattr(type(stuff.selfy), test.__name__, None) is wrapped_test - else None + + nonlocal prev_self + # Check selfy really is self (not e.g. a mock) before we health-check + cur_self = ( + stuff.selfy + if getattr(type(stuff.selfy), test.__name__, None) is wrapped_test + else None + ) + if prev_self is Unset: + prev_self = cur_self + elif cur_self is not prev_self: + msg = ( + f"The method {test.__qualname__} was called from multiple " + "different executors. This may lead to flaky tests and " + "nonreproducible errors when replaying from database." ) - if prev_self is Unset: - prev_self = cur_self - elif cur_self is not prev_self: - msg = ( - f"The method {test.__qualname__} was called from multiple " - "different executors. This may lead to flaky tests and " - "nonreproducible errors when replaying from database." - ) - fail_health_check(settings, msg, HealthCheck.differing_executors) + fail_health_check(settings, msg, HealthCheck.differing_executors) state = StateForActualGivenExecution( stuff, test, settings, random, wrapped_test @@ -1675,7 +1675,6 @@ def wrapped_test(*arguments, **kwargs): # The exception caught here should either be an actual test # failure (or BaseExceptionGroup), or some kind of fatal error # that caused the engine to stop. - generated_seed = wrapped_test._hypothesis_internal_use_generated_seed with local_settings(settings): if not (state.failed_normally or generated_seed is None): diff --git a/hypothesis-python/src/hypothesis/provisional.py b/hypothesis-python/src/hypothesis/provisional.py index 06e829f3c4..7f43762a30 100644 --- a/hypothesis-python/src/hypothesis/provisional.py +++ b/hypothesis-python/src/hypothesis/provisional.py @@ -166,13 +166,18 @@ def domains( @defines_strategy(force_reusable_values=True) def urls() -> st.SearchStrategy[str]: - """A strategy for :rfc:`3986`, generating http/https URLs.""" + """A strategy for :rfc:`3986`, generating http/https URLs. + + The generated URLs could, at least in theory, be passed to an HTTP client + and fetched. + + """ def url_encode(s: str) -> str: return "".join(c if c in URL_SAFE_CHARACTERS else "%%%02X" % ord(c) for c in s) schemes = st.sampled_from(["http", "https"]) - ports = st.integers(min_value=0, max_value=2**16 - 1).map(":{}".format) + ports = st.integers(min_value=1, max_value=2**16 - 1).map(":{}".format) paths = st.lists(st.text(string.printable).map(url_encode)).map("/".join) return st.builds( diff --git a/hypothesis-python/src/hypothesis/version.py b/hypothesis-python/src/hypothesis/version.py index 1ae0682d39..4b867d7100 100644 --- a/hypothesis-python/src/hypothesis/version.py +++ b/hypothesis-python/src/hypothesis/version.py @@ -8,5 +8,5 @@ # 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/. -__version_info__ = (6, 115, 6) +__version_info__ = (6, 118, 0) __version__ = ".".join(map(str, __version_info__)) diff --git a/hypothesis-python/tests/common/costbounds.py b/hypothesis-python/tests/common/costbounds.py index fc82a4a142..2acb68c4dc 100644 --- a/hypothesis-python/tests/common/costbounds.py +++ b/hypothesis-python/tests/common/costbounds.py @@ -19,12 +19,13 @@ def find_integer_cost(n): except KeyError: pass - cost = [0] + cost = 0 def test(i): - cost[0] += 1 + nonlocal cost + cost += 1 return i <= n find_integer(test) - return FIND_INTEGER_COSTS.setdefault(n, cost[0]) + return FIND_INTEGER_COSTS.setdefault(n, cost) diff --git a/hypothesis-python/tests/common/setup.py b/hypothesis-python/tests/common/setup.py index 8695c334cd..8881985137 100644 --- a/hypothesis-python/tests/common/setup.py +++ b/hypothesis-python/tests/common/setup.py @@ -12,7 +12,7 @@ from warnings import filterwarnings from hypothesis import HealthCheck, Phase, Verbosity, settings -from hypothesis._settings import not_set +from hypothesis._settings import CI, is_in_ci, not_set from hypothesis.internal.conjecture.data import AVAILABLE_PROVIDERS from hypothesis.internal.coverage import IN_COVERAGE_TESTS @@ -45,13 +45,14 @@ def run(): v = getattr(x, s.name) # Check if it has a dynamically defined default and if so skip comparison. if getattr(settings, s.name).show_default: - assert ( - v == s.default + assert v == s.default or ( + is_in_ci() and v == getattr(CI, s.name) ), f"({v!r} == x.{s.name}) != (s.{s.name} == {s.default!r})" settings.register_profile( "default", settings( + settings.get_profile("default"), max_examples=20 if IN_COVERAGE_TESTS else not_set, phases=list(Phase), # Dogfooding the explain phase ), diff --git a/hypothesis-python/tests/conftest.py b/hypothesis-python/tests/conftest.py index 9b2c7ee5c3..360ba48349 100644 --- a/hypothesis-python/tests/conftest.py +++ b/hypothesis-python/tests/conftest.py @@ -85,20 +85,23 @@ def _consistently_increment_time(monkeypatch): Replacing time with a fake version under our control avoids this problem. """ - frozen = [False] + frozen = False - current_time = [time_module.time()] + current_time = time_module.time() def time(): - if not frozen[0]: - current_time[0] += TIME_INCREMENT - return current_time[0] + nonlocal current_time + if not frozen: + current_time += TIME_INCREMENT + return current_time def sleep(naptime): - current_time[0] += naptime + nonlocal current_time + current_time += naptime def freeze(): - frozen[0] = True + nonlocal frozen + frozen = True def _patch(name, fn): monkeypatch.setattr(time_module, name, wraps(getattr(time_module, name))(fn)) @@ -119,14 +122,16 @@ def _patch(name, fn): # ensure timer callback is added, then bracket it by freeze/unfreeze below junkdrawer.gc_cumulative_time() - _was_frozen = [False] + _was_frozen = False def _freezer(*_): - _was_frozen[0] = frozen[0] - frozen[0] = True + nonlocal _was_frozen, frozen + _was_frozen = frozen + frozen = True def _unfreezer(*_): - frozen[0] = _was_frozen[0] + nonlocal _was_frozen, frozen + frozen = _was_frozen gc.callbacks.insert(0, _freezer) # freeze before gc callback gc.callbacks.append(_unfreezer) # unfreeze after diff --git a/hypothesis-python/tests/conjecture/test_engine.py b/hypothesis-python/tests/conjecture/test_engine.py index 2ab0ee3a23..b518894426 100644 --- a/hypothesis-python/tests/conjecture/test_engine.py +++ b/hypothesis-python/tests/conjecture/test_engine.py @@ -144,20 +144,21 @@ def generate_new_examples(self): def test_detects_flakiness(): - failed_once = [False] - count = [0] + failed_once = False + count = 0 def tf(data): + nonlocal count, failed_once data.draw_bytes(1, 1) - count[0] += 1 - if not failed_once[0]: - failed_once[0] = True + count += 1 + if not failed_once: + failed_once = True data.mark_interesting() runner = ConjectureRunner(tf) runner.run() assert runner.exit_reason == ExitReason.flaky - assert count == [MIN_TEST_CALLS + 1] + assert count == MIN_TEST_CALLS + 1 def recur(i, data): @@ -258,15 +259,17 @@ def f(data): @pytest.mark.parametrize("examples", [1, 5, 20, 50]) def test_stops_after_max_examples_when_generating_more_bugs(examples): seen = [] - bad = [False, False] + err_common = False + err_rare = False def f(data): seen.append(data.draw_integer(0, 2**32 - 1)) # Rare, potentially multi-error conditions + nonlocal err_common, err_rare if seen[-1] > 2**31: - bad[0] = True + err_rare = True raise ValueError - bad[1] = True + err_common = True raise Exception runner = ConjectureRunner( @@ -278,7 +281,7 @@ def f(data): pass # No matter what, whether examples is larger or smalller than MAX_TEST_CALLS, # we stop looking at max_examples. (and re-run each failure for the traceback) - assert len(seen) <= examples + sum(bad) + assert len(seen) <= examples + err_common + err_rare def test_interleaving_engines(): @@ -337,30 +340,32 @@ def test(data): def test_erratic_draws(): - n = [0] + n = 0 with pytest.raises(FlakyStrategyDefinition): @run_to_buffer def x(data): - data.draw_bytes(n[0], n[0]) - data.draw_bytes(255 - n[0], 255 - n[0]) - if n[0] == 255: + nonlocal n + data.draw_bytes(n, n) + data.draw_bytes(255 - n, 255 - n) + if n == 255: data.mark_interesting() else: - n[0] += 1 + n += 1 def test_no_read_no_shrink(): - count = [0] + count = 0 @run_to_buffer def x(data): - count[0] += 1 + nonlocal count + count += 1 data.mark_interesting() assert x == b"" - assert count == [1] + assert count == 1 def test_one_dead_branch(): @@ -597,21 +602,21 @@ def f(data): def test_detects_too_small_block_starts(): - call_count = [0] + call_count = 0 def f(data): - assert call_count[0] == 0 - call_count[0] += 1 + nonlocal call_count + call_count += 1 data.draw_bytes(8, 8) data.mark_interesting() runner = ConjectureRunner(f, settings=settings(database=None)) r = runner.cached_test_function(bytes(8)) assert r.status == Status.INTERESTING - assert call_count[0] == 1 + assert call_count == 1 r2 = runner.cached_test_function(bytes([255] * 7)) assert r2.status == Status.OVERRUN - assert call_count[0] == 1 + assert call_count == 1 def test_shrinks_both_interesting_examples(monkeypatch): @@ -656,7 +661,7 @@ def x(data): def test_can_remove_discarded_data(): @shrinking_from(bytes([0] * 10 + [11])) - def shrinker(data): + def shrinker(data: ConjectureData): while True: data.start_example(SOME_LABEL) b = data.draw_integer(0, 2**8 - 1) @@ -671,7 +676,7 @@ def shrinker(data): def test_discarding_iterates_to_fixed_point(): @shrinking_from(bytes(list(range(100, -1, -1)))) - def shrinker(data): + def shrinker(data: ConjectureData): data.start_example(0) data.draw_integer(0, 2**8 - 1) data.stop_example(discard=True) @@ -685,7 +690,7 @@ def shrinker(data): def test_discarding_is_not_fooled_by_empty_discards(): @shrinking_from(bytes([1, 1])) - def shrinker(data): + def shrinker(data: ConjectureData): data.draw_integer(0, 2**1 - 1) data.start_example(0) data.stop_example(discard=True) @@ -698,7 +703,7 @@ def shrinker(data): def test_discarding_can_fail(monkeypatch): @shrinking_from(bytes([1])) - def shrinker(data): + def shrinker(data: ConjectureData): data.start_example(0) data.draw_boolean() data.stop_example(discard=True) @@ -838,11 +843,12 @@ def f(data): def test_exit_because_shrink_phase_timeout(monkeypatch): - val = [0] + val = 0 def fast_time(): - val[0] += 1000 - return val[0] + nonlocal val + val += 1000 + return val def f(data): if data.draw_integer(0, 2**64 - 1) > 2**33: @@ -857,7 +863,7 @@ def f(data): def test_dependent_block_pairs_can_lower_to_zero(): @shrinking_from([1, 0, 1]) - def shrinker(data): + def shrinker(data: ConjectureData): if data.draw_boolean(): n = data.draw_integer(0, 2**16 - 1) else: @@ -872,7 +878,7 @@ def shrinker(data): def test_handle_size_too_large_during_dependent_lowering(): @shrinking_from([1, 255, 0]) - def shrinker(data): + def shrinker(data: ConjectureData): if data.draw_boolean(): data.draw_integer(0, 2**16 - 1) data.mark_interesting() @@ -886,7 +892,7 @@ def test_block_may_grow_during_lexical_shrinking(): initial = bytes([2, 1, 1]) @shrinking_from(initial) - def shrinker(data): + def shrinker(data: ConjectureData): n = data.draw_integer(0, 2**8 - 1) if n == 2: data.draw_integer(0, 2**8 - 1) @@ -901,7 +907,7 @@ def shrinker(data): def test_lower_common_node_offset_does_nothing_when_changed_blocks_are_zero(): @shrinking_from([1, 0, 1, 0]) - def shrinker(data): + def shrinker(data: ConjectureData): data.draw_boolean() data.draw_boolean() data.draw_boolean() @@ -916,7 +922,7 @@ def shrinker(data): def test_lower_common_node_offset_ignores_zeros(): @shrinking_from([2, 2, 0]) - def shrinker(data): + def shrinker(data: ConjectureData): n = data.draw_integer(0, 2**8 - 1) data.draw_integer(0, 2**8 - 1) data.draw_integer(0, 2**8 - 1) @@ -930,10 +936,11 @@ def shrinker(data): def test_cached_test_function_returns_right_value(): - count = [0] + count = 0 def tf(data): - count[0] += 1 + nonlocal count + count += 1 data.draw_integer(0, 3) data.mark_interesting() @@ -944,14 +951,15 @@ def tf(data): d = runner.cached_test_function(b) assert d.status == Status.INTERESTING assert d.buffer == b - assert count[0] == 2 + assert count == 2 def test_cached_test_function_does_not_reinvoke_on_prefix(): - call_count = [0] + call_count = 0 def test_function(data): - call_count[0] += 1 + nonlocal call_count + call_count += 1 data.draw_integer(0, 2**8 - 1) data.draw_bytes(1, 1, forced=bytes([7])) data.draw_integer(0, 2**8 - 1) @@ -964,16 +972,17 @@ def test_function(data): for n in [2, 1, 0]: prefix_data = runner.cached_test_function(bytes(n)) assert prefix_data is Overrun - assert call_count[0] == 1 + assert call_count == 1 def test_will_evict_entries_from_the_cache(monkeypatch): monkeypatch.setattr(engine_module, "CACHE_SIZE", 5) - count = [0] + count = 0 def tf(data): + nonlocal count data.draw_bytes(1, 1) - count[0] += 1 + count += 1 runner = ConjectureRunner(tf, settings=TEST_SETTINGS) @@ -984,7 +993,7 @@ def tf(data): # Because we exceeded the cache size, our previous # calls will have been evicted, so each call to # cached_test_function will have to reexecute. - assert count[0] == 30 + assert count == 30 def test_branch_ending_in_write(): @@ -1435,10 +1444,11 @@ def test(data): def test_does_cache_if_extend_is_not_used(): - calls = [0] + calls = 0 def test(data): - calls[0] += 1 + nonlocal calls + calls += 1 data.draw_bytes(1, 1) with deterministic_PRNG(): @@ -1448,14 +1458,15 @@ def test(data): d2 = runner.cached_test_function(b"\0", extend=8) assert d1.status == d2.status == Status.VALID assert d1.buffer == d2.buffer - assert calls[0] == 1 + assert calls == 1 def test_does_result_for_reuse(): - calls = [0] + calls = 0 def test(data): - calls[0] += 1 + nonlocal calls + calls += 1 data.draw_bytes(1, 1) with deterministic_PRNG(): @@ -1465,7 +1476,7 @@ def test(data): d2 = runner.cached_test_function(d1.buffer) assert d1.status == d2.status == Status.VALID assert d1.buffer == d2.buffer - assert calls[0] == 1 + assert calls == 1 def test_does_not_cache_overrun_if_extending(): diff --git a/hypothesis-python/tests/conjecture/test_junkdrawer.py b/hypothesis-python/tests/conjecture/test_junkdrawer.py index e122791355..5890133743 100644 --- a/hypothesis-python/tests/conjecture/test_junkdrawer.py +++ b/hypothesis-python/tests/conjecture/test_junkdrawer.py @@ -191,19 +191,20 @@ def test_self_organising_list_raises_not_found_when_none_satisfy(): def test_self_organising_list_moves_to_front(): - count = [0] + count = 0 def zero(n): - count[0] += 1 + nonlocal count + count += 1 return n == 0 x = SelfOrganisingList(range(20)) assert x.find(zero) == 0 - assert count[0] == 20 + assert count == 20 assert x.find(zero) == 0 - assert count[0] == 21 + assert count == 21 @given(st.binary(), st.binary()) diff --git a/hypothesis-python/tests/conjecture/test_lstar.py b/hypothesis-python/tests/conjecture/test_lstar.py index 473f900f06..63e40ccd55 100644 --- a/hypothesis-python/tests/conjecture/test_lstar.py +++ b/hypothesis-python/tests/conjecture/test_lstar.py @@ -124,10 +124,11 @@ def test_iteration_with_dead_nodes(): def test_learning_is_just_checking_when_fully_explored(): - count = [0] + count = 0 def accept(s): - count[0] += 1 + nonlocal count + count += 1 return len(s) <= 5 and all(c == 0 for c in s) learner = LStar(accept) @@ -138,20 +139,21 @@ def accept(s): assert list(learner.dfa.all_matching_strings()) == [bytes(n) for n in range(6)] - (prev,) = count + prev = count learner.learn([2] * 11) - calls = count[0] - prev + calls = count - prev assert calls == 1 def test_canonicalises_values_to_zero_where_appropriate(): - calls = [0] + calls = 0 def member(s): - calls[0] += 1 + nonlocal calls + calls += 1 return len(s) == 10 learner = LStar(member) @@ -159,11 +161,11 @@ def member(s): learner.learn(bytes(10)) learner.learn(bytes(11)) - (prev,) = calls + prev = calls assert learner.dfa.matches(bytes([1] * 10)) - assert calls[0] == prev + assert calls == prev def test_normalizing_defaults_to_zero(): diff --git a/hypothesis-python/tests/conjecture/test_shrinker.py b/hypothesis-python/tests/conjecture/test_shrinker.py index 7c35fcdcbb..8240d337c2 100644 --- a/hypothesis-python/tests/conjecture/test_shrinker.py +++ b/hypothesis-python/tests/conjecture/test_shrinker.py @@ -12,6 +12,7 @@ import pytest +from hypothesis.internal.conjecture.data import ConjectureData from hypothesis.internal.conjecture.engine import ConjectureRunner from hypothesis.internal.conjecture.shrinker import ( Shrinker, @@ -27,7 +28,7 @@ @pytest.mark.parametrize("n", [1, 5, 8, 15]) def test_can_shrink_variable_draws_with_just_deletion(n): @shrinking_from([n] + [0] * (n - 1) + [1]) - def shrinker(data): + def shrinker(data: ConjectureData): n = data.draw_integer(0, 2**4 - 1) b = [data.draw_integer(0, 2**8 - 1) for _ in range(n)] if any(b): @@ -71,7 +72,7 @@ def base_buf(data): data.mark_interesting() @shrinking_from(base_buf) - def shrinker(data): + def shrinker(data: ConjectureData): x = data.draw_integer(0, 2**24 - 1) y = data.draw_integer(0, 2**24 - 1) if x != y: @@ -88,7 +89,7 @@ def shrinker(data): def test_accidental_duplication(): @shrinking_from([18] * 20) - def shrinker(data): + def shrinker(data: ConjectureData): x = data.draw_integer(0, 2**8 - 1) y = data.draw_integer(0, 2**8 - 1) if x != y: @@ -106,7 +107,7 @@ def shrinker(data): def test_can_zero_subintervals(): @shrinking_from(bytes([3, 0, 0, 0, 1]) * 10) - def shrinker(data): + def shrinker(data: ConjectureData): for _ in range(10): data.start_example(SOME_LABEL) n = data.draw_integer(0, 2**8 - 1) @@ -137,7 +138,7 @@ def tree(data): good = {initial, target} @shrinking_from(initial) - def shrinker(data): + def shrinker(data: ConjectureData): tree(data) if bytes(data.buffer) in good: data.mark_interesting() @@ -160,7 +161,7 @@ def accept(f): def test_shrinking_blocks_from_common_offset(): @shrinking_from([11, 10]) - def shrinker(data): + def shrinker(data: ConjectureData): m = data.draw_integer(0, 2**8 - 1) n = data.draw_integer(0, 2**8 - 1) if abs(m - n) <= 1 and max(m, n) > 0: @@ -194,7 +195,7 @@ def x(data): def test_can_reorder_examples(): # grouped by iteration: (1, 0, 1) (1, 0, 1) (0) (0) (0) @shrinking_from([1, 0, 1, 1, 0, 1, 0, 0, 0]) - def shrinker(data): + def shrinker(data: ConjectureData): total = 0 for _ in range(5): data.start_example(label=0) @@ -230,7 +231,7 @@ def x(data): def test_block_deletion_can_delete_short_ranges(): @shrinking_from([v for i in range(5) for _ in range(i + 1) for v in [0, i]]) - def shrinker(data): + def shrinker(data: ConjectureData): while True: n = data.draw_integer(0, 2**16 - 1) for _ in range(n): @@ -261,7 +262,7 @@ def buf(data): data.mark_interesting() @shrinking_from(buf) - def shrinker(data): + def shrinker(data: ConjectureData): size = sizes[distribution.sample(data)] result = data.draw_integer(0, 2**size - 1) sign = (-1) ** (result & 1) @@ -294,7 +295,7 @@ def tree(data): # Starting from an unbalanced tree of depth six @shrinking_from([1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0]) - def shrinker(data): + def shrinker(data: ConjectureData): _, b = tree(data) if not b: data.mark_interesting() @@ -306,7 +307,7 @@ def shrinker(data): def test_node_programs_are_adaptive(): @shrinking_from(bytes(1000) + bytes([1])) - def shrinker(data): + def shrinker(data: ConjectureData): while not data.draw_boolean(): pass data.mark_interesting() @@ -320,7 +321,7 @@ def shrinker(data): def test_zero_examples_with_variable_min_size(): @shrinking_from(bytes([255]) * 100) - def shrinker(data): + def shrinker(data: ConjectureData): any_nonzero = False for i in range(1, 10): any_nonzero |= data.draw_integer(0, 2**i - 1) > 0 @@ -334,7 +335,7 @@ def shrinker(data): def test_zero_contained_examples(): @shrinking_from(bytes([1]) * 8) - def shrinker(data): + def shrinker(data: ConjectureData): for _ in range(4): data.start_example(1) if data.draw_integer(0, 2**8 - 1) == 0: @@ -351,7 +352,7 @@ def shrinker(data): def test_zig_zags_quickly(): @shrinking_from(bytes([255]) * 4) - def shrinker(data): + def shrinker(data: ConjectureData): m = data.draw_integer(0, 2**16 - 1) n = data.draw_integer(0, 2**16 - 1) if m == 0 or n == 0: @@ -398,7 +399,7 @@ def buf(data): data.mark_interesting() @shrinking_from(buf) - def shrinker(data): + def shrinker(data: ConjectureData): m = data.draw_integer(min_value, max_value, shrink_towards=shrink_towards) n = data.draw_integer(min_value, max_value, shrink_towards=shrink_towards) # avoid trivial counterexamples @@ -414,7 +415,7 @@ def shrinker(data): def test_zero_irregular_examples(): @shrinking_from([255] * 6) - def shrinker(data): + def shrinker(data: ConjectureData): data.start_example(1) data.draw_integer(0, 2**8 - 1) data.draw_integer(0, 2**16 - 1) @@ -433,7 +434,7 @@ def shrinker(data): def test_retain_end_of_buffer(): @shrinking_from([1, 2, 3, 4, 5, 6, 0]) - def shrinker(data): + def shrinker(data: ConjectureData): interesting = False while True: n = data.draw_integer(0, 2**8 - 1) @@ -450,7 +451,7 @@ def shrinker(data): def test_can_expand_zeroed_region(): @shrinking_from([255] * 5) - def shrinker(data): + def shrinker(data: ConjectureData): seen_non_zero = False for _ in range(5): if data.draw_integer(0, 2**8 - 1) == 0: @@ -466,7 +467,7 @@ def shrinker(data): def test_can_expand_deleted_region(): @shrinking_from([1, 2, 3, 4, 0, 0]) - def shrinker(data): + def shrinker(data: ConjectureData): def t(): data.start_example(1) @@ -494,7 +495,7 @@ def t(): def test_shrink_pass_method_is_idempotent(): @shrinking_from([255]) - def shrinker(data): + def shrinker(data: ConjectureData): data.draw_integer(0, 2**8 - 1) data.mark_interesting() @@ -510,7 +511,7 @@ def test_will_terminate_stalled_shrinks(): time.freeze() @shrinking_from([255] * 100) - def shrinker(data): + def shrinker(data: ConjectureData): count = 0 for _ in range(100): @@ -526,7 +527,7 @@ def shrinker(data): def test_will_let_fixate_shrink_passes_do_a_full_run_through(): @shrinking_from(range(50)) - def shrinker(data): + def shrinker(data: ConjectureData): for i in range(50): if data.draw_integer(0, 2**8 - 1) != i: data.mark_invalid() @@ -545,7 +546,7 @@ def shrinker(data): @pytest.mark.parametrize("n_gap", [0, 1, 2, 3]) def test_can_simultaneously_lower_non_duplicated_nearby_blocks(n_gap): @shrinking_from([1, 1] + [0] * n_gap + [0, 2]) - def shrinker(data): + def shrinker(data: ConjectureData): # Block off lowering the whole buffer if data.draw_integer(0, 2**1 - 1) == 0: data.mark_invalid() @@ -570,7 +571,7 @@ def buf(data): data.mark_interesting() @shrinking_from(buf) - def shrinker(data): + def shrinker(data: ConjectureData): n1 = data.draw_integer(0, 100) n2 = data.draw_integer(0, 100, forced=10) if n1 + n2 > 20: diff --git a/hypothesis-python/tests/cover/test_cache_implementation.py b/hypothesis-python/tests/cover/test_cache_implementation.py index c5282ae84b..70c298a2ed 100644 --- a/hypothesis-python/tests/cover/test_cache_implementation.py +++ b/hypothesis-python/tests/cover/test_cache_implementation.py @@ -137,11 +137,12 @@ def new_score(key): scores[key] = data.draw(st.integers(0, 1000), label=f"scores[{key!r}]") return scores[key] - last_entry = [None] + last_entry = None class Cache(GenericCache): def new_entry(self, key, value): - last_entry[0] = key + nonlocal last_entry + last_entry = key evicted.discard(key) assert key not in scores return new_score(key) @@ -155,7 +156,7 @@ def on_evict(self, key, value, score): assert score == scores[key] del scores[key] if len(scores) > 1: - assert score <= min(v for k, v in scores.items() if k != last_entry[0]) + assert score <= min(v for k, v in scores.items() if k != last_entry) evicted.add(key) target = Cache(max_size=size) diff --git a/hypothesis-python/tests/cover/test_core.py b/hypothesis-python/tests/cover/test_core.py index ecc9cd8170..3dfaa8cafd 100644 --- a/hypothesis-python/tests/cover/test_core.py +++ b/hypothesis-python/tests/cover/test_core.py @@ -34,10 +34,11 @@ def track(x): def test_stops_after_ten_times_max_examples_if_not_satisfying(): - count = [0] + count = 0 def track(x): - count[0] += 1 + nonlocal count + count += 1 reject() max_examples = 100 @@ -48,7 +49,7 @@ def track(x): # Very occasionally we can generate overflows in generation, which also # count towards our example budget, which means that we don't hit the # maximum. - assert count[0] <= 10 * max_examples + assert count <= 10 * max_examples some_normal_settings = settings() @@ -65,18 +66,19 @@ def test_settings_are_default_in_given(x): def test_given_shrinks_pytest_helper_errors(): - final_value = [None] + final_value = None @settings(derandomize=True, max_examples=100) @given(s.integers()) def inner(x): - final_value[0] = x + nonlocal final_value + final_value = x if x > 100: pytest.fail(f"{x=} is too big!") with pytest.raises(Failed): inner() - assert final_value[0] == 101 + assert final_value == 101 def test_pytest_skip_skips_shrinking(): diff --git a/hypothesis-python/tests/cover/test_deadline.py b/hypothesis-python/tests/cover/test_deadline.py index e40e493c66..3bb4583ba4 100644 --- a/hypothesis-python/tests/cover/test_deadline.py +++ b/hypothesis-python/tests/cover/test_deadline.py @@ -51,13 +51,14 @@ def test_slow_with_none_deadline(i): def test_raises_flaky_if_a_test_becomes_fast_on_rerun(): - once = [True] + once = True @settings(deadline=500, backend="hypothesis") @given(st.integers()) def test_flaky_slow(i): - if once[0]: - once[0] = False + nonlocal once + if once: + once = False time.sleep(1) with pytest.raises(FlakyFailure): @@ -80,17 +81,18 @@ def slow_if_large(i): def test_keeps_you_well_above_the_deadline(): seen = set() - failed_once = [False] + failed_once = False @settings(deadline=100, backend="hypothesis") @given(st.integers(0, 2000)) def slow(i): + nonlocal failed_once # Make sure our initial failure isn't something that immediately goes flaky. - if not failed_once[0]: + if not failed_once: if i * 0.9 <= 100: return else: - failed_once[0] = True + failed_once = True t = i / 1000 if i in seen: @@ -104,13 +106,14 @@ def slow(i): def test_gives_a_deadline_specific_flaky_error_message(): - once = [True] + once = True @settings(deadline=100, backend="hypothesis") @given(st.integers()) def slow_once(i): - if once[0]: - once[0] = False + nonlocal once + if once: + once = False time.sleep(0.2) with pytest.raises(FlakyFailure) as err: diff --git a/hypothesis-python/tests/cover/test_flakiness.py b/hypothesis-python/tests/cover/test_flakiness.py index 5a360ba025..06e1803c75 100644 --- a/hypothesis-python/tests/cover/test_flakiness.py +++ b/hypothesis-python/tests/cover/test_flakiness.py @@ -25,12 +25,13 @@ class Nope(Exception): def test_fails_only_once_is_flaky(): - first_call = [True] + first_call = True @given(integers()) def rude(x): - if first_call[0]: - first_call[0] = False + nonlocal first_call + if first_call: + first_call = False raise Nope with pytest.raises(FlakyFailure, match="Falsified on the first call but") as e: diff --git a/hypothesis-python/tests/cover/test_functions.py b/hypothesis-python/tests/cover/test_functions.py index 2ce9ae700e..3f02212869 100644 --- a/hypothesis-python/tests/cover/test_functions.py +++ b/hypothesis-python/tests/cover/test_functions.py @@ -98,16 +98,17 @@ def test_functions_strategy_return_type_inference(f): def test_functions_valid_within_given_invalid_outside(): - cache = [None] + cache = None @given(functions()) def t(f): + nonlocal cache + cache = f assert f() is None - cache[0] = f t() with pytest.raises(InvalidState): - cache[0]() + cache() def test_can_call_default_like_arg(): diff --git a/hypothesis-python/tests/cover/test_random_module.py b/hypothesis-python/tests/cover/test_random_module.py index f83e4b0453..d4f02296c6 100644 --- a/hypothesis-python/tests/cover/test_random_module.py +++ b/hypothesis-python/tests/cover/test_random_module.py @@ -102,15 +102,16 @@ def test_registered_Random_is_seeded_by_random_module_strategy(): register_random(r) state = r.getstate() results = set() - count = [0] + count = 0 @given(st.integers()) def inner(x): + nonlocal count results.add(r.random()) - count[0] += 1 + count += 1 inner() - assert count[0] > len(results) * 0.9, "too few unique random numbers" + assert count > len(results) * 0.9, "too few unique random numbers" assert state == r.getstate() diff --git a/hypothesis-python/tests/cover/test_reproduce_failure.py b/hypothesis-python/tests/cover/test_reproduce_failure.py index db3942f02d..3f382a494e 100644 --- a/hypothesis-python/tests/cover/test_reproduce_failure.py +++ b/hypothesis-python/tests/cover/test_reproduce_failure.py @@ -119,14 +119,15 @@ def test(x): def test_prints_reproduction_if_requested(): - failing_example = [None] + failing_example = None @settings(print_blob=True, database=None, max_examples=100) @given(st.integers()) def test(i): - if failing_example[0] is None and i != 0: - failing_example[0] = i - assert i not in failing_example + nonlocal failing_example + if failing_example is None and i != 0: + failing_example = i + assert i != failing_example with pytest.raises(AssertionError) as err: test() diff --git a/hypothesis-python/tests/cover/test_settings.py b/hypothesis-python/tests/cover/test_settings.py index d85ced33cf..44eace34bf 100644 --- a/hypothesis-python/tests/cover/test_settings.py +++ b/hypothesis-python/tests/cover/test_settings.py @@ -9,6 +9,7 @@ # obtain one at https://mozilla.org/MPL/2.0/. import datetime +import os import subprocess import sys from unittest import TestCase @@ -25,7 +26,7 @@ note_deprecation, settings, ) -from hypothesis.database import ExampleDatabase +from hypothesis.database import ExampleDatabase, InMemoryExampleDatabase from hypothesis.errors import ( HypothesisDeprecationWarning, InvalidArgument, @@ -108,12 +109,13 @@ def test_can_not_set_verbosity_to_non_verbosity(): @pytest.mark.parametrize("db", [None, ExampleDatabase()]) def test_inherits_an_empty_database(db): - assert settings.default.database is not None - s = settings(database=db) - assert s.database is db - with local_settings(s): - t = settings() - assert t.database is db + with local_settings(settings(database=InMemoryExampleDatabase())): + assert settings.default.database is not None + s = settings(database=db) + assert s.database is db + with local_settings(s): + t = settings() + assert t.database is db @pytest.mark.parametrize("db", [None, ExampleDatabase()]) @@ -273,6 +275,7 @@ def test_settings_as_decorator_must_be_on_callable(): from hypothesis.configuration import set_hypothesis_home_dir from hypothesis.database import DirectoryBasedExampleDatabase +settings.load_profile("default") settings.default.database if __name__ == '__main__': @@ -476,8 +479,12 @@ def __repr__(self): assert "parent=(not settings repr)" in str(excinfo.value) +def test_default_settings_do_not_use_ci(): + assert settings.get_profile("default").suppress_health_check == () + + def test_show_changed(): - s = settings(max_examples=999, database=None) + s = settings(settings.get_profile("default"), max_examples=999, database=None) assert s.show_changed() == "database=None, max_examples=999" @@ -511,3 +518,80 @@ def test_deprecated_settings_not_in_settings_all_list(): assert al == ls assert HealthCheck.return_value not in ls assert HealthCheck.not_a_test_method not in ls + + +@skipif_emscripten +def test_check_defaults_to_derandomize_when_running_on_ci(): + env = dict(os.environ) + env["CI"] = "true" + + assert ( + subprocess.check_output( + [ + sys.executable, + "-c", + "from hypothesis import settings\nprint(settings().derandomize)", + ], + env=env, + text=True, + encoding="utf-8", + ).strip() + == "True" + ) + + +@skipif_emscripten +def test_check_defaults_to_randomize_when_not_running_on_ci(): + env = dict(os.environ) + env.pop("CI", None) + env.pop("TF_BUILD", None) + assert ( + subprocess.check_output( + [ + sys.executable, + "-c", + "from hypothesis import settings\nprint(settings().derandomize)", + ], + env=env, + text=True, + encoding="utf-8", + ).strip() + == "False" + ) + + +def test_reloads_the_loaded_profile_if_registered_again(): + prev_profile = settings._current_profile + try: + test_profile = "some nonsense profile purely for this test" + test_value = 123456 + settings.register_profile(test_profile, settings(max_examples=test_value)) + settings.load_profile(test_profile) + assert settings.default.max_examples == test_value + test_value_2 = 42 + settings.register_profile(test_profile, settings(max_examples=test_value_2)) + assert settings.default.max_examples == test_value_2 + finally: + if prev_profile is not None: + settings.load_profile(prev_profile) + + +CI_TESTING_SCRIPT = """ +from hypothesis import settings + +if __name__ == '__main__': + settings.register_profile("ci", settings(max_examples=42)) + assert settings.default.max_examples == 42 +""" + + +@skipif_emscripten +def test_will_automatically_pick_up_changes_to_ci_profile_in_ci(): + env = dict(os.environ) + env["CI"] = "true" + subprocess.check_call( + [sys.executable, "-c", CI_TESTING_SCRIPT], + env=env, + text=True, + encoding="utf-8", + ) diff --git a/hypothesis-python/tests/cover/test_slippage.py b/hypothesis-python/tests/cover/test_slippage.py index b4a3493ed7..0e1ca807ee 100644 --- a/hypothesis-python/tests/cover/test_slippage.py +++ b/hypothesis-python/tests/cover/test_slippage.py @@ -34,19 +34,20 @@ def capture_reports(test): def test_raises_multiple_failures_with_varying_type(): - target = [None] + target = None @settings(database=None, max_examples=100, report_multiple_bugs=True) @given(st.integers()) def test(i): + nonlocal target if abs(i) < 1000: return - if target[0] is None: + if target is None: # Ensure that we have some space to shrink into, so we can't # trigger an minimal example and mask the other exception type. assume(1003 < abs(i)) - target[0] = i - exc_class = TypeError if target[0] == i else ValueError + target = i + exc_class = TypeError if target == i else ValueError raise exc_class output = capture_reports(test) @@ -66,16 +67,17 @@ def test(i): def test_raises_multiple_failures_when_position_varies(): - target = [None] + target = None @settings(max_examples=100, report_multiple_bugs=True) @given(st.integers()) def test(i): + nonlocal target if abs(i) < 1000: return - if target[0] is None: - target[0] = i - if target[0] == i: + if target is None: + target = i + if target == i: raise ValueError("loc 1") else: raise ValueError("loc 2") @@ -86,18 +88,19 @@ def test(i): def test_replays_both_failing_values(): - target = [None] + target = None @settings( database=InMemoryExampleDatabase(), max_examples=500, report_multiple_bugs=True ) @given(st.integers()) def test(i): + nonlocal target if abs(i) < 1000: return - if target[0] is None: - target[0] = i - exc_class = TypeError if target[0] == i else ValueError + if target is None: + target = i + exc_class = TypeError if target == i else ValueError raise exc_class with pytest.raises(ExceptionGroup): @@ -216,25 +219,26 @@ def test_handles_flaky_tests_where_only_one_is_flaky(): flaky_fixed = False target = [] - flaky_failed_once = [False] + flaky_failed_once = False @settings( database=InMemoryExampleDatabase(), max_examples=1000, report_multiple_bugs=True ) @given(st.integers()) def test(i): + nonlocal flaky_failed_once if abs(i) < 1000: return if not target: target.append(i) if i == target[0]: raise TypeError - if flaky_failed_once[0] and not flaky_fixed: + if flaky_failed_once and not flaky_fixed: return if len(target) == 1: target.append(i) if i == target[1]: - flaky_failed_once[0] = True + flaky_failed_once = True raise ValueError with pytest.raises(ExceptionGroup) as err: diff --git a/hypothesis-python/tests/cover/test_stateful.py b/hypothesis-python/tests/cover/test_stateful.py index 2dd51081cc..b8be7b9dda 100644 --- a/hypothesis-python/tests/cover/test_stateful.py +++ b/hypothesis-python/tests/cover/test_stateful.py @@ -334,23 +334,26 @@ def bye(self, hi): def test_minimizes_errors_in_teardown(): - counter = [0] + counter = 0 class Foo(RuleBasedStateMachine): @initialize() def init(self): - counter[0] = 0 + nonlocal counter + counter = 0 @rule() def increment(self): - counter[0] += 1 + nonlocal counter + counter += 1 def teardown(self): - assert not counter[0] + nonlocal counter + assert not counter with raises(AssertionError): run_state_machine_as_test(Foo) - assert counter[0] == 1 + assert counter == 1 class RequiresInit(RuleBasedStateMachine): diff --git a/hypothesis-python/tests/cover/test_statistical_events.py b/hypothesis-python/tests/cover/test_statistical_events.py index a8ef19823c..038e61b7ac 100644 --- a/hypothesis-python/tests/cover/test_statistical_events.py +++ b/hypothesis-python/tests/cover/test_statistical_events.py @@ -130,14 +130,15 @@ def test(i): def test_flaky_exit(): - first = [True] + first = True @settings(derandomize=True) @given(st.integers()) def test(i): + nonlocal first if i > 1001: - if first[0]: - first[0] = False + if first: + first = False print("Hi") raise AssertionError diff --git a/hypothesis-python/tests/cover/test_testdecorators.py b/hypothesis-python/tests/cover/test_testdecorators.py index 0cb9cd3c21..7597bb164e 100644 --- a/hypothesis-python/tests/cover/test_testdecorators.py +++ b/hypothesis-python/tests/cover/test_testdecorators.py @@ -159,12 +159,13 @@ def test_integers_from_are_from(x): def test_does_not_catch_interrupt_during_falsify(): - calls = [0] + called = False @given(integers()) def flaky_base_exception(x): - if not calls[0]: - calls[0] = 1 + nonlocal called + if not called: + called = True raise KeyboardInterrupt with raises(KeyboardInterrupt): diff --git a/hypothesis-python/tests/nocover/test_baseexception.py b/hypothesis-python/tests/nocover/test_baseexception.py index 09c53a43bf..f125b14b58 100644 --- a/hypothesis-python/tests/nocover/test_baseexception.py +++ b/hypothesis-python/tests/nocover/test_baseexception.py @@ -48,13 +48,14 @@ def test_do_nothing(x): @pytest.mark.parametrize("e", [KeyboardInterrupt, ValueError]) def test_baseexception_no_rerun_no_flaky(e): - runs = [0] + runs = 0 interrupt = 3 @given(integers()) def test_raise_baseexception(x): - runs[0] += 1 - if runs[0] == interrupt: + nonlocal runs + runs += 1 + if runs == interrupt: raise e if issubclass(e, (KeyboardInterrupt, SystemExit, GeneratorExit)): @@ -62,7 +63,7 @@ def test_raise_baseexception(x): with pytest.raises(e): test_raise_baseexception() - assert runs[0] == interrupt + assert runs == interrupt else: with pytest.raises(FlakyFailure): test_raise_baseexception() diff --git a/hypothesis-python/tests/nocover/test_cacheable.py b/hypothesis-python/tests/nocover/test_cacheable.py index 23e9831379..f9748a8b2d 100644 --- a/hypothesis-python/tests/nocover/test_cacheable.py +++ b/hypothesis-python/tests/nocover/test_cacheable.py @@ -52,7 +52,7 @@ def test_cacheable_things_are_cached(): def test_local_types_are_garbage_collected_issue_493(): - store = [None] + store = None def run_locally(): class Test: @@ -61,11 +61,12 @@ class Test: def test(self, i): pass - store[0] = weakref.ref(Test) + nonlocal store + store = weakref.ref(Test) Test().test() run_locally() del run_locally - assert store[0]() is not None + assert store() is not None gc.collect() - assert store[0]() is None + assert store() is None diff --git a/hypothesis-python/tests/nocover/test_conjecture_engine.py b/hypothesis-python/tests/nocover/test_conjecture_engine.py index 3771960c7f..d1df29fbfc 100644 --- a/hypothesis-python/tests/nocover/test_conjecture_engine.py +++ b/hypothesis-python/tests/nocover/test_conjecture_engine.py @@ -103,7 +103,7 @@ def test_node_programs_fail_efficiently(monkeypatch): # Create 256 byte-sized blocks. None of the blocks can be deleted, and # every deletion attempt produces a different buffer. @shrinking_from(bytes(range(256))) - def shrinker(data): + def shrinker(data: ConjectureData): values = set() for _ in range(256): v = data.draw_integer(0, 2**8 - 1) diff --git a/hypothesis-python/tests/nocover/test_deferred_errors.py b/hypothesis-python/tests/nocover/test_deferred_errors.py index 8333adf1d6..0d8abfef63 100644 --- a/hypothesis-python/tests/nocover/test_deferred_errors.py +++ b/hypothesis-python/tests/nocover/test_deferred_errors.py @@ -54,16 +54,17 @@ def test_errors_on_example(): def test_does_not_recalculate_the_strategy(): - calls = [0] + calls = 0 @defines_strategy() def foo(): - calls[0] += 1 + nonlocal calls + calls += 1 return st.just(1) f = foo() - assert calls == [0] + assert calls == 0 check_can_generate_examples(f) - assert calls == [1] + assert calls == 1 check_can_generate_examples(f) - assert calls == [1] + assert calls == 1 diff --git a/hypothesis-python/tests/nocover/test_limits.py b/hypothesis-python/tests/nocover/test_limits.py index 8b7762a778..1d36705231 100644 --- a/hypothesis-python/tests/nocover/test_limits.py +++ b/hypothesis-python/tests/nocover/test_limits.py @@ -12,12 +12,13 @@ def test_max_examples_are_respected(): - counter = [0] + counter = 0 @given(st.random_module(), st.integers()) @settings(max_examples=100) def test(rnd, i): - counter[0] += 1 + nonlocal counter + counter += 1 test() - assert counter == [100] + assert counter == 100 diff --git a/hypothesis-python/tests/nocover/test_targeting.py b/hypothesis-python/tests/nocover/test_targeting.py index 25d74881f9..e1cab85a98 100644 --- a/hypothesis-python/tests/nocover/test_targeting.py +++ b/hypothesis-python/tests/nocover/test_targeting.py @@ -61,7 +61,7 @@ def test_targeting_can_be_disabled(): strat = st.lists(st.integers(0, 255)) def score(enabled): - result = [0] + result = 0 phases = [Phase.generate] if enabled: phases.append(Phase.target) @@ -70,12 +70,13 @@ def score(enabled): @settings(database=None, max_examples=200, phases=phases) @given(strat) def test(ls): + nonlocal result score = float(sum(ls)) - result[0] = max(result[0], score) + result = max(result, score) target(score) test() - return result[0] + return result assert score(enabled=True) > score(enabled=False) diff --git a/hypothesis-python/tests/nocover/test_threading.py b/hypothesis-python/tests/nocover/test_threading.py index 07b2316fd9..2e2e52d387 100644 --- a/hypothesis-python/tests/nocover/test_threading.py +++ b/hypothesis-python/tests/nocover/test_threading.py @@ -14,13 +14,14 @@ def test_can_run_given_in_thread(): - has_run_successfully = [False] + has_run_successfully = False @given(st.integers()) def test(n): - has_run_successfully[0] = True + nonlocal has_run_successfully + has_run_successfully = True t = threading.Thread(target=test) t.start() t.join() - assert has_run_successfully[0] + assert has_run_successfully diff --git a/hypothesis-python/tests/pytest/test_capture.py b/hypothesis-python/tests/pytest/test_capture.py index f2765dd3a3..d5fb88ed9e 100644 --- a/hypothesis-python/tests/pytest/test_capture.py +++ b/hypothesis-python/tests/pytest/test_capture.py @@ -93,7 +93,8 @@ def test_healthcheck_traceback_is_hidden(x): """ -def test_healthcheck_traceback_is_hidden(testdir): +def test_healthcheck_traceback_is_hidden(testdir, monkeypatch): + monkeypatch.delenv("CI", raising=False) script = testdir.makepyfile(TRACEBACKHIDE_HEALTHCHECK) result = testdir.runpytest(script, "--verbose") def_token = "__ test_healthcheck_traceback_is_hidden __" diff --git a/hypothesis-python/tests/pytest/test_seeding.py b/hypothesis-python/tests/pytest/test_seeding.py index 04a61eec35..954628596e 100644 --- a/hypothesis-python/tests/pytest/test_seeding.py +++ b/hypothesis-python/tests/pytest/test_seeding.py @@ -82,7 +82,10 @@ def test_failure(i): """ -def test_repeats_healthcheck_when_following_seed_instruction(testdir, tmp_path): +def test_repeats_healthcheck_when_following_seed_instruction( + testdir, tmp_path, monkeypatch +): + monkeypatch.delenv("CI", raising=False) health_check_test = HEALTH_CHECK_FAILURE.replace( "", repr(str(tmp_path / "seen")) )