Repository navigation
Strict known-bug tests, issue 2 byte oracle, issue 4 fastapi<0.142 pin, pre-commit hooks in CI - #3
Merged
Merged
Conversation
Replace the imperative pytest.xfail() calls, which reported green whether or not a platform bug reproduced: - Resolved issue 2 is now a plain regression test: a truncated StreamingResponse fails the run. - Active issues (3, 4a, 4b) use xfail(strict=True, raises=PlatformBugReproduced). The bug reproducing is XFAIL; the bug disappearing is XPASS(strict), which fails the run and is summarised as NOT REPRODUCED; any other error is an ordinary failure. dev_server no longer skips starting the server for xfail-marked tests. Give every repro directory the same pywrangler dev startup budget (PYWRANGLER_DEV_TIMEOUT, default 300 s under CI, 30 s otherwise) instead of special-casing 2-, warm every directory in the CI install step, and drain server output on a thread so a silent hang still hits the deadline and an early exit fails immediately. Run pre-commit (ruff, ruff format, codespell, hygiene hooks) in a new CI lint job, align ruff to 0.15.2 in pyproject and the pre-commit hook, and apply the ruff format changes the hooks require on the current tree. Document the conventions in README and LESSONS_LEARNED. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012eisRQmQvg4TphwztxhQGJ
`uv run pywrangler dev` forks npx -> wrangler -> workerd, but teardown only terminated `uv`. After every test, wrangler and two workerd processes kept running (7 processes per directory after `pytest -k test_5`). Across repeated runs they accumulated and slowed later cold starts, the flakiness the startup budget change targets. Start the server with start_new_session=True and stop its process group (SIGTERM, then SIGKILL after 10 s) on normal teardown, on the startup timeout, and when pywrangler exits before it is ready. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012eisRQmQvg4TphwztxhQGJ
fastapi 0.142.0 (2026-09-29) depends on opentelemetry-api, whose context
module calls uuid4() at import time. Python Workers forbid randomness
during startup, so `pywrangler dev` fails with
"OSError: [Errno 29] Randomness is not allowed while a Worker is starting".
pywrangler resolves the unpinned "fastapi" requirement to the newest
release (the directory's uv.lock is not used for python_modules), so main
broke without a code change.
Red: on main, CI=1 uv run pytest -vv errors at setup of
test_2_fastapi_r2_streaming ("Server failed to start within 300 seconds"),
and `pywrangler dev` in 4-r2-large-binary-roundtrip vendors fastapi
0.142.2 and dies with the same OSError.
Green: with the pin, fastapi 0.141.1 is vendored, test_2 passes and the
issue 4 Worker serves GET /.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SNmM8CmBcqJZ87FWEH5DiF
…eports
test_2_fastapi_r2_streaming checked only the length of GET /read. The
seed was bytes(range(256)) * 512, which repeats every 256 bytes, so even a
byte check could not see reordered 4 KB R2 chunks. /seed now writes 32768
big-endian words where word i holds i, and the test reports the first
misplaced word (a plain bytes == made pytest diff 128 KB for 7.5 minutes
on failure).
test_5_sync_http_libraries took its expected headers from the Worker's
own "headers_sent" field and its observations from the Worker's report of
what httpbin.org echoed. A Worker that skipped a library or reported
HEADERS without sending them stayed green. The Worker now accepts a
loopback-only ?echo= URL; the test runs tests/echo_server.py, a local
httpbin-style echo server, and asserts on the requests that server
recorded: one per library, each with both headers. It no longer needs
outbound network. Against the same Worker, httpbin.org and the local echo
server produced identical results.
Planted bugs, old tests (main) vs new tests:
seed zeroed survived / killed
/read chunks reversed survived / killed
/read drops last chunk killed / killed
urllib3 skipped, result copied survived / killed
requests sends no headers,
Worker reports HEADERS survived / killed
urllib3 drops X-Custom killed / killed
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SNmM8CmBcqJZ87FWEH5DiF
Review findings on the previous commit: - `?echo=http://evil.com\@127.0.0.1/` passed the loopback check, because urllib.parse reads the host as 127.0.0.1 while requests/urllib3 send the request to evil.com. The Worker now rejects userinfo, backslashes, queries and fragments, rebuilds the URL from the parsed host, port and path, and answers 400 instead of raising a 500. test_5 asserts the 400 (red on the previous Worker: 500 == 400 fails). - Moving test_5 to the local echo server dropped coverage of the default path: sync requests/urllib3 to a real HTTPS host. test_5 calls it once more and checks both headers; the wire-level assertions stay on the local echo server. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SNmM8CmBcqJZ87FWEH5DiF
…ation-audit-fixes
PR #4 left this as a follow-up because PR #3 rewrote the /stream lines. With both folded together, assert the streamed body against the word-index seed the same way /read is checked. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012E6xQEfTtV2ws3dCSMnoCo
…udit-fixes Conflicts resolved by asking whether main already does what this PR did: - Ruff pin (pyproject.toml, uv.lock, .pre-commit-config.yaml): main's 0.16.0 supersedes this PR's 0.15.2. Took main's. - ci.yml: main already runs `uv run ruff check .` in its test job. The PR's separate `lint` job is dropped; its remaining hooks (ruff format, codespell, file hygiene) run as one step in main's test job with SKIP=ruff, so each check runs exactly once. Main's uv 0.12.3 pin and UV_PYTHON stay; the PR's warm-every-directory install loop stays. - 2-fastapi-r2-streaming: main fixed the FastAPI >=0.142 startup failure by importing the app inside the request handler (src/app.py), so the PR's fastapi<0.142 pin there is dropped. The PR's word-pattern /seed data moves into src/app.py. - 4-r2-large-binary-roundtrip: main did not change its module-level FastAPI import, so the fastapi<0.142 pin is kept on top of main's dependency list (webtypy removed, workers-py pinned). - 5-sync-http-libraries and test_5: main's ECHO_URL Worker variable and header_echo_server fixture supersede the PR's request-controlled ?echo= override, its loopback validation, tests/echo_server.py and the live httpbin.org call (main removed httpbin from CI on purpose). Took main's entry.py, README and test_5; deleted tests/echo_server.py. - test_3: main's echo-server assertions plus the PR's strict xfail marker and PlatformBugReproduced. - tests/conftest.py: main's ECHO_URL wiring and process-group teardown; the PR's output-drain thread and early-exit error for startup. - LESSONS_LEARNED.md: main's entries 7 and 8 kept; the PR's entries are renumbered 9 and 10. - Lockfiles regenerated with `uv lock` (uv 0.12.3), not hand-merged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012eisRQmQvg4TphwztxhQGJ
This PR had raised the CI startup budget for every repro directory from 30 s to 300 s. The owner's verification-cost standard allows no raised timeouts: a longer budget only makes a hung start burn more CI time. The PR's real remedy for order-dependent startup stays: CI warms every directory in its install step. After warming, 2-, 3- and 5- reached "Ready on" in 10-13 s locally, inside the existing 30 s budget. So the budgets go back to main's values (300 s for 2-fastapi-r2-streaming under CI, 30 s otherwise). PYWRANGLER_DEV_TIMEOUT stays as an opt-in override for cold local runs; CI never sets it. README "Run the tests" and LESSONS_LEARNED entry 10 now describe this, and the README lists the lint commands CI runs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012eisRQmQvg4TphwztxhQGJ
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This PR now carries two earlier PRs, folded together at the owner's request. #4 is merged in with a merge commit, so its commits keep their original SHAs in this history.
From #3 (this PR's original change): strict known-bug semantics, uniform startup, lint in CI
@pytest.mark.xfail(strict=True, raises=PlatformBugReproduced). If an upstream fix lands, the run fails withXPASS(strict)and printsNOT REPRODUCED ... move it to Resolved Issues.tests/conftest.py.main's values: 300 s for2-underCI, 30 s otherwise.PYWRANGLER_DEV_TIMEOUToverrides them when set by hand.pywranglerexit fails immediately.wrangler/workerdprocesses no longer leak.pre-commit run --all-filesruns as a step inmain's existingtestjob, not as a newlintjob. It usesSKIP=ruff, becausemainalready runsruff check.main's 0.16.0 (Upgrade Ruff to 0.16.0 and make Workers CI reliable #2), which supersedes this PR's 0.15.2 pin.ruff formatcleanup of files that already failed the hook onmain.main's new entries 7 and 8).From #4: unbreak
main, byte-level issue 2 oracle, wire-level issue 5 oraclefastapi<0.142in4-.2-no longer needs the pin, becausemain(Upgrade Ruff to 0.16.0 and make Workers CI reliable #2) now imports its FastAPI app inside the request handler.opentelemetry-api, which callsuuid4()at import time. That raisesErrno 29 Randomness is not allowedduring Worker startup.pyproject.toml, not the lockfile, so the new release brokemainwithout any commit.test_2./seednow writes 32768 big-endian words, where word i holds i./readis checked word by word, so a reordered, repeated or zeroed chunk is caught.test_5. Superseded bymain(Upgrade Ruff to 0.16.0 and make Workers CI reliable #2). ItsECHO_URLWorker variable andheader_echo_serverfixture assert on what the local echo server received. This PR's?echo=override, its loopback validation and bypass check,tests/echo_server.pyand the live httpbin.org call were dropped in the merge.Deferred patch applied
7a68976:test_2checks the/streambody word by word. Test audit: unbreak main (pin fastapi below 0.142), byte-level issue 2 oracle, wire-level issue 5 oracle #4 wrote this as an exact patch and deferred it only because Strict known-bug tests, issue 2 byte oracle, issue 4 fastapi<0.142 pin, pre-commit hooks in CI #3 rewrote those lines. It is applied verbatim andruff formatted.Follow-ups still open
tests/conftest.py::_worker_name_from_config. No longer blocked: Upgrade Ruff to 0.16.0 and make Workers CI reliable #2 is merged intomain, andmainis merged into this branch. Not done here.test_3_httpx_headers. Done bymain(Upgrade Ruff to 0.16.0 and make Workers CI reliable #2):test_3now asserts onheader_echo_serverand keeps this PR's strict marker.bytes(range(256))in three places.LESSONS_LEARNED.mdentry for Test audit: unbreak main (pin fastapi below 0.142), byte-level issue 2 oracle, wire-level issue 5 oracle #4's findings.Local checks on the combined tree (
7a68976)These results predate the merge of
mainand the pruning. The current checks are under Cost and limits.Commands are taken from
.github/workflows/ci.ymland run withCI=true. Results are decided by exit status.uv sync --dev && for dir in [0-9]*-*/; do (cd "$dir" && uv run pywrangler dev --help); done(underset -e)uv run pre-commit run --all-files --show-diff-on-failure(lint job)uv run pytest -vv -rxXs(test job)test_2PASSED,test_5PASSED,test_3XFAIL, 4a/4b/4c SKIPPED (deploy-only). 2 passed, 3 skipped, 1 xfailed in 36 sgit merge-tree --write-tree HEAD origin/mainorigin/mainis already an ancestor, so no main merge was neededorigin/claude/test-audit-campaignwas clean:tests/test_examples.pyauto-merged.test_5contacts httpbin.org, a public echo service, as the test itself does.semgrep.yml, which needsSEMGREP_APP_TOKEN;--deploy;Needs the owner
--deploy. 4a and 4b now carry strict markers, so their outcome is unverified, and a 4bXPASS(strict)is worth investigating. Thefastapi<0.142pin's effect on a deployed issue 4 Worker is also unverified.main.mainis now merged into this branch (merge commit24bf211), so there is no merge order left to decide.Cost and limits
Pruned to the owner's verification-cost standard on 2026-10-10.
mainmerged in first. Merge commit24bf211brings in #2 (Ruff 0.16.0, reliable Workers CI) and #5 (CI lessons). Wheremainalready did what this PR did,main's version won:2-,mainimports the FastAPI app inside the request handler, so2-no longer needs thefastapi<0.142pin.mainhas theECHO_URLWorker variable and theheader_echo_serverfixture. They replace this PR's?echo=override, its loopback validation and bypass check,tests/echo_server.py, and the live httpbin.org call.main's process-group stop. Nowrangler/workerdprocess from this repo was left after a local run.Lockfiles were regenerated with
uv lock(uv 0.12.3), not hand-merged.What CI runs for this PR. It is still one workflow (
ci.yml) with one job (test). There is no new workflow, job, matrix entry or schedule.uv run pywrangler dev --help, not only2-. The first local warm took 30, 18, 16 and 14 s for2-,3-,4-and5-. For3-and5-, that time would otherwise be spent inside their timed dev-server start, so the net addition is mostly the 16 s for4-. No CI test starts4-; warming it only checks that its dependencies install and vendor.main'suv run ruff check .. It runsSKIP=ruff uv run pre-commit run --all-files --show-diff-on-failure: ruff format, codespell and file hygiene. It replaces this PR's separatelintjob, soruff checkruns once. It took 17-19 s locally with an empty hook cache.uv run pytest -vvand the same three local tests. Locally: 41 s, with 2 passed, 3 skipped and 1 xfailed. Each dev server was ready in 10-13 s.main's: 300 s for2-underCI, 30 s otherwise. This PR had raised every directory to 300 s underCI, and that was reverted.PYWRANGLER_DEV_TIMEOUTis a manual override that CI never sets. There are no retries.Removed from CI
lintjob, folded into thetestjob as described above.test_5.maintook httpbin out of CI on purpose.Manual only (exact commands)
Live HTTPS from
requests/urllib3to a real host (issue 5):cd 5-sync-http-libraries && uv run pywrangler dev, thencurl http://localhost:8787/test. WithoutECHO_URL, the Worker calls httpbin.org.Deployed tests 4a, 4b and 4c (unchanged, always manual):
uv run pytest -vv -rxXs -k test_4 --deployor--deployed-url <url>. This deploys to Cloudflare, so it was not run here.Issue 4 Worker startup:
cd 4-r2-large-binary-roundtrip && uv run pywrangler dev, thencurl http://localhost:8787/. Checked locally:fastapi<0.142, the Worker vendors FastAPI 0.141.1 and/returns 200;Errno 29 Randomness is not allowed.CI does not start issue 4.
Cold local test run:
PYWRANGLER_DEV_TIMEOUT=120 uv run pytest -vv, or warm each directory first withuv run pywrangler dev --help.PBT, fuzzing and mutation testing. This PR has none, so there are no budgets to set and no mutation lanes to remove.
Checks on
11ba67c. Run withCI=true UV_PYTHON=3.13, uv 0.12.3 and Python 3.13.15. Results are decided by exit status.uv sync --dev+for dir in [0-9]*-*/; do (cd "$dir" && uv run pywrangler dev --help); doneuv run pytest -vvuv run ruff check .SKIP=ruff uv run pre-commit run --all-files --show-diff-on-failureNot verified
semgrep.yml, which needsSEMGREP_APP_TOKEN; it is unchanged.main.Permission denials: none.
🤖 Generated with Claude Code
https://claude.ai/code/session_012E6xQEfTtV2ws3dCSMnoCo
Generated by Claude Code