Repository navigation
Audit the asyncio filesystem tests (GH-1328) - #1349
Conversation
…dit (GH-1328) Remove the tests whose behavior other tests already fail without: the asyncio copy tests that run through the shared cleanup and write session, the checksum copy test of S3Core, the AioS3File part tests that S3File's tests cover, put_file(mode=), the real-S3 find, glob, rm, and upload tests of delegated code, and the pandas test of the fsspec registration, whose string registration had no effect. Rewrite the tests whose waits were unbounded or unsynchronized, the copy test that asserted an internal call, and the transaction compression test, and merge the cancelled close() test into the cancelled flush test. Add the asyncio methods that call the synchronous filesystem to the delegation test, and test invalidate_cache() and the maxdepth check of a version expansion. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A signed URL carries the time of signing, and botocore signs with Signature Version 2 unless an endpoint or the configuration selects version 4, so comparing the URLs of the synchronous and asynchronous runs depended on the clock and the environment. The fake now records the arguments of S3Core.generate_presigned_url as a request. The maxdepth test no longer stubs requests that it does not make, or matches an undocumented message. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
| return {"Uploads": [{"Key": "dir/a", "UploadId": "u1"}]} | ||
| return {} | ||
|
|
||
| def generate_presigned_url(*args, **kwargs): |
There was a problem hiding this comment.
Self-review round one (behavior and implementation), base ad48ad5271d35148f267c5e4cdd44455d41bb8c6, head 47da52cb55fadbedf355d1c8e5149a2bfe9ad606. Covered: every removed, rewritten, merged and added test of test_s3_async.py, the _serve_keys fake, the fs fixture teardown, and the mutation evidence of the PR body.
FINDINGS:
- The
signcase compared signed URLs after droppingX-Amz-Date/X-Amz-Signatureonly forhttps://URLs. botocore signs with Signature Version 2 by default (absoluteExpires,Signature), and with the offline environment's endpoint the URL starts withhttp://, so the whole URL, including the time of signing, was compared: the case passed only when both runs signed in the same second, and without an endpoint override (as in CI)query.pop("X-Amz-Date")raisedKeyError. Repaired in5004add7:_serve_keysrecords the arguments ofS3Core.generate_presigned_url, signing's only input from the filesystem, as a request, and the URL normalization and thesignexception of the request guard are gone. Rechecked:signdroppingexpirationor**kwargsfails the case; three repeated runs and a run withoutAWS_ENDPOINT_URLpass. test_expand_path_version_maxdepth(line 1744) added a use of the_callseam that Remove the duplication between S3FileSystem and AioS3FileSystem #1340 removes, and matched the undocumented message. Repaired in5004add7: theconnection=MagicMock()filesystem sends nothing anyway, andValueErroralone fails the mutation (# noqa: PT011, as elsewhere in the file).
Checked without findings: the delegation cases with allow_bucket_creation/allow_bucket_deletion (no other case creates or deletes buckets); the double spy of cmu_cancelled_queued_completion (submit_to reaches the patched submit with item.run, which does not match); test_commit_cancelled waits for f._pool, in which the commit and a discard run; the class fs fixture's close() (the five remaining S3 tests of the class pass on S3).
| ) | ||
| @pytest.mark.parametrize("intrans", [False, True]) | ||
| def test_pipe_file_compression(self, path, compression, intrans): | ||
| def test_transaction_pipe_file_compression(self, path, compression): |
There was a problem hiding this comment.
Self-review round two (claims, callers, evidence), base ad48ad5271d35148f267c5e4cdd44455d41bb8c6, head 5004add7c1ca692e69ef92d7aad17470759c6f6b. Claims of the PR body and both commit messages checked against the code and rerun mutations.
FINDINGS:
- The PR body said each added case was the only detector of its mutation; the
touchcases replacetest_touch_sync_wrapper, which detected some of the same mutations. Narrowed to delegations that no test called or whose mutations no test failed. - The TEST section described
47da52cb. It now names5004add7, says which runs were repeated there (thesignandmaxdepthmutations, the five remaining S3 tests with the closing fixture, the removal evidence below), and that the full offline run belongs to47da52cb, whose only difference is the fake of thesigncase and themaxdepthtest. - The message of
5004add7says that botocore signs with Signature Version 2 "unless an endpoint or the configuration selects version 4"; withAWS_ENDPOINT_URLset, the URL was still version 2, so only the configuration selects version 4 here. The branch is not rewritten for it; the repair itself does not depend on the signature version.
Verified claims: the removal evidence reruns at this head (_S3WriteSession.abort not waiting fails cmu_cancelled, cmu_loop_shutdown and TestS3File tests; _stop_requests not setting the stop fails the sync cmu_failed_part_stops_running_parts and cmu_failed_part_with_threads_taken; skipping the shielded abort fails cmu_cancelled* and nine others; dropping the part checksum fails the sync and TestS3Core creation-algorithm tests; _put_file dropping mode fails the delegation case); outside a transaction the asyncio pipe_file runs S3FileSystem.pipe_file (s3_async.py:224), so the compression test keeps only the transaction cases; no other file references the removed or renamed tests. Callers: tests only, no public behavior changes. AWS: five S3 tests fewer per run of the class, and the class fixture now closes its threads.
…ng cache (GH-1328) The real-S3 find test that was removed was the only test of the entries that the asynchronous find returns with detail=True, which it builds itself; the delegation test now compares them with the synchronous find's by type and content. The worker gates of two cancellation tests are bounded, so that a regression that blocks the event loop fails instead of hanging, and the cache test enables the listing cache, which fsspec's configuration could disable. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
| ("_rmdir", "rmdir", ("s3://bucket",), {}), | ||
| ("rmdir", "rmdir", ("s3://bucket",), {}), | ||
| ("_find", "find", ("s3://bucket/dir",), {"maxdepth": 1, "withdirs": True}), | ||
| ("_find", "find", ("s3://bucket/dir",), {"detail": True}), |
There was a problem hiding this comment.
Independent review (relayed): Codex CLI 0.164.0, model gpt-6-astra, codex exec --sandbox read-only, session 01a178f8-94ee-7c42-aa6b-88a35fb8abdb; static review of the diff ad48ad5271d35148f267c5e4cdd44455d41bb8c6..5004add7c1ca692e69ef92d7aad17470759c6f6b on a detached snapshot, with docs/testing.md "Choose what to test" and no PR text, commit messages or prior findings. The first run hit its 30-minute limit before the report; the same session was resumed only to output it. Result: FINDINGS (4). Its coverage table found every other removed test covered with the same kind of evidence, and the added and rewritten tests protective. The snapshot and the PR worktree were unchanged.
Finding 1 (P2), verified and repaired in f35cb2dd. Reviewer: removing the real-S3 test_find loses the only check of the entries that AioS3FileSystem._find(detail=True) builds itself (s3_async.py:797); returning {f.name: f.to_dict() ...} would break callers that read .name/.size. Confirmed: with that mutation no offline test failed. Repair: this detail=True case, and comparable() compares S3Object entries by type and content (a dict equals an S3Object as a Mapping). The mutation now fails this case.
There was a problem hiding this comment.
Self-review of the repair 5004add7..f35cb2dd (both perspectives). Behavior: comparable() now recurses into dicts and compares S3Object by type and content; S3Object is a MutableMapping, not a dict, so the branches do not overlap, and the other delegation cases (_info, _ls(detail=True), get_tags, touch) compare as before (all 37 selected cases pass). The two gates are released by the tests' finally blocks in a correct run, so the bound changes only a regression's outcome. The cache arguments are fsspec's DirCache options. Claims: the commit message and the PR body's Added/Rewritten/TEST entries match the repair and the reruns listed there. CLEAN.
| @@ -966,8 +973,9 @@ async def test_copy_object_with_multipart_upload_cancelled_queued_completion(sel | |||
| release = threading.Event() | |||
|
|
|||
| def block(): | |||
There was a problem hiding this comment.
Finding 2 (P2), verified and repaired in f35cb2dd. Reviewer: the worker gates of cmu_cancelled_queued_completion (here) and test_flush_cancelled_while_queued_then_exit_with_exception used an unbounded release.wait() while holding the only thread; a regression that blocks the event loop on the queued work would keep the loop from reaching release.set(), and the bounded await could not fire. Both gates now wait at most EVENT_TIMEOUT. Rechecked: the completion left uncancelled still fails this test, and begin_abort aborting a COMPLETING write still fails the flush tests.
There was a problem hiding this comment.
Independent follow-up (relayed): Codex CLI 0.164.0, model gpt-6-astra, read-only, session 01a1791b-493b-76e2-b3e2-dcd906b37351, static review of 5004add7..f35cb2dd. Findings 1 and 3: resolved; comparable() keeps the other cases' comparisons. New finding (P2) on this repair: a bounded gate lets a regression that blocks the event loop end, and the timeout then releases the held call, so the test can pass; for example the copy cleanup waiting for the queued abort on the loop.
Verified and repaired in beb30b11: the gates of cmu_cancelled_queued_completion, test_write_cancelled, test_commit_cancelled and test_flush_cancelled_while_queued_then_exit_with_exception record release.wait(EVENT_TIMEOUT)'s result and the tests assert that none timed out. Rechecked: _wait_ready blocking the loop on the part waiter fails test_write_cancelled[1,2] in 20 s (it hung before this PR), and the copy cleanup blocking the loop on submit(_abort).result() fails cmu_cancelled_queued_completion in 10 s; the six affected tests pass; just lint passed.
There was a problem hiding this comment.
Self-review of f35cb2dd..beb30b11 (both perspectives): in a correct run each gate is released by the test's finally and appends True before the asserted point (the abort behind the only worker, the abort after both parts, the joined pool), so the assertions do not depend on timing; the commit message describes the change as made. CLEAN.
Independent follow-up (relayed): Codex CLI 0.164.0, model gpt-6-astra, read-only, session 01a17921-a7f8-7001-9ec8-4556e8ee52e8, static review of f35cb2dd..beb30b11 on a detached snapshot: CLEAN; a regression that blocks the event loop until a gate times out records False, so the assertions fail, and the appends precede the assertions at lines 1039, 2634, 2682 and 2742. No tests were run by the reviewer. Offline at beb30b11: test_s3_async.py 142 passed, the 5 failures are its S3 tests.
| key="dummy", | ||
| secret="dummy", | ||
| region_name="us-east-1", | ||
| use_listings_cache=True, |
There was a problem hiding this comment.
Finding 3 (P3), verified and repaired in f35cb2dd. Reviewer: test_invalidate_cache assumed fsspec's default listing cache; with FSSPEC_S3='{"use_listings_cache": false}' its premise fails. Confirmed: at 5004add7 both cases failed with that variable. The test now passes use_listings_cache=True, listings_expiry_time=None, passes with the variable set, and still fails when invalidate_cache does nothing.
| ) | ||
| running = [] | ||
| concurrency = [] | ||
| overlap, state = overlapping_calls(1) |
There was a problem hiding this comment.
Finding 4 (P3), not changed. Reviewer: overlapping_calls(1) releases the first call after its 0.5 s grace, so a regression whose second part copy starts later than that would pass. This is the documented design of the shared helper (tests/pyathena/util.py:310), used by the other bound tests: an exceeded bound can be missed under load but never fails a correct implementation, and waiting for a second call without the grace would make every correct run wait the full timeout. The mutation that ignores max_workers fails this test.
…-1328) A bounded gate lets a regression that blocks the event loop end, but the timeout then releases the held call, and the test could pass. The gates of the four cancellation tests record whether they were released, and the tests assert it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
WHAT
Implements the
test_s3_async.pyrows of the #1328 audit (comment). Onlytests/pyathena/filesystem/test_s3_async.pychanges;pyathena/filesystem/does not.Removed (each named behavior fails another test, offline unless noted):
register_async_filesystem(fixture),test_pandas_read_csvS3FileSystemclass thatimport pyathena.pandasregisters, so the pandas test read throughS3FileSystem; synctest_pandas_read_csv(S3) fails without the registrationtest_copy_object_with_multipart_upload_waits_for_running_parts..._cancelled,..._loop_shutdown[part],..._failed_part_with_threads_taken(same cleanup path)test_copy_object_with_multipart_upload_failed_part_stops_running_parts..._failed_part_with_threads_taken(session code)test_copy_object_with_multipart_upload_cancelled_creation_stops_retries..._cancelled_creationfailstest_multipart_copy_uses_creation_algorithmTestS3Core::test_complete_multipart_upload_uses_creation_algorithmTestAioS3File::test_open_parts_in_flight,test_open_failed_part_while_waitingTestS3File::test_write_parts_in_flight(5 cases),test_write_failed_part_while_waiting(4 cases)test_put_file_mode_put_filecases of the delegation test; synctest_put_file_modetest_touch_sync_wrappertouchcases of the delegation testtest_find,test_glob,test_rm_recursive,test_upload_cp_file(S3)test_expand_path_glob_lists_stem_prefix, the offline rm tests,test_cp_file_multipart_parameters[10]; on S3, synctest_glob,test_rm_recursive,test_upload_cp_fileRewritten:
test_copy_object_with_multipart_upload_cancelled_queued_completion: boundedawait; the completion is detected whichever pool method queues it.test_cp_file_multipart_parameters: the bound is checked withoverlapping_callsinstead of atime.sleep(0.01)overlap.test_copy_version: asserts the CopyObject request through_serve_keysinstead of the awaited_cp_filearguments.test_transaction_pipe_file_compression(wastest_pipe_file_compression): only the transaction cases, whosepipe_file()is the asyncio filesystem's own.test_transaction_pipe_put_file_create_existing: no internalexists()call count.test_write_cancelled: bounded part wait andawait.TestAioS3FileSystem::fs: closes the filesystem.test_flush_cancelled_while_queued_then_exit_with_exception: bounded pool shutdown.cmu_cancelled_queued_completion,test_write_cancelled,test_commit_cancelledand the queued flush test are bounded and record whether they timed out, so a regression that blocks the event loop fails instead of hanging or passing.Merged:
test_close_cancelledandtest_flush_cancelled_then_exit_with_exceptionintotest_commit_cancelled[close|flush], with a bounded pool shutdown instead ofasyncio.sleep(0.1).Added: the delegation test covers
_find(detail=True)(its entries are built by the asyncio method),touch,_mkdir,_makedirs,_rmdir,rmdir,getxattr,setxattr,list_multipart_uploads,clear_multipart_uploads,created,modified, andsign;test_invalidate_cache;test_expand_path_version_maxdepth.WHY
Part of #1328: tests that repeat another test's contract, depend on unsynchronized timing, or have unbounded waits, and asyncio delegations that no test called or whose mutations no test failed. Rewriting the
fs._callseams stays with #1340.TEST
At
5004add7(the offline full run at47da52cb;5004add7changes only the fake of thesigncase and themaxdepthtest, rerun below):just lint: passed.uv run --env-file <offline env> pytest --noconftest -p no:rerunfailures -n 8 tests/pyathena/filesystem(every AWS endpoint pointed at a closed port): 1,050 passed; the failures are exactly the tests that need S3 (the baseline at766d79d0plus the newtest_fsspec_conformance.py), without the five removed S3 tests.pyathena/filesystem/, reverted after each run:_mkdirdropping**kwargs,_makedirsdroppingexist_ok,_rmdir/rmdirdoing nothing,signdroppingexpirationor**kwargs(rerun at5004add7, also without an endpoint override),getxattrdropping**kwargs,setxattrdroppingcopy_kwargs,list_multipart_uploadsreturning[],clear_multipart_uploadsdoing nothing,created/modifiedreturningNone,touchdroppingtruncateor**kwargs,invalidate_cachedoing nothing, the asynciomaxdepthcheck removed (rerun at5004add7),_copynot pairing a version.max_workers,begin_abortaborting aCOMPLETINGwrite (test_commit_cancelled[flush]and the queued flush test), the completion not cancelled._rmpassingrecursive=Truefails the offline rm tests.uv run --env-file .env pytest -n 1), with the synchronous code mutated: synctest_rm_recursive(_delete_pathsalways recursive),test_upload_cp_file(no CopyObject),test_pandas_read_csv(no registration), andtest_glob(_findignoringwithdirs) failed. Synctest_findpasses when_finddropsprefix, as the removed asynciotest_find, with the same assertions, would;test_expand_path_glob_lists_stem_prefixfails under it. Themaxdepthline of the removed test is covered offline:_findignoringmaxdepthfailstest_find_matches_fsspec,test_glob_matches_fsspecandtest_find_directory_without_extra_requests, and the_finddelegation case checks that the asyncio method passes it.f35cb2dd(repairs of the independent review):just lintpassed; offlinetest_s3_async.py142 passed, the 5 failures are its S3 tests;_find(detail=True)returningto_dict()entries fails the new case, which no test failed before;invalidate_cachedoing nothing still failstest_invalidate_cache, which now also passes withFSSPEC_S3='{"use_listings_cache": false}'(both cases failed at5004add7); the copy ignoringmax_workers,begin_abortaborting aCOMPLETINGwrite, and the completion left uncancelled still fail their tests.beb30b11:just lintpassed; offlinetest_s3_async.py142 passed, the 5 failures are its S3 tests;_wait_readyblocking the loop on the part waiter failstest_write_cancelled[1,2]in 20 s (it hung at the base), and the copy cleanup blocking the loop on the queued abort failscmu_cancelled_queued_completionin 10 s.5004add7: the five remaining S3 tests ofTestAioS3FileSystemthat use the closingfsfixture passed.5004add7: the removal evidence for the cleanup, session-stop and checksum tests (_S3WriteSession.abortnot waiting,_stop_requestsnot setting the stop, the shielded abort skipped, the part checksum dropped) and the_put_filedelegation ofmode.🤖 Generated with Claude Code