[{"data":1,"prerenderedAt":982},["ShallowReactive",2],{"page-\u002Fproperty-based-fuzz-testing-strategies\u002Fhypothesis-framework-fundamentals\u002Fusing-assume-without-tripping-the-filter-health-check\u002F":3},{"id":4,"title":5,"body":6,"description":948,"extension":949,"meta":950,"navigation":95,"path":978,"seo":979,"stem":980,"__hash__":981},"content\u002Fproperty-based-fuzz-testing-strategies\u002Fhypothesis-framework-fundamentals\u002Fusing-assume-without-tripping-the-filter-health-check\u002Findex.md","Using assume() Without Tripping the Filter Health Check",{"type":7,"value":8,"toc":937},"minimark",[9,29,36,41,63,67,121,183,213,343,347,361,394,401,405,415,422,425,478,570,574,577,596,610,613,706,710,756,760,763,798,801,859,865,869,880,889,895,899,928,933],[10,11,12,16,17,20,21,24,25,28],"p",{},[13,14,15],"code",{},"hypothesis.errors.FailedHealthCheck: It looks like your strategy is filtering out a lot of data"," is Hypothesis telling you that most of what it generates never reaches your assertion. Every call to ",[13,18,19],{},"assume(...)"," that returns false and every ",[13,22,23],{},".filter(...)"," that rejects a value throws away an example and asks for another. When too many are thrown away in a row, Hypothesis gives up and raises the ",[13,26,27],{},"filter_too_much"," health check rather than silently running a test that barely tests anything.",[10,30,31,32,35],{},"The check is worth respecting. A strategy that rejects ninety percent of its output is slow, and worse, the ten percent it keeps are skewed: they cluster around whatever values happen to satisfy the condition most easily. The fix is almost never to suppress the check. It is to generate valid inputs directly, keeping ",[13,33,34],{},"assume()"," for the rare conditions that are genuinely easier to reject than to construct.",[37,38,40],"h2",{"id":39},"prerequisites","Prerequisites",[42,43,44,55],"ul",{},[45,46,47,50,51,54],"li",{},[13,48,49],{},"hypothesis >= 6.100"," and ",[13,52,53],{},"pytest >= 8.0",".",[45,56,57,58,54],{},"The strategy basics from ",[59,60,62],"a",{"href":61},"\u002Fproperty-based-fuzz-testing-strategies\u002Fhypothesis-framework-fundamentals\u002F","Hypothesis framework fundamentals",[37,64,66],{"id":65},"solution","Solution",[68,69,74],"pre",{"className":70,"code":71,"language":72,"meta":73,"style":73},"language-python shiki shiki-themes github-light github-dark","# Before — rejects most generated data.\nfrom hypothesis import assume, given, strategies as st\n\n@given(st.integers(), st.integers())\ndef test_range_slice(lo, hi):\n    assume(0 \u003C= lo \u003C hi \u003C= 1000)          # rejects the vast majority of pairs\n    assert len(list(range(lo, hi))) == hi - lo\n","python","",[13,75,76,84,90,97,103,109,115],{"__ignoreMap":73},[77,78,81],"span",{"class":79,"line":80},"line",1,[77,82,83],{},"# Before — rejects most generated data.\n",[77,85,87],{"class":79,"line":86},2,[77,88,89],{},"from hypothesis import assume, given, strategies as st\n",[77,91,93],{"class":79,"line":92},3,[77,94,96],{"emptyLinePlaceholder":95},true,"\n",[77,98,100],{"class":79,"line":99},4,[77,101,102],{},"@given(st.integers(), st.integers())\n",[77,104,106],{"class":79,"line":105},5,[77,107,108],{},"def test_range_slice(lo, hi):\n",[77,110,112],{"class":79,"line":111},6,[77,113,114],{},"    assume(0 \u003C= lo \u003C hi \u003C= 1000)          # rejects the vast majority of pairs\n",[77,116,118],{"class":79,"line":117},7,[77,119,120],{},"    assert len(list(range(lo, hi))) == hi - lo\n",[68,122,124],{"className":70,"code":123,"language":72,"meta":73,"style":73},"# After — constructs valid pairs directly; nothing is rejected.\n@st.composite\ndef ordered_pair(draw, upper=1000):\n    lo = draw(st.integers(0, upper - 1))\n    hi = draw(st.integers(lo + 1, upper))\n    return lo, hi\n\n@given(ordered_pair())\ndef test_range_slice(pair):\n    lo, hi = pair\n    assert len(list(range(lo, hi))) == hi - lo\n",[13,125,126,131,136,141,146,151,156,160,166,172,178],{"__ignoreMap":73},[77,127,128],{"class":79,"line":80},[77,129,130],{},"# After — constructs valid pairs directly; nothing is rejected.\n",[77,132,133],{"class":79,"line":86},[77,134,135],{},"@st.composite\n",[77,137,138],{"class":79,"line":92},[77,139,140],{},"def ordered_pair(draw, upper=1000):\n",[77,142,143],{"class":79,"line":99},[77,144,145],{},"    lo = draw(st.integers(0, upper - 1))\n",[77,147,148],{"class":79,"line":105},[77,149,150],{},"    hi = draw(st.integers(lo + 1, upper))\n",[77,152,153],{"class":79,"line":111},[77,154,155],{},"    return lo, hi\n",[77,157,158],{"class":79,"line":117},[77,159,96],{"emptyLinePlaceholder":95},[77,161,163],{"class":79,"line":162},8,[77,164,165],{},"@given(ordered_pair())\n",[77,167,169],{"class":79,"line":168},9,[77,170,171],{},"def test_range_slice(pair):\n",[77,173,175],{"class":79,"line":174},10,[77,176,177],{},"    lo, hi = pair\n",[77,179,181],{"class":79,"line":180},11,[77,182,120],{},[68,184,186],{"className":70,"code":185,"language":72,"meta":73,"style":73},"# Still fine — assume() rejecting a small fraction.\n@given(st.lists(st.integers(), min_size=1))\ndef test_mean_within_bounds(xs):\n    assume(len(set(xs)) > 1)              # rejects only all-equal lists\n    assert min(xs) \u003C= sum(xs) \u002F len(xs) \u003C= max(xs)\n",[13,187,188,193,198,203,208],{"__ignoreMap":73},[77,189,190],{"class":79,"line":80},[77,191,192],{},"# Still fine — assume() rejecting a small fraction.\n",[77,194,195],{"class":79,"line":86},[77,196,197],{},"@given(st.lists(st.integers(), min_size=1))\n",[77,199,200],{"class":79,"line":92},[77,201,202],{},"def test_mean_within_bounds(xs):\n",[77,204,205],{"class":79,"line":99},[77,206,207],{},"    assume(len(set(xs)) > 1)              # rejects only all-equal lists\n",[77,209,210],{"class":79,"line":105},[77,211,212],{},"    assert min(xs) \u003C= sum(xs) \u002F len(xs) \u003C= max(xs)\n",[214,215,218,339],"figure",{"className":216},[217],"diagram",[219,220,227,228,227,232,227,236,227,254,227,262,227,272,227,282,227,288,227,294,227,299,227,303,227,307,227,311,227,315,227,319,227,327,227,331,227,335],"svg",{"viewBox":221,"role":222,"ariaLabelledBy":223,"xmlns":226},"0 0 800 236","img",[224,225],"af-t","af-d","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","\n  ",[229,230,231],"title",{"id":224},"Rejection versus construction",[233,234,235],"desc",{"id":225},"On the left, a wide strategy generates many integer pairs and assume discards most of them, so only a few reach the test. On the right, a composite strategy draws the lower bound and then draws the upper bound above it, so every generated pair is valid and reaches the test.",[237,238,239,240,227],"defs",{},"\n    ",[241,242,249],"marker",{"id":243,"viewBox":244,"refX":245,"refY":246,"markerWidth":247,"markerHeight":247,"orient":248},"af-a","0 0 10 10","9","5","7","auto-start-reverse",[250,251],"path",{"d":252,"fill":253},"M0 0 L10 5 L0 10 z","#81b29a",[255,256],"rect",{"x":257,"y":257,"width":258,"height":259,"rx":260,"fill":261},"0","800","236","14","#fffdf8",[263,264,271],"text",{"x":265,"y":266,"textAnchor":267,"fontSize":268,"fontWeight":269,"fill":270},"400","28","middle","15.5","700","#3d405b","Throw away, or build right the first time",[255,273],{"x":274,"y":275,"width":276,"height":277,"rx":278,"fill":279,"stroke":280,"strokeWidth":281},"26","50","360","164","12","#fbe9e3","#e07a5f","2",[263,283,287],{"x":284,"y":285,"textAnchor":267,"fontSize":286,"fontWeight":269,"fill":270},"206","76","12.5","integers() x integers() + assume",[263,289,293],{"x":290,"y":291,"fontSize":292,"fill":270},"44","106","11","1000 pairs generated",[263,295,298],{"x":290,"y":296,"fontSize":292,"fill":297},"130","#8f3d22","~990 rejected by assume",[263,300,302],{"x":290,"y":301,"fontSize":292,"fill":270},"154","~10 reach the assertion",[263,304,306],{"x":290,"y":305,"fontSize":292,"fontWeight":269,"fill":297},"192","health check: filter_too_much",[255,308],{"x":309,"y":275,"width":276,"height":277,"rx":278,"fill":310,"stroke":253,"strokeWidth":281},"414","#e6f0ea",[263,312,314],{"x":313,"y":285,"textAnchor":267,"fontSize":286,"fontWeight":269,"fill":270},"594","ordered_pair() composite",[263,316,318],{"x":317,"y":291,"fontSize":292,"fill":270},"432","draw lo in [0, 999]",[79,320],{"x1":321,"y1":322,"x2":323,"y2":324,"stroke":253,"strokeWidth":325,"markerEnd":326},"560","102","600","120","1.6","url(#af-a)",[263,328,330],{"x":317,"y":329,"fontSize":292,"fill":270},"138","draw hi in [lo+1, 1000]",[263,332,334],{"x":317,"y":333,"fontSize":292,"fill":270},"162","every pair valid",[263,336,338],{"x":317,"y":305,"fontSize":292,"fontWeight":269,"fill":337},"#2a5f49","full budget spent on real tests",[340,341,342],"figcaption",{},"Constructing the relationship between values replaces a rejection rate of almost everything with none at all.",[37,344,346],{"id":345},"why-this-works","Why this works",[10,348,349,350,353,354,356,357,360],{},"Hypothesis counts valid examples towards ",[13,351,352],{},"max_examples"," and tracks invalid ones separately. Rejection by ",[13,355,34],{}," or ",[13,358,359],{},".filter()"," marks the current example invalid, and generation starts again from scratch. The health check fires when the ratio of invalid to valid examples is high early in the run — Hypothesis would otherwise burn through a large generation budget and still test only a handful of inputs.",[10,362,363,364,367,368,371,372,375,376,379,380,383,384,356,387,390,391,54],{},"Construction avoids the problem because every draw is conditioned on the earlier ones. Drawing ",[13,365,366],{},"hi"," from ",[13,369,370],{},"integers(lo + 1, upper)"," makes ",[13,373,374],{},"lo \u003C hi"," true by construction, not by luck. The same principle covers most filters: a lower bound becomes ",[13,377,378],{},"min_value",", a non-empty requirement becomes ",[13,381,382],{},"min_size=1",", uniqueness becomes ",[13,385,386],{},"st.lists(..., unique=True)",[13,388,389],{},"st.sets(...)",", and \"one of these values\" becomes ",[13,392,393],{},"st.sampled_from(...)",[10,395,396,397,400],{},"Construction also shrinks better. When a test fails, Hypothesis shrinks by simplifying the underlying choices it made. A composite strategy with explicit dependencies shrinks towards the simplest valid pair — ",[13,398,399],{},"(0, 1)"," — while a heavily filtered strategy often shrinks poorly, because many simplifications produce inputs the filter rejects, and shrinking stalls on a messy example.",[37,402,404],{"id":403},"recognising-which-conditions-to-rebuild","Recognising which conditions to rebuild",[10,406,407,408,410,411,414],{},"Not every ",[13,409,34],{}," needs replacing. The question is what fraction of inputs it rejects, and ",[13,412,413],{},"--hypothesis-show-statistics"," answers it directly:",[68,416,420],{"className":417,"code":419,"language":263,"meta":73},[418],"language-text","- during generate phase (0.41 seconds):\n    - Typical runtimes: \u003C 1ms, of which \u003C 1ms in data generation\n    - 100 passing examples, 0 failing examples, 912 invalid examples\n",[13,421,419],{"__ignoreMap":73},[10,423,424],{},"Nine invalid examples for every valid one is well into rebuild territory. As a rule of thumb, a rejection rate below about a third is harmless and not worth restructuring; above half, look for a construction; near the health-check threshold, rebuilding is required.",[10,426,427,428,432,433,436,437,440,441,432,444,447,448,451,452,455,456,436,459,462,463,466,467,470,471,474,475,477],{},"Conditions fall into a few recognisable families. ",[429,430,431],"strong",{},"Range conditions"," (",[13,434,435],{},"x > 0",", ",[13,438,439],{},"len(s) \u003C 50",") map to strategy bounds. ",[429,442,443],{},"Ordering conditions",[13,445,446],{},"a \u003C b",", sorted lists) map to derived draws or ",[13,449,450],{},"sorted()"," inside a composite. ",[429,453,454],{},"Distinctness conditions"," map to ",[13,457,458],{},"unique=True",[13,460,461],{},"st.sets",", or ",[13,464,465],{},"unique_by",". ",[429,468,469],{},"Membership conditions"," — a key must be in a dict — map to drawing the dict first, then ",[13,472,473],{},"st.sampled_from(sorted(d))",". What remains after those families are the genuinely irregular conditions: \"the matrix is invertible\", \"the graph is connected\". Those are the legitimate home of ",[13,476,34],{},", and for most generators they reject a small enough fraction to be fine.",[214,479,481,567],{"className":480},[217],[219,482,227,487,227,490,227,493,227,496,227,499,227,504,227,509,227,513,227,519,227,523,227,526,227,530,227,533,227,536,227,539,227,543,227,546,227,549,227,552,227,555,227,559,227,563],{"viewBox":483,"role":222,"ariaLabelledBy":484,"xmlns":226},"0 0 800 260",[485,486],"afm-t","afm-d",[229,488,489],{"id":485},"Mapping filter conditions to strategy constructions",[233,491,492],{"id":486},"A table-like diagram maps four families of conditions to constructions: range conditions to min and max bounds, ordering conditions to derived draws, distinctness conditions to unique lists or sets, and membership conditions to sampling from a previously drawn collection. Irregular conditions such as invertibility remain a good use of assume.",[255,494],{"x":257,"y":257,"width":258,"height":495,"rx":260,"fill":261},"260",[263,497,498],{"x":265,"y":266,"textAnchor":267,"fontSize":268,"fontWeight":269,"fill":270},"Most filters have a constructive twin",[255,500],{"x":274,"y":501,"width":502,"height":503,"rx":247,"fill":270},"46","748","34",[263,505,508],{"x":501,"y":506,"fontSize":507,"fontWeight":269,"fill":261},"68","11.5","condition",[263,510,512],{"x":511,"y":506,"fontSize":507,"fontWeight":269,"fill":261},"330","construct it with",[255,514],{"x":274,"y":515,"width":502,"height":516,"rx":517,"fill":518},"84","30","6","#f4f1de",[263,520,522],{"x":501,"y":521,"fontSize":292,"fill":270},"104","assume(x > 0), len(s) \u003C 50",[263,524,525],{"x":511,"y":521,"fontSize":292,"fill":337},"min_value \u002F max_size bounds",[255,527],{"x":274,"y":528,"width":502,"height":516,"rx":517,"fill":261,"stroke":529},"118","rgba(61,64,91,0.14)",[263,531,532],{"x":501,"y":329,"fontSize":292,"fill":270},"assume(a \u003C b), sorted input",[263,534,535],{"x":511,"y":329,"fontSize":292,"fill":337},"draw a, then draw b from (a, upper]",[255,537],{"x":274,"y":538,"width":502,"height":516,"rx":517,"fill":518},"152",[263,540,542],{"x":501,"y":541,"fontSize":292,"fill":270},"172","assume(len(set(xs)) == len(xs))",[263,544,545],{"x":511,"y":541,"fontSize":292,"fill":337},"st.lists(..., unique=True), st.sets",[255,547],{"x":274,"y":548,"width":502,"height":516,"rx":517,"fill":261,"stroke":529},"186",[263,550,551],{"x":501,"y":284,"fontSize":292,"fill":270},"assume(k in d)",[263,553,554],{"x":511,"y":284,"fontSize":292,"fill":337},"draw d, then sampled_from(sorted(d))",[255,556],{"x":274,"y":557,"width":502,"height":516,"rx":517,"fill":558},"220","#f7f0da",[263,560,562],{"x":501,"y":561,"fontSize":292,"fill":270},"240","matrix invertible, graph connected",[263,564,566],{"x":511,"y":561,"fontSize":292,"fill":565},"#8a5a00","keep assume() — if it rarely rejects",[340,568,569],{},"Work down the table before reaching for suppression: the last row is the only one where assume() is the natural tool.",[37,571,573],{"id":572},"how-rejection-distorts-the-inputs-you-do-test","How rejection distorts the inputs you do test",[10,575,576],{},"Speed is the obvious cost of heavy filtering; the subtler cost is bias. When a filter keeps only a small slice of the generated space, the values that survive are not a fair sample of the valid inputs. They are whichever valid inputs the underlying strategy happens to produce most often.",[10,578,579,580,583,584,436,586,436,589,592,593,595],{},"Take ",[13,581,582],{},"assume(0 \u003C= lo \u003C hi \u003C= 1000)"," over two unbounded integers. Hypothesis favours small integers and boundary values, so the pairs that survive are dominated by tiny ranges near zero — ",[13,585,399],{},[13,587,588],{},"(0, 2)",[13,590,591],{},"(1, 3)",". Ranges near the top of the interval, or spanning most of it, almost never appear. A bug that only shows up when ",[13,594,366],{}," equals the upper bound could go unfound for thousands of runs, not because the test is wrong but because the filtered strategy rarely reaches that region.",[10,597,598,599,602,603,605,606,609],{},"The composite version draws ",[13,600,601],{},"lo"," across the whole interval and ",[13,604,366],{}," across whatever remains, so both ends of the range get explored, and Hypothesis's own boundary heuristics apply to the bounds you passed — it tries ",[13,607,608],{},"upper"," itself on purpose. Constructed strategies inherit that boundary-seeking behaviour; filtered ones mostly lose it.",[10,611,612],{},"This is also why a test that passes reliably with a heavy filter is weaker evidence than it looks. The statistics line saying \"100 passing examples\" is true, but those hundred examples may cover only a narrow corner of the valid space. After rebuilding a strategy, it is common for a test that had passed for months to fail on its first run — not because the code changed, but because the inputs finally reached the corner where the bug was.",[214,614,616,703],{"className":615},[217],[219,617,227,622,227,625,227,628,227,631,227,634,227,639,227,645,227,651,227,656,227,659,227,661,227,664,227,669,227,672,227,675,227,678,227,680,227,683,227,686,227,689,227,692,227,696,227,699],{"viewBox":618,"role":222,"ariaLabelledBy":619,"xmlns":226},"0 0 800 232",[620,621],"afb-t","afb-d",[229,623,624],{"id":620},"Filtered versus constructed coverage of the valid range",[233,626,627],{"id":621},"Two horizontal bars represent the valid range from 0 to 1000. In the filtered bar, surviving examples cluster near zero, leaving the upper end unexplored. In the constructed bar, examples spread across the whole range and include the upper bound itself.",[255,629],{"x":257,"y":257,"width":258,"height":630,"rx":260,"fill":261},"232",[263,632,633],{"x":265,"y":266,"textAnchor":267,"fontSize":268,"fontWeight":269,"fill":270},"Where the surviving examples land",[263,635,638],{"x":636,"y":637,"fontSize":507,"fontWeight":269,"fill":297},"40","78","filtered",[255,640],{"x":641,"y":642,"width":643,"height":274,"rx":517,"fill":518,"stroke":644},"140","62","620","rgba(61,64,91,0.35)",[255,646],{"x":647,"y":648,"width":501,"height":649,"rx":650,"fill":279},"142","64","22","4",[652,653],"circle",{"cx":654,"cy":655,"r":650,"fill":280},"150","75",[652,657],{"cx":658,"cy":655,"r":650,"fill":280},"160",[652,660],{"cx":541,"cy":655,"r":650,"fill":280},[652,662],{"cx":663,"cy":655,"r":650,"fill":280},"183",[263,665,668],{"x":321,"y":666,"textAnchor":267,"fontSize":667,"fill":297},"108","10.5","upper range almost never reached",[263,670,671],{"x":636,"y":658,"fontSize":507,"fontWeight":269,"fill":337},"constructed",[255,673],{"x":641,"y":674,"width":643,"height":274,"rx":517,"fill":310,"stroke":253},"144",[652,676],{"cx":654,"cy":677,"r":650,"fill":253},"157",[652,679],{"cx":495,"cy":677,"r":650,"fill":253},[652,681],{"cx":682,"cy":677,"r":650,"fill":253},"390",[652,684],{"cx":685,"cy":677,"r":650,"fill":253},"520",[652,687],{"cx":688,"cy":677,"r":650,"fill":253},"640",[652,690],{"cx":691,"cy":677,"r":650,"fill":253},"752",[263,693,695],{"x":691,"y":305,"textAnchor":694,"fontSize":667,"fill":337},"end","upper bound tried on purpose",[263,697,257],{"x":641,"y":698,"fontSize":667,"fill":270},"210",[263,700,702],{"x":701,"y":698,"textAnchor":694,"fontSize":667,"fill":270},"760","1000",[340,704,705],{},"Rebuilding the strategy fixes coverage as well as speed: boundary values come back into play.",[37,707,709],{"id":708},"edge-cases-and-failure-modes","Edge cases and failure modes",[42,711,712,721,727,739,747],{},[45,713,714,717,718,720],{},[429,715,716],{},"Filters hidden in shared strategies."," A ",[13,719,359],{}," deep inside a reusable domain strategy makes every test using it slow. Check statistics for the tests that use shared strategies, not only the one you are writing.",[45,722,723,726],{},[429,724,725],{},"Chained filters multiply."," Two filters that each keep half the values keep a quarter together. Rebuild the one that rejects most first.",[45,728,729,734,735,738],{},[429,730,731,733],{},[13,732,34],{}," after expensive work."," Calling ",[13,736,737],{},"assume"," late in the test body wastes all the work done before it. Put the check as early as possible, or better, in the strategy.",[45,740,741,746],{},[429,742,743,745],{},[13,744,27],{}," only on CI."," Different profiles and database contents can change early rejection rates. Treat it as a strategy problem even if it only fires in one environment.",[45,748,749,717,752,755],{},[429,750,751],{},"Suppression spreading.",[13,753,754],{},"suppress_health_check=[HealthCheck.filter_too_much]"," in a shared profile hides the problem for every test. Keep any suppression on the individual test, with a comment.",[37,757,759],{"id":758},"a-worked-rebuild-generating-valid-date-ranges","A worked rebuild: generating valid date ranges",[10,761,762],{},"Consider a booking system with the rule that a stay starts on or after today, lasts one to thirty nights, and must not cross a blackout period. The first attempt generates two dates and filters:",[68,764,766],{"className":70,"code":765,"language":72,"meta":73,"style":73},"@given(st.dates(), st.dates())\ndef test_booking(start, end):\n    assume(TODAY \u003C= start \u003C end)\n    assume((end - start).days \u003C= 30)\n    assume(not overlaps_blackout(start, end))\n    ...\n",[13,767,768,773,778,783,788,793],{"__ignoreMap":73},[77,769,770],{"class":79,"line":80},[77,771,772],{},"@given(st.dates(), st.dates())\n",[77,774,775],{"class":79,"line":86},[77,776,777],{},"def test_booking(start, end):\n",[77,779,780],{"class":79,"line":92},[77,781,782],{},"    assume(TODAY \u003C= start \u003C end)\n",[77,784,785],{"class":79,"line":99},[77,786,787],{},"    assume((end - start).days \u003C= 30)\n",[77,789,790],{"class":79,"line":105},[77,791,792],{},"    assume(not overlaps_blackout(start, end))\n",[77,794,795],{"class":79,"line":111},[77,796,797],{},"    ...\n",[10,799,800],{},"The first two assumptions together reject almost everything: two arbitrary dates spanning several millennia are rarely ordered, in the future, and within a month of each other. Rebuilding with construction handles them completely — draw a start within a sensible horizon, then draw a length:",[68,802,804],{"className":70,"code":803,"language":72,"meta":73,"style":73},"stays = st.builds(\n    lambda start, nights: (start, start + timedelta(days=nights)),\n    st.dates(min_value=TODAY, max_value=TODAY + timedelta(days=730)),\n    st.integers(1, 30),\n)\n\n@given(stays)\ndef test_booking(stay):\n    start, end = stay\n    assume(not overlaps_blackout(start, end))   # rejects a small fraction\n    ...\n",[13,805,806,811,816,821,826,831,835,840,845,850,855],{"__ignoreMap":73},[77,807,808],{"class":79,"line":80},[77,809,810],{},"stays = st.builds(\n",[77,812,813],{"class":79,"line":86},[77,814,815],{},"    lambda start, nights: (start, start + timedelta(days=nights)),\n",[77,817,818],{"class":79,"line":92},[77,819,820],{},"    st.dates(min_value=TODAY, max_value=TODAY + timedelta(days=730)),\n",[77,822,823],{"class":79,"line":99},[77,824,825],{},"    st.integers(1, 30),\n",[77,827,828],{"class":79,"line":105},[77,829,830],{},")\n",[77,832,833],{"class":79,"line":111},[77,834,96],{"emptyLinePlaceholder":95},[77,836,837],{"class":79,"line":117},[77,838,839],{},"@given(stays)\n",[77,841,842],{"class":79,"line":162},[77,843,844],{},"def test_booking(stay):\n",[77,846,847],{"class":79,"line":168},[77,848,849],{},"    start, end = stay\n",[77,851,852],{"class":79,"line":174},[77,853,854],{},"    assume(not overlaps_blackout(start, end))   # rejects a small fraction\n",[77,856,857],{"class":79,"line":180},[77,858,797],{},[10,860,861,862,864],{},"The blackout condition stays as an ",[13,863,34],{}," because blackouts are sparse and constructing around them would need the strategy to know the calendar. With the other conditions built in, the remaining rejection rate is small, the health check stops firing, and — because the start date is now bounded to a two-year horizon — every generated stay is the kind of input the production system actually receives. The rebuild improved both speed and the realism of what the test exercises.",[37,866,868],{"id":867},"frequently-asked-questions","Frequently Asked Questions",[10,870,871,874,875,356,877,879],{},[429,872,873],{},"What does filter_too_much mean?","\nHypothesis generated many inputs that were rejected by ",[13,876,34],{},[13,878,359],{}," before it found enough valid ones. It stops because the test is spending most of its effort on inputs it throws away, which also means the valid inputs it does test are poorly distributed.",[10,881,882,885,886,888],{},[429,883,884],{},"Is assume() bad practice?","\nNo. ",[13,887,34],{}," is fine for rejecting a small fraction of inputs, especially conditions that are awkward to express in a strategy. It becomes a problem when it rejects most inputs, which is when the strategy should be rebuilt to generate valid values directly.",[10,890,891,894],{},[429,892,893],{},"Should I suppress the filter_too_much health check?","\nRarely. Suppressing it hides the fact that most examples are wasted. Suppress it only when the rejection rate is inherently high, the valid inputs cannot be constructed directly, and you have checked with statistics that enough valid examples still run.",[37,896,898],{"id":897},"related","Related",[42,900,901,907,914,921],{},[45,902,903,906],{},[59,904,905],{"href":61},"Hypothesis Framework Fundamentals"," — strategies and health checks.",[45,908,909,913],{},[59,910,912],{"href":911},"\u002Fproperty-based-fuzz-testing-strategies\u002Fhypothesis-framework-fundamentals\u002Ffixing-hypothesis-flaky-health-check-failures\u002F","Fixing Hypothesis Flaky Health Check Failures"," — the other health checks and their causes.",[45,915,916,920],{},[59,917,919],{"href":918},"\u002Fproperty-based-fuzz-testing-strategies\u002Fadvanced-property-based-testing\u002Fcomposing-strategies-with-flatmap-and-composite\u002F","Composing Strategies with flatmap and @composite"," — dependent draws in depth.",[45,922,923,927],{},[59,924,926],{"href":925},"\u002Fproperty-based-fuzz-testing-strategies\u002Fadvanced-property-based-testing\u002Fwhy-hypothesis-shrinking-stalls-and-how-to-fix-it\u002F","Why Hypothesis Shrinking Stalls"," — how filters hurt shrinking.",[10,929,930,931],{},"← Back to ",[59,932,905],{"href":61},[934,935,936],"style",{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":73,"searchDepth":86,"depth":86,"links":938},[939,940,941,942,943,944,945,946,947],{"id":39,"depth":86,"text":40},{"id":65,"depth":86,"text":66},{"id":345,"depth":86,"text":346},{"id":403,"depth":86,"text":404},{"id":572,"depth":86,"text":573},{"id":708,"depth":86,"text":709},{"id":758,"depth":86,"text":759},{"id":867,"depth":86,"text":868},{"id":897,"depth":86,"text":898},"Why Hypothesis raises FailedHealthCheck filter_too_much, how assume() and .filter() spend the budget, and how to rebuild strategies so they generate valid inputs directly.","md",{"slug":951,"type":952,"breadcrumb":953,"datePublished":954,"dateModified":954,"faq":955,"howto":962},"using-assume-without-tripping-the-filter-health-check","article","assume() & filtering","2026-09-18",[956,958,960],{"q":873,"a":957},"Hypothesis generated many inputs that were rejected by assume() or .filter() before it found enough valid ones. It stops because the test is spending most of its effort on inputs it throws away, which also means the valid inputs it does test are poorly distributed.",{"q":884,"a":959},"No. assume() is fine for rejecting a small fraction of inputs, especially conditions that are awkward to express in a strategy. It becomes a problem when it rejects most inputs, which is when the strategy should be rebuilt to generate valid values directly.",{"q":893,"a":961},"Rarely. Suppressing it hides the fact that most examples are wasted. Suppress it only when the rejection rate is inherently high, the valid inputs cannot be constructed directly, and you have checked with statistics that enough valid examples still run.",{"name":963,"description":964,"steps":965},"How to fix filter_too_much in Hypothesis","Measure the rejection rate, replace rejection with construction, and keep assume() for rare conditions.",[966,969,972,975],{"name":967,"text":968},"Measure rejection","Run with --hypothesis-show-statistics and read the invalid example count.",{"name":970,"text":971},"Move bounds into the strategy","Replace assume(x > 0) with st.integers(min_value=1) and similar bounded strategies.",{"name":973,"text":974},"Construct instead of filtering","Build relationships such as a \u003C b by generating one value and deriving the other.",{"name":976,"text":977},"Keep assume for rare cases","Retain assume() only for conditions that reject a small fraction of inputs.","\u002Fproperty-based-fuzz-testing-strategies\u002Fhypothesis-framework-fundamentals\u002Fusing-assume-without-tripping-the-filter-health-check",{"title":5,"description":948},"property-based-fuzz-testing-strategies\u002Fhypothesis-framework-fundamentals\u002Fusing-assume-without-tripping-the-filter-health-check\u002Findex","rcD-a10LEDzlE733JJEGJhV4evaxEfdx8dbICmQY5IU",1789718767632]