Repository: HypothesisWorks/hypothesis
Branch: master
Commit: 9fe714de9b90
Files: 666
Total size: 4.6 MB
Directory structure:
gitextract_nlv4f5th/
├── .claude/
│ ├── CLAUDE.md
│ └── commands/
│ └── hypothesis.md
├── .gitattributes
├── .github/
│ ├── CODEOWNERS
│ ├── CODE_OF_CONDUCT.rst
│ ├── actions/
│ │ └── install-base/
│ │ └── action.yml
│ └── workflows/
│ ├── fuzz.yml
│ ├── main.yml
│ ├── update-deps.yml
│ └── website.yml
├── .gitignore
├── .readthedocs.yml
├── AUTHORS.rst
├── CITATION.cff
├── CONTRIBUTING.rst
├── LICENSE.txt
├── README.md
├── brand/
│ ├── README.md
│ ├── hypothesis.gpl
│ ├── hypothesis.sketch
│ └── hypothesis2.sketch
├── build.sh
├── guides/
│ ├── README.md
│ ├── api-style.rst
│ ├── documentation.rst
│ ├── internals.rst
│ ├── review.rst
│ ├── strategies-that-shrink.rst
│ └── testing-hypothesis.rst
├── hypothesis-python/
│ ├── LICENSE.txt
│ ├── README.md
│ ├── RELEASE-sample.rst
│ ├── benchmark/
│ │ ├── README.md
│ │ ├── graph.py
│ │ └── spec.json
│ ├── docs/
│ │ ├── _ext/
│ │ │ ├── hypothesis_linkcheck.py
│ │ │ └── hypothesis_redirects.py
│ │ ├── _static/
│ │ │ ├── better-signatures.css
│ │ │ ├── dark-fix.css
│ │ │ ├── no-scroll.css
│ │ │ └── wrap-in-tables.css
│ │ ├── changelog.rst
│ │ ├── community.rst
│ │ ├── compatibility.rst
│ │ ├── conf.py
│ │ ├── development.rst
│ │ ├── explanation/
│ │ │ ├── domain.rst
│ │ │ ├── example-count.rst
│ │ │ └── index.rst
│ │ ├── extensions.rst
│ │ ├── extras.rst
│ │ ├── how-to/
│ │ │ ├── custom-database.rst
│ │ │ ├── detect-hypothesis-tests.rst
│ │ │ ├── external-fuzzers.rst
│ │ │ ├── index.rst
│ │ │ ├── suppress-healthchecks.rst
│ │ │ └── type-strategies.rst
│ │ ├── index.rst
│ │ ├── packaging.rst
│ │ ├── prolog.rst
│ │ ├── quickstart.rst
│ │ ├── redirect.html.template
│ │ ├── reference/
│ │ │ ├── api.rst
│ │ │ ├── index.rst
│ │ │ ├── integrations.rst
│ │ │ ├── internals.rst
│ │ │ ├── schema_metadata.json
│ │ │ ├── schema_metadata_choices.json
│ │ │ ├── schema_observations.json
│ │ │ └── strategies.rst
│ │ ├── stateful.rst
│ │ ├── tutorial/
│ │ │ ├── adapting-strategies.rst
│ │ │ ├── adding-notes.rst
│ │ │ ├── builtin-strategies.rst
│ │ │ ├── custom-strategies.rst
│ │ │ ├── flaky.rst
│ │ │ ├── index.rst
│ │ │ ├── introduction.rst
│ │ │ ├── replaying-failures.rst
│ │ │ └── settings.rst
│ │ └── usage.rst
│ ├── examples/
│ │ ├── README.md
│ │ ├── example_hypothesis_entrypoint/
│ │ │ ├── example_hypothesis_entrypoint.py
│ │ │ ├── setup.py
│ │ │ └── test_entrypoint.py
│ │ ├── test_basic.py
│ │ ├── test_binary_search.py
│ │ └── test_rle.py
│ ├── pyproject.toml
│ ├── pyrightconfig.json
│ ├── scripts/
│ │ ├── basic-test.sh
│ │ ├── other-tests.sh
│ │ └── validate_branch_check.py
│ ├── src/
│ │ ├── _hypothesis_ftz_detector.py
│ │ ├── _hypothesis_globals.py
│ │ ├── _hypothesis_pytestplugin.py
│ │ └── hypothesis/
│ │ ├── __init__.py
│ │ ├── _settings.py
│ │ ├── configuration.py
│ │ ├── control.py
│ │ ├── core.py
│ │ ├── database.py
│ │ ├── entry_points.py
│ │ ├── errors.py
│ │ ├── extra/
│ │ │ ├── __init__.py
│ │ │ ├── _array_helpers.py
│ │ │ ├── _patching.py
│ │ │ ├── array_api.py
│ │ │ ├── cli.py
│ │ │ ├── codemods.py
│ │ │ ├── dateutil.py
│ │ │ ├── django/
│ │ │ │ ├── __init__.py
│ │ │ │ ├── _fields.py
│ │ │ │ └── _impl.py
│ │ │ ├── dpcontracts.py
│ │ │ ├── ghostwriter.py
│ │ │ ├── lark.py
│ │ │ ├── numpy.py
│ │ │ ├── pandas/
│ │ │ │ ├── __init__.py
│ │ │ │ └── impl.py
│ │ │ ├── pytestplugin.py
│ │ │ ├── pytz.py
│ │ │ └── redis.py
│ │ ├── internal/
│ │ │ ├── __init__.py
│ │ │ ├── cache.py
│ │ │ ├── cathetus.py
│ │ │ ├── charmap.py
│ │ │ ├── compat.py
│ │ │ ├── conjecture/
│ │ │ │ ├── __init__.py
│ │ │ │ ├── choice.py
│ │ │ │ ├── data.py
│ │ │ │ ├── datatree.py
│ │ │ │ ├── engine.py
│ │ │ │ ├── floats.py
│ │ │ │ ├── junkdrawer.py
│ │ │ │ ├── optimiser.py
│ │ │ │ ├── pareto.py
│ │ │ │ ├── provider_conformance.py
│ │ │ │ ├── providers.py
│ │ │ │ ├── shrinker.py
│ │ │ │ ├── shrinking/
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── bytes.py
│ │ │ │ │ ├── choicetree.py
│ │ │ │ │ ├── collection.py
│ │ │ │ │ ├── common.py
│ │ │ │ │ ├── floats.py
│ │ │ │ │ ├── integer.py
│ │ │ │ │ ├── ordering.py
│ │ │ │ │ └── string.py
│ │ │ │ └── utils.py
│ │ │ ├── constants_ast.py
│ │ │ ├── coverage.py
│ │ │ ├── detection.py
│ │ │ ├── entropy.py
│ │ │ ├── escalation.py
│ │ │ ├── filtering.py
│ │ │ ├── floats.py
│ │ │ ├── healthcheck.py
│ │ │ ├── intervalsets.py
│ │ │ ├── lambda_sources.py
│ │ │ ├── observability.py
│ │ │ ├── reflection.py
│ │ │ ├── scrutineer.py
│ │ │ └── validation.py
│ │ ├── provisional.py
│ │ ├── py.typed
│ │ ├── reporting.py
│ │ ├── stateful.py
│ │ ├── statistics.py
│ │ ├── strategies/
│ │ │ ├── __init__.py
│ │ │ └── _internal/
│ │ │ ├── __init__.py
│ │ │ ├── attrs.py
│ │ │ ├── collections.py
│ │ │ ├── core.py
│ │ │ ├── datetime.py
│ │ │ ├── deferred.py
│ │ │ ├── featureflags.py
│ │ │ ├── flatmapped.py
│ │ │ ├── functions.py
│ │ │ ├── ipaddress.py
│ │ │ ├── lazy.py
│ │ │ ├── misc.py
│ │ │ ├── numbers.py
│ │ │ ├── random.py
│ │ │ ├── recursive.py
│ │ │ ├── regex.py
│ │ │ ├── shared.py
│ │ │ ├── strategies.py
│ │ │ ├── strings.py
│ │ │ ├── types.py
│ │ │ └── utils.py
│ │ ├── utils/
│ │ │ ├── __init__.py
│ │ │ ├── conventions.py
│ │ │ ├── deprecation.py
│ │ │ ├── dynamicvariables.py
│ │ │ ├── terminal.py
│ │ │ └── threading.py
│ │ ├── vendor/
│ │ │ ├── __init__.py
│ │ │ ├── pretty.py
│ │ │ └── tlds-alpha-by-domain.txt
│ │ └── version.py
│ ├── tests/
│ │ ├── README.md
│ │ ├── __init__.py
│ │ ├── array_api/
│ │ │ ├── README.md
│ │ │ ├── __init__.py
│ │ │ ├── common.py
│ │ │ ├── conftest.py
│ │ │ ├── test_argument_validation.py
│ │ │ ├── test_arrays.py
│ │ │ ├── test_from_dtype.py
│ │ │ ├── test_indices.py
│ │ │ ├── test_partial_adoptors.py
│ │ │ ├── test_pretty.py
│ │ │ ├── test_scalar_dtypes.py
│ │ │ └── test_strategies_namespace.py
│ │ ├── attrs/
│ │ │ ├── test_attrs.py
│ │ │ ├── test_inference.py
│ │ │ └── test_pretty.py
│ │ ├── codemods/
│ │ │ ├── test_codemod_cli.py
│ │ │ └── test_codemods.py
│ │ ├── common/
│ │ │ ├── __init__.py
│ │ │ ├── arguments.py
│ │ │ ├── costbounds.py
│ │ │ ├── debug.py
│ │ │ ├── setup.py
│ │ │ ├── strategies.py
│ │ │ └── utils.py
│ │ ├── conftest.py
│ │ ├── conjecture/
│ │ │ ├── __init__.py
│ │ │ ├── common.py
│ │ │ ├── test_choice.py
│ │ │ ├── test_choice_tree.py
│ │ │ ├── test_data_tree.py
│ │ │ ├── test_engine.py
│ │ │ ├── test_float_encoding.py
│ │ │ ├── test_forced.py
│ │ │ ├── test_inquisitor.py
│ │ │ ├── test_intlist.py
│ │ │ ├── test_junkdrawer.py
│ │ │ ├── test_local_constants.py
│ │ │ ├── test_minimizer.py
│ │ │ ├── test_mutations.py
│ │ │ ├── test_optimiser.py
│ │ │ ├── test_order_shrinking.py
│ │ │ ├── test_pareto.py
│ │ │ ├── test_provider.py
│ │ │ ├── test_provider_contract.py
│ │ │ ├── test_shrinker.py
│ │ │ ├── test_shrinking_interface.py
│ │ │ ├── test_test_data.py
│ │ │ └── test_utils.py
│ │ ├── cover/
│ │ │ ├── __init__.py
│ │ │ ├── test_annotations.py
│ │ │ ├── test_arbitrary_data.py
│ │ │ ├── test_asyncio.py
│ │ │ ├── test_cache_implementation.py
│ │ │ ├── test_caching.py
│ │ │ ├── test_cathetus.py
│ │ │ ├── test_charmap.py
│ │ │ ├── test_compat.py
│ │ │ ├── test_complex_numbers.py
│ │ │ ├── test_composite.py
│ │ │ ├── test_composite_kwonlyargs.py
│ │ │ ├── test_constants_ast.py
│ │ │ ├── test_control.py
│ │ │ ├── test_core.py
│ │ │ ├── test_custom_reprs.py
│ │ │ ├── test_database_backend.py
│ │ │ ├── test_datetimes.py
│ │ │ ├── test_deadline.py
│ │ │ ├── test_debug_information.py
│ │ │ ├── test_deferred_strategies.py
│ │ │ ├── test_detection.py
│ │ │ ├── test_direct_strategies.py
│ │ │ ├── test_draw_example.py
│ │ │ ├── test_error_in_draw.py
│ │ │ ├── test_escalation.py
│ │ │ ├── test_example.py
│ │ │ ├── test_exceptiongroup.py
│ │ │ ├── test_executors.py
│ │ │ ├── test_explicit_examples.py
│ │ │ ├── test_falsifying_example_output.py
│ │ │ ├── test_feature_flags.py
│ │ │ ├── test_filestorage.py
│ │ │ ├── test_filter_rewriting.py
│ │ │ ├── test_filtered_strategy.py
│ │ │ ├── test_find.py
│ │ │ ├── test_flakiness.py
│ │ │ ├── test_float_nastiness.py
│ │ │ ├── test_float_utils.py
│ │ │ ├── test_functions.py
│ │ │ ├── test_fuzz_one_input.py
│ │ │ ├── test_given_error_conditions.py
│ │ │ ├── test_health_checks.py
│ │ │ ├── test_interactive_example.py
│ │ │ ├── test_internal_helpers.py
│ │ │ ├── test_intervalset.py
│ │ │ ├── test_lambda_formatting.py
│ │ │ ├── test_lazy_import.py
│ │ │ ├── test_lookup.py
│ │ │ ├── test_lookup_py310.py
│ │ │ ├── test_lookup_py314.py
│ │ │ ├── test_lookup_py37.py
│ │ │ ├── test_lookup_py38.py
│ │ │ ├── test_lookup_py39.py
│ │ │ ├── test_map.py
│ │ │ ├── test_mock.py
│ │ │ ├── test_monitoring.py
│ │ │ ├── test_nothing.py
│ │ │ ├── test_numerics.py
│ │ │ ├── test_observability.py
│ │ │ ├── test_one_of.py
│ │ │ ├── test_permutations.py
│ │ │ ├── test_phases.py
│ │ │ ├── test_posonly_args_py38.py
│ │ │ ├── test_pretty.py
│ │ │ ├── test_provisional_strategies.py
│ │ │ ├── test_random_module.py
│ │ │ ├── test_randoms.py
│ │ │ ├── test_recursive.py
│ │ │ ├── test_reflection.py
│ │ │ ├── test_regex.py
│ │ │ ├── test_regressions.py
│ │ │ ├── test_replay_logic.py
│ │ │ ├── test_reporting.py
│ │ │ ├── test_reproduce_failure.py
│ │ │ ├── test_runner_strategy.py
│ │ │ ├── test_sampled_from.py
│ │ │ ├── test_searchstrategy.py
│ │ │ ├── test_seed_printing.py
│ │ │ ├── test_settings.py
│ │ │ ├── test_setup_teardown.py
│ │ │ ├── test_shrink_budgeting.py
│ │ │ ├── test_sideeffect_warnings.py
│ │ │ ├── test_simple_characters.py
│ │ │ ├── test_simple_collections.py
│ │ │ ├── test_simple_strings.py
│ │ │ ├── test_slices.py
│ │ │ ├── test_slippage.py
│ │ │ ├── test_stateful.py
│ │ │ ├── test_statistical_events.py
│ │ │ ├── test_subnormal_floats.py
│ │ │ ├── test_targeting.py
│ │ │ ├── test_testdecorators.py
│ │ │ ├── test_threading.py
│ │ │ ├── test_traceback_elision.py
│ │ │ ├── test_type_lookup.py
│ │ │ ├── test_type_lookup_forward_ref.py
│ │ │ ├── test_typealias_py312.py
│ │ │ ├── test_unicode_identifiers.py
│ │ │ ├── test_unittest.py
│ │ │ ├── test_uuids.py
│ │ │ ├── test_validation.py
│ │ │ └── test_verbosity.py
│ │ ├── crosshair/
│ │ │ ├── test_conformance.py
│ │ │ └── test_crosshair.py
│ │ ├── datetime/
│ │ │ ├── __init__.py
│ │ │ ├── test_dateutil_timezones.py
│ │ │ ├── test_pytz_timezones.py
│ │ │ └── test_zoneinfo_timezones.py
│ │ ├── django/
│ │ │ ├── __init__.py
│ │ │ ├── manage.py
│ │ │ ├── toys/
│ │ │ │ ├── __init__.py
│ │ │ │ ├── settings/
│ │ │ │ │ ├── no_urls.py
│ │ │ │ │ ├── settings.py
│ │ │ │ │ ├── settings_no_contrib.py
│ │ │ │ │ └── urls.py
│ │ │ │ └── wsgi.py
│ │ │ └── toystore/
│ │ │ ├── __init__.py
│ │ │ ├── admin.py
│ │ │ ├── forms.py
│ │ │ ├── models.py
│ │ │ ├── test_basic_configuration.py
│ │ │ ├── test_given_forms.py
│ │ │ ├── test_given_models.py
│ │ │ └── views.py
│ │ ├── dpcontracts/
│ │ │ ├── __init__.py
│ │ │ └── test_contracts.py
│ │ ├── ghostwriter/
│ │ │ ├── example_code/
│ │ │ │ ├── __init__.py
│ │ │ │ └── future_annotations.py
│ │ │ ├── recorded/
│ │ │ │ ├── add_custom_classes.txt
│ │ │ │ ├── addition_op_magic.txt
│ │ │ │ ├── addition_op_multimagic.txt
│ │ │ │ ├── base64_magic.txt
│ │ │ │ ├── division_binop_error_handler.txt
│ │ │ │ ├── division_fuzz_error_handler.txt
│ │ │ │ ├── division_operator.txt
│ │ │ │ ├── division_operator_with_annotations.txt
│ │ │ │ ├── division_roundtrip_arithmeticerror_handler.txt
│ │ │ │ ├── division_roundtrip_error_handler.txt
│ │ │ │ ├── division_roundtrip_error_handler_without_annotations.txt
│ │ │ │ ├── division_roundtrip_typeerror_handler.txt
│ │ │ │ ├── eval_equivalent.txt
│ │ │ │ ├── fuzz_classmethod.txt
│ │ │ │ ├── fuzz_sorted.txt
│ │ │ │ ├── fuzz_sorted_with_annotations.txt
│ │ │ │ ├── fuzz_staticmethod.txt
│ │ │ │ ├── fuzz_ufunc.txt
│ │ │ │ ├── fuzz_with_docstring.txt
│ │ │ │ ├── hypothesis_module_magic.txt
│ │ │ │ ├── invalid_types.txt
│ │ │ │ ├── magic_base64_roundtrip.txt
│ │ │ │ ├── magic_base64_roundtrip_with_annotations.txt
│ │ │ │ ├── magic_builtins.txt
│ │ │ │ ├── magic_class.txt
│ │ │ │ ├── magic_gufunc.txt
│ │ │ │ ├── magic_numpy.txt
│ │ │ │ ├── matmul_magic.txt
│ │ │ │ ├── merge_dicts.txt
│ │ │ │ ├── multiplication_magic.txt
│ │ │ │ ├── multiplication_operator.txt
│ │ │ │ ├── multiplication_operator_unittest.txt
│ │ │ │ ├── nothing_found.txt
│ │ │ │ ├── optional_parameter.txt
│ │ │ │ ├── optional_union_parameter.txt
│ │ │ │ ├── re_compile.txt
│ │ │ │ ├── re_compile_except.txt
│ │ │ │ ├── re_compile_unittest.txt
│ │ │ │ ├── sequence_from_collections.txt
│ │ │ │ ├── sorted_idempotent.txt
│ │ │ │ ├── sorted_self_equivalent.txt
│ │ │ │ ├── sorted_self_equivalent_with_annotations.txt
│ │ │ │ ├── sorted_self_error_equivalent_1error.txt
│ │ │ │ ├── sorted_self_error_equivalent_2error_unittest.txt
│ │ │ │ ├── sorted_self_error_equivalent_simple.txt
│ │ │ │ ├── sorted_self_error_equivalent_threefuncs.txt
│ │ │ │ ├── timsort_idempotent.txt
│ │ │ │ ├── timsort_idempotent_asserts.txt
│ │ │ │ └── union_sequence_parameter.txt
│ │ │ ├── test_expected_output.py
│ │ │ ├── test_ghostwriter.py
│ │ │ ├── test_ghostwriter_cli.py
│ │ │ └── try-writing-for-installed.py
│ │ ├── lark/
│ │ │ ├── __init__.py
│ │ │ └── test_grammar.py
│ │ ├── nocover/
│ │ │ ├── __init__.py
│ │ │ ├── test_argument_validation.py
│ │ │ ├── test_bad_repr.py
│ │ │ ├── test_baseexception.py
│ │ │ ├── test_boundary_exploration.py
│ │ │ ├── test_build_signature.py
│ │ │ ├── test_cache_implementation.py
│ │ │ ├── test_cacheable.py
│ │ │ ├── test_characters.py
│ │ │ ├── test_collective_minimization.py
│ │ │ ├── test_compat.py
│ │ │ ├── test_completion.py
│ │ │ ├── test_complex_numbers.py
│ │ │ ├── test_conjecture_engine.py
│ │ │ ├── test_conjecture_int_list.py
│ │ │ ├── test_conjecture_utils.py
│ │ │ ├── test_conventions.py
│ │ │ ├── test_database_agreement.py
│ │ │ ├── test_database_usage.py
│ │ │ ├── test_deferred_errors.py
│ │ │ ├── test_drypython_returns.py
│ │ │ ├── test_duplication.py
│ │ │ ├── test_dynamic_variable.py
│ │ │ ├── test_emails.py
│ │ │ ├── test_eval_as_source.py
│ │ │ ├── test_exceptiongroup.py
│ │ │ ├── test_explore_arbitrary_languages.py
│ │ │ ├── test_fancy_repr.py
│ │ │ ├── test_filtering.py
│ │ │ ├── test_find.py
│ │ │ ├── test_fixtures.py
│ │ │ ├── test_flatmap.py
│ │ │ ├── test_floating.py
│ │ │ ├── test_from_type_recipe.py
│ │ │ ├── test_given_error_conditions.py
│ │ │ ├── test_given_reuse.py
│ │ │ ├── test_health_checks.py
│ │ │ ├── test_imports.py
│ │ │ ├── test_integer_ranges.py
│ │ │ ├── test_interesting_origin.py
│ │ │ ├── test_labels.py
│ │ │ ├── test_large_examples.py
│ │ │ ├── test_limits.py
│ │ │ ├── test_modify_inner_test.py
│ │ │ ├── test_nesting.py
│ │ │ ├── test_precise_shrinking.py
│ │ │ ├── test_pretty_repr.py
│ │ │ ├── test_randomization.py
│ │ │ ├── test_recursive.py
│ │ │ ├── test_regex.py
│ │ │ ├── test_regressions.py
│ │ │ ├── test_reusable_values.py
│ │ │ ├── test_sampled_from.py
│ │ │ ├── test_scrutineer.py
│ │ │ ├── test_sets.py
│ │ │ ├── test_sharing.py
│ │ │ ├── test_simple_numbers.py
│ │ │ ├── test_simple_strings.py
│ │ │ ├── test_skipping.py
│ │ │ ├── test_stateful.py
│ │ │ ├── test_strategy_state.py
│ │ │ ├── test_subnormal_floats.py
│ │ │ ├── test_targeting.py
│ │ │ ├── test_testdecorators.py
│ │ │ ├── test_threading.py
│ │ │ ├── test_type_lookup.py
│ │ │ ├── test_type_lookup_forward_ref.py
│ │ │ ├── test_type_lookup_future_annotations.py
│ │ │ ├── test_unusual_settings_configs.py
│ │ │ └── test_uuids.py
│ │ ├── numpy/
│ │ │ ├── __init__.py
│ │ │ ├── test_argument_validation.py
│ │ │ ├── test_deprecation.py
│ │ │ ├── test_fill_values.py
│ │ │ ├── test_floor_ceil.py
│ │ │ ├── test_from_dtype.py
│ │ │ ├── test_from_type.py
│ │ │ ├── test_gen_data.py
│ │ │ ├── test_gufunc.py
│ │ │ ├── test_import.py
│ │ │ ├── test_narrow_floats.py
│ │ │ ├── test_randomness.py
│ │ │ └── test_sampled_from.py
│ │ ├── pandas/
│ │ │ ├── __init__.py
│ │ │ ├── helpers.py
│ │ │ ├── test_argument_validation.py
│ │ │ ├── test_data_frame.py
│ │ │ ├── test_indexes.py
│ │ │ └── test_series.py
│ │ ├── patching/
│ │ │ ├── __init__.py
│ │ │ ├── callables.py
│ │ │ ├── test_patching.py
│ │ │ └── toplevel.py
│ │ ├── pytest/
│ │ │ ├── test__pytest.py
│ │ │ ├── test_capture.py
│ │ │ ├── test_checks.py
│ │ │ ├── test_collection_warning.py
│ │ │ ├── test_compat.py
│ │ │ ├── test_constant_collection_timing.py
│ │ │ ├── test_doctest.py
│ │ │ ├── test_fixtures.py
│ │ │ ├── test_junit.py
│ │ │ ├── test_mark.py
│ │ │ ├── test_parametrized_db_keys.py
│ │ │ ├── test_profiles.py
│ │ │ ├── test_pytest_detection.py
│ │ │ ├── test_reporting.py
│ │ │ ├── test_runs.py
│ │ │ ├── test_seeding.py
│ │ │ ├── test_sideeffect_warnings.py
│ │ │ ├── test_skipping.py
│ │ │ └── test_statistics.py
│ │ ├── quality/
│ │ │ ├── __init__.py
│ │ │ ├── test_deferred_strategies.py
│ │ │ ├── test_discovery_ability.py
│ │ │ ├── test_float_shrinking.py
│ │ │ ├── test_integers.py
│ │ │ ├── test_poisoned_lists.py
│ │ │ ├── test_poisoned_trees.py
│ │ │ ├── test_shrink_quality.py
│ │ │ └── test_zig_zagging.py
│ │ ├── redis/
│ │ │ ├── __init__.py
│ │ │ └── test_redis_exampledatabase.py
│ │ ├── test_annotated_types.py
│ │ ├── typing_extensions/
│ │ │ ├── __init__.py
│ │ │ └── test_backported_types.py
│ │ └── watchdog/
│ │ ├── __init__.py
│ │ ├── test_database.py
│ │ └── test_database_cover.py
│ └── tox.ini
├── notebooks/
│ └── Designing a better simplifier.ipynb
├── paper.bib
├── paper.md
├── pyproject.toml
├── requirements/
│ ├── coverage.in
│ ├── coverage.txt
│ ├── crosshair.in
│ ├── crosshair.txt
│ ├── fuzzing.in
│ ├── fuzzing.txt
│ ├── test.in
│ ├── test.txt
│ ├── tools.in
│ └── tools.txt
├── tooling/
│ ├── README.md
│ ├── codespell-dict.txt
│ ├── codespell-ignore.txt
│ ├── scripts/
│ │ ├── common.sh
│ │ ├── ensure-python.sh
│ │ └── tool-hash.py
│ ├── setup.py
│ └── src/
│ └── hypothesistooling/
│ ├── __init__.py
│ ├── __main__.py
│ ├── installers.py
│ ├── junkdrawer.py
│ ├── projects/
│ │ ├── __init__.py
│ │ └── hypothesispython.py
│ ├── releasemanagement.py
│ └── scripts.py
├── website/
│ ├── archive-redirect.html
│ ├── content/
│ │ ├── 2016-04-15-economics-of-software-correctness.md
│ │ ├── 2016-04-15-getting-started-with-hypothesis.md
│ │ ├── 2016-04-16-anatomy-of-a-test.md
│ │ ├── 2016-04-16-encode-decode-invariant.md
│ │ ├── 2016-04-16-quickcheck-in-every-language.md
│ │ ├── 2016-04-16-the-purpose-of-hypothesis.md
│ │ ├── 2016-04-19-rule-based-stateful-testing.md
│ │ ├── 2016-04-29-testing-performance-optimizations.md
│ │ ├── 2016-05-02-referential-transparency.md
│ │ ├── 2016-05-11-generating-the-right-data.md
│ │ ├── 2016-05-13-what-is-property-based-testing.md
│ │ ├── 2016-05-26-exploring-voting-with-hypothesis.md
│ │ ├── 2016-05-29-testing-optimizers-with-hypothesis.md
│ │ ├── 2016-05-31-looking-for-guest-posts.md
│ │ ├── 2016-06-05-incremental-property-based-testing.md
│ │ ├── 2016-06-13-testing-configuration-parameters.md
│ │ ├── 2016-06-30-tests-as-complete-specifications.md
│ │ ├── 2016-07-04-calculating-the-mean.md
│ │ ├── 2016-07-09-hypothesis-3.4.1-release.md
│ │ ├── 2016-07-13-hypothesis-3.4.2-release.md
│ │ ├── 2016-07-23-what-is-hypothesis.md
│ │ ├── 2016-08-09-hypothesis-pytest-fixtures.md
│ │ ├── 2016-08-19-recursive-data.md
│ │ ├── 2016-08-31-how-many-tests.md
│ │ ├── 2016-09-04-hypothesis-vs-eris.md
│ │ ├── 2016-09-23-hypothesis-3.5.0-release.md
│ │ ├── 2016-10-01-pytest-integration-sponsorship.md
│ │ ├── 2016-10-17-canonical-serialization.md
│ │ ├── 2016-10-31-hypothesis-3.6.0-release.md
│ │ ├── 2016-12-05-integrated-shrinking.md
│ │ ├── 2016-12-08-compositional-shrinking.md
│ │ ├── 2016-12-10-how-hypothesis-works.md
│ │ ├── 2017-03-09-hypothesis-for-researchers.md
│ │ ├── 2017-04-05-how-not-to-die-hard-with-hypothesis.md
│ │ ├── 2017-07-16-types-and-properties.md
│ │ ├── 2017-09-14-multi-bug-discovery.md
│ │ ├── 2017-09-28-threshold-problem.md
│ │ ├── 2018-01-08-smarkets.md
│ │ ├── 2018-02-27-continuous-releases.md
│ │ ├── 2020-06-08-complex-data-strategies.md
│ │ ├── 2025-08-07-thread-safe.md
│ │ ├── 2025-11-01-claude-code-plugin.md
│ │ ├── 2025-11-16-introducing-hypofuzz.md
│ │ └── pages/
│ │ └── testimonials.md
│ ├── pelicanconf.py
│ └── theme/
│ ├── static/
│ │ ├── prism.css
│ │ ├── prism.js
│ │ └── style.css
│ └── templates/
│ ├── article-card.html
│ ├── article.html
│ ├── base.html
│ ├── category.html
│ ├── index.html
│ └── page.html
└── whole_repo_tests/
├── __init__.py
├── documentation/
│ ├── __init__.py
│ └── test_documentation.py
├── types/
│ ├── __init__.py
│ ├── revealed_types.py
│ ├── test_hypothesis.py
│ ├── test_mypy.py
│ └── test_pyright.py
└── whole_repo/
├── __init__.py
├── test_ci_config.py
├── test_deploy.py
├── test_release_files.py
├── test_release_management.py
├── test_requirements.py
├── test_rst_is_valid.py
├── test_shellcheck.py
└── test_validate_branch_check.py
================================================
FILE CONTENTS
================================================
================================================
FILE: .claude/CLAUDE.md
================================================
# Hypothesis Development Guide
## Essential Reading
**Always read `CONTRIBUTING.rst` before starting work**, especially before writing tests or creating a PR.
## Testing
### Running Tests
Run tests using the build system:
- **Quick test run**: `./build.sh check-coverage` (curated subset with coverage verification)
- **Python version-specific**: `./build.sh check-py311` (replace with target version)
- **Fine-grained control**: `./build.sh tox py311-custom 3.11.3 -- [pytest args]`
- **Direct pytest** (after setup): `pytest hypothesis-python/tests/cover/`
### Writing Tests
**Never use `.example()` method in tests.** Instead:
- Use `@given` decorator directly for property-based tests
- Use helper functions from `tests.common.debug`:
- `minimal()` - find minimal failing example
- `find_any()` - find any example matching condition
- `assert_all_examples()` - verify all examples match predicate
- `assert_simple_property()` - verify simple properties with few examples
- `check_can_generate_examples()` - verify strategy can generate without error
## Changelog & Pull Requests
When creating a PR that changes `hypothesis-python/src/`:
1. Create `hypothesis-python/RELEASE.rst` with `RELEASE_TYPE: patch` (bugfixes) or `minor` (features)
2. See `RELEASE-sample.rst` for examples
3. **Imitate the style in `changelog.rst`** for consistency
4. Follow all changelog instructions in `CONTRIBUTING.rst`
**Note:** Test-only changes (no modifications to `src/`) do not require a RELEASE.rst file.
## Before Committing
1. Do a final edit pass on all code to ensure it is:
- **Concise** - remove unnecessary verbosity
- **Idiomatic** - follows Python and Hypothesis conventions
- **Minimally commented** - code should be self-documenting; only add comments where truly needed
2. **Run `./build.sh format; ./build.sh lint`** immediately before committing to auto-format and lint code
3. **Do not reference issues or PRs in commit messages** (e.g., avoid `Fixes #1234` or `See #5678`) - this clutters the issue timeline with unnecessary links
================================================
FILE: .claude/commands/hypothesis.md
================================================
---
description: Write property-based tests with Hypothesis
---
You are an expert developer of property-based tests, specifically using Hypothesis. Your goal is to identify and implement a small number of the most valuable Hypothesis tests that would benefit an existing codebase right now. You focus on clarity and maintainability, as your code will be reviewed by a developer. Your goal is to write precise tests, not comprehensive test suites.
Create and follow this todo list using the `Todo` tool:
1. [ ] Explore the provided code and identify valuable properties.
2. [ ] For each property, explore how its related code is used.
3. [ ] Write Hypothesis tests based on those properties.
4. [ ] Run the new Hypothesis tests, and reflect on the result.
## 1. Explore the code provided and identify valuable properties
First, explore the provided code, and identify valuable properties to test. A "valuable property" is an invariant or property about the code that is valuable to the codebase right now and that a knowledgeable developer for this codebase would have written a Hypothesis test for. The following are indicative of a valuable property:
- Would catch important bugs: Testing this property would reveal bugs that could cause serious issues.
- Documents important behavior: The property captures essential assumptions or guarantees that are important to future or current developers.
- Benefits significantly from Hypothesis: The property is concisely and powerfully expressed as a Hypothesis test, rather than a series of unit tests.
Keep the following in mind:
- Only identify properties that you strongly believe to be true and that are supported by evidence in the codebase, for example in docstrings, comments, code use patterns, type hints, etc. Do not include properties you are at all unsure about.
- Each property should provide a substantial improvement in testing power or clarity when expressed as a Hypothesis test, rather than a unit test. Properties which could have been equally well tested with a unit test are not particularly valuable.
- You may come across many possible properties. Your goal is to identify only a small number of the most valuable of those properties that would benefit the codebase right now.
If the provided code is large, focus on exploring in this order:
1. Public API functions/classes
2. Well-documented implementations of core functionality
3. Other implementations of core functionality
4. Internal/private helpers or utilities
Here are some examples of typical properties:
- Round-trip property: `decode(encode(x)) = x`, `parse(format(x)) = x`.
- Inverse relationship: `add/remove`, `push/pop`, `create/destroy`.
- Multiple equivalent implementations: Optimized vs reference implementation, complicated vs simple implementation.
- Mathematical property: Idempotence `f(f(x)) = f(x)`, commutativity `f(x, y) = f(y, x)`.
- Invariants: `len(filter(x)) <= len(x)`, `set(sort(x)) == set(x)`.
- Confluence: the order of function application doesn't matter (for example, in compiler optimization passes).
- Metamorphic property: some relationship between `f(x)` and `g(x)` holds for all x. For example, `sin(π − x) = sin(x)`.
- Single entry point. If a library has a narrow public API, a nice property-based test simply calls the library with valid inputs. Common in parsers.
While the following should generally not be tested:
- Obvious code wrappers
- Implementation details
The user has provided the following guidance for where and how to add Hypothesis tests: $ARGUMENTS.
- If the user has provided no direction, explore the entire codebase.
- If the user has provided a specific module, explore that module.
- If the user has provided a specific file, explore that file.
- If the user has provided a specific function, explore that function.
- If the user has given more complex guidance, follow that instead.
If you don't identify any valuable properties during exploration, that's fine; just tell the user as much, and then stop.
At the end of this step, you should tell the user the small list of the most valuable properties that you intend to test.
## 2. For each valuable property, explore how its related code is used
Before writing Hypothesis tests, explore how the codebase uses the related code of each valuable property. For example, if a property involves a function `some_function`, explore how the codebase calls `some_function`: what kinds of inputs are passed to it? in what context? etc. This helps correct any misunderstanding about the property before writing a test for it.
## 3. Write Hypothesis tests based on those properties.
For each property, write a new Hypothesis test for it, and add it to the codebase's test suite, following its existing testing conventions.
When writing Hypothesis tests, follow these guidelines:
- Each Hypothesis test should be both sound (tests only inputs the code can actually be called with) and complete (tests all inputs the code can actually be called with). Sometimes this is difficult. In those cases, prefer sound and mostly-complete tests; stopping at 90% completeness is better than over-complicating a test.
- Only place constraints on Hypothesis strategies if required by the code. For example, prefer `st.lists(...)` (with no size bound) to `st.lists(..., max_size=100)`, unless the property explicitly happens to only be valid for lists with no more than 100 elements.
## 4. Run the new Hypothesis tests, and reflect on the result.
Run the new Hypothesis tests that you just added. If any fail, reflect on why. Is the test failing because of a genuine bug, or because it's not testing the right thing? Often, when a new Hypothesis test fails, it's because the test generates inputs that the codebase assumes will never occur. If necessary, re-explore related parts of the codebase to check your understanding. You should only report that the codebase has a bug to the user if you are truly confident, and can justify why.
# Hypothesis Reference
Documentation reference (fetch with the `WebFetch` tool if required):
- **Strategies API reference**: https://hypothesis.readthedocs.io/en/latest/reference/strategies.html
- **Other API reference**: https://hypothesis.readthedocs.io/en/latest/reference/api.html
- Documents `@settings`, `@given`, etc.
These Hypothesis strategies are under-appreciated for how effective they are. Use them if they are a perfect or near-perfect fit for a property:
- `st.from_regex`
- `st.from_lark` - for context-free grammars
- `st.functions` - generates arbitrary callable functions
================================================
FILE: .gitattributes
================================================
* text eol=lf
# Denote all files that are truly binary and should not be modified.
*.png binary
*.jpg binary
*.gif binary
================================================
FILE: .github/CODEOWNERS
================================================
# Engine changes need to be approved by Zac-HD, as per
# https://github.com/HypothesisWorks/hypothesis/blob/master/guides/review.rst#engine-changes
/hypothesis-python/src/hypothesis/internal/conjecture/ @Zac-HD @Liam-DeVoe
# Changes to the paper also need to be approved by DRMacIver or Zac, as authors
/paper.md @DRMacIver @Zac-HD
/paper.bib @DRMacIver @Zac-HD
================================================
FILE: .github/CODE_OF_CONDUCT.rst
================================================
---------------
Code of conduct
---------------
Hypothesis's community is an inclusive space, and everyone in it is expected to abide by a code of conduct.
This applies in issues, pull requests, etc. as well as in the various Hypothesis community spaces.
At the high level the code of conduct goes like this:
1. Be kind
2. Be respectful
3. Be helpful
While it is impossible to enumerate everything that is unkind, disrespectful or unhelpful, here are some specific things that are definitely against the code of conduct:
1. -isms and -phobias (e.g. racism, sexism, transphobia and homophobia) are unkind, disrespectful *and* unhelpful. Just don't.
2. All software is broken. This is not a moral failing on the part of the authors. Don't give people a hard time for bad code.
3. It's OK not to know things. Everybody was a beginner once, nobody should be made to feel bad for it.
4. It's OK not to *want* to know something. If you think someone's question is fundamentally flawed, you should still ask permission before explaining what they should actually be asking.
5. Note that "I was just joking" is not a valid defence.
6. Don't suggest violence as a response to things, e.g. "People who do/think X should be Y-ed".
Even if you think it is obvious hyperbole and that it's very clear that no actual threat is meant,
it still contributes to a culture that makes people feel unsafe.
~~~~~~~~~~~~~~~~~~~~~~~~
Resolution of Violations
~~~~~~~~~~~~~~~~~~~~~~~~
David R. MacIver (the project lead) acts as the main point of contact and enforcer for code of conduct violations.
You can email him at david@drmaciver.com, or for violations on GitHub that you want to draw his attention to you can also mention him as @DRMacIver.
Other people (especially Hypothesis team members) should feel free to call people on code of conduct violations when they see them,
and it is appreciated but not required (especially if doing so would make you feel uncomfortable or unsafe).
We don't currently have a formal policy for resolutions and it's mostly based on subjective judgement calls,
but the high level intent is as follows:
* minor one-off infractions will just be met with a request not to repeat the behaviour and, where it would be useful,
for an apology.
* Major infractions and repeat offenders will be banned from the community.
If you disagree with David's judgement on any particular event, please feel free to tell him so.
Also, people who have a track record of bad behaviour outside of the Hypothesis community may be banned even
if they obey all these rules if their presence is making people uncomfortable.
================================================
FILE: .github/actions/install-base/action.yml
================================================
name: "Install"
description: "Install python, then cache"
inputs:
python-version:
description: "Python version"
required: true
python-architecture:
description: "Python architecture, if not default for platform"
task:
description: "Task name"
required: true
runs:
using: "composite"
steps:
- name: Set up Python ${{ inputs.python-version }} ${{ inputs.python-architecture }}
uses: actions/setup-python@v4
with:
python-version: ${{ inputs.python-version }}
architecture: ${{ inputs.python-architecture }}
- name: Install dotnet6 for Pyjion
if: ${{ endsWith(inputs.task, '-pyjion') }}
shell: bash
run: |
wget https://packages.microsoft.com/config/ubuntu/21.04/packages-microsoft-prod.deb -O packages-microsoft-prod.deb
sudo dpkg -i packages-microsoft-prod.deb
rm packages-microsoft-prod.deb
sudo apt-get update
sudo apt-get install -y apt-transport-https && \
sudo apt-get update && \
sudo apt-get install -y dotnet-sdk-6.0
- name: Restore cache
uses: actions/cache@v3
with:
path: |
.tox/
vendor/bundle
~/.cache
~/.local
~/appdata/local/pip/cache
~/Library/Caches/pip
~/wheelhouse
key: deps-${{ runner.os }}-${{ hashFiles('requirements/*.txt') }}-${{ inputs.python-version }}-${{ inputs.python-architecture }}-${{ inputs.task }}
restore-keys: |
deps-${{ runner.os }}-${{ hashFiles('requirements/*.txt') }}-${{ inputs.python-version }}-${{ inputs.python-architecture }}
deps-${{ runner.os }}-${{ hashFiles('requirements/*.txt') }}
deps-${{ runner.os }}
================================================
FILE: .github/workflows/fuzz.yml
================================================
name: Fuzzing
env:
# Tell pytest and other tools to produce coloured terminal output.
# Make sure this is also in the "passenv" section of the tox config.
PY_COLORS: 1
on:
# Run every six hours, for six hours each time
schedule:
- cron: '0 */6 * * *'
# Allow manual launching too so we can test any branch we like
workflow_dispatch:
# # Enable this and reduce the timeout below to check a PR is working
# pull_request:
# branches: [ master ]
jobs:
fuzz:
if: github.repository == 'HypothesisWorks/hypothesis' || github.event_name == 'workflow_dispatch'
# Keep all of this stuff synced with the setup in main.yml for CI
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Set up Python 3.14
uses: actions/setup-python@v4
with:
python-version: "3.14.2"
- name: Restore cache
uses: actions/cache@v3
with:
path: |
~/.cache
~/wheelhouse
~/.local
vendor/bundle
.tox/
key: deps-${{ runner.os }}-${{ hashFiles('requirements/*.txt') }}-${{ matrix.task }}
restore-keys: |
deps-${{ runner.os }}-${{ hashFiles('requirements/*.txt') }}
deps-${{ runner.os }}
# OK, on to the fuzzing-specific part.
# We're going to stick everything into a single run for now instead of
# sharding it, because we'd have to manually specify all the databases
# we want to multiplex across and that would be annoying to manage.
# TODO: revisit this later; a redis-like service would be so much nicer.
- name: Download example database
uses: dawidd6/action-download-artifact@v9
with:
name: hypothesis-example-db
path: .hypothesis/examples
if_no_artifact_found: warn
workflow_conclusion: completed
- name: Install dependencies
run: |
pip install --upgrade setuptools pip wheel
pip install -r requirements/fuzzing.txt
pip install hypothesis-python/[all]
- name: Run hypofuzz session
continue-on-error: true
# The timeout ensures that we finish all steps within the six-hour
# maximum runtime for Github Actions.
# Then run the fuzzer on everything, as for our Windows CI; avoiding
# the --no-dashboard option because that also disables .patch writing.
run: |
timeout --preserve-status 5.5h \
hypothesis fuzz -- hypothesis-python/tests/ \
--ignore=hypothesis-python/tests/quality/ \
--ignore=hypothesis-python/tests/ghostwriter/
- name: Upload patch files with covering and failing `@example()`s
uses: actions/upload-artifact@v4
if: always()
with:
name: explicit-example-patches
path: .hypothesis/patches/latest_hypofuzz_*.patch
# Upload the database so it'll be persisted between runs.
# Note that we can also pull it down to use locally via
# https://hypothesis.readthedocs.io/en/latest/database.html#hypothesis.database.GitHubArtifactDatabase
- name: Upload example database
uses: actions/upload-artifact@v4
if: always()
with:
name: hypothesis-example-db
path: .hypothesis/examples
================================================
FILE: .github/workflows/main.yml
================================================
name: Hypothesis CI
env:
# Tell pytest and other tools to produce coloured terminal output.
# Make sure this is also in the "passenv" section of the tox config.
PY_COLORS: 1
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
workflow_dispatch:
# Cancel in-progress PR builds if another commit is pushed.
# On non-PR builds, fall back to the globally-unique run_id and don't cancel.
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
cancel-in-progress: true
jobs:
basic:
runs-on: ubuntu-latest
strategy:
matrix:
task:
- tox-cover
- tox-nocover
- tox-rest
- check-whole-repo-tests
- check-types-hypothesis
- lint
- check-format
fail-fast: false
env:
PYTHON_VERSION: "3.14"
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- name: "Install base for Python ${{ env.PYTHON_VERSION }}"
uses: ./.github/actions/install-base
with:
python-version: ${{ env.PYTHON_VERSION }}
task: ${{ matrix.task }}
- name: Install tox
if: ${{ startsWith(matrix.task, 'tox-') }}
run: |
pip install --upgrade setuptools pip wheel
pip install tox
- name: Run tests
run: |
export TASK=${{ matrix.task }}
if [[ $TASK == tox-* ]]; then
TOX_TASK="${TASK#tox-}"
cd hypothesis-python
tox -e $TOX_TASK
else
./build.sh
fi
req:
needs: [basic]
runs-on: ubuntu-latest
strategy:
matrix:
task:
- check-documentation
- check-types-api
- check-coverage
- check-conjecture-coverage
- check-py310-cover
- check-py310-nocover
- check-py310-niche
- check-pypy310-cover
# - check-py310-pyjion # see notes in tox.ini
- check-py311-cover
- check-py311-nocover
- check-py311-niche
- check-pypy311-cover
- check-py312-cover
- check-py312-nocover
- check-py312-niche
- check-py313-cover
- check-py313-nocover
- check-py313-niche
- check-py313t-cover
- check-py313t-nocover
- check-py313t-niche
- check-quality
- check-pytest9
- check-pytest84
- check-pytest74
- check-py311-pytest62
- check-django60
- check-django42
- check-py313-pandas22
- check-py312-pandas21
- check-py311-pandas20
- check-py311-pandas15
- check-py310-pandas14
- check-py310-pandas13
## FIXME: actions update means Python builds without eg _bz2, which was required
# - check-py310-pandas12
# - check-py310-pandas11
fail-fast: false
env:
PYTHON_VERSION: "3.14"
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- name: "Install base for Python ${{ env.PYTHON_VERSION }}"
uses: ./.github/actions/install-base
with:
python-version: ${{ env.PYTHON_VERSION }}
task: ${{ matrix.task }}
- name: Run tests
run: |
export TASK=${{ matrix.task }}
if [[ $TASK == check-crosshair-custom-* ]]; then
GROUP="${TASK#check-crosshair-custom-}"
./build.sh check-crosshair-custom -- -n auto $(cd hypothesis-python && echo tests/$GROUP | xargs -n1 echo | grep -Ev "_py312|_py314" | xargs)
else
./build.sh
fi
- name: Upload coverage data
uses: actions/upload-artifact@v4
# Invoke the magic `always` function to run on both success and failure.
if: ${{ always() && endsWith(matrix.task, '-coverage') }}
with:
name: ${{ matrix.task }}-data
include-hidden-files: true
path: |
hypothesis-python/.coverage*
!hypothesis-python/.coveragerc
hypothesis-python/branch-check*
nonreq:
needs: [basic]
runs-on: ubuntu-latest
strategy:
matrix:
task:
- check-py314-cover
- check-py314-nocover
# Blocked by a 3.14rc1 bug. see
# https://github.com/HypothesisWorks/hypothesis/pull/4490#issuecomment-3144989862.
# Can revisit in 3.14rc2.
# - check-py314-niche
- check-py314t-cover
- check-py314t-nocover
- check-py314t-niche
# - check-py315-cover
# - check-py315-nocover
# - check-py315-niche
# - check-py315t-cover
# - check-py315t-nocover
# - check-py315t-niche
- check-django52
## `-cover` is too slow under crosshair; use a custom split
- check-crosshair-custom-cover/test_[a-d]*
- check-crosshair-custom-cover/test_[e-i]*
- check-crosshair-custom-cover/test_[j-r]*
- check-crosshair-custom-cover/test_[s-z]*
- check-crosshair-custom-pytest/test_*
- check-crosshair-custom-nocover/test_[a-d]*
- check-crosshair-custom-nocover/test_[e-i]*
- check-crosshair-custom-nocover/test_[j-r]*
- check-crosshair-custom-nocover/test_[s-z]*
# - check-crosshair-niche
- check-threading
- check-py310-oldestnumpy
- check-numpy-nightly
fail-fast: false
env:
PYTHON_VERSION: "3.14"
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- name: "Install base for Python ${{ env.PYTHON_VERSION }}"
uses: ./.github/actions/install-base
with:
python-version: ${{ env.PYTHON_VERSION }}
task: ${{ matrix.task }}
- name: Run tests
run: |
export TASK=${{ matrix.task }}
if [[ $TASK == check-crosshair-custom-* ]]; then
GROUP="${TASK#check-crosshair-custom-}"
./build.sh check-crosshair-custom -- -n auto $(cd hypothesis-python && echo tests/$GROUP | xargs -n1 echo | grep -Ev "_py312|_py314" | xargs)
else
./build.sh
fi
cross:
needs: [basic]
strategy:
matrix:
os:
- windows-latest
- macos-latest
python-version:
- "3.11"
- "3.14"
python-architecture:
- null
- "x86"
task:
- cover
- nocover
- rest
- alt-nocover
- alt-rest
exclude:
- { os: macos-latest, python-architecture: "x86" }
- { python-version: "3.14", python-architecture: "x86" }
- { python-version: "3.11", task: nocover }
- { python-version: "3.11", task: rest }
- { python-version: "3.14", task: alt-nocover }
- { python-version: "3.14", task: alt-rest }
fail-fast: false
runs-on: ${{ matrix.os }}
env:
# Override default from tox.ini
PYTHONWARNDEFAULTENCODING: ""
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- name: "Install base for Python ${{ matrix.python-version }} ${{ matrix.python-architecture }}"
uses: ./.github/actions/install-base
with:
python-version: ${{ matrix.python-version }}
python-architecture: ${{ matrix.python-architecture }}
task: ${{ matrix.task }}
- name: Install tox
run: |
pip install --upgrade setuptools pip wheel
pip install tox
- name: Run tests
working-directory: ./hypothesis-python
run: |
tox -e ${{ matrix.task }}
# See https://pyodide.org/en/stable/usage/building-and-testing-packages.html
# and https://github.com/numpy/numpy/blob/9a650391651c8486d8cb8b27b0e75aed5d36033e/.github/workflows/emscripten.yml
test-pyodide:
needs: [basic]
runs-on: ubuntu-latest
env:
NODE_VERSION: 22
# Note that the versions below are updated by `update_pyodide_versions()` in our weekly cronjob.
# The versions of pyodide-build and the Pyodide runtime may differ.
PYODIDE_VERSION: 0.29.3
PYODIDE_BUILD_VERSION: 0.33.0
# pyodide 0.29.0 (latest at time of writing) doesn't yet support 3.14
PYTHON_VERSION: 3.13.2
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- name: "Install base for Python ${{ env.PYTHON_VERSION }}"
uses: ./.github/actions/install-base
with:
python-version: ${{ env.PYTHON_VERSION }}
task: pyodide
- name: Set up Node
uses: actions/setup-node@b39b52d1213e96004bfcb1c61a8a6fa8ab84f3e8 # v4.0.1
with:
node-version: ${{ env.NODE_VERSION }}
- name: Install pyodide-build and Pyodide cross-build environment
run: |
pip install pyodide-build==${{ env.PYODIDE_BUILD_VERSION }}
pyodide xbuildenv install ${{ env.PYODIDE_VERSION }}
- name: Set up Pyodide venv and install dependencies
run: |
pip install --upgrade setuptools pip wheel build
python -m build --wheel hypothesis-python --outdir dist/
pip download --dest=dist/ hypothesis-python/ pytest tzdata # fetch all the wheels
rm dist/packaging-*.whl # fails with `invalid metadata entry 'name'`
pyodide venv .venv-pyodide
source .venv-pyodide/bin/activate
pip install dist/*.whl
- name: Run tests
run: |
source .venv-pyodide/bin/activate
# pyodide can't run multiple processes internally, so parallelize explicitly over
# discovered test files instead (20 at a time)
TEST_FILES=$(ls hypothesis-python/tests/cover/test*.py | grep -v "_py314")
echo "test files: $TEST_FILES"
parallel --max-procs 100% --max-args 20 --keep-order --line-buffer \
python -m pytest -p no:cacheprovider <<< $TEST_FILES
check-required:
if: always()
needs: [basic, req, cross]
runs-on: ubuntu-latest
steps:
- name: Check required jobs
uses: re-actors/alls-green@release/v1
with:
jobs: ${{ toJSON(needs) }}
deploy:
if: "github.event_name == 'push' && github.repository == 'HypothesisWorks/hypothesis'"
runs-on: ubuntu-latest
needs: [check-required]
strategy:
matrix:
task:
- deploy
fail-fast: false
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
token: ${{ secrets.GH_TOKEN }}
- uses: ./.github/actions/install-base
with:
python-version: "3.14"
- name: Deploy package
env:
GH_TOKEN: ${{ secrets.GH_TOKEN }}
TWINE_PASSWORD: ${{ secrets.PYPI_TOKEN }}
run: |
TASK=${{ matrix.task }} ./build.sh
================================================
FILE: .github/workflows/update-deps.yml
================================================
name: Update pinned dependencies
on:
schedule:
- cron: 0 0 * * 0
workflow_dispatch:
jobs:
release:
name: Update pinned dependencies
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Python 3.14
uses: actions/setup-python@v4
with:
python-version: '3.14'
- name: Update pinned dependencies
run: ./build.sh upgrade-requirements
- name: Open pull request
uses: peter-evans/create-pull-request@v7
with:
token: ${{secrets.GH_TOKEN}}
delete-branch: true
title: Update pinned dependencies
body: |
Automatically update pinned dependencies
commit-message: 'Update pinned dependencies'
committer: 'CI on behalf of the Hypothesis team '
author: 'CI on behalf of the Hypothesis team '
================================================
FILE: .github/workflows/website.yml
================================================
name: Build website & deploy to GitHub Pages
on:
# Runs on pushes targeting the default branch
push:
branches: ["master"]
# Allows you to run this workflow manually from the Actions tab
workflow_dispatch:
# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
permissions:
contents: read
pages: write
id-token: write
# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued.
# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete.
concurrency:
group: "pages"
cancel-in-progress: false
jobs:
# Single deploy job since we're just deploying
deploy:
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Build website
run: ./build.sh website
- name: Upload artifact
uses: actions/upload-pages-artifact@v3
with:
path: 'website/output/'
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4
================================================
FILE: .gitignore
================================================
# mypyc
*.so
# misc (editors, file systems, etc)
*.swo
*.swp
.idea
.vagrant
.DS_Store
.hypothesis
.vscode/
.claude/settings.local.json
# generic build components
.runtimes
/hypothesis-python/branch-check*
/pythonpython3.*
/pythonpypy3.*
.pyodide-xbuildenv
# python
*.pyc
*.pyo
venv*
.cache
.pytest_cache
.mypy_cache
docs/_build
*.egg-info
_build
.tox
.coverage
.coverage.*
.pypirc
htmlcov
build
dist
.doctrees/
.v*/
# encrypted files
secrets.tar
secrets
_site/
.sass-cache/
.docker
# =========================
# Operating System Files
# =========================
# OSX
# =========================
.AppleDouble
.LSOverride
# Thumbnails
._*
# Files that might appear on external disk
.Spotlight-V100
.Trashes
# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk
# Windows
# =========================
# Windows image file caches
Thumbs.db
ehthumbs.db
# Folder config file
Desktop.ini
# Recycle Bin used on file shares
$RECYCLE.BIN/
# Windows Installer files
*.cab
*.msi
*.msm
*.msp
# Windows shortcuts
*.lnk
sftp-config.json
# Vim files
*.sw*
__pycache__
.jekyll-metadata
HypothesisWorks.github.io.iml
jekyll.log
/website/output/
/t.py
================================================
FILE: .readthedocs.yml
================================================
# Read the Docs configuration file
# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
# Required
version: 2
# Optionally build your docs in additional formats such as PDF and ePub
formats:
- htmlzip
- epub
# - pdf # busted by latex crash on unicode U+030A combining ring above, in text() docs
# Optionally set the version of Python and requirements required to build your docs
build:
os: ubuntu-22.04
tools:
python: "3.14"
python:
install:
- requirements: requirements/tools.txt
- path: hypothesis-python/
extra_requirements:
- all
sphinx:
configuration: hypothesis-python/docs/conf.py
================================================
FILE: AUTHORS.rst
================================================
--------------------
List of Contributors
--------------------
The primary author for most of Hypothesis is David R. MacIver (me). However the following
people have also contributed work. As well as my thanks, they also have copyright over
their individual contributions.
.. NOTE - this list is in alphabetical order by first name (or handle).
* `A. Jesse Jiryu Davis `_
* `Aaron Meurer `_
* `Adam Johnson `_
* `Adam Matan _`
* `Adam Sven Johnson `_
* `Afrida Tabassum `_ (afrida@gmail.com)
* `Afonso Silva `_ (ajcerejeira@gmail.com)
* `Agriya Khetarpal `_
* `Agustín Covarrubias `_ (gh@agucova.dev)
* `Akash Suresh `_ (akashsuresh36@gmail.com)
* `Alex Gaynor `_
* `Alex Stapleton `_
* `Alex Willmer `_ (alex@moreati.org.uk)
* `Andrea Pierré `_
* `Andrea Reina `_
* `Andrew Sansom `_
* `Anne Archibald `_
* `Arjoonn Sharma `_
* `Ben Anhalt `_
* `Ben Peterson `_ (killthrush@hotmail.com)
* `Benjamin Lee `_ (benjamindlee@me.com)
* `Benjamin Palmer `_
* `Bex Dunn `_ (bex.dunn@gmail.com)
* `Bill Tucker `_ (imbilltucker@gmail.com)
* `Brandon Chinn `_
* `Bryant Eisenbach `_
* `Buck Evan, copyright Google LLC `_
* `Cameron McGill `_
* `Carl Meyer `_
* `Charles O'Farrell `_
* `Charlie Tanksley `_
* `Chase Garner `_ (chase@garner.red)
* `Cheuk Ting Ho `_
* `Chris Down `_
* `Chris van Dronkelaar `_
* `Chris Wesseling `_
* `Christopher Martin `_ (ch.martin@gmail.com)
* `Claudio Jolowicz `_
* `Conrad Ho `_ (conrad.alwin.ho@gmail.com)
* `Cory Benfield `_
* `Cristi Cobzarenco `_ (cristi@reinfer.io)
* `Damon Francisco `_ (damontfrancisco@yahoo.com)
* `Daniel J. West `_
* `Daniel Knell `_ (contact@danielknell.co.uk)
* `David Bonner `_ (dbonner@gmail.com)
* `David Chudzicki `_ (dchudz@gmail.com)
* `David Mascharka `_
* `Dawn E. Collett `_
* `Derek Gustafson `_
* `Dion Misic `_ (dion.misic@gmail.com)
* `Dmitry Dygalo `_
* `Ed Rogers `-
* `Eduardo Enriquez `_ (eduardo.a.enriquez@gmail.com)
* `El Awbery `_
* `Emmanuel Leblond `_
* `Evan Tey `_
* `Felix Divo `_
* `Felix Grünewald `_
* `Felix Sheldon `_
* `Florian Bruhin `_
* `follower `_
* `Francesc Elies `_
* `Gabe Joseph `_
* `Gary Donovan `_
* `Genevieve Mendoza `_
* `George Macon `_
* `Glenn Lehman `_
* `Graham Williamson `_
* `Grant David Bachman `_ (grantbachman@gmail.com)
* `Gregory Petrosyan `_
* `Grzegorz Zieba `_ (g.zieba@erax.pl)
* `Grigorios Giannakopoulos `_
* `Hal Blackburn `_
* `Hugo van Kemenade `_
* `Humberto Rocha `_
* `Ilya Lebedev `_ (melevir@gmail.com)
* `Israel Fruchter `_
* `Ivan Tham `_
* `Iyassou Shimels `_
* `Jack Massey `_
* `Jakub Nabaglo `_ (j@nab.gl)
* `James Lamb `_
* `Jenny Rouleau `_
* `Jens Heinrich `_
* `Jens Tröger `_
* `Jeremy Thurgood `_
* `J.J. Green `_
* `JP Viljoen `_ (froztbyte@froztbyte.net)
* `Jochen Müller `_
* `Joseph Weston `_
* `Joey Tuong `_
* `Jonathan Gayvallet `_ (jonathan.gayvallet@orange.com)
* `Jonty Wareing `_ (jonty@jonty.co.uk)
* `Joshua Boone `_ (joshuaboone4190@gmail.com)
* `Joshua Munn `_ (public@elysee-munn.family)
* `jmhsi `_
* `Justus Magin `_
* `jwg4 `_
* `Kai Chen `_ (kaichen120@gmail.com)
* `Karthikeyan Singaravelan `_ (tir.karthi@gmail.com)
* `Katelyn Gigante `_
* `Katrina Durance `_
* `kbara `_
* `Keeri Tramm `_
* `Kristian Glass `_
* `Krzysztof Przybyła `_
* `Kyle Reeve `_ (krzw92@gmail.com)
* `Lampros Mountrakis `_
* `Lea Provenzano `_
* `Lee Begg `_
* `Liam DeVoe `_
* `Libor Martínek `_
* `Lisa Goeller `_
* `Louis Taylor `_
* `Luke Barone-Adesi `_
* `Lundy Bernard `_
* `Marco Ricci `_
* `Marco Sirabella `_
* `marekventur `_
* `Marius Gedminas `_ (marius@gedmin.as)
* `Markus Unterwaditzer `_ (markus@unterwaditzer.net)
* `Mateusz Sokół `_
* `Mathieu Paturel `_ (mathieu.paturel@gmail.com)
* `Matt Bachmann `_ (bachmann.matt@gmail.com)
* `Matthew Barber `_ (quitesimplymatt@gmail.com)
* `Max Nordlund `_ (max.nordlund@gmail.com)
* `Maxim Kulkin `_ (maxim.kulkin@gmail.com)
* `Mel Seto `_
* `Michel Alexandre Salim `_ (michel@michel-slm.name)
* `mulkieran `_
* `Munir Abdinur `_
* `Nathan Goldbaum `_
* `Nicholas Chammas `_
* `Nick Anyos `_
* `Nick Collins ` _
* `Nick Muoh `_ (nickspirit3@gmail.com)
* `Nicolas Erni `_
* `Nikita Sobolev `_ (mail@sobolevn.me)
* `Oleg Höfling `_ (oleg.hoefling@gmail.com)
* `Paul Ganssle `_ (paul@ganssle.io)
* `Paul Kehrer `_
* `Paul Lorett Amazona `_
* `Paul Stiverson `_
* `Pax (R. Margret) W. `_
* `Peadar Coyle `_ (peadarcoyle@gmail.com)
* `Petr Viktorin `_
* `Phillip Schanely `_ (pschanely@gmail.com)
* `Pierre-Jean Campigotto `_
* `Przemek Konopko `_
* `Reagan Lee `_
* `Richard Boulton `_ (richard@tartarus.org)
* `Richard Scholtens `_ (richardscholtens2@gmail.com)
* `Robert Howlett `_
* `Robert Knight `_ (robertknight@gmail.com)
* `Rodrigo Girão Serrão `_ (rodrigo@mathspp.com)
* `Rónán Carrigan `_ (rcarriga@tcd.ie)
* `Ruben Opdebeeck `_
* `Ryan Soklaski `_ (rsoklaski@gmail.com)
* `Ryan Turner `_ (ryan.turner@uber.com)
* `Sam Bishop (TechDragon) `_ (sam@techdragon.io)
* `Sam Clamons `_ (sclamons@gmail.com)
* `Sam Hames `_
* `Sam Watts `_
* `Sangarshanan `_ (sangarshanan1998@gmail.com)
* `Sanyam Khurana `_
* `Saul Shanabrook `_ (s.shanabrook@gmail.com)
* `Sebastiaan Zeeff `_ (sebastiaan.zeeff@ordina.nl)
* `Sharyar Memon `_ (smemon.cal@gmail.com)
* `Shaun Read `_
* `Shlok Gandhi `_ (shlok.gandhi@gmail.com)
* `Sogata Ray `_ (rayardinanda@gmail.com)
* `Stuart Cook `_
* `SuperStormer `_
* `Sushobhit `_ (sushobhitsolanki@gmail.com)
* `Tariq Khokhar `_ (tariq@khokhar.net)
* `Tessa Bradbury `_
* `Thea Koutsoukis `_
* `Thomas Ball `_ (bomtall1@hotmail.com)
* `Thomas Grainge `_
* `Thomas Kluyver `_ (thomas@kluyver.me.uk)
* `Tim Martin `_ (tim@asymptotic.co.uk)
* `Tom McDermott `_ (sponster@gmail.com)
* `Tom Milligan `_ (code@tommilligan.net)
* `Tyler Gibbons `_ (tyler.gibbons@flexport.com)
* `Tyler Nickerson `_
* `Vidya Rani `_ (vidyarani.d.g@gmail.com)
* `Vince Reuter `_ (vince.reuter@gmail.com)
* `Vincent Michel `_ (vxgmichel@gmail.com)
* `Viorel Pluta `_ (viopluta@gmail.com)
* `Vytautas Strimaitis `_
* `Will Hall `_ (wrsh07@gmail.com)
* `Will Thompson `_ (will@willthompson.co.uk)
* `Wilfred Hughes `_
* `Yiyang Zhan `_
* `Zac Hatfield-Dodds `_ (zac.hatfield.dodds@gmail.com)
* `Zebulun Arendsee `_ (zbwrnz@gmail.com)
================================================
FILE: CITATION.cff
================================================
cff-version: 1.2.0
message: |
If you use Hypothesis as part of a published research project,
please cite our paper in the Journal of Open Source Software:
Text:
MacIver et al., (2019). Hypothesis: A new approach to property-based testing.
Journal of Open Source Software, 4(43), 1891, https://doi.org/10.21105/joss.01891
BibTeX:
@article{MacIver2019Hypothesis,
journal = {Journal of Open Source Software},
doi = {10.21105/joss.01891},
issn = {2475-9066},
number = {43},
publisher = {The Open Journal},
title = {Hypothesis: A new approach to property-based testing},
url = {http://dx.doi.org/10.21105/joss.01891},
volume = {4},
author = {MacIver, David and Hatfield-Dodds, Zac and Contributors, Many},
pages = {1891},
date = {2019-11-21},
year = {2019},
month = {11},
day = {21},
}
To reference a particular version of Hypothesis as a software artifact,
you can use the version-specific DOIs we create for each release under
https://doi.org/10.5281/zenodo.1412597
preferred-citation:
title: 'Hypothesis: A new approach to property-based testing'
date-released: 2019-11-21
type: article
doi: 10.21105/joss.01891
authors:
- family-names: MacIver
given-names: David R.
orcid: https://orcid.org/0000-0002-8635-3223
affiliation: Imperial College London
- family-names: Hatfield-Dodds
given-names: Zac
orcid: https://orcid.org/0000-0002-8646-8362
affiliation: Australian National University
- name: "many other contributors"
# Citation metadata for the software itself, as required by the CFF spec
doi: 10.5281/zenodo.1412597 # Version-independent DOI for the software archive
title: 'Hypothesis: Property-Based Testing for Python'
repository-code: https://github.com/HypothesisWorks/hypothesis
license: MPL-2.0
authors:
- family-names: MacIver
given-names: David R.
orcid: https://orcid.org/0000-0002-8635-3223
affiliation: Imperial College London
- family-names: Hatfield-Dodds
given-names: Zac
orcid: https://orcid.org/0000-0002-8646-8362
affiliation: Australian National University
- name: "many other contributors"
================================================
FILE: CONTRIBUTING.rst
================================================
=============
Contributing
=============
First off: It's great that you want to contribute to Hypothesis! Thanks!
---------------------------------------
Just tell me how to make a pull request
---------------------------------------
1. Make your change and ensure it has adequate tests
2. Create ``hypothesis-python/RELEASE.rst`` with ``RELEASE_TYPE: patch``
for small bugfixes, or ``minor`` for new features. See ``RELEASE-sample.rst``
as an example.
3. Add yourself to the list in ``AUTHORS.rst`` and open a PR!
For more detail, read on; for even more, continue to the ``guides/`` directory!
------------------
Ways to Contribute
------------------
Hypothesis is a mature yet active project. This means that there are many
ways in which you can contribute.
For example, it's super useful and highly appreciated if you do any of:
* Submit bug reports
* Submit feature requests
* Write about Hypothesis
* Give a talk about Hypothesis
* Build libraries and tools on top of Hypothesis outside the main repo
* Submit PRs
If you build a Hypothesis strategy that you would like to be more widely known
please add it to the list of external strategies by preparing a PR against
the docs/strategies.rst file.
If you find an error in the documentation, please feel free to submit a PR that
fixes the error. Spot a tyop? Fix it up and send us a PR!
You can read more about how we document Hypothesis in ``guides/documentation.rst``
The process for submitting source code PRs is generally more involved
(don't worry, we'll help you through it), so do read the rest of this document.
If you're planning a larger change, the contributor guides (in the ``guides/``
directory) will make sure you're on the right track.
----------------------------------
Installing from source and testing
----------------------------------
If you want to install directly from the source code (e.g. because you want to
make changes and install the changed version) you can do this with:
.. code:: bash
pip install -r requirements/test.in
pip install -r requirements/tools.in
pip install -e hypothesis-python/
# You don't need to run the tests, but here's the command:
pytest hypothesis-python/tests/cover/
You may wish to do all of this in a
`virtualenv `_. For example:
.. code:: bash
python3 -m venv .venv
source .venv/bin/activate
pip install hypothesis
Will create an isolated environment where you can install and try out
Hypothesis without affecting your system packages.
-----------------------
Copyright and Licensing
-----------------------
It's important to make sure that you own the rights to the work you are submitting.
If it is done on work time, or you have a particularly onerous contract, make sure
you've checked with your employer.
All work in Hypothesis is licensed under the terms of the
`Mozilla Public License, version 2.0 `_. By
submitting a contribution you are agreeing to licence your work under those
terms.
Finally, if it is not there already, add your name (and a link to your GitHub
and email address if you want) to the list of contributors found in
AUTHORS.rst, in alphabetical order. It doesn't have to be your
"real" name (whatever that means), any sort of public identifier
is fine. In particular a GitHub account is sufficient.
-----------------------
The actual contribution
-----------------------
OK, so you want to make a contribution and have sorted out the legalese. What now?
First off: If you're planning on implementing a new feature, talk to us
first! Come `join us on the mailing list `_,
or open an issue. If it's really small feel free to open a work in progress pull request sketching
out the idea, but it's best to get feedback from the Hypothesis maintainers
before sinking a bunch of work into it.
If you're working on an existing issue, leave a comment so we can try to avoid
duplicating your work before you open a pull request.
In general work-in-progress pull requests are totally welcome if you want early feedback
or help with some of the tricky details. Don't be afraid to ask for help.
In order to get merged, a pull request will have to have a green build (naturally) and
to be approved by a Hypothesis maintainer (and, depending on what it is, possibly specifically
by DRMacIver). Most pull requests will also need to `write a changelog entry in
hypothesis-python/RELEASE.rst `_.
The review process is the same one that all changes to Hypothesis go through, regardless of
whether you're an established maintainer or entirely new to the project. It's very much
intended to be a collaborative one: It's not us telling you what we think is wrong with
your code, it's us working with you to produce something better together.
We have `a lengthy check list `_ of things we look for in a review. Feel
free to have a read of it in advance and go through it yourself if you'd like to. It's not
required, but it might speed up the process.
Once your pull request has a green build and has passed review, it will be merged to
master fairly promptly. This will immediately trigger a release! Don't be scared. If that
breaks things, that's our fault not yours - the whole point of this process is to ensure
that problems get caught before we merge rather than after.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Pull request or external package?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
New strategies can be added to Hypothesis, or published as an external package
on PyPI - either is fine for most strategies. If in doubt, ask!
It's generally much easier to get things working outside, because there's
more freedom to experiment and fewer requirements in stability and API style.
We're happy to review and help with external packages as well as pull requests;
several parts of Hypothesis started life outside and were integrated later
(with permission, of course).
To help people find your package, please use the `Framework :: Hypothesis
`__ `trove classifier
`__. We also recommend naming your package
in the pattern of ``hypothesis-graphql`` and ``hypothesis-protobuf`` on PyPI.
On the other hand, being inside gets you access to some deeper implementation
features (if you need them) and better long-term guarantees about maintenance.
We particularly encourage pull requests for new composable primitives that
make implementing other strategies easier, or for widely used types in the
Python standard library. Strategies for other things are also welcome;
anything with external dependencies just goes in ``hypothesis.extra``.
~~~~~~~~~
The build
~~~~~~~~~
The build is driven by a ``build.sh`` shell script, which delegates to a custom Python-based build system.
Actually running the tests is managed by `tox `_, but the build system
will call out to the relevant tox environments so you mostly don't have to know anything about that
unless you want to make changes to the test config. You also mostly don't need to know anything about the build system
except to type ``./build.sh`` followed by the name of the task you want to run.
All of it will be checked on CI so you don't *have* to run anything locally, but you might
find it useful to do so: A full CI run can take up to twenty minutes,
so running a smaller set of tests locally can be helpful.
The build system should be "fairly" portable, but is currently only known to work on Linux or macOS. It *might* work
on a BSD or on Windows with cygwin installed, but it hasn't been tried. Windows with WSL does work,
as for Linux, and since OS-specific issues are rare for Hypothesis that's pretty useful.
If you try it and find it doesn't work, please do submit patches to fix that.
Some notable commands:
``./build.sh check-coverage`` will verify 100% code coverage by running a
curated subset of the test suite.
``./build.sh check-py311`` (etc.) will run most of the test suite against a
particular python version.
``./build.sh format`` will reformat your code according to the Hypothesis coding style. You should use this before each
commit ideally, but you only really have to use it when you want your code to be ready to merge.
You can also use ``./build.sh check-format``, which will run format and some linting and will then error if you have a
git diff. Note: This will error even if you started with a git diff, so if you've got any uncommitted changes
this will necessarily report an error.
Run ``./build.sh tasks`` for a list of all supported build task names.
Note: The build requires a lot of different versions of python, so rather than have you install them yourself,
the build system will install them itself in a local directory. This means that the first time you run a task you
may have to wait a while as the build downloads and installs the right version of python for you.
~~~~~~~~~~~~~
Running Tests
~~~~~~~~~~~~~
The tasks described above will run all of the tests (e.g. ``check-py311``). But
the ``tox`` task will give finer-grained control over the test runner. At a
high level, the task takes the form:
.. code-block::
./build.sh tox py311-custom 3.11.3 [tox args] -- [pytest args]
Namely, first provide the tox environment (see ``tox.ini``), then the python
version to test with, then any ``tox`` or ``pytest`` args as needed. For
example, to run all of the tests in the file
``tests/nocover/test_conjecture_engine.py`` with python 3.12:
.. code-block::
./build.sh tox py312-custom 3.12.7 -- tests/nocover/test_conjecture_engine.py
See the ``tox`` docs and ``pytest`` docs for more information:
* https://docs.pytest.org/en/latest/how-to/usage.html
* https://tox.wiki/en/latest/config.html#cli
^^^^^^^^^^^
Test Layout
^^^^^^^^^^^
See ``hypothesis-python/tests/README.rst``
^^^^^^^^^^^^^^^^
Useful Arguments
^^^^^^^^^^^^^^^^
Some useful arguments to pytest include:
* You can pass ``-n 0`` to turn off ``pytest-xdist``'s parallel test execution.
Sometimes for running just a small number of tests its startup time is longer
than the time it saves (this will vary from system to system), so this can
be helpful if you find yourself waiting on test runners to start a lot.
* You can use ``-k`` to select a subset of tests to run. This matches on substrings
of the test names. For example ``-kfoo`` will only run tests that have "foo" as
a substring of their name. You can also use composite expressions here.
e.g. ``-k'foo and not bar'`` will run anything containing foo that doesn't
also contain bar. `More information on how to select tests to run can be found
in the pytest documentation `__.
================================================
FILE: LICENSE.txt
================================================
Copyright (c) 2013, David R. MacIver
All code in this repository except where explicitly noted otherwise is released
under the Mozilla Public License v 2.0. You can obtain a copy at https://mozilla.org/MPL/2.0/.
Some code in this repository comes from other projects. Where applicable, the
original copyright and license are noted and any modifications made are released
dual licensed with the original license.
================================================
FILE: README.md
================================================
# Hypothesis
* [Website](https://hypothesis.works/)
* [Documentation](https://hypothesis.readthedocs.io/en/latest/)
* [Source code](https://github.com/hypothesisWorks/hypothesis/)
* [Contributing](https://github.com/HypothesisWorks/hypothesis/blob/master/CONTRIBUTING.rst)
* [Community](https://hypothesis.readthedocs.io/en/latest/community.html)
Hypothesis is the property-based testing library for Python. With Hypothesis, you write tests which should pass for all inputs in whatever range you describe, and let Hypothesis randomly choose which of those inputs to check - including edge cases you might not have thought about. For example:
```python
from hypothesis import given, strategies as st
@given(st.lists(st.integers()))
def test_matches_builtin(ls):
assert sorted(ls) == my_sort(ls)
```
This randomized testing can catch bugs and edge cases that you didn't think of and wouldn't have found. In addition, when Hypothesis does find a bug, it doesn't just report any failing example — it reports the simplest possible one. This makes property-based tests a powerful tool for debugging, as well as testing.
For instance,
```python
def my_sort(ls):
return sorted(set(ls))
```
fails with the simplest possible failing example:
```
Falsifying example: test_matches_builtin(ls=[0, 0])
```
### Installation
To install Hypothesis:
```
pip install hypothesis
```
There are also [optional extras available](https://hypothesis.readthedocs.io/en/latest/extras.html).
================================================
FILE: brand/README.md
================================================
# Logos and other pretty things
Hypothesis has a beautiful logo, thanks to the generous work of Libby Berrie in [issue #1519](https://github.com/HypothesisWorks/hypothesis/issues/1519). The [CogWorks class of 2019](https://github.com/CogWorksBWSI) named the dragonfly "Scout", as a job description and after *To Kill a Mockingbird*.
General guidelines:
- Prefer vector (`.svg`) formats to raster formats (`.png`) wherever possible. However, some viewers don't handle the layers well - e.g. the left eye might be drawn in front of the head - in which case you might prefer `.png`.
- We consider the rainbow version to be canonical. The blue variant is provided for cases such as monochrome versions or printing with a limited palette.
With that in mind, you are welcome to use these logos to refer to Hypothesis - and if you're not sure whether a specific use is OK, please get in touch and ask!
For example, we often bring Hypothesis stickers to conferences but can't make it to everything. If you want to print your own Hypothesis stickers, upload `sticker.png` to [StickerMule](https://www.stickermule.com/custom-stickers) and pick one of the die-cut vinyl options - that's how we get ours!

## Colour palette in GIMP format
A [colour palette in GIMP format](hypothesis.gpl) (`.gpl`) is also provided with the intent of making it easier to produce graphics and documents which reuse the colours in the Hypothesis Dragonfly logo by Libby Berrie.
The `hypothesis.gpl` file should be copied or imported to the appropriate location on your filesystem. For example:
- `/usr/share/inkscape/palettes/` for Inkscape on Ubuntu 18.08
- Edit -> Colors -> Import... then select the `hypothesis.gpl` file in Scribus on Ubuntu 18.08
- Windows -> Dockable Dialogs -> Palettes -> Palettes Menu -> Add Palette -> Import from file... then select the `hypothesis.gpl` file in GIMP on Ubuntu 18.08
Once imported, the colour palette is then available for easy manipulation of colours within the user interface.
Inkscape:

GIMP:

================================================
FILE: brand/hypothesis.gpl
================================================
GIMP Palette
Name: Hypothesis Palette
Columns: 1
# generated by @kathyreid
# from the Dragonfly logo created by @libbyberrie
# the purpose of this file is to make it easy to import colours into
# Inkscape, GIMP, Scribus etc
# Colours deliberately prefixed with `Hypothesis.xxx` to appear grouped in apps
0 0 0 Black (#000000)
255 255 255 White (#FFFFFF)
165 100 250 Hypothesis.Violet (#A564FA)
98 183 255 Hypothesis.DuckEggBlue (#62B7FF)
136 255 127 Hypothesis.AppleGreen (#88FF7F)
255 252 54 Hypothesis.WarmYellow (#FFFC36)
255 202 120 Hypothesis.Apricot (#FFCA78)
255 128 128 Hypothesis.LightCoral (#FF8080)
163 255 247 Hypothesis.Turquoise (#A3FFF7)
163 233 255 Hypothesis.SkyBlue (#A3E9FF)
128 211 231 Hypothesis.DarkSky (#80D3E7)
101 195 213 Hypothesis.MediumTeal (#65C3D5)
92 201 236 Hypothesis.OceanBlue (#5CC9EC)
39 135 178 Hypothesis.DarkTeal (#2787B2)
87 83 139 Hypothesis.OceanPurple (#57538B)
68 66 95 Hypothesis.DarkOceanPurple (#44425F)
================================================
FILE: build.sh
================================================
#!/usr/bin/env bash
# This script is here to bootstrap the Hypothesis build process into a working
# version of Python, then hand over to the actual Hypothesis build runner (which
# is written in Python instead of bash).
if [ -n "${CI:-}" ] ; then echo "::group::Build setup" ; fi
set -o xtrace
set -o errexit
set -o nounset
ROOT="$(git -C "$(dirname "$0")" rev-parse --show-toplevel)"
export HYPOTHESIS_ROOT="$ROOT"
SCRIPTS="$ROOT/tooling/scripts"
# shellcheck source=tooling/scripts/common.sh
source "$SCRIPTS/common.sh"
PYTHON_VERSION="3.14.3"
if [ -n "${GITHUB_ACTIONS-}" ] || [ -n "${CODESPACES-}" ] || [ -n "${CLAUDECODE-}" ] ; then
# We're on GitHub Actions, Codespaces, or Claude Code and already have a suitable Python
PYTHON=$(command -v python3 || command -v python)
else
# Otherwise, we install it from scratch
# NOTE: tooling keeps this version in sync with ci_version in tooling
"$SCRIPTS/ensure-python.sh" "$PYTHON_VERSION"
PYTHON=$(pythonloc "$PYTHON_VERSION")/bin/python
fi
TOOL_REQUIREMENTS="$ROOT/requirements/tools.txt"
# append PYTHON_VERSION to bust caches when we upgrade versions
TOOL_HASH=$( (cat "$TOOL_REQUIREMENTS" && echo "$PYTHON_VERSION") | "$PYTHON" "$SCRIPTS/tool-hash.py")
TOOL_VIRTUALENV="$VIRTUALENVS/build-$TOOL_HASH"
TOOL_PYTHON="$TOOL_VIRTUALENV/bin/python"
export PYTHONPATH="$ROOT/tooling/src"
if ! "$TOOL_PYTHON" -m hypothesistooling check-installed ; then
rm -rf "$TOOL_VIRTUALENV"
if [ -n "${CLAUDECODE-}" ] ; then
# Claude Code: use venv (available) and skip pip upgrades (debian-managed)
"$PYTHON" -m venv "$TOOL_VIRTUALENV"
else
"$PYTHON" -m pip install --upgrade pip
"$PYTHON" -m pip install --upgrade virtualenv
"$PYTHON" -m virtualenv "$TOOL_VIRTUALENV"
fi
"$TOOL_PYTHON" -m pip install --no-warn-script-location -r requirements/tools.txt
fi
if [ -n "${CI:-}" ] ; then echo "::endgroup::" ; fi
"$TOOL_PYTHON" -m hypothesistooling "$@"
================================================
FILE: guides/README.md
================================================
# Guides for Hypothesis Development
This is a general collection of useful documentation for people working on Hypothesis.
It is separate from the main documentation because it is not much use if you are merely *using* Hypothesis. It's purely for working on it, and aimed more at maintainers than casual contributors.
================================================
FILE: guides/api-style.rst
================================================
===============
House API Style
===============
Here are some guidelines for how to write APIs so that they "feel" like
a Hypothesis API. This is particularly focused on writing new strategies, as
that's the major place where we add APIs, but also applies more generally.
Note that it is not a guide to *code* style, only API design.
The Hypothesis style evolves over time, and earlier strategies in particular
may not be consistent with this style, and we've tried some experiments
that didn't work out, so this style guide is more normative than descriptive
and existing APIs may not match it. Where relevant, backwards compatibility is
much more important than conformance to the style.
We also encourage `third-party extensions `_
to follow this style guide, for consistent and user-friendly testing APIs,
or get in touch to discuss changing it if it doesn't fit their domain.
~~~~~~~~~~~~~~~~~~
General Guidelines
~~~~~~~~~~~~~~~~~~
* When writing extras modules, consistency with Hypothesis trumps consistency
with the library you're integrating with.
* *Absolutely no subclassing as part of the public API*
* We should not strive too hard to be pythonic, but if an API seems weird to a
normal Python user we should see if we can come up with an API we like as
much but is less weird.
* Code which adds a dependency on a third party package should be put in a
hypothesis.extra module.
* Complexity should not be pushed onto the user. An easy to use API is more
important than a simple implementation.
~~~~~~~~~~~~~~~~~~~~~~~~~
Guidelines for strategies
~~~~~~~~~~~~~~~~~~~~~~~~~
* A strategy function should be somewhere between a recipe for how to build a
value and a range of valid values.
* It should not include distribution hints. The arguments should only specify
how to produce a valid value, not statistical properties of values.
* Strategies should try to paper over non-uniformity in the underlying types
as much as possible (e.g. ``hypothesis.extra.numpy`` has a number of
workarounds for numpy's odd behaviour around object arrays).
* Strategies should usually default to allowing generation of any example they
can support. The only exceptions should be cases where certain inputs would
trigger test failures which are almost never of interest: currently just
non-UTF8 characters in ``st.text()``, and Numpy array shapes with zero
dimensions or sides of length zero. In each case opting in should be trivial.
~~~~~~~~~~~~~~~~~
Argument handling
~~~~~~~~~~~~~~~~~
We have a reasonably distinctive style when it comes to handling arguments:
* Arguments must be validated to the greatest extent possible. Hypothesis
should reject bad arguments with an InvalidArgument error, not fail with an
internal exception.
* We make extensive use of default arguments. If an argument could reasonably
have a default, it should.
* Exception to the above: strategies for collection types should *not* have a
default argument for element strategies.
* Arguments which have a default value should also be keyword-only, with the
exception of ``min_value`` and ``max_value`` (see "Argument Names" below).
* ``min_value`` and ``max_value`` should default to None for unbounded types
such as integers, and the minimal or maximal values for bounded types such
as datetimes. ``floats()`` is an explicit exception to this rule due to
special handling for infinities and not-a-number.
* Interacting arguments (e.g. arguments that must be in a particular order, or
where at most one is valid, or where one argument restricts the valid range
of the other) are fine, but when this happens the behaviour of defaults
should automatically be adjusted. e.g. if the normal default of an argument
would become invalid, the function should still do the right thing if that
default is used.
* Where the actual default used depends on other arguments, the default parameter
should be None.
* It's worth thinking about the order of arguments: the first one or two
arguments are likely to be passed positionally, so try to put values there
where this is useful and not too confusing.
* When adding arguments to strategies, think carefully about whether the user
is likely to want that value to vary often. If so, make it a strategy instead
of a value. In particular if it's likely to be common that they would want to
write ``some_strategy.flatmap(lambda x: my_new_strategy(argument=x))`` then
it should be a strategy.
* Arguments should not be "a value or a strategy for generating that value".
If you find yourself inclined to write something like that, instead make it
take a strategy. If a user wants to pass a value they can wrap it in a call
to ``just``.
* If a combination of arguments make it impossible to generate anything,
``raise InvalidArgument`` instead of ``return nothing()``. Returning the
null strategy is conceptually nice, but can lead to silently dropping parts
from composed strategies and thus unexpectedly weak tests.
~~~~~~~~~~~~~~
Function Names
~~~~~~~~~~~~~~
We don't have any real consistency here. The rough approach we follow is:
* Names are `snake_case` as is standard in Python.
* Strategies for a particular type are typically named as a plural name for
that type. Where that type has some truncated form (e.g. int, str) we use a
longer form name.
* Other strategies have no particular common naming convention.
~~~~~~~~~~~~~~
Argument Names
~~~~~~~~~~~~~~
We should try to use the same argument names and orders across different
strategies wherever possible. In particular:
* For collection types, the element strategy (or strategies) should always be
the first arguments. Where there is only one element strategy it should be
called ``elements`` (but e.g. ``dictionaries`` has element strategies named
``keys`` and ``values`` and that's fine).
* For ordered types, the first two arguments should be a lower and an upper
bound. They should be called ``min_value`` and ``max_value``.
* Collection types should have a ``min_size`` and a ``max_size`` parameter that
controls the range of their size. ``min_size`` should default to zero and
``max_size`` to ``None`` (even if internally it is bounded).
~~~~~~~~~~~~~~~
Deferred Errors
~~~~~~~~~~~~~~~
As far as is reasonable, functions should raise errors when the test is run
(typically by deferring them until you try to draw from the strategy),
not when they are called.
This mostly applies to strategy functions and some error conditions in
``@given`` itself.
Generally speaking this should be taken care of automatically by use of the
``@defines_strategy`` decorator.
We do not currently do this for the ``TypeError`` that you will get from
calling the function incorrectly (e.g. with invalid keyword arguments or
missing required arguments).
In principle we could, but it would result in much harder to read function
signatures, so we would be trading off one form of comprehensibility for
another, and so far that hasn't seemed to be worth it.
The main reasons for preferring this style are:
* Errors at test import time tend to throw people and be correspondingly hard
for them to debug.
There's an expectation that errors in your test code result in failures in
your tests, and the fact that that test code happens to be defined in a
decorator doesn't seem to change that expectation for people.
* Things like deprecation warnings etc. localize better when they happen
inside the test - test runners will often swallow them or put them in silly
places if they're at import time, but will attach any output that happens
in the test to the test itself.
* There are a lot of cases where raising an error, deprecation warning, etc.
is *only* possible in a test - e.g. if you're using the inline style with
`data `_,
or if you're using
`flatmap `_
or
`@composite `_
then the strategy won't actually get evaluated until we run the test,
so that's the only place they can happen.
It's nice to be consistent, and it's weird if sometimes strategy errors result in
definition time errors and sometimes they result in test errors.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Inferring strategies from specifications
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Functions which infer a strategy from some specification or schema are both
convenient for users, and offer a single source of truth about what inputs
are allegedly valid and actually tested for correctness.
* Such functions should be named "``from_foo()``" and the first argument should
be the thing from which a strategy is inferred - like ``st.from_type()``,
``st.from_regex()``, ``extra.lark.from_lark()``, ``extra.numpy.from_dtype()``,
etc. Any other arguments should be optional keyword-only parameters.
* There should be a smooth path to customise *parts* of an inferred strategy,
i.e. not require the user to start from scratch if they need something a
little more specific. ``from_dtype()`` does this well; ``from_type()`` supports
it by `pointing users to builds() instead `_.
* Where practical, ensure that the ``repr`` of the returned strategy shows
how it was constructed - only using e.g. ``@st.composite`` if required.
For example, ``repr(from_type(int)) == "integers()"``.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
A catalogue of current violations
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The following are places where we currently deviate from this style. Some of
these should be considered targets for deprecation and/or improvement.
* ``hypothesis.extra.numpy`` has some arguments which can be either
strategies or values.
* ``hypothesis.extra.numpy`` assumes arrays are fixed size and doesn't have
``min_size`` and ``max_size`` arguments (but this is probably OK because of
more complicated shapes of array).
* ``hypothesis.stateful`` is a great big subclassing based train wreck.
================================================
FILE: guides/documentation.rst
================================================
=====================================
The Hypothesis Documentation Handbook
=====================================
Good documentation can make the difference between good code and useful code -
and Hypothesis is written to be used, as widely as possible.
This is a working document-in-progress with some tips for how we try to write
our docs, with a little of the what and a bigger chunk of the how.
If you have ideas about how to improve these suggestions, meta issues or pull
requests are just as welcome as for docs or code :D
----------------------------
What docs should be written?
----------------------------
All public APIs should be comprehensively described. If the docs are
confusing to new users, incorrect or out of date, or simply incomplete - we
consider all of those to be bugs; if you see them please raise an issue and
perhaps submit a pull request.
That's not much advice, but it's what we have so far.
------------
Using Sphinx
------------
We use `the Sphinx documentation system `_ to
convert the .rst files into html with formatting and
cross-references. Without repeating the docs for Sphinx, here are some tips:
- When documenting a Python object (function, class, module, etc.), you can
use autodoc to insert and interpret the docstring.
- When referencing a function, you can insert a reference to a function as
(eg) ``:func:`hypothesis.given`\``, which will appear as
``hypothesis.given()`` with a hyperlink to the appropriate docs. You can
show only the last part (unqualified name) by adding a tilde at the start,
like ``:func:`~hypothesis.given`\ `` -> ``given()``. Finally, you can give
it alternative link text in the usual way:
``:func:`other text `\ `` -> ``other text``.
- For the formatting and also hyperlinks, all cross-references should use the
Sphinx cross-referencing syntax rather than plain text.
-----------------
Changelog Entries
-----------------
`Hypothesis does continuous deployment `_,
where every pull request that touches ``./src`` results in a new release.
That means every contributor gets to write their changelog!
A changelog entry should be written in a new ``RELEASE.rst`` file in
the `hypothesis-python` directory. The first line of the file specifies the component
of the version number that will be updated, according to our
`semantic versioning `_ policy.
- ``RELEASE_TYPE: major`` is for breaking changes, and will only be used by the
core team after extensive discussion.
- ``RELEASE_TYPE: minor`` is for anything that adds to the public (ie documented)
API, changes an argument signature, or adds a new deprecation or health check.
Minor (or patch) releases **must not** cause errors in any code that runs
without errors on an earlier version of Hypothesis, using only the public API.
Silent errors *may* be converted to noisy errors, but generally we prefer
to issue a deprecation warning and use the new behaviour if possible.
This stability policy only applies to use of Hypothesis itself, not the
results of user-written tests that use Hypothesis.
- ``RELEASE_TYPE: patch`` is for changes that are not visible in the public
interface, from improving a docstring to backwards-compatible improvements
in shrinking behaviour.
This first line will be removed from the final change log entry.
The remaining lines are the actual changelog text for this release,
which should:
- concisely describe what changed and why
- use Sphinx cross-references to any functions or classes mentioned
- if closing an issue, mention it with the ``:issue:`` role to generate a link
- finish with a note of thanks from the maintainers:
"Thanks to for this bug fix / feature / contribution"
(depending on which it is). If this is your first contribution,
don't forget to add yourself to AUTHORS.rst!
================================================
FILE: guides/internals.rst
================================================
===================================
How to Work on Hypothesis Internals
===================================
This is a guide to how to work on Hypothesis internals,
with a particular focus on helping people who are new to it.
Right now it is very rudimentary and is intended primarily for people who are
looking to get started writing shrink passes as part of our `current outreach
program to get more people doing that `_,
but it will expand over time.
------------------------
Bird's Eye View Concepts
------------------------
The core engine of Hypothesis is called Conjecture.
The "fundamental idea" of Conjecture is that you can represent an arbitrary
randomized test case as the sequence of bytes read from the pseudo-random
number generator (PRNG) that produced it.
Whenever the test did something "random" it actually read the next bytes and
did what they told it to do.
But those bytes didn't *have* to come from a PRNG, and we can run the test
given any byte sequence we like. By manipulating the choice of bytes, we can achieve
more interesting effects than pure randomness would allow us to do, while
retaining the power and ease of use of random testing.
The greatest strength of this idea is that we have a single source of truth
for what an example should look like: Every byte sequence is one that *could*
have come from a PRNG, and thus is a valid thing to try for our test.
The only ways it can fail to be a valid test input are for it to be too short
or for it to not satisfy one of the test's preconditions, and both are easily
detectable.
The idea of shrinking in particular is that once we have this representation,
we can shrink arbitrary test cases based on it. We try to produce a string that
is *shortlex minimal*. What this means is that it has the shortest possible
length and among those strings of minimal length is lexicographically (i.e. the
normal order on strings - find the first byte at which they differ and use that
to decide) smallest.
Ideally we could think of the shrinker as a generic function that takes a
string satisfying some predicate and returns the shortlex minimal string that
also satisfies it.
We depart from this ideal in two ways:
* we can only *approximate* such a minimal string. Finding the actual minimum is
intractable in general.
* we are only interested in minimizing things where the predicate goes through
the Hypothesis API, which lets us track how the data is used and use that to
guide the process.
We then use a number of different transformations of the string to try and
reduce our input. These vary from principled general transformations to shameless
hacks that special case something we need to work well.
One such example of a hack is the handling of floating point numbers. There are
a couple of lexicographic shrinks that are always valid but only really make
sense for our particular encoding of floats. We check if we're working
on something that is of the right size to be a float and apply those
transformations regardless of whether it is actually meant to be a float.
Worst case scenario it's not a float and they don't work, and we've run a few
extra test cases.
--------------------------
Useful Files to Know About
--------------------------
The code associated with Conjecture lives in
`src/hypothesis/internal/conjecture `_.
There are a number of files in there,
but the most important ones are ``engine.py`` and ``data.py``.
``data.py`` defines the core type that is used to represent test cases,
and ``engine.py`` contains the main driver for deciding what test cases to run.
There is also ``minimizer.py``, which contains a general purpose lexicographic
minimizer. This is responsible for taking some byte string and a predicate over
byte strings and producing a string of the same length which is lexicographically
smaller. Unlike the shrinker in general, this *is* supposed to work on arbitrary
predicates and doesn't know anything about the testing API. We typically apply
this to subsets of the bytes for a test input with a predicate that knows how
to integrate those subsets into a larger test. This is the part of the code
that means we can do things like replacing an integer with a smaller one.
-------
Testing
-------
For general information about how to test Hypothesis, take a look at
the `testing guide `_, but there are a couple
of areas that it's worth specifically highlighting for making changes
to the engine:
The first is `tests/conjecture/ `_,
which is a set of unit tests designed to put the engine into particular scenarios to exercise specific behaviours,
with a goal of achieving 100% coverage on it in isolation (though it currently does not quite achieve that for some specific edge cases.
We may fix and enforce this later).
The other set of tests that are worth knowing about are the quality tests,
in `tests/quality `_.
These assert specific hard to satisfy properties about the examples that Hypothesis finds -
either their existence, or something about the final shrunk result.
-----------------------
Engine Design Specifics
-----------------------
There are a couple of code patterns that are mostly peculiar to Conjecture that
you may not have encountered before and are worth being aware of.
~~~~~~~~~~~~~~~~~~~~
Search State Objects
~~~~~~~~~~~~~~~~~~~~
There are a number of cases where we find ourself with a user-provided function
(where the "user" might still be something that is entirely our code) and we
want to pass a whole bunch of different examples to it in order to achieve some
result. Currently this includes each of the main engine, the Shrinker (in
``engine.py``) and the minimizer, but there are likely to be more in future.
We typically organise such things in terms of an object that you create with
the function and possibly an initial argument that stores these on self and
has some ``run`` or similar method. They then run for a while, repeatedly
calling the function they were given.
Generally speaking they do not call the function directly, but instead wrap
calls to it. This allows them to implement a certain amount of decision caching,
e.g. avoiding trying the same shrink twice, but also gives us a place where we
can update metadata about the search process.
For objects whose goal is some form of optimisation (Shrinker, Minimizer) one
of the pieces of metadata they will typically track is a "current target". This
is typically the best example they have seen so far. By wrapping every call to
the predicate, we ensure that we never miss an example even when we're passing
through other things.
For objects whose goal is some broader form of search (currently only
``ConjectureRunner``) this also allows them to keep track of *other* examples
of interest. For example, as part of our multiple bug discovery,
``ConjectureRunner`` keeps track of the smallest example of each distinct
failure that it has seen, and updates this automatically each time the test
function is called. This means that if during shrinking we "slip" and find a
different bug than the one we started with, we will *not* shrink to that, but
it will get remembered by the runner if it was either novel or better than our
current example.
~~~~~~~~~~~
Weird Loops
~~~~~~~~~~~
The loops inside a lot of the engine look very strange and unidiomatic. For
example:
.. code-block:: python
i = 0
while i < len(self.intervals):
u, v = self.intervals[i]
if not self.incorporate_new_buffer(
self.shrink_target.buffer[:u] + self.shrink_target.buffer[v:]
):
i += 1
The more natural way to write this in Python would be:
.. code-block:: python
for u, v in self.intervals:
self.incorporate_new_buffer(
self.shrink_target.buffer[:u] + self.shrink_target.buffer[v:]
)
This is not equivalent in this case, and would exhibit the wrong behaviour.
Every time ``incorporate_new_buffer`` succeeds, it changes the shape of the
current shrink target. This consequently changes the shape of intervals, both
its particular values and its current length - on each loop iteration the loop
might stop either because ``i`` increases or because ``len(self.intervals)``
decreases.
We do not reset ``i`` to zero on success, as this would cause us to retry deleting
things that we have already tried. This *might* work, but is less likely to.
In the event that none of the earlier deletions succeed, this causes us to do
retry the entire prefix uselessly, which can result in a pass taking O(n^2) time
to do O(n) deletions.
An additional quirk is that we only increment ``i`` on failure. The reason for
this is that if we successfully deleted the current interval then the interval
in position ``i`` has been replaced with something else, which is probably the
next thing we would have tried deleting if we hadn't succeeded (or something
like it), so we don't want to advance past it.
This is specific to deletion: If we are just replacing the contents of
something then we expect it to still be in the same place, so there we increment
unconditionally.
Examples of this include ``zero_draws`` and ``minimize_individual_blocks``.
------------
The Shrinker
------------
The shrinking part of Hypothesis is organised into a single class called ``Shrinker``
that lives in ``hypothesis/internal/conjecture/shrinker.py``.
Its job is to take an initial ``ConjectureData`` object and some predicate that
it satisfies, and to try to produce a simpler ``ConjectureData`` object that
also satisfies that predicate.
The search process mostly happens in the ``shrink`` method, which tries various
shrink passes in the ``greedy_shrink`` method and then reports on the outcome.
For details, you are strongly encouraged to read the source code. It is very
well commented, and as the subject of active research often has newer techniques
than are documented here.
~~~~~~~~~~~~~
Search Passes
~~~~~~~~~~~~~
Search passes are methods on the ``Shrinker`` class. They are
designed to take the current shrink target and try a number of things that might
be sensible shrinks of it.
Typically the design of a search pass is that it should always try to run to
completion rather than exiting as soon as it's found something good, but that
it shouldn't retry things that are too like stuff it has already tried just
because something worked. So for example in the above loop, we try deleting
each interval (these roughly correspond to regions of the input that are
responsible for some particular value or small number of adjacent values).
When we succeed, we keep going and try deleting more intervals, but we don't
try to delete any intervals before the current index.
The reason for this is that retrying things from the beginning might work but
probably won't. Thus if we restarted every time we made a change we would end
up doing a lot of useless work. Additionally, they are *more* likely to work
after other shrink passes have run because frequently other changes are likely
to unlock changes in the current pass that were previously impossible. e.g.
when we reorder some examples we might make a big region deletable that
previously contained something critical to the relevant behaviour of the test
but is now just noise.
Because the shrinker runs in a big loop, if we've made progress the shrink pass
will always be run again (assuming we don't hit some limit that terminates the
shrink early, but by making the shrinker better we try to ensure that that
never happens).
This means that we will always get an opportunity to start again later if we
made progress, and if we didn't make progress we've tried everything anyway.
~~~~~~~~~~~~~~~~~~~~~~~
Expensive Shrink Passes
~~~~~~~~~~~~~~~~~~~~~~~
We have a bunch of search passes that are considered "expensive". Typically
this means "quadratic or worse complexity". When shrinking we initially don't
run these, and the first time that we get to the end of our main passes and
have failed to make the input any smaller, we then turn them on.
This allows the shrinker to switch from a good but slightly timid mode while its
input is large into a more aggressive DELETE ALL THE THINGS mode once that stops
working. By that point we've usually made our input small enough that quadratic
complexity is acceptable.
We turn these on once and then they stay on. The reason for this is to avoid a
"flip-flopping" scenario where an expensive pass unlocks one trivial change that
the cheap passes can find and then they get stuck again and have to do an extra
useless run through the passes to prove that.
~~~~~~~~~~~~~~~~~~~~~~
Adaptive Shrink Passes
~~~~~~~~~~~~~~~~~~~~~~
A useful trick that some of the shrink passes use is to try a thing and if it
doesn't work take a look at what the test function did to guess *why* it didn't
work and try to repair that.
Two example such passes are ``zero_examples`` and the various passes that try to
minimize individual blocks lexicographically.
What happens in ``zero_examples`` is that we try replacing the region corresponding
to a draw with all zero bytes. If that doesn't work, we check if that was because
of changing the size of the example (e.g. doing that with a list will make the
list much shorter) and messing up the byte stream after that point. If this
was what happened then we try again with a sequence of zeroes that corresponds
to the size of the draw call in the version we tried that didn't work.
The logic for what we do with block minimization is in ``try_shrinking_blocks``.
When it tries shrinking a block and it doesn't work, it checks if the sized
changed. If it does then it tries deleting the number of bytes that were lost
immediately after the shrunk block to see if it helps.
--------------
Playing Around
--------------
I often find that it is informative to watch the shrink process in action using
Hypothesis's verbosity settings. This can give you an idea of what the format
of your data is, and how the shrink process transforms it.
In particular, it is often useful to run a test with the flag ``-s`` to tell it
not to hide output and the environment variable ``HYPOTHESIS_VERBOSITY_LEVEL=debug``.
This will give you a very detailed log of what the testing process is running,
along with information about what passes in the shrinker rare running and how
they transform it.
---------------
Getting Started
---------------
The best way of getting started on working on the engine is to work on the
shrinker. This is because it has the most well defined problems, the best
documented code among the engine, and it's generally fun to work on.
If you have not already done so, check out `Issue #1093 `_,
which collates a number of other issues about shrink quality that are good starting
points for people.
The best place to get started thus is to take a look at those linked issues and
jump in and try things! Find one that you think sounds fun. Note that some
of them suggest not doing these as your first foray into the shrinker, as some
are harder than others.
*Please* ask questions if you have any - either the main issue for general
purpose questions or specific issues for questions about a particular problem -
if you get stuck or if anything doesn't make sense. We're trying to make this
process easier for everyone to work on, so asking us questions is actively
helpful to us and we will be very grateful to you for doing so.
================================================
FILE: guides/review.rst
================================================
===================================
The Hypothesis Code Review Handbook
===================================
This document outlines the process for reviewing changes to Hypothesis. It's
partly descriptive, partly prescriptive, and entirely prone to change in
response to circumstance and need. We're still figuring this thing out!
-----------------
What Needs Review
-----------------
All changes must be signed off by at least one person
with write access to the repo other than the author of the change.
----------------
How Review Works
----------------
Once the build is green and a reviewer has approved the change, anyone on the
maintainer team may merge the request.
More than one maintainer *may* review a change if they wish to, but it's
not required. Any maintainer may block a pull request by requesting changes.
Consensus on a review is best but not required. If some reviewers have
approved a pull request and some have requested changes, ideally you
would try to address all of the changes, but it is OK to dismiss dissenting
reviews if you feel it appropriate.
We've not tested the case of differing opinions much in practice yet, so
we may grow firmer guidelines on what to do there over time.
------------
Review Goals
------------
At a high level, the two things we're looking for in review are answers
to the following questions:
1. Is this change going to make users' lives worse?
2. Is this change going to make the maintainers' lives worse?
Code review is a collaborative process between the author and the
reviewer to try to ensure that the answer to both of those questions
is no.
Ideally of course the change should also make one or both of the users'
and our lives *better*, but it's OK for changes to be mostly neutral.
The author should be presumed to have a good reason for submitting the
change in the first place, so neutral is good enough!
--------------
Social Factors
--------------
* Always thank external contributors. Thank maintainers too, ideally!
* Remember that the `Code of Conduct `_
applies to pull requests and issues too. Feel free to throw your weight
around to enforce this if necessary.
* Anyone, maintainer or not, is welcome to do a code review. Only official
maintainers have the ability to actually approve and merge a pull
request, but outside review is also welcome.
------------
Requirements
------------
The rest of this document outlines specific things reviewers should
focus on in aid of this, broken up by sections according to their area
of applicability.
All of these conditions must be satisfied for merge. Where the reviewer
thinks this conflicts with the above higher level goals, they may make
an exception if both the author and another maintainer agree.
~~~~~~~~~~~~~
Orthogonality
~~~~~~~~~~~~~
For all minor or patch releases, we enforce a hard and fast rule that they
contain no more than one user-visible change. Major releases are allowed
to bundle multiple changes together, but these should be structured as
smaller pull requests into some tracking branch.
We are currently very bad at this, so reviewers should feel empowered
to be extra strict and provide a lot of push back on this.
What counts as a user visible change is somewhat up to individual
judgement, but you should err in the direction of assuming that
if it might count then it does count.
A good rule of thumb is that if the ``RELEASE.rst`` uses the words "additionally"
or needs bullet points to be clear, it is likely too large.
Ideally changes that are not user visible should also be self-contained
into their own releases, but a certain amount of leniency is permitted -
it's certainly OK to do a moderate amount of refactoring while you're
in the area, and if a pull request involves no release at all then the same
level of orthogonality is not required (but is still desirable).
~~~~~~~~~~~~~~~~~~~~~~
Clarity of Description
~~~~~~~~~~~~~~~~~~~~~~
The ``RELEASE.rst`` should contain a description of the change that
makes clear:
1. The motivation for the change
2. The likely consequences of the change
This doesn't have to be an essay. If you're following the orthogonality
requirements a paragraph or two is likely sufficient.
Any additional information that is useful to reviewers should be provided
in the pull request comment. This can include e.g. background, why the
particular approach was taken, references to internals that are unlikely
to be of interest to users.
~~~~~~~~~~~~~~~~~~~~~
Functionality Changes
~~~~~~~~~~~~~~~~~~~~~
This section applies to any changes in Hypothesis's behaviour, regardless
of their nature. A good rule of thumb is that if it touches a file in
src then it counts.
1. The code should be clear in its intent and behaviour.
2. Behaviour changes should come with appropriate tests to demonstrate
the new behaviour.
3. Hypothesis must never be *flaky*. Flakiness here is
defined as anything where a test fails and this does not indicate
a bug in Hypothesis or in the way the user wrote the code or the test.
4. The changelog (in ``RELEASE.rst``) should bump the minor or patch version
(see guides/documentation.rst for details), accurately describe the
changes, and shouldn't refer to internal-only APIs. For complicated
markup, consider building the docs and manually checking the changelog
for formatting errors that didn't result in a compilation error.
~~~~~~~~~~~
API Changes
~~~~~~~~~~~
Public API changes require the most careful scrutiny of all reviews,
because they are the ones we are stuck with for the longest: Hypothesis
follows semantic versioning, and we don't release new major versions
very often.
Public API changes must satisfy the following:
1. All public API changes must be well documented. If it's not documented,
it doesn't count as public API!
2. Changes must be backwards compatible. Where this is not possible, they
must first introduce a deprecation warning, then once the major version
is bumped the deprecation warning and the functionality may be removed.
3. If an API is deprecated, the deprecation warning must make it clear
how the user should modify their code to adapt to this change (
possibly by referring to documentation).
If the required code change could be automated, the deprecation should have either
`a codemod to fix it `__
or a tracking issue to write one (see "asking for more work" below).
4. If it is likely that we will want to make backwards incompatible changes
to an API later, to whatever extent possible these should be made immediately
when it is introduced instead.
5. APIs should give clear and helpful error messages in response to invalid inputs.
In particular error messages should always display
the value that triggered the error, and ideally be specific about the
relevant feature of it that caused this failure (e.g. the type).
6. Incorrect usage should never "fail silently" - when a user accidentally
misuses an API this should result in an explicit error.
7. Functionality should be limited to that which is easy to support in the
long-term. In particular functionality which is very tied to the
current Hypothesis internals should be avoided.
8. `DRMacIver `_ or
`Zac-HD `_ must approve the changes
though other maintainers are welcome and likely to chip in to review as
well.
9. We have a separate guide for `house API style `_ which should
be followed.
~~~~~~~~~
Bug Fixes
~~~~~~~~~
1. All bug fixes must come with a test that demonstrates the bug on master and
which is fixed in this branch. An exception *may* be made here if the submitter
can convincingly argue that testing this would be prohibitively difficult.
2. Where possible, a fix that makes it impossible for similar bugs to occur is
better.
3. Where possible, a test that will catch both this bug and a more general class
of bug that contains it is better.
~~~~~~~~~~~~~~~~
Settings Changes
~~~~~~~~~~~~~~~~
Note: This section currently only applies to the Python version.
It is tempting to use the Hypothesis settings object as a dumping ground for
anything and everything that you can think of to control Hypothesis. This
rapidly gets confusing for users and should be carefully avoided.
New settings should:
1. Be something that the user can meaningfully have an opinion on. Many of the
settings that have been added to Hypothesis are just cases where Hypothesis
is abdicating responsibility to do the right thing to the user.
2. Make sense without reference to Hypothesis internals.
3. Correspond to behaviour which can meaningfully differ between tests - either
between two different tests or between two different runs of the same test
(e.g. one use case is the profile system, where you might want to run Hypothesis
differently in CI and development). If you would never expect a test suite to
have more than one value for a setting across any of its runs, it should be
some sort of global configuration, not a setting.
When deprecating a setting for later removal, we prefer to change the default
value of the setting to a private singleton (``not_set``), and implement the
future behaviour immediately. Passing any other value triggers a deprecation
warning, but is otherwise a no-op (i.e. we still use the future behaviour).
For settings where this would be especially disruptive, we have also prefixed
that deprecation process with a process where we emit a warning, add a special
value that can be passed to opt-in to the future behaviour, and then in the
following major release we deprecate *that*, make it an no-op, and make it an
error to pass any other value.
~~~~~~~~~~~~~~
Engine Changes
~~~~~~~~~~~~~~
Engine changes are anything that change a "fundamental" of how Hypothesis
works. A good rule of thumb is that an engine change is anything that touches
a file in ``hypothesis.internal.conjecture`` (Python version).
All such changes should:
1. Be approved (or authored) by DRMacIver or Zac-HD.
2. Be approved (or authored) by someone who *isn't* DRMacIver (a major problem
with this section of the code is that there is too much that only DRMacIver
understands properly and we want to fix this).
3. If appropriate, come with a test in test_discovery_ability.py showing new
examples that were previously hard to discover.
4. If appropriate, come with a test in test_shrink_quality.py showing how they
improve the shrinker.
~~~~~~~~~~~~~~~~~~~~~~
Non-Blocking Questions
~~~~~~~~~~~~~~~~~~~~~~
These questions should *not* block merge, but may result in additional
issues or changes being opened, either by the original author or by the
reviewer.
1. Is this change well covered by the review items and is there
anything that could usefully be added to the guidelines to improve
that?
2. Were any of the review items confusing or annoying when reviewing this
change? Could they be improved?
3. Are there any more general changes suggested by this, and do they have
appropriate issues and/or pull requests associated with them?
~~~~~~~~~~~~~~~~~~~~
Asking for more work
~~~~~~~~~~~~~~~~~~~~
Reviewers should in general not request changes that expand the scope of
a pull request beyond its original intended goal. The primary design
philosophy of our work-flow is that making correct changes should be cheap,
and scope creep on pull requests works against that - If you can't touch
something without having to touch a number of related areas as well,
changing things becomes expensive again.
This of course doesn't cover things where additional work is required to
ensure the change is actually correct - for example, if you change public
functionality you certainly need to update its documentation. That isn't
scope creep, that's just the normal scope.
If a pull request suggests additional work then between the reviewer and the
author people should ensure that there are relevant tracking issues for that
work (as per question 3 in "Non-Blocking Questions" above), but there is no
obligation for either of them to actually do any of the work on those issues.
By default it is the reviewer who should open these issues, but the author
is welcome to as well.
That being said, it's legitimate to expand the scope of a pull request in
some cases. For example:
* If not doing so is likely to cause problems later. For example, because
of backwards compatibility requirements it might make sense to ask for some
additional functionality that is likely to be added later so that the arguments
to a function are in a more sensible order.
* Cases where the added functionality feels extremely incomplete in some
way without an additional change. The litmus test here should be "this will
almost never be useful because...". This is still fairly subjective, but at
least one good use case where the change is a clear improvement over the status
quo is enough to indicate that this doesn't apply.
If it's unclear, the reviewer should feel free to suggest additional work
(but if the author is someone new, please make sure that it's clear that this
is a suggestion and not a requirement!), but the author of the pull request should
feel equally free to decline the suggestion.
================================================
FILE: guides/strategies-that-shrink.rst
================================================
===================================
Designing strategies to shrink well
===================================
Reducing test cases to a minimal example is a great feature of Hypothesis,
the implementation of which depends on both the shrinking engine and the
structure of the strategy (or combination of strategies) which created the
example to reduce.
This document is organised into three parts:
1. How to tell if you need to think about shrinking (you probably don't!)
2. Designing for shrinking 'above' the Hypothesis public API
3. Implementation tricks used in our internals, for interested contributors
It is written for people implementing complex third-party strategies (such
as `hypothesis-networkx `__),
current or potential contributors to Hypothesis itself, and anyone interested
in how this works under the hood.
------------------------------------
Do you need to design for shrinking?
------------------------------------
You should only attempt to tune custom strategies for better shrinking
behaviour if more time would otherwise be spent reducing examples by hand
or debugging more complex examples. It *may* be worthwhile if:
- Your custom strategy will be used by many people, so that spending
the same effort tuning the strategy has much larger benefits, or
- You have personally spent time debugging failures which better example
shrinking could have avoided and think this might happen again.
If neither of these apply to you, relax! Hypothesis' test-case reduction
is among the best in the world, and our built-in strategies are carefully
designed to work well with it as discussed below.
------------------------------------
Shrinking for third-party strategies
------------------------------------
That is, strategies built out of other strategies until you get down to
Hypothesis' public API. These often but not always use ``@composite``.
Composition of shrinking
~~~~~~~~~~~~~~~~~~~~~~~~
The first and most important rule is that Hypothesis shrinks from the
'bottom up'. If any component of your strategy is replaced with a simpler
example, the end result should also become simpler. We usually try to define
"simpler" here to match a reasonable intuition about the strategy, and avoid
weird edge cases when it's combined with another strategy or predicate.
`Issue #1076 `_,
where magnitude constraints were added to the ``complex_numbers`` strategy,
makes a nice case study. We wanted to continue shrinking the real and
imaginary parts like ``builds(complex, floats(), floats())``.
In a worst-case scenario, the performance of filtering could be arbitrarily
bad, while a 'generate and scale' approach would mean that simple inputs
could lead to irrational outputs. Instead, we choose an imaginary part
between +/- max_magnitude, then calculate the resulting bounds on the real
part and draw it from a strategy that will always be valid. This ensures
that the imaginary part shrinks to zero first, as we think real-valued
complex numbers are simpler than imaginary-valued complex numbers.
Let generation be lucky
~~~~~~~~~~~~~~~~~~~~~~~
Sometimes, it's worth searching for a particularly nasty value to try.
This trick should be used sparingly, and always behind a branch that the
shrinker can decide not to take such as ``if draw(booleans()):``, but might
occasionally worth trying. Measure the results before you keep it!
`Issue #69 `_ provides
a nice case study: when generating tz-aware datetimes, we would like to generate
instants that are skipped or repeated due to a daylight-savings transition more
often than by chance. Of course, there may or may not be any such moments
allowed by the bounds and tz strategy!
Eliding much of the detail, a key part is to find such a moment between two
endpoints, when we can only check whether one or more exists. The traditional
approach would be to use a binary search, but this would be relatively expensive
to shrink as we would pay the log-n cost on every attempted shrink.
Instead of choosing the midpoint, we draw a *random* point between our known
endpoints, and repeat this until we find a satisfactory moment. This allows
the shrinker to delete all the intermediate draws - and appear lucky enough
to find the moment we were looking for on the first guess!
Keep things local
~~~~~~~~~~~~~~~~~
Hypothesis' shrinking engine sees every example as a labelled tree of choices,
with possible reductions represented as operations on the tree. An attempted
shrink succeeds if the new tree can be converted into an example, and the
resulting example triggers the same bug in the test function.
The most common way we see users breaking data locality is by drawing a size,
then drawing a collection of that size. This is tempting because it's simple
and it _works_, but it's often much slower than the alternatives.
.. code:: python
# Both of these strategies can generate exactly the same kind of examples,
# but the second has better performance as well as style.
integers(0, 10).flatmap(lambda n: st.lists(..., min_size=n, max_size=n))
st.lists(..., min_size=1, max_size=10)
Another easy way to keep things local is to ensure that any ``.filter(...)``
or ``assume(...)`` calls you use are as close as possible to the relevant part
of the strategy. That way, Hypothesis can retry just the part that failed
instead of the entire strategy, which might be much slower.
For efficient shrinking, local operations on the tree should correspond with
valid (and preferably local) shrinks to the final example. For example:
.. code:: python
# This form of loop is hard to shrink, because we'd have to reduce `n` and
# delete something in the loop simultaneously. It's equivalent to the
# `.flatmap` example above. We _do_ shrink this, but much more slowly.
n = draw(integers(0, 10))
for _ in range(n):
...
draw(...)
...
# In this form, the shrinker can see a repeated structure of labels
# and delete one loop iteration without touching anything else.
# We use a variant of this trick to generate collections internally!
while draw(integers(0, x)) > threshold:
...
draw(...)
...
Similarly, it's better to draw all the attributes or inputs you need for an
object at the same time, again so they can be modified or deleted together.
The exact behaviour of the shrinking is a topic of active research and
development, so if you are interested in the details we recommend reading
the `internals guide `_
and the well-commented source code in
``hypothesis.internal.conjecture`` as well as David's ECOOP 2020 paper
`Test-Case Reduction via Test-Case Generation: Insights From the Hypothesis Reducer
`__.
-------------------------------------
Shrinking in the Hypothesis internals
-------------------------------------
The last section is for current or prospective Hypothesis contributors only.
These tricks rely on implementation details that are not available to
third-party libraries or users, **and can change in any patch release**.
Occasionally they are also indispensable to get good performance in underlying
primitives, so please contact us if the public API is not enough and we may
be able to work something out.
What do internals get you?
~~~~~~~~~~~~~~~~~~~~~~~~~~
Using the low-level, internal APIs complements, rather than changing, the
principles above. The bytestream-level view has some important advantages:
Because we operate at the level of bits, the relationship between a value and
the corresponding buffer is much more obvious. If we're careful, that means
we can calculate the value we want and then write the corresponding buffer
to recreate it when the test case is shrunk or replayed.
A small step up from bits, we can also see the spans that indicate a subset
of the buffer to consider for various transformations such as transposition
or deletion.
Sometimes these features are the only way to maintain acceptable performance
in very rare or even pathological cases - consider shrinking a complex number
with a single allowed magnitude - but it's almost certain that someone will
need the core strategies to do just that.
However, using low-level APIs also comes at a cost - they are verbose and
generally more difficult to use, and can violate key invariants of the engine
if misused.
Internally, our strategies mostly use the public API or something that looks
a lot like ``@composite``, so it's fairly easy to follow along. There are
just a few tricks enabled by those low-level advantages that we wanted to
name and document, so we can recognise them discuss them and invent more...
Make your own luck
~~~~~~~~~~~~~~~~~~
This is the simplest trick that uses our ability to write choices to the
buffer. We use it for ``sampled_from(...).filter(...)``, after trying an
initial draw with the usual rejection sampling technique, and added the
``SearchStrategy.do_filtered_draw`` method so other strategies can opt-in
as we design similar tricks for their structure.
It was originally designed for stateful testing, where "lucky generation"
might be inefficient if there are many rules but only a few allowed by their
preconditions. Here's how it works for stateful testing:
1. Draw an index into the unfiltered list of rules. Return the corresponding
rule if it's allowed - we got lucky! (or someone set us up...)
2. Create a list of allowed rules, and choose one from that shortlist instead.
3. Find the index of the chosen rule *in the unfiltered list*, and write that
index to the buffer. Finally, return the chosen rule.
When the shrinker tries to delete the first two draws, the resulting buffer
will lead to the same rule being chosen at step *one* instead. We've made
our own luck!
This trick is especially useful when we want to avoid rejection sampling
(the ``.filter`` method, ``assume``) for performance reasons, but also
need to give the shrinker the same low-level representation for each instance
of a repeated choice.
Flags "shrink open"
~~~~~~~~~~~~~~~~~~~
An important insight from `Swarm Testing (PDF) `__
is that randomly disabling some features can actually reduce the expected time
before finding a bug, because some bugs may be suppressed by otherwise common
features or attributes of the data.
As discussed on `issue #1401 `__,
there are a few points to keep in mind when implementing shrinkable swarm testing:
- You need swarm flags to "shrink open" so that once the shrinker has run to
completion, all flags are enabled. e.g. you could do this by generating a
set of banned flags.
- You need to use rejection sampling rather than anything more clever, or at
least look like it to the shrinker. (see e.g. *Make your own luck*, above)
Taking Unicode as an example, we'd like to use our knowledge of Unicode
categories to generate more complex examples, but shrink the generated string
without reference to categories. While we haven't actually implemented this
yet - it's pretty hairy - the simple version of the idea goes like this:
1. Generate a set of banned categories.
2. Use ``characters().filter(category_is_not_banned)``
When shrinking, we start by removing categories from the banned set, after
which characters in the string can be reduced as usual. In a serious version,
the make-your-own-luck approach would be essential to make the filter
reasonably efficient, but that's not a problem internally.
In more complicated structures, it would be nice to generate the flags on first
use rather than up front before we know if we need them. The trick there is
to write each flag to the buffer every time we check it, in such a way that if
we delete the first use the second turns into an initialisation.
Explicit example boundaries
~~~~~~~~~~~~~~~~~~~~~~~~~~~
This is almost always handled implicitly, e.g. by ``cu.many``, but *sometimes*
it can be useful to explicitly insert boundaries around draws that should be
deleted simultaneously using ``data.start_span``. This is used to group
the value and sign of floating-point numbers, for example, which we split up
in order to provide a more natural shrinking order.
Explicit example management can also be useful to delineate variably-sized
draws, such as our internal helper ``cu.biased_coin``, which makes eliminating
dead bytes much cheaper. Finally, labelling otherwise indistinguishable draws
means the shrinker can attempt to swap only the like values.
================================================
FILE: guides/testing-hypothesis.rst
================================================
==================
Testing Hypothesis
==================
Note: This guide is currently entirely specific to the Python version of
Hypothesis.
This is a guide to the process of testing Hypothesis itself, both how to
run its tests and how to write new ones.
--------------------------
General Testing Philosophy
--------------------------
The test suite for Hypothesis is unusually powerful - as you might hope! -
but the secret is actually more about attitude than technology.
The key is that we treat any bug in Hypothesis as a bug in our test suite
too - and think about the kinds of bugs that might not be caught, then write
tests that would catch them.
We also use a variety of tools to check our code automatically, including
formatting, import order, linting, and typing our API with Mypy.
All of this is checked in CI - which means that once the build is
green, humans can all focus on meaningful review rather than nitpicking
operator spacing.
Similarly, we require all code to have tests with 100% branch coverage - as
a starting point, not the final goal.
- Requiring full coverage can't guarantee that we've written all the tests
worth writing (for example, maybe we left off a useful assertion about the
result), but less than full coverage guarantees that there's some code we're
not testing at all.
- Tests beyond full coverage generally aim to demonstrate that a particular
feature works, or that some subtle failure case is not present - often
because when it was found and fixed, someone wrote a test to make sure it
couldn't come back!
The ``hypothesis-python/tests/`` directory has some notes in the README file on where various
kinds of tests can be found or added. Go there for the practical stuff, or
just ask one of the maintainers for help on a pull request!
Further reading: How `SQLite is tested `_,
`how the Space Shuttle was tested `_,
`how to misuse code coverage `_
(for inspiration, *not* implementation).
Dan Luu writes about `fuzz testing `_ and
`broken processes `_, among other things.
-------------
Running Tests
-------------
Tests are run via ``build.sh``. See ``CONTRIBUTING.rst`` for more details.
================================================
FILE: hypothesis-python/LICENSE.txt
================================================
Copyright (c) 2013, David R. MacIver
All code in this repository except where explicitly noted otherwise is released
under the Mozilla Public License v 2.0. You can obtain a copy at https://mozilla.org/MPL/2.0/.
Some code in this repository comes from other projects. Where applicable, the
original copyright and license are noted and any modifications made are released
dual licensed with the original license.
Mozilla Public License Version 2.0
==================================
1. Definitions
--------------
1.1. "Contributor"
means each individual or legal entity that creates, contributes to
the creation of, or owns Covered Software.
1.2. "Contributor Version"
means the combination of the Contributions of others (if any) used
by a Contributor and that particular Contributor's Contribution.
1.3. "Contribution"
means Covered Software of a particular Contributor.
1.4. "Covered Software"
means Source Code Form to which the initial Contributor has attached
the notice in Exhibit A, the Executable Form of such Source Code
Form, and Modifications of such Source Code Form, in each case
including portions thereof.
1.5. "Incompatible With Secondary Licenses"
means
(a) that the initial Contributor has attached the notice described
in Exhibit B to the Covered Software; or
(b) that the Covered Software was made available under the terms of
version 1.1 or earlier of the License, but not also under the
terms of a Secondary License.
1.6. "Executable Form"
means any form of the work other than Source Code Form.
1.7. "Larger Work"
means a work that combines Covered Software with other material, in
a separate file or files, that is not Covered Software.
1.8. "License"
means this document.
1.9. "Licensable"
means having the right to grant, to the maximum extent possible,
whether at the time of the initial grant or subsequently, any and
all of the rights conveyed by this License.
1.10. "Modifications"
means any of the following:
(a) any file in Source Code Form that results from an addition to,
deletion from, or modification of the contents of Covered
Software; or
(b) any new file in Source Code Form that contains any Covered
Software.
1.11. "Patent Claims" of a Contributor
means any patent claim(s), including without limitation, method,
process, and apparatus claims, in any patent Licensable by such
Contributor that would be infringed, but for the grant of the
License, by the making, using, selling, offering for sale, having
made, import, or transfer of either its Contributions or its
Contributor Version.
1.12. "Secondary License"
means either the GNU General Public License, Version 2.0, the GNU
Lesser General Public License, Version 2.1, the GNU Affero General
Public License, Version 3.0, or any later versions of those
licenses.
1.13. "Source Code Form"
means the form of the work preferred for making modifications.
1.14. "You" (or "Your")
means an individual or a legal entity exercising rights under this
License. For legal entities, "You" includes any entity that
controls, is controlled by, or is under common control with You. For
purposes of this definition, "control" means (a) the power, direct
or indirect, to cause the direction or management of such entity,
whether by contract or otherwise, or (b) ownership of more than
fifty percent (50%) of the outstanding shares or beneficial
ownership of such entity.
2. License Grants and Conditions
--------------------------------
2.1. Grants
Each Contributor hereby grants You a world-wide, royalty-free,
non-exclusive license:
(a) under intellectual property rights (other than patent or trademark)
Licensable by such Contributor to use, reproduce, make available,
modify, display, perform, distribute, and otherwise exploit its
Contributions, either on an unmodified basis, with Modifications, or
as part of a Larger Work; and
(b) under Patent Claims of such Contributor to make, use, sell, offer
for sale, have made, import, and otherwise transfer either its
Contributions or its Contributor Version.
2.2. Effective Date
The licenses granted in Section 2.1 with respect to any Contribution
become effective for each Contribution on the date the Contributor first
distributes such Contribution.
2.3. Limitations on Grant Scope
The licenses granted in this Section 2 are the only rights granted under
this License. No additional rights or licenses will be implied from the
distribution or licensing of Covered Software under this License.
Notwithstanding Section 2.1(b) above, no patent license is granted by a
Contributor:
(a) for any code that a Contributor has removed from Covered Software;
or
(b) for infringements caused by: (i) Your and any other third party's
modifications of Covered Software, or (ii) the combination of its
Contributions with other software (except as part of its Contributor
Version); or
(c) under Patent Claims infringed by Covered Software in the absence of
its Contributions.
This License does not grant any rights in the trademarks, service marks,
or logos of any Contributor (except as may be necessary to comply with
the notice requirements in Section 3.4).
2.4. Subsequent Licenses
No Contributor makes additional grants as a result of Your choice to
distribute the Covered Software under a subsequent version of this
License (see Section 10.2) or under the terms of a Secondary License (if
permitted under the terms of Section 3.3).
2.5. Representation
Each Contributor represents that the Contributor believes its
Contributions are its original creation(s) or it has sufficient rights
to grant the rights to its Contributions conveyed by this License.
2.6. Fair Use
This License is not intended to limit any rights You have under
applicable copyright doctrines of fair use, fair dealing, or other
equivalents.
2.7. Conditions
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
in Section 2.1.
3. Responsibilities
-------------------
3.1. Distribution of Source Form
All distribution of Covered Software in Source Code Form, including any
Modifications that You create or to which You contribute, must be under
the terms of this License. You must inform recipients that the Source
Code Form of the Covered Software is governed by the terms of this
License, and how they can obtain a copy of this License. You may not
attempt to alter or restrict the recipients' rights in the Source Code
Form.
3.2. Distribution of Executable Form
If You distribute Covered Software in Executable Form then:
(a) such Covered Software must also be made available in Source Code
Form, as described in Section 3.1, and You must inform recipients of
the Executable Form how they can obtain a copy of such Source Code
Form by reasonable means in a timely manner, at a charge no more
than the cost of distribution to the recipient; and
(b) You may distribute such Executable Form under the terms of this
License, or sublicense it under different terms, provided that the
license for the Executable Form does not attempt to limit or alter
the recipients' rights in the Source Code Form under this License.
3.3. Distribution of a Larger Work
You may create and distribute a Larger Work under terms of Your choice,
provided that You also comply with the requirements of this License for
the Covered Software. If the Larger Work is a combination of Covered
Software with a work governed by one or more Secondary Licenses, and the
Covered Software is not Incompatible With Secondary Licenses, this
License permits You to additionally distribute such Covered Software
under the terms of such Secondary License(s), so that the recipient of
the Larger Work may, at their option, further distribute the Covered
Software under the terms of either this License or such Secondary
License(s).
3.4. Notices
You may not remove or alter the substance of any license notices
(including copyright notices, patent notices, disclaimers of warranty,
or limitations of liability) contained within the Source Code Form of
the Covered Software, except that You may alter any license notices to
the extent required to remedy known factual inaccuracies.
3.5. Application of Additional Terms
You may choose to offer, and to charge a fee for, warranty, support,
indemnity or liability obligations to one or more recipients of Covered
Software. However, You may do so only on Your own behalf, and not on
behalf of any Contributor. You must make it absolutely clear that any
such warranty, support, indemnity, or liability obligation is offered by
You alone, and You hereby agree to indemnify every Contributor for any
liability incurred by such Contributor as a result of warranty, support,
indemnity or liability terms You offer. You may include additional
disclaimers of warranty and limitations of liability specific to any
jurisdiction.
4. Inability to Comply Due to Statute or Regulation
---------------------------------------------------
If it is impossible for You to comply with any of the terms of this
License with respect to some or all of the Covered Software due to
statute, judicial order, or regulation then You must: (a) comply with
the terms of this License to the maximum extent possible; and (b)
describe the limitations and the code they affect. Such description must
be placed in a text file included with all distributions of the Covered
Software under this License. Except to the extent prohibited by statute
or regulation, such description must be sufficiently detailed for a
recipient of ordinary skill to be able to understand it.
5. Termination
--------------
5.1. The rights granted under this License will terminate automatically
if You fail to comply with any of its terms. However, if You become
compliant, then the rights granted under this License from a particular
Contributor are reinstated (a) provisionally, unless and until such
Contributor explicitly and finally terminates Your grants, and (b) on an
ongoing basis, if such Contributor fails to notify You of the
non-compliance by some reasonable means prior to 60 days after You have
come back into compliance. Moreover, Your grants from a particular
Contributor are reinstated on an ongoing basis if such Contributor
notifies You of the non-compliance by some reasonable means, this is the
first time You have received notice of non-compliance with this License
from such Contributor, and You become compliant prior to 30 days after
Your receipt of the notice.
5.2. If You initiate litigation against any entity by asserting a patent
infringement claim (excluding declaratory judgment actions,
counter-claims, and cross-claims) alleging that a Contributor Version
directly or indirectly infringes any patent, then the rights granted to
You by any and all Contributors for the Covered Software under Section
2.1 of this License shall terminate.
5.3. In the event of termination under Sections 5.1 or 5.2 above, all
end user license agreements (excluding distributors and resellers) which
have been validly granted by You or Your distributors under this License
prior to termination shall survive termination.
************************************************************************
* *
* 6. Disclaimer of Warranty *
* ------------------------- *
* *
* Covered Software is provided under this License on an "as is" *
* basis, without warranty of any kind, either expressed, implied, or *
* statutory, including, without limitation, warranties that the *
* Covered Software is free of defects, merchantable, fit for a *
* particular purpose or non-infringing. The entire risk as to the *
* quality and performance of the Covered Software is with You. *
* Should any Covered Software prove defective in any respect, You *
* (not any Contributor) assume the cost of any necessary servicing, *
* repair, or correction. This disclaimer of warranty constitutes an *
* essential part of this License. No use of any Covered Software is *
* authorized under this License except under this disclaimer. *
* *
************************************************************************
************************************************************************
* *
* 7. Limitation of Liability *
* -------------------------- *
* *
* Under no circumstances and under no legal theory, whether tort *
* (including negligence), contract, or otherwise, shall any *
* Contributor, or anyone who distributes Covered Software as *
* permitted above, be liable to You for any direct, indirect, *
* special, incidental, or consequential damages of any character *
* including, without limitation, damages for lost profits, loss of *
* goodwill, work stoppage, computer failure or malfunction, or any *
* and all other commercial damages or losses, even if such party *
* shall have been informed of the possibility of such damages. This *
* limitation of liability shall not apply to liability for death or *
* personal injury resulting from such party's negligence to the *
* extent applicable law prohibits such limitation. Some *
* jurisdictions do not allow the exclusion or limitation of *
* incidental or consequential damages, so this exclusion and *
* limitation may not apply to You. *
* *
************************************************************************
8. Litigation
-------------
Any litigation relating to this License may be brought only in the
courts of a jurisdiction where the defendant maintains its principal
place of business and such litigation shall be governed by laws of that
jurisdiction, without reference to its conflict-of-law provisions.
Nothing in this Section shall prevent a party's ability to bring
cross-claims or counter-claims.
9. Miscellaneous
----------------
This License represents the complete agreement concerning the subject
matter hereof. If any provision of this License is held to be
unenforceable, such provision shall be reformed only to the extent
necessary to make it enforceable. Any law or regulation which provides
that the language of a contract shall be construed against the drafter
shall not be used to construe this License against a Contributor.
10. Versions of the License
---------------------------
10.1. New Versions
Mozilla Foundation is the license steward. Except as provided in Section
10.3, no one other than the license steward has the right to modify or
publish new versions of this License. Each version will be given a
distinguishing version number.
10.2. Effect of New Versions
You may distribute the Covered Software under the terms of the version
of the License under which You originally received the Covered Software,
or under the terms of any subsequent version published by the license
steward.
10.3. Modified Versions
If you create software not governed by this License, and you want to
create a new license for such software, you may create and use a
modified version of this License if you rename the license and remove
any references to the name of the license steward (except to note that
such modified license differs from this License).
10.4. Distributing Source Code Form that is Incompatible With Secondary
Licenses
If You choose to distribute Source Code Form that is Incompatible With
Secondary Licenses under the terms of this version of the License, the
notice described in Exhibit B of this License must be attached.
Exhibit A - Source Code Form License Notice
-------------------------------------------
This Source Code Form is subject to the terms of the Mozilla Public
License, 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/.
If it is not possible or desirable to put the notice in a particular
file, then You may include the notice in a location (such as a LICENSE
file in a relevant directory) where a recipient would be likely to look
for such a notice.
You may add additional accurate notices of copyright ownership.
Exhibit B - "Incompatible With Secondary Licenses" Notice
---------------------------------------------------------
This Source Code Form is "Incompatible With Secondary Licenses", as
defined by the Mozilla Public License, v. 2.0.
================================================
FILE: hypothesis-python/README.md
================================================
The Hypothesis python readme has moved to [the main readme](../README.md)!
================================================
FILE: hypothesis-python/RELEASE-sample.rst
================================================
RELEASE_TYPE: patch
This patch improves import-detection in :doc:`the Ghostwriter `
(:issue:`3884`), particularly for :func:`~hypothesis.strategies.from_type`
and strategies from ``hypothesis.extra.*``.
Thanks to for this !
---
In the example above, "patch" on the first line should be replaced by
"minor" if changes are visible in the public API, or "major" if there are
breaking changes. Note that only maintainers should ever make a major
release.
The remaining lines are the actual changelog text for this release,
which should:
- concisely describe what changed _in the public API_, and why.
Internal-only changes can be documented as e.g. "This release improves
an internal invariant." (the complete changelog for version 6.99.11)
- use ``double backticks`` for verbatim code,
- use Sphinx cross-references to any functions or classes mentioned:
- :pypi:`package` for links to external packages.
- :func:`package.function` for link to functions, where the link text will
be ``package.function``, or :func:`~package.function` to show ``function``.
- :class:`package.class` for link to classes (abbreviated as above).
- :issue:`issue-number` for referencing issues.
- Similarly, :pull:`pr-number` can be used for PRs, but it's usually
preferred to refer to version numbers with :v:`6.98.9`,
as they are meaningful to end users.
- :doc:`link text ` for documentation references.
- `link text `__
is the same link, for general web addresses.
- finish with a note of thanks from the maintainers. If this is your first
contribution, don't forget to add yourself to AUTHORS.rst!
After the PR is merged, the contents of this file (except the first line)
are automatically added to ``docs/changelog.rst``. More examples can be found
in that file.
================================================
FILE: hypothesis-python/benchmark/README.md
================================================
This directory contains plotting code for our shrinker benchmarking. The code for collecting the data is in `conftest.py`. This directory handles plotting the results.
The plotting script (but not collecting benchmark data) requires additional dependencies: `pip install scipy vl-convert-python`.
To run a benchmark:
- `pytest tests/ -n auto --hypothesis-benchmark-shrinks new --hypothesis-benchmark-output data.json` (starting on the newer version)
- `pytest tests/ -n auto --hypothesis-benchmark-shrinks old --hypothesis-benchmark-output data.json` (after switching to the old version)
- Use the same `data.json` path, the benchmark will append data. You can append `-k ...` for both commands to subset the benchmark.
- `python benchmark/graph.py data.json shrinking.png`
This hooks any `minimal()` calls any reports the number of shrinks. Default (and currently unchangeable) number of iterations is 5 per test.
================================================
FILE: hypothesis-python/benchmark/graph.py
================================================
# This file is part of Hypothesis, which may be found at
# https://github.com/HypothesisWorks/hypothesis/
#
# Copyright the Hypothesis Authors.
# Individual contributors are listed in AUTHORS.rst and the git log.
#
# This Source Code Form is subject to the terms of the Mozilla Public License,
# 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 json
import math
import statistics
from pathlib import Path
import click
def plot_vega(vega_spec, data, *, to, parameters=None):
import vl_convert
parameters = parameters or {}
spec = json.loads(vega_spec.read_text())
spec["data"].insert(0, {"name": "source", "values": data})
if "signals" not in spec:
spec["signals"] = []
for key, value in parameters.items():
spec["signals"].append({"name": key, "value": value})
with open(to, "wb") as f:
# default ppi is 72, which is somewhat blurry.
f.write(vl_convert.vega_to_png(spec, ppi=200))
def _mean_difference_ci(n1, n2, *, confidence):
from scipy import stats
var1 = statistics.variance(n1)
var2 = statistics.variance(n2)
df = len(n1) + len(n2) - 2
# this assumes equal variances between the populations of n1 and n2. This
# is not necessarily true (new might be more consistent than old), but it's
# good enough.
pooled_std = math.sqrt(((len(n1) - 1) * var1 + (len(n2) - 1) * var2) / df)
se = pooled_std * math.sqrt(1 / len(n1) + 1 / len(n2))
t_crit = stats.t.ppf((1 + confidence) / 2, df)
return t_crit * se
def _process_benchmark_data(data):
assert set(data) == {"old", "new"}
old_calls = data["old"]["calls"]
new_calls = data["new"]["calls"]
assert set(old_calls) == set(new_calls), set(old_calls).symmetric_difference(
set(new_calls)
)
graph_data = []
def _diff_times(old, new):
if old == 0 and new == 0:
return 0
if old == 0:
# there aren't any great options here, but 0 is more reasonable than inf.
return 0
v = (old - new) / old
if 0 < v < 1:
v = (1 / (1 - v)) - 1
return v
sums = {"old": 0, "new": 0}
for node_id in old_calls:
old = old_calls[node_id]
new = new_calls[node_id]
if (
set(old) | set(new) == {0}
or len(old) != len(new)
or len(old) == len(new) == 0
):
print(f"skipping {node_id}")
continue
sums["old"] += statistics.mean(old)
sums["new"] += statistics.mean(new)
diffs = [n_old - n_new for n_old, n_new in zip(old, new, strict=True)]
diffs_times = [
_diff_times(n_old, n_new) for n_old, n_new in zip(old, new, strict=True)
]
ci_shrink = (
_mean_difference_ci(old, new, confidence=0.95) if len(old) > 1 else 0
)
graph_data.append(
{
"node_id": node_id,
"absolute": statistics.mean(diffs),
"absolute_ci_lower": ci_shrink,
"absolute_ci_upper": ci_shrink,
"nx": statistics.mean(diffs_times),
"nx_ci_lower": 0,
"nx_ci_upper": 0,
}
)
graph_data = sorted(graph_data, key=lambda d: d["absolute"])
return graph_data, sums
@click.command()
@click.argument("data", type=click.Path(exists=True, path_type=Path))
@click.argument("out", type=click.Path(path_type=Path))
def plot(data, out):
data = json.loads(data.read_text())
data, sums = _process_benchmark_data(data)
plot_vega(
Path(__file__).parent / "spec.json",
data=data,
to=out,
parameters={
"title": "Shrinking benchmark (calls)",
"sum_old": sums["old"],
"sum_new": sums["new"],
"absolute_axis_title": ("shrink call change (old - new, larger is good)"),
},
)
if __name__ == "__main__":
plot()
================================================
FILE: hypothesis-python/benchmark/spec.json
================================================
{
"$schema": "https://vega.github.io/schema/vega/v5.json",
"width": 1600,
"height": 800,
"padding": 5,
"background": "#ffffff",
"title": {
"text": {"signal": "title"},
"fontSize": 18
},
"data": [
{
"name": "example",
"values": [
{"node_id": "test_node_1", "absolute": 100, "absolute_ci_lower": 10, "absolute_ci_upper": 15, "nx": 0.5, "nx_ci_lower": 0.05, "nx_ci_upper": 0.07},
{"node_id": "test_node_2", "absolute": -50, "absolute_ci_lower": 5, "absolute_ci_upper": 8, "nx": -0.2, "nx_ci_lower": 0.02, "nx_ci_upper": 0.03},
{"node_id": "test_node_3", "absolute": 150, "absolute_ci_lower": 15, "absolute_ci_upper": 20, "nx": 0.6, "nx_ci_lower": 0.04, "nx_ci_upper": 0.09},
{"node_id": "test_node_4", "absolute": -120, "absolute_ci_lower": 10, "absolute_ci_upper": 12, "nx": -0.4, "nx_ci_lower": 0.03, "nx_ci_upper": 0.05},
{"node_id": "test_node_5", "absolute": 80, "absolute_ci_lower": 8, "absolute_ci_upper": 10, "nx": 0.3, "nx_ci_lower": 0.02, "nx_ci_upper": 0.04}
]
},
{
"name": "shrink_stats",
"source": "source",
"transform": [
{
"type": "aggregate",
"fields": ["absolute", "absolute", "nx", "nx"],
"ops": ["mean", "sum", "mean", "sum"],
"as": ["mean_shrink", "sum_shrink", "mean_nx", "sum_nx"]
}
]
},
{
"name": "shrink_domain",
"source": "source",
"transform": [
{
"type": "aggregate",
"fields": ["absolute", "absolute", "absolute_ci_lower", "absolute_ci_upper"],
"ops": ["min", "max", "min", "max"],
"as": ["min_value", "max_value", "min_ci", "max_ci"]
},
{
"type": "formula",
"expr": "datum.min_value - datum.min_ci",
"as": "domain_min"
},
{
"type": "formula",
"expr": "datum.max_value + datum.max_ci",
"as": "domain_max"
}
]
},
{
"name": "nx_domain",
"source": "source",
"transform": [
{
"type": "aggregate",
"fields": ["nx", "nx", "nx_ci_lower", "nx_ci_upper"],
"ops": ["min", "max", "min", "max"],
"as": ["min_value", "max_value", "min_ci", "max_ci"]
},
{
"type": "formula",
"expr": "datum.min_value - datum.min_ci",
"as": "domain_min"
},
{
"type": "formula",
"expr": "datum.max_value + datum.max_ci",
"as": "domain_max"
}
]
}
],
"scales": [
{
"name": "x",
"type": "band",
"domain": {"data": "source", "field": "node_id"},
"range": "width",
"padding": 0.2
},
{
"name": "y",
"type": "linear",
"domain": {"data": "shrink_domain", "fields": ["domain_min", "domain_max"]},
"range": "height",
"nice": true,
"zero": true
},
{
"name": "y2",
"type": "linear",
"domain": {"data": "nx_domain", "fields": ["domain_min", "domain_max"]},
"range": "height",
"nice": true,
"zero": true
},
{
"name": "color",
"type": "ordinal",
"domain": ["shrink call change", "n× change"],
"range": ["#4285F4", "#DB4437"]
}
],
"axes": [
{
"orient": "bottom",
"scale": "x",
"labelAngle": -90,
"labelAlign": "right",
"labelBaseline": "middle",
"labelLimit": 300
},
{
"orient": "left",
"scale": "y",
"title": {"signal": "absolute_axis_title"},
"titleColor": "#4285F4",
"tickColor": "#4285F4",
"labelColor": "#4285F4",
"grid": true,
"gridColor": "#e0e0e0",
"gridOpacity": 0.5,
"titleFontSize": 15,
"tickCount": 25
},
{
"orient": "right",
"scale": "y2",
"title": "n× change",
"titleColor": "#DB4437",
"tickColor": "#DB4437",
"labelColor": "#DB4437",
"titleFontSize": 15,
"tickCount": 25
}
],
"marks": [
{
"type": "rule",
"encode": {
"enter": {
"y": {"scale": "y", "value": 0},
"x": {"value": 0},
"x2": {"field": {"group": "width"}},
"stroke": {"value": "#4285F4"},
"strokeWidth": {"value": 1},
"strokeOpacity": {"value": 0.7}
}
}
},
{
"type": "rule",
"encode": {
"enter": {
"y": {"scale": "y2", "value": 0},
"x": {"value": 0},
"x2": {"field": {"group": "width"}},
"stroke": {"value": "#DB4437"},
"strokeWidth": {"value": 1},
"strokeOpacity": {"value": 0.7}
}
}
},
{
"type": "rule",
"from": {"data": "source"},
"encode": {
"enter": {
"x": {"scale": "x", "field": "node_id", "band": 0.5},
"y": {"scale": "y", "field": "absolute", "offset": {"signal": "-datum.absolute_ci_lower * height / (domain('y')[1] - domain('y')[0])"}},
"y2": {"scale": "y", "field": "absolute", "offset": {"signal": "datum.absolute_ci_upper * height / (domain('y')[1] - domain('y')[0])"}},
"stroke": {"value": "#4285F4"},
"strokeOpacity": {"value": 0.5}
}
}
},
{
"type": "rect",
"from": {"data": "source"},
"encode": {
"enter": {
"x": {"scale": "x", "field": "node_id", "band": 0.5, "offset": -5},
"width": {"value": 10},
"y": {"scale": "y", "field": "absolute", "offset": {"signal": "-datum.absolute_ci_lower * height / (domain('y')[1] - domain('y')[0])"}},
"height": {"value": 1},
"fill": {"value": "#4285F4"},
"fillOpacity": {"value": 0.5}
}
}
},
{
"type": "rect",
"from": {"data": "source"},
"encode": {
"enter": {
"x": {"scale": "x", "field": "node_id", "band": 0.5, "offset": -5},
"width": {"value": 10},
"y": {"scale": "y", "field": "absolute", "offset": {"signal": "datum.absolute_ci_upper * height / (domain('y')[1] - domain('y')[0])"}},
"height": {"value": 1},
"fill": {"value": "#4285F4"},
"fillOpacity": {"value": 0.5}
}
}
},
{
"type": "symbol",
"from": {"data": "source"},
"encode": {
"enter": {
"x": {"scale": "x", "field": "node_id", "band": 0.5},
"y": {"scale": "y", "field": "absolute"},
"fill": {"value": "#4285F4"},
"size": {"value": 100},
"opacity": {"value": 0.35}
}
}
},
{
"type": "symbol",
"from": {"data": "source"},
"encode": {
"enter": {
"x": {"scale": "x", "field": "node_id", "band": 0.5},
"y": {"scale": "y", "field": "absolute"},
"fill": {"value": "#4285F4"},
"size": {"value": 38},
"opacity": {"value": 0.85}
}
}
},
{
"type": "rule",
"from": {"data": "source"},
"encode": {
"enter": {
"x": {"scale": "x", "field": "node_id", "band": 0.5},
"y": {"scale": "y2", "field": "nx", "offset": {"signal": "-datum.nx_ci_lower * height / (domain('y2')[1] - domain('y2')[0])"}},
"y2": {"scale": "y2", "field": "nx", "offset": {"signal": "datum.nx_ci_upper * height / (domain('y2')[1] - domain('y2')[0])"}},
"stroke": {"value": "#DB4437"},
"strokeOpacity": {"value": 0.5}
}
}
},
{
"type": "rect",
"from": {"data": "source"},
"encode": {
"enter": {
"x": {"scale": "x", "field": "node_id", "band": 0.5, "offset": -5},
"width": {"value": 10},
"y": {"scale": "y2", "field": "nx", "offset": {"signal": "-datum.nx_ci_lower * height / (domain('y2')[1] - domain('y2')[0])"}},
"height": {"value": 1},
"fill": {"value": "#DB4437"},
"fillOpacity": {"value": 0.5}
}
}
},
{
"type": "rect",
"from": {"data": "source"},
"encode": {
"enter": {
"x": {"scale": "x", "field": "node_id", "band": 0.5, "offset": -5},
"width": {"value": 10},
"y": {"scale": "y2", "field": "nx", "offset": {"signal": "datum.nx_ci_upper * height / (domain('y2')[1] - domain('y2')[0])"}},
"height": {"value": 1},
"fill": {"value": "#DB4437"},
"fillOpacity": {"value": 0.5}
}
}
},
{
"type": "symbol",
"from": {"data": "source"},
"encode": {
"enter": {
"x": {"scale": "x", "field": "node_id", "band": 0.5},
"y": {"scale": "y2", "field": "nx"},
"fill": {"value": "#DB4437"},
"size": {"value": 100},
"opacity": {"value": 0.35}
}
}
},
{
"type": "symbol",
"from": {"data": "source"},
"encode": {
"enter": {
"x": {"scale": "x", "field": "node_id", "band": 0.5},
"y": {"scale": "y2", "field": "nx"},
"fill": {"value": "#DB4437"},
"size": {"value": 38},
"opacity": {"value": 0.85}
}
}
},
{
"type": "group",
"encode": {
"enter": {
"x": {"value": 1630},
"y": {"value": 20},
"width": {"value": 140},
"height": {"value": 130},
"cornerRadius": {"value": 5},
"fill": {"value": "#f0f0f0"},
"stroke": {"value": "#cccccc"}
}
},
"marks": [
{
"type": "text",
"from": {"data": "shrink_stats"},
"encode": {
"enter": {
"x": {"value": 10},
"y": {"value": 20},
"text": {"signal": "'Mean: ' + format(datum.mean_shrink, ',.1f')"},
"fontSize": {"value": 14},
"fill": {"value": "#4285F4"}
}
}
},
{
"type": "text",
"from": {"data": "shrink_stats"},
"encode": {
"enter": {
"x": {"value": 10},
"y": {"value": 40},
"text": {"signal": "'Sum: ' + format(datum.sum_shrink, ',d')"},
"fontSize": {"value": 14},
"fill": {"value": "#4285F4"}
}
}
},
{
"type": "text",
"from": {"data": "shrink_stats"},
"encode": {
"enter": {
"x": {"value": 10},
"y": {"value": 60},
"text": {"signal": "'Mean: ' + format(datum.mean_nx, ',.1f')"},
"fontSize": {"value": 14},
"fill": {"value": "#DB4437"}
}
}
},
{
"type": "text",
"encode": {
"enter": {
"x": {"value": 10},
"y": {"value": 80},
"text": {"signal": "'sum(old): ' + format(sum_old, ',.1f')"},
"fontSize": {"value": 14},
"fill": {"value": "#505050"}
}
}
},
{
"type": "text",
"encode": {
"enter": {
"x": {"value": 10},
"y": {"value": 100},
"text": {"signal": "'sum(new): ' + format(sum_new, ',.1f')"},
"fontSize": {"value": 14},
"fill": {"value": "#505050"}
}
}
},
{
"type": "text",
"encode": {
"enter": {
"x": {"value": 10},
"y": {"value": 120},
"text": {"signal": "'old / new: ' + format(sum_old / sum_new, ',.2f')"},
"fontSize": {"value": 14},
"fill": {"value": "#505050"}
}
}
}
]
}
]
}
================================================
FILE: hypothesis-python/docs/_ext/hypothesis_linkcheck.py
================================================
# This file is part of Hypothesis, which may be found at
# https://github.com/HypothesisWorks/hypothesis/
#
# Copyright the Hypothesis Authors.
# Individual contributors are listed in AUTHORS.rst and the git log.
#
# This Source Code Form is subject to the terms of the Mozilla Public License,
# 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 sphinx
from sphinx.application import Sphinx
from sphinx.builders.linkcheck import HyperlinkAvailabilityChecker
# We want to customize the linkcheck behavior so that references from intersphinx
# mappings are not checked. We use these liberally and don't want to spend CI time
# checking their validity. If it's in an inventory, sphinx should guarantee
# it's valid, sans very weird things happening.
#
# Sphinx splits the link check logic across a CheckExternalLinksBuilder builder
# and a HyperlinkCollector post_transform (and a HyperlinkAvailabilityChecker
# helper class). There are various points in each where we could add this
# ignore-intersphinx hook.
#
# Monkey-patching HyperlinkAvailabilityChecker isn't great, but is the best way
# I found to go about this.
# set by on_builder_inited
inventories = {}
def is_intersphinx_link(uri):
for inventory in inventories.values():
uris = {uri for _name, _version, uri, _display_name in inventory.values()}
if uri in uris:
return True
return False
class HypothesisLinkChecker(HyperlinkAvailabilityChecker):
def is_ignored_uri(self, uri: str) -> bool:
if is_intersphinx_link(uri):
return True
return super().is_ignored_uri(uri)
sphinx.builders.linkcheck.HyperlinkAvailabilityChecker = HypothesisLinkChecker
# Hook the builder to get access to the intersphinx inventory. app.env is not
# available in setup()
def on_builder_inited(app: Sphinx) -> None:
global inventories
inventories = getattr(app.env, "intersphinx_inventory", {})
def setup(app: Sphinx):
app.connect("builder-inited", on_builder_inited)
================================================
FILE: hypothesis-python/docs/_ext/hypothesis_redirects.py
================================================
# This file is part of Hypothesis, which may be found at
# https://github.com/HypothesisWorks/hypothesis/
#
# Copyright the Hypothesis Authors.
# Individual contributors are listed in AUTHORS.rst and the git log.
#
# This Source Code Form is subject to the terms of the Mozilla Public License,
# 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/.
# vendored from https://github.com/documatt/sphinx-reredirects under the MIT
# license, with thanks
# ruff: noqa: G004
import re
from collections.abc import Mapping, Sequence
from fnmatch import fnmatch
from pathlib import Path
from string import Template
from urllib.parse import urlparse
from sphinx.application import Sphinx
from sphinx.builders.linkcheck import CheckExternalLinksBuilder, Hyperlink
from sphinx.util import logging
from sphinx.util.osutil import SEP
OPTION_REDIRECTS = "redirects"
OPTION_REDIRECTS_DEFAULT: dict[str, str] = {}
OPTION_TEMPLATE_FILE = "redirect_html_template_file"
OPTION_TEMPLATE_FILE_DEFAULT = None
REDIRECT_FILE_DEFAULT_TEMPLATE = (
''
)
logger = logging.getLogger(__name__)
wildcard_pattern = re.compile(r"[\*\?\[\]]")
def setup(app: Sphinx) -> dict:
"""
Extension setup, called by Sphinx
"""
app.connect("html-collect-pages", init)
app.connect("builder-inited", collect_redirects_for_linkcheck)
app.add_config_value(OPTION_REDIRECTS, OPTION_REDIRECTS_DEFAULT, "env")
app.add_config_value(OPTION_TEMPLATE_FILE, OPTION_TEMPLATE_FILE_DEFAULT, "env")
return {"parallel_read_safe": True}
def init(app: Sphinx) -> Sequence | None:
if not app.config[OPTION_REDIRECTS]:
logger.debug("No redirects configured")
return []
rr = Reredirects(app)
to_be_redirected = rr.grab_redirects()
rr.create_redirects(to_be_redirected)
# html-collect-pages requires to return iterable of pages to write,
# we have no additional pages to write
return []
class Reredirects:
def __init__(self, app: Sphinx) -> None:
self.app = app
self.redirects_option: dict[str, str] = getattr(app.config, OPTION_REDIRECTS)
self.template_file_option: str = getattr(app.config, OPTION_TEMPLATE_FILE)
def grab_redirects(self) -> Mapping[str, str]:
"""Inspect redirects option in conf.py and returns dict mapping
docname to target (with expanded placeholder)."""
# docname-target dict
to_be_redirected = {}
# For each source-target redirect pair in conf.py
for source, target in self.redirects_option.items():
# no wildcard, append source as-is
if not self._contains_wildcard(source):
to_be_redirected[source] = target
continue
assert self.app.env
# wildcarded source, expand to docnames
expanded_docs = [
doc for doc in self.app.env.found_docs if fnmatch(doc, source)
]
if not expanded_docs:
logger.warning(f"No documents match to '{source}' redirect.")
continue
for doc in expanded_docs:
new_target = self._apply_placeholders(doc, target)
to_be_redirected[doc] = new_target
return to_be_redirected
def docname_out_path(self, docname: str, suffix: str) -> Sequence[str]:
"""
For a Sphinx docname (the path to a source document without suffix),
returns path to outfile that would be created by the used builder.
"""
# Return as-is, if the docname already has been passed with a suffix
if docname.endswith(suffix):
return [docname]
# Remove any trailing slashes, except for "/"" index
if len(docname) > 1 and docname.endswith(SEP):
docname = docname.rstrip(SEP)
# Figure out whether we have dirhtml builder
out_uri = self.app.builder.get_target_uri(docname=docname) # type: ignore
if not out_uri.endswith(suffix):
# If dirhtml builder is used, need to append "index"
return [out_uri, "index"]
# Otherwise, convert e.g. 'source' to 'source.html'
return [out_uri]
def create_redirects(self, to_be_redirected: Mapping[str, str]) -> None:
"""Create actual redirect file for each pair in passed mapping of
docnames to targets."""
# Corresponds to value of `html_file_suffix`, but takes into account
# modifications done by the builder class
try:
suffix = self.app.builder.out_suffix # type: ignore
except Exception:
suffix = ".html"
for docname, target in to_be_redirected.items():
out = self.docname_out_path(docname, suffix)
redirect_file_abs = Path(self.app.outdir).joinpath(*out).with_suffix(suffix)
redirect_file_rel = redirect_file_abs.relative_to(self.app.outdir)
if redirect_file_abs.exists():
logger.info(
f"Overwriting '{redirect_file_rel}' with redirect to '{target}'."
)
else:
logger.info(f"Creating redirect '{redirect_file_rel}' to '{target}'.")
self._create_redirect_file(redirect_file_abs, target)
@staticmethod
def _contains_wildcard(text: str) -> bool:
"""Tells whether passed argument contains wildcard characters."""
return bool(wildcard_pattern.search(text))
@staticmethod
def _apply_placeholders(source: str, target: str) -> str:
"""Expand "source" placeholder in target and return it"""
return Template(target).substitute({"source": source})
def _create_redirect_file(self, at_path: Path, to_uri: str) -> None:
"""Actually create a redirect file according to redirect template"""
content = self._render_redirect_template(to_uri)
# create any missing parent folders
at_path.parent.mkdir(parents=True, exist_ok=True)
at_path.write_text(content, encoding="utf-8")
def _render_redirect_template(self, to_uri: str) -> str:
# HTML used as redirect file content
redirect_template = REDIRECT_FILE_DEFAULT_TEMPLATE
if self.template_file_option:
redirect_file_abs = Path(self.app.srcdir, self.template_file_option)
redirect_template = redirect_file_abs.read_text(encoding="utf-8")
return Template(redirect_template).substitute({"to_uri": to_uri})
def collect_redirects_for_linkcheck(app):
# Ignore when not invoked with linkcheck builder
if not isinstance(app.builder, CheckExternalLinksBuilder):
return
redirects = Reredirects(app).grab_redirects()
for docname, target in redirects.items():
# Give a Sphinx or extensions change to modify original target URL
if new_target := app.emit_firstresult("linkcheck-process-uri", target):
target = new_target
if urlparse(target).scheme not in ("http", "https"):
# Checking redirects to other pages of the same documentation is not
# supported for now.
continue
# Add target external URL to hyperlinks which linkcheck builder will check
docpath = app.env.doc2path(docname)
hyperlink = Hyperlink(uri=target, docname=docname, docpath=docpath, lineno=-1)
app.builder.hyperlinks[target] = hyperlink
================================================
FILE: hypothesis-python/docs/_static/better-signatures.css
================================================
/* dl gets used both for defining each top-level `.. autofunc` on a page (where we want vertical margsin)
and is wrapped around multiline signatures (where we don't).
If a dl is being used inside a .sig, that's a multiline signature; remove its margins. */
.sig > dl {
margin-block-start: 0rem;
margin-block-end: 0rem;
}
/* with thanks to https://github.com/pradyunsg/furo/discussions/749 */
.sig:not(.sig-inline) {
padding-left: 0.5em;
text-indent: 0;
}
================================================
FILE: hypothesis-python/docs/_static/dark-fix.css
================================================
/*
See https://github.com/HypothesisWorks/hypothesis/issues/4588 and
https://github.com/pradyunsg/furo/discussions/909. Once this is fixed in furo,
we can remove this.
*/
body[data-theme="dark"] .tooltip .tooltip-content {
background-color: var(--color-background-primary);
}
body[data-theme="dark"] .tooltip .arrow {
background: var(--color-background-primary);
}
================================================
FILE: hypothesis-python/docs/_static/no-scroll.css
================================================
/* disable autoscroll-to-target behavior
https://github.com/pradyunsg/furo/discussions/384#discussioncomment-2249243 */
html {
scroll-behavior: auto;
}
================================================
FILE: hypothesis-python/docs/_static/wrap-in-tables.css
================================================
/* override table width restrictions */
/* thanks to https://github.com/readthedocs/sphinx_rtd_theme/issues/117#issuecomment-153083280 */
@media screen and (min-width: 767px) {
.wy-table-responsive table td {
/* !important prevents the common CSS stylesheets from
overriding this as on RTD they are loaded after this stylesheet */
white-space: normal !important;
}
.wy-table-responsive {
overflow: visible !important;
}
}
================================================
FILE: hypothesis-python/docs/changelog.rst
================================================
=========
Changelog
=========
This is a record of all past Hypothesis releases and what went into them,
in reverse chronological order. All previous releases are still available
:pypi:`on PyPI `.
Hypothesis 6.x
==============
.. only:: has_release_file
--------------------
Current pull request
--------------------
.. include:: ../RELEASE.rst
.. _v6.151.9:
--------------------
6.151.9 - 2026-02-16
--------------------
Remove some old unused code.
.. _v6.151.8:
--------------------
6.151.8 - 2026-02-16
--------------------
This patch fixes a crash when :obj:`sys.modules` contains unhashable values,
such as :class:`~types.SimpleNamespace` objects (:issue:`4660`).
.. _v6.151.7:
--------------------
6.151.7 - 2026-02-16
--------------------
This patch updates our vendored `list of top-level domains `__,
which is used by the provisional :func:`~hypothesis.provisional.domains` strategy.
.. _v6.151.6:
--------------------
6.151.6 - 2026-02-11
--------------------
This patch fixes several duplicate word typos in comments and documentation.
.. _v6.151.5:
--------------------
6.151.5 - 2026-02-03
--------------------
This patch teaches our pytest plugin to :ref:` find interesting constants `
when pytest is collecting tests, to avoid arbitrarily attributing the latency
to whichever test function happened to be executed first (:issue:`4627`).
.. _v6.151.4:
--------------------
6.151.4 - 2026-01-29
--------------------
This patch adjusts how we compute the stopping threshold introduced in :version:`6.151.3`, while still maintaining 99% confidence that <1% of test cases pass.
.. _v6.151.3:
--------------------
6.151.3 - 2026-01-28
--------------------
This patch makes Hypothesis more tolerant of slow-to-satisfy ``assume()`` calls.
Previously, Hypothesis would give up after ``max_examples * 10`` attempts; now it
uses a statistical test to stop only when 99% confident that <1% of examples
would pass (:issue:`4623`).
Thanks to @ajdavis for this improvement!
.. _v6.151.2:
--------------------
6.151.2 - 2026-01-26
--------------------
Format our code with the latest version of :pypi:`black`.
.. _v6.151.1:
--------------------
6.151.1 - 2026-01-26
--------------------
Improve internal categorization of test cases when an :ref:`alternative backend ` raises |BackendCannotProceed|.
.. _v6.151.0:
--------------------
6.151.0 - 2026-01-25
--------------------
Add 2025.12 to the list of recognized Array API versions in
``hypothesis.extra.array_api``.
.. _v6.150.3:
--------------------
6.150.3 - 2026-01-23
--------------------
Hypothesis now generates powers of 2 more often when using |st.integers|.
.. _v6.150.2:
--------------------
6.150.2 - 2026-01-13
--------------------
Update some internal type hints.
.. _v6.150.1:
--------------------
6.150.1 - 2026-01-12
--------------------
This patch fixes a bug where |st.recursive| would fail in cases where the
``extend=`` function does not reference it's argument - which was assumed
by the recent ``min_leaves=`` feature, because the strategy can't actually
recurse otherwise. (:issue:`4638`)
Now, the historical behavior is working-but-deprecated, or an error if you
explicitly pass ``min_leaves=``.
.. _v6.150.0:
--------------------
6.150.0 - 2026-01-06
--------------------
This release adds a ``min_leaves`` argument to :func:`~hypothesis.strategies.recursive`,
which ensures that generated recursive structures have at least the specified number
of leaf nodes (:issue:`4205`).
.. _v6.149.1:
--------------------
6.149.1 - 2026-01-05
--------------------
Add type hints to an internal class.
.. _v6.149.0:
--------------------
6.149.0 - 2026-01-05
--------------------
This release extends the explain-phase ``# or any other generated value`` comments
to sub-arguments within :func:`~hypothesis.strategies.builds`,
:func:`~hypothesis.strategies.tuples`, and :func:`~hypothesis.strategies.fixed_dictionaries`.
Previously, these comments only appeared on top-level test arguments. Now, when
the explain phase determines that a sub-argument can vary freely without affecting
the test failure, you'll see comments like::
Falsifying example: test_foo(
obj=MyClass(
x=0, # or any other generated value
y=True,
),
data=(
'', # or any other generated value
42,
),
)
This makes it easier to understand which parts of complex inputs actually matter
for reproducing a failure.
.. _v6.148.13:
---------------------
6.148.13 - 2026-01-05
---------------------
Clean up an internal helper.
.. _v6.148.12:
---------------------
6.148.12 - 2026-01-04
---------------------
This patch fixes :func:`~hypothesis.strategies.from_type` to properly handle
parameterized type aliases created with Python 3.12+'s :pep:`695` ``type``
statement. For example, ``st.from_type(A[int])`` where ``type A[T] = list[T]``
now correctly resolves to ``lists(integers())`` instead of raising a
``TypeError`` (:issue:`4628`).
.. _v6.148.11:
---------------------
6.148.11 - 2026-01-03
---------------------
Hypothesis now prints a |Verbosity.verbose| log when we switch away from an :ref:`alternative backend `.
.. _v6.148.10:
---------------------
6.148.10 - 2026-01-03
---------------------
Fixes :ref:`Ghostwriter ` output for :pypi:`numpy` >= 2.4.0. Also adds support |st.from_type| for :pypi:`numpy` 2.5.0 nightly (which has not yet been released).
.. _v6.148.9:
--------------------
6.148.9 - 2026-01-01
--------------------
|.example| no longer emits |NonInteractiveExampleWarning| when running a python file directly. This means that e.g. ``python my_sandbox.py`` during exploratory work with |.example| will no longer raise warnings.
.. _v6.148.8:
--------------------
6.148.8 - 2025-12-23
--------------------
Add ``__dict__`` and ``__proto__`` to the list of constant strings Hypothesis sometimes generates.
.. _v6.148.7:
--------------------
6.148.7 - 2025-12-05
--------------------
When multiple explicit |@example| decorators fail with the same error,
Hypothesis now shows only the simplest failing example (by shortlex order)
with a note about how many other examples also failed (:issue:`4520`).
To see all failing examples, use |Verbosity.verbose| or higher.
.. _v6.148.6:
--------------------
6.148.6 - 2025-12-04
--------------------
Fix a bug where we persisted symbolics from solver-based :ref:`alternative backends ` in |event|.
.. _v6.148.5:
--------------------
6.148.5 - 2025-12-01
--------------------
This patch improves the error message for :class:`~hypothesis.errors.FlakyStrategyDefinition`
when the precondition for a rule is flaky (:issue:`4206`).
.. _v6.148.4:
--------------------
6.148.4 - 2025-12-01
--------------------
This patch improves the type annotations for :func:`~hypothesis.extra.numpy.basic_indices`.
The return type now accurately reflects the ``allow_ellipsis`` and ``allow_newaxis``
parameters, excluding ``EllipsisType`` or ``None`` from the union when those index
types are disabled (:issue:`4607`).
Additionally, :func:`~hypothesis.assume` now has overloaded type annotations:
``assume(True)`` returns ``Literal[True]``, while ``assume(False)`` and
``assume(None)`` return ``NoReturn``.
.. _v6.148.3:
--------------------
6.148.3 - 2025-11-27
--------------------
Clean up some internal code.
.. _v6.148.2:
--------------------
6.148.2 - 2025-11-18
--------------------
Document |fuzz_one_input|.
.. _v6.148.1:
--------------------
6.148.1 - 2025-11-16
--------------------
This patch updates our vendored `list of top-level domains `__,
which is used by the provisional :func:`~hypothesis.provisional.domains` strategy.
.. _v6.148.0:
--------------------
6.148.0 - 2025-11-15
--------------------
Calling :func:`~hypothesis.settings.register_profile` from within a test
decorated with :func:`@settings ` is now deprecated,
to avoid confusion about which settings are used as the baseline for the
new profile.
.. _v6.147.0:
--------------------
6.147.0 - 2025-11-06
--------------------
This release drops support for :pypi:`nose`, which ceased development 9 years ago and does not support Python 3.10 or newer.
Hypothesis still supports :pypi:`nose2`. While we do not test ``nose2`` in our CI, we will fix any bugs that get reported.
.. _v6.146.0:
--------------------
6.146.0 - 2025-11-05
--------------------
|@settings| now accepts equivalent string representations for |settings.verbosity|, |settings.phases|, and |settings.suppress_health_check|. For example:
.. code-block:: python
# these two are now equivalent...
settings(verbosity=Verbosity.verbose)
settings(verbosity="verbose")
# ...as are these two...
settings(phases=[Phase.explicit])
settings(phases=["explicit"])
# ...and these two.
settings(suppress_health_check=[HealthCheck.filter_too_much])
settings(suppress_health_check=["filter_too_much"])
This release also changes the canonical value of |Verbosity|, |Phase|, and |HealthCheck| members to a string instead of an integer. For example, ``Phase.reuse.value == "explicit"`` as of this release, where previously ``Phase.reuse.value == 1``.
Instantiating |Verbosity|, |Phase|, or |HealthCheck| with an integer, such as ``Verbosity(0)``, is now deprecated.
.. _v6.145.1:
--------------------
6.145.1 - 2025-11-03
--------------------
Refactor some internal logic around strategy definitions.
.. _v6.145.0:
--------------------
6.145.0 - 2025-11-03
--------------------
Hypothesis previously required :pypi:`attrs` as a dependency. This release removes that dependency, so that the only required dependency of Hypothesis is :pypi:`sortedcontainers`.
All attrs-specific features of Hypothesis, such as using |st.from_type| with attrs classes, will continue to behave as before.
.. _v6.144.1:
--------------------
6.144.1 - 2025-11-03
--------------------
Tweak how Hypothesis hides internal tracebacks to fix an error under rare conditions (:issue:`3822`).
.. _v6.144.0:
--------------------
6.144.0 - 2025-11-02
--------------------
This release adds support for :class:`~fractions.Fraction` objects as ``min_value``
and ``max_value`` bounds in :func:`~hypothesis.strategies.decimals`, if they can
be exactly represented as decimals in the target precision (:issue:`4466`).
Bounding :func:`~hypothesis.strategies.decimals` with *other* values that cannot
be exactly represented is now deprecated; previously the bounds could be off by one.
.. _v6.143.1:
--------------------
6.143.1 - 2025-11-02
--------------------
:func:`~hypothesis.strategies.from_type` now correctly handles :pypi:`annotated-types`
annotations on :class:`typing.TypedDict` fields which are also marked as being
:obj:`~typing.ReadOnly`, :obj:`~typing.Required`, or :obj:`~typing.NotRequired`
(:issue:`4474`).
.. _v6.143.0:
--------------------
6.143.0 - 2025-11-01
--------------------
The extras for |hypothesis-numpy| and |hypothesis-pandas| now support automatically inferring a strategy for ``dtype="O"``. Previously, Hypothesis required an explicit elements strategy to be passed, for example ``nps.arrays("O", shape=(1,), elements=st.just(object()))``. Now, Hypothesis automatically infers ``elements=st.from_type(object)``.
Thanks to Shaun Read for identifying and fixing this!
.. _v6.142.5:
--------------------
6.142.5 - 2025-10-31
--------------------
This patch fixes :func:`~hypothesis.extra.ghostwriter.binary_operation` to
include imports for :mod:`hypothesis.extra.numpy` strategies such as
:func:`~hypothesis.extra.numpy.arrays`, :func:`~hypothesis.extra.numpy.scalar_dtypes`,
and :func:`~hypothesis.extra.numpy.array_shapes` when ghostwriting tests for
functions with numpy array parameters (:issue:`4576`).
.. _v6.142.4:
--------------------
6.142.4 - 2025-10-25
--------------------
Improve the accuracy of test timing reports, by tracking the start time of each test case closer to when the test is executed.
.. _v6.142.3:
--------------------
6.142.3 - 2025-10-22
--------------------
Fix a recursion error when :ref:`observability ` is enabled and a test generates an object with a recursive reference, like ``a = []; a.append(a)``.
.. _v6.142.2:
--------------------
6.142.2 - 2025-10-20
--------------------
Remove a case where Hypothesis would interact with the global |random.Random| instance if Hypothesis internals were used directly.
.. _v6.142.1:
--------------------
6.142.1 - 2025-10-16
--------------------
Simplify some internal typing logic after dropping Python 3.9.
.. _v6.142.0:
--------------------
6.142.0 - 2025-10-16
--------------------
This release drops support for Python 3.9, `which reached end of life in
October 2025 `__.
.. _v6.141.1:
--------------------
6.141.1 - 2025-10-15
--------------------
Fixes an error when using :ref:`the Ghostwriter ` with annotations that include :obj:`python:typing.ForwardRef` on Python 3.14 (:issue:`4565`).
.. _v6.141.0:
--------------------
6.141.0 - 2025-10-15
--------------------
The |django.from_field| and |django.from_form| strategies from our :ref:`Django extra ` now support :obj:`~django:django.db.models.FileField`.
Thanks to Arjoonn Sharma for this fix!
.. _v6.140.4:
--------------------
6.140.4 - 2025-10-14
--------------------
Clean up internal ``@overload`` type annotations.
.. _v6.140.3:
--------------------
6.140.3 - 2025-10-04
--------------------
Fixes our bundled |run_conformance_test| not respecting |PrimitiveProvider.avoid_realization|.
.. _v6.140.2:
--------------------
6.140.2 - 2025-09-23
--------------------
The automatic switch to the CI :class:`settings profile ` now works under :pypi:`tox` (for ``tox >= 4.30.0``).
.. _v6.140.1:
--------------------
6.140.1 - 2025-09-22
--------------------
This patch re-enables the warning for incompatible :func:`~hypothesis.strategies.shared`
strategies that was first enabled in :v:`6.133.0` but disabled in :v:`6.135.15`.
.. _v6.140.0:
--------------------
6.140.0 - 2025-09-22
--------------------
|st.characters| now validates that the elements of the ``exclude_characters`` and ``include_characters`` arguments are single characters, which was always assumed internally. For example, ``exclude_characters=["a", "b"]`` is valid while ``exclude_characters=["ab"]`` will now raise an error up-front.
.. _v6.139.3:
--------------------
6.139.3 - 2025-09-22
--------------------
Add ``phase`` to the :ref:`hypothesis-specific metadata ` in :ref:`observability `.
.. _v6.139.2:
--------------------
6.139.2 - 2025-09-18
--------------------
Internal refactoring for new lint rules.
.. _v6.139.1:
--------------------
6.139.1 - 2025-09-16
--------------------
Fixed another typo in error message around function-scoped fixtures.
.. _v6.139.0:
--------------------
6.139.0 - 2025-09-16
--------------------
Add |settings.get_current_profile_name|, which returns the name of the current settings profile.
.. _v6.138.17:
---------------------
6.138.17 - 2025-09-15
---------------------
Fixed typo in error message around function-scoped fixtures.
.. _v6.138.16:
---------------------
6.138.16 - 2025-09-13
---------------------
Improved error message for |DeadlineExceeded|.
.. _v6.138.15:
---------------------
6.138.15 - 2025-09-08
---------------------
Refactor some stateful testing internals for easier use by third-party libraries.
.. _v6.138.14:
---------------------
6.138.14 - 2025-09-02
---------------------
Patch files written by hypothesis now use a deterministic ordering when multiple |@example| decorators are present.
.. _v6.138.13:
---------------------
6.138.13 - 2025-09-01
---------------------
Fix a typo affecting pretty-printing of lambdas with complex default
arguments.
.. _v6.138.12:
---------------------
6.138.12 - 2025-09-01
---------------------
Improve automatic detection of the :ref:`CI profile ` on various vendor-specific CI systems.
.. _v6.138.11:
---------------------
6.138.11 - 2025-09-01
---------------------
This patch updates our vendored `list of top-level domains `__,
which is used by the provisional :func:`~hypothesis.provisional.domains` strategy.
.. _v6.138.10:
---------------------
6.138.10 - 2025-08-31
---------------------
Internal refactor to simplify |SearchStrategy|.
.. _v6.138.9:
--------------------
6.138.9 - 2025-08-31
--------------------
This patch further improves stringification of lambdas, by
never returning a lambda source unless it is confirmed to
compile to the same code object. This stricter check makes
it possible to widen the search for a matching source block,
so that it can often be found even if the file has been
edited.
.. _v6.138.8:
--------------------
6.138.8 - 2025-08-29
--------------------
Fixes a race condition under threading when using |st.deferred|.
.. _v6.138.7:
--------------------
6.138.7 - 2025-08-28
--------------------
Improves upon the cache eviction problem workaround
of :v:`6.135.12`.
.. _v6.138.6:
--------------------
6.138.6 - 2025-08-27
--------------------
Documentation tweaks.
.. _v6.138.5:
--------------------
6.138.5 - 2025-08-27
--------------------
Fixes a race condition under threading for strategies which trigger our filter-rewriting rules, like ``st.integers().filter(lambda x: abs(x) > 100)``.
.. _v6.138.4:
--------------------
6.138.4 - 2025-08-27
--------------------
One of our shrinking passes for reducing failing inputs targets failures which require two numbers to add to the same value. This pass previously only worked for positive numbers. This patch fixes that, so it also works for negative numbers.
.. _v6.138.3:
--------------------
6.138.3 - 2025-08-24
--------------------
This patch slightly improves the cache-hit rate for
|st.dictionaries| and certain unique |st.lists|.
.. _v6.138.2:
--------------------
6.138.2 - 2025-08-16
--------------------
The type annotations for |st.register_type_strategy| now indicate that it accepts registering types created with |TypeAliasType| (aka ``type MyType = int``).
.. _v6.138.1:
--------------------
6.138.1 - 2025-08-15
--------------------
Internal refactoring and cleanup. As a result, ``hypothesis[cli]`` and ``hypothesis[ghostwriter]`` now require ``black>=20.8b0`` instead of the previous ``black>=19.10b0``.
.. _v6.138.0:
--------------------
6.138.0 - 2025-08-13
--------------------
On Python 3.14, |memoryview| is newly generic. This release adds the ability for |st.from_type| to resolve generic |memoryview| types on 3.14, like ``st.from_type(memoryview[CustomBufferClass])`` . ``CustomBufferClass`` must implement ``__buffer__``, as expected by |memoryview|.
.. _v6.137.3:
--------------------
6.137.3 - 2025-08-11
--------------------
This patch makes the stringification of lambdas, and as
a result certain automatic filter rewriting operations,
more robust. This fixes :issue:`4498`, where a lambda
was mistakenly identified as the identity operator due
to :func:`inspect.getsource` only returning the first
line of the lambda definition.
As a result, the ``repr`` of strategies filtered or
mapped by lambda functions may change slightly.
.. _v6.137.2:
--------------------
6.137.2 - 2025-08-11
--------------------
Add support for Python 3.14, `which is currently in release candidate 1