Repository navigation
Remove PyHive-derived DB API tests repeated on the same code path - #1267
Conversation
|
|
||
|
|
||
| class TestAsyncCursor: | ||
| def test_fetchone(self, async_cursor): |
There was a problem hiding this comment.
Self-review round 1 (implementation behavior). Base 0b18cdb, head a8e7439. Result: CLEAN.
Covered: all 13 changed files (NOTICE and the 12 test files), traced against the fetch, iteration, close, and execute code of Cursor, DictCursor, AsyncCursor, AsyncDictCursor, the sync and async pandas, Arrow, Polars, and S3FS cursors, and AioPandasCursor.
- Behavior: the sync cursors define no
fetchone/fetchmany/fetchall/__next__/__iter__/close, soWithFetch,CursorIterator, andWithResultSet.close()are shared; the async cursors other thanAsyncCursordo not overrideclose(). The__next__/__iter__inpandas/result_set.pyandpolars/result_set.pybelong to the DataFrame iterators, not to the result sets. - Data boundaries: reading many_rows with UNLOAD (several result files) is still checked by
test_many_as_pandas/test_many_as_arrow/test_many_as_polarswith the unload parameter. Fetching across chunks and record batches is checked offline (test_fetch_across_chunks,test_fetch_across_empty_batches). Hypothesis: S3FS refilling across_fetch()batches lost its only test with the removed 10000-rowtest_fetchall. Not a finding: the offline helper ins3fs/test_result_set.pyusesarraysize=1, and a mutation that drops the reader after the first batch failed 11 offline tests there (reverted;git diff -- pyathena/empty). - Test quality: the kept
test_arraysizedetects a broken passthrough, because without it the result set usesDEFAULT_FETCH_SIZE(1000) andfetchmany()returns all 20 rows instead of 5. - Docs and agent skills do not refer to the removed tests.
Limitations: the live AWS run has not been done yet; it runs once after the reviews.
There was a problem hiding this comment.
Rebased the reviewed series 8e85cb2 (on 0b18cdb) onto 82754d2 as 9e652a5 without conflicts; git range-diff 0b18cdb7..8e85cb22 82754d2f..9e652a50 shows the three patches unchanged. Upstream contracts checked: #1266 rewrote test_reader_closes_at_end_of_results and test_managed_results_preserve_api_values in s3fs/test_result_set.py, which this PR cites as offline covers; #1264 added a live test to test_cursor.py. At 9e652a5, just lint passed, the offline suite has no new failure against 82754d2, and all 22 offline mutations were rerun: every test that failed a mutation at c5356c4e also failed it at 9e652a5.
a8e7439 to
f986774
Compare
| cursor.execute("SELECT a FROM many_rows ORDER BY a") | ||
| assert cursor.fetchall() == [(i,) for i in range(10000)] | ||
|
|
||
| def test_iterator(self, cursor): |
There was a problem hiding this comment.
Self-review round 2 (claims, callers, operations, evidence). Base 0b18cdb, head a8e7439 (reviewed), republished as f986774 with the same tree. Result: FINDINGS, repaired in the PR body and the commit message.
Claims checked:
- No file under
pyathena/changes:git diff --stat 0b18cdb7..HEAD -- pyathenais empty. test_arraysizechecks the cursor's own passthrough:arraysize=self.arraysize/self._arraysizein each syncexecute()and async_collect_result_set()(pandas/cursor.py:245,pandas/async_cursor.py:185, and likewise for Arrow, Polars, S3FS, andasync_cursor.py:200).test_bad_query: the Arrow, Polars, and S3FS result set constructors branch onSTATE_SUCCEEDED(arrow/result_set.py:142,polars/result_set.py:373,s3fs/result_set.py:127).test_executemany: each result set checks the output suffix and substatement of an INSERT result in its own CSV read (arrow/result_set.py:259,polars/result_set.py:668,s3fs/result_set.py:177).- Counts: 105 to 54 test functions with a PyHive name, 3745 to 3662 collected tests, 97 + 2 queries; Python 3.15.0.
Findings and repairs:
- The PR body and the commit message said that each result set's own
test_result_set.pychecks its fetch methods, rownumber, and iteration. The S3FS and Arrow result sets inheritfetchmany()and the row counting fromAthenaResultSet, checked intests/pyathena/test_result_set.py, and iteration is checked only through the Arrow result set. Both texts now say so. - The offline comparison was made against fb22d08, not 0b18cdb. Rerun at 0b18cdb: 1769 passed, 18 failed, 681 errors; at the head 1763 passed, 16 failed, 606 errors. No failure is new, and the 6 fewer passes are the removed offline
test_open_closecopies. The mutation results come from c5356c4e;git diff fb22d08a 0b18cdb7touches none of the offline covers named. - The WHY covered only async/aio copies, and "cursors other than Cursor and AsyncCursor" was broader than checked; both are narrowed to what was checked.
Operator: the change removes 99 Athena queries per run and changes no retry or quota behavior. Documentation: no doc or skill refers to the removed tests. Limitation: the live run has not been done yet.
| result_set = future.result() | ||
| assert result_set.fetchall() == [(i,) for i in range(10000)] | ||
|
|
||
| def test_arraysize(self, async_s3fs_cursor): |
There was a problem hiding this comment.
Independent review (relayed). Reviewer: Codex CLI 0.163.0, model gpt-6-astra, reasoning effort max, codex exec -s read-only, session 01a14cb6-4e35-7922-abc2-c8d191602c15. Base 0b18cdb, head f986774, reviewed in a detached snapshot with the literal diff, without the PR text, commit messages, or earlier findings. Static review: no edits, tests, builds, or network. Result: FINDINGS (2).
Covered: 51 removed test methods (83 parametrized cases), the two shortened test_fetchall, and NOTICE; traced fixtures, cursor construction, execute(), _collect_result_set(), fetching, iteration, arraysize, and cleanup against the remaining live and offline tests, and compared PyHive's test files.
Finding 1 (P2): removing test_fetchone, test_fetchall, and test_open_close from TestAsyncS3FSCursor removes every check of non-empty row values returned through AsyncS3FSCursor. The remaining successful query only checks len(result_set.fetchmany()) == 5; test_read_options mocks AthenaS3FSResultSet. Scenario: _collect_result_set() passes another converter instead of self._converter, so ("1",) is returned instead of (1,), and no test fails; the tests that build the result set directly or use the sync and aio S3FS cursors bypass this collector.
Observations without change: the one_row queries removed from the kept test_fetchall were redundant; the removed pandas test_fetchall did not pass chunksize to its 10000-row query; the removed test_open_close copies keep their context-manager and close paths through fixtures and test_no_ops; no imports, helpers, fixtures, or comments became stale.
Author verification: confirmed. Every other async cursor keeps a value check through its own cursor (test_description asserts fetchall() == [(1,)] in test_async_cursor.py, pandas/, arrow/, and polars/test_async_cursor.py); s3fs/test_async_cursor.py has none.
There was a problem hiding this comment.
Repaired in 8e85cb2: test_arraysize runs SELECT a FROM many_rows ORDER BY a LIMIT 20 and asserts fetchmany() == [(i,) for i in range(5)], which checks the arraysize and the converter that _collect_result_set() passes, in the same single query. Self-review of the repair (both perspectives): a is the integer column of many_rows; the other async cursors keep their value checks in test_description; no query is added. Limitation: this is a live test, so the mutation (another converter in _collect_result_set()) is checked by reasoning, not run: the rows would be strings and not equal (0,) to (4,); the live run after the reviews checks that the assertion passes.
| @@ -32,8 +32,6 @@ Parts that remain recognizable in PyAthena: | |||
| - tests/pyathena/sqlalchemy/test_base.py: test cases including | |||
There was a problem hiding this comment.
Independent review (relayed), finding 2 (P2), same reviewer and session as the review on tests/pyathena/s3fs/test_async_cursor.py: deleting the whole reference to tests/pyathena/aio/sqlalchemy/test_base.py leaves recognizable PyHive material unlisted. Its test_basic_query runs SELECT * FROM one_row and checks one row, number_of_rows == 1, and one column, as PyHive's SqlAlchemyTestCase.test_basic_query does. Replace the obsolete example with the surviving test instead of removing the file's attribution.
Author verification: confirmed; the aio test_reflect_schemas also checks the test schema and default as PyHive's does. Checking the same question for the DB API tests found that test_arraysize (arraysize = 5, SELECT * FROM many_rows LIMIT 20, len(fetchmany()) == 5) and the aio test_fetchone remain PyHive's test cases in the cursor tests of the other cursor classes, which NOTICE did not mention before this change either.
There was a problem hiding this comment.
Repaired in 8e85cb2: NOTICE states that tests/pyathena/aio/sqlalchemy/test_base.py ports test_basic_query and test_reflect_schemas to the asynchronous dialect, and that the cursor tests of the other cursor classes under tests/pyathena/ repeat some of PyHive's DB API test cases, such as test_arraysize and test_fetchone. Self-review of the repair: both statements were checked against the remaining tests and PyHive ae3c9a96 (SqlAlchemyTestCase.test_basic_query/test_reflect_schemas, DBAPITestCase.test_arraysize/test_fetchone); the PR body now describes the NOTICE change.
| result_set = future.result() | ||
| assert len(result_set.fetchmany()) == 5 | ||
| # The values check that the cursor passes its converter to the result set too. | ||
| assert result_set.fetchmany() == [(i,) for i in range(5)] |
There was a problem hiding this comment.
Independent follow-up review (relayed). Reviewer: Codex CLI 0.163.0, model gpt-6-astra, codex exec -s read-only, session 01a14cc4-353f-7f61-88bc-cd2b2879589c. Scope: the repair f986774..8e85cb2 (NOTICE and TestAsyncS3FSCursor::test_arraysize), in a detached snapshot of 8e85cb2. Static review: no edits, tests, builds, or network. Result: CLEAN.
Covered: the follow-up diff, both touched files, the cursor, converter, and result set flow, the many_rows fixtures, the previous arraysize check, and the NOTICE statements against the remaining tests and PyHive's test files. ORDER BY a makes (0,) to (4,) the first five rows; the assertion fails on wrong values, including unconverted strings; fetchmany() without a size on 20 rows still checks arraysize. NOTICE:31 describes the remaining repetitions accurately, and NOTICE:37 resolves the missing aio SQLAlchemy attribution. The snapshots and the PR worktree were unchanged after both reviews.
#1251 removed the aio TestAsyncSQLAlchemyAthena::test_unicode because the synchronous test_unicode covers the shared bind formatting. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…1260) Count each copy of the PyHive DB API fetch tests by its path: the result set class and its fetch implementation, the read path, and the sync, async, or aio cursor. Keep one test per path, test_arraysize, which also checks that the cursor passes arraysize to the result set. The fetch methods of each result set are checked offline in test_result_set.py of that result set or of AthenaResultSet, whose methods it inherits; iteration is checked through the Arrow result set. The WithFetch delegation and the query execution properties, which every synchronous cursor shares, remain checked by TestCursor. - Remove test_fetchone, test_fetchmany, test_fetchall, and test_iterator of the pandas, Arrow, Polars, and S3FS cursors and their async variants, the copies of AsyncCursor, AsyncDictCursor, and DictCursor, and TestCursor::test_iterator and TestAioPandasCursor::test_fetchall. - Remove test_open_close of the pandas, Arrow, Polars, and S3FS cursors and their async variants; none overrides close(), which TestCursor and TestAsyncCursor::test_open_close check. - Drop the one_row query from the default and aio test_fetchall, which the many_rows query covers. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…of the remaining tests The removed AsyncS3FSCursor fetch tests were the last to check row values returned through its _collect_result_set(), which passes the converter to the result set; test_arraysize now compares the rows of an ordered query. NOTICE names the aio SQLAlchemy tests that remain ports of PyHive's test_basic_query and test_reflect_schemas, and the cursor tests of the other cursor classes that repeat PyHive's DB API test cases. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
8e85cb2 to
9e652a5
Compare
WHAT
Review the PyHive-derived DB API tests that are repeated for each cursor class, on the tests that #1252, #1253, #1256, #1258, #1261, and #1265 left. Each copy is counted by its code path: the result set class and its fetch implementation, the read path (CSV, CSV in chunks, or UNLOAD), and the sync, async, or aio cursor. One test stays per path. This diff changes no file under
pyathena/.AthenaResultSet, GetQueryResults rows,Cursortest_fetchone(also the query execution properties),test_fetchmany,test_fetchall,test_arraysizetest_iteratorAthenaDictResultSet,DictCursortest_dict_type,test_null_vs_empty_stringtest_fetchone,test_fetchmany,test_fetchallAthenaResultSet/AthenaDictResultSet,AsyncCursor/AsyncDictCursortest_arraysize,test_query_execution,test_dict_typetest_fetchone,test_fetchmany,test_fetchall,test_iteratortest_arraysizefor each read pathtest_fetchone,test_fetchmany,test_fetchall,test_iterator,test_open_closeAioPandasCursortest_fetchonetest_fetchalltest_arraysizeis the test kept on each pandas, Arrow, Polars, and S3FS path because the cursor's ownexecute()passesarraysizeto the result set.TestAsyncS3FSCursor::test_arraysizenow also compares the rows ofSELECT a FROM many_rows ORDER BY a LIMIT 20, because the removed copies were the last tests to check row values returned throughAsyncS3FSCursor, whose_collect_result_set()passes the converter; each other async cursor keeps such a check intest_description. Every fetch on these result sets is a method of the result set. Those methods are checked offline in thetest_result_set.pyof that result set, or ofAthenaResultSetfor the methods that the Arrow and S3FS result sets inherit. The other parts of a removed copy are code that no cursor class overrides, and a kept test runs them:WithFetch.fetchone(),fetchmany(), andfetchall()delegate to the result set for every synchronous cursor;TestCursorchecks them.CursorIterator.__next__()is shared by every synchronous cursor and result set;TestAthenaArrowResultSet::test_fetch_across_empty_batchesiterates a result set.DictCursor, the pandas, Arrow, Polars, and S3FS cursors, and their async variants do not overrideclose();TestCursor::test_open_closeandTestAsyncCursor::test_open_closecheckWithResultSet.close()andAsyncCursor.close().execute()of a cursor class with a given read path is the one that the kepttest_arraysizeof that class and read path runs.The default and aio
test_fetchallno longer runSELECT * FROM one_row; themany_rowsquery reads the first page and the later ones.These copies stay because they run code of their own:
AioCursor'stest_fetchone,test_fetchmany,test_fetchall, andtest_arraysize, andAioDictCursor'stest_fetchone:AioCursorhas its own fetch methods andAthenaAioResultSetfetches asynchronously.test_executemanyof the default, pandas, Arrow, Polars, and S3FS cursors: each result set reads the result of an INSERT through its own reader, also on a cursor with the unload option, which leaves INSERT unwrapped.test_bad_query: each async result set class handles a failed query in its constructor; the aio cursor raises.test_basic_query,test_reflect_schemas,test_has_table, and the others with PyHive names): the aio copies run the aio connection adapter, and the per-driver cases run each dialect's cursor class.NOTICEno longer says thattests/pyathena/aio/sqlalchemy/test_base.pyportstest_unicode, which #1251 removed; it names the PyHive test cases that file still ports,test_basic_queryandtest_reflect_schemas. It also states that the cursor tests of the other cursor classes repeat some of PyHive's DB API test cases, such astest_arraysizeandtest_fetchone. The PyHive-derived tests thatNOTICEnames remain.tests/pyathena/The removed cases ran 97 Athena queries per run, and the trimmed
test_fetchallcases 2 more.WHY
Closes #1260. Part of #1239: the removed copies repeated the contract of a kept test on the same code path with the same kind of evidence, including copies of synchronous tests for async or aio variants that run through the same code, which "Choose what to test" in
docs/testing.mdexcludes.TEST
Python 3.15.0.
At 9e652a5, on 82754d2. 9e652a5 is the reviewed series (8e85cb2, on 0b18cdb) rebased without conflicts;
git range-diffshows the three patches unchanged. The upstream changes in between changetests/pyathena/s3fs/test_result_set.pyandtests/pyathena/test_cursor.py, outside the hunks of this change.just format: no changes.just lint: passed.--noconftestwith placeholder AWS settings,tests/pyathenawithoutfilesystem/): 1763 passed, 16 failed, 607 errors, 24 skipped, 2 xfailed. At 82754d2: 1769 passed, 18 failed, 682 errors. The failures and errors are tests that need AWS; none is new at the head, and the 6 fewer passes are the removed offlinetest_open_closecopies.testjob of another PR, run 39089607580, started at the same time and passed):uv run --env-file .env pytest -n 4 -q -p no:cacheprovideron the 12 changed test files: 515 passed, 3 skipped (conditional skips in those files for the Polars version and the environment), 2 xfailed, in 448 s. The full AWS suite runs in CI on this PR.Offline mutation checks, with
--noconftestand placeholder settings. Each mutation was reverted andgit diff -- pyathena/was empty after each. Before the removals, at c5356c4e, and again at 9e652a5, each mutation below failed the offline tests named; every test that failed a mutation at c5356c4e also failed it at 9e652a5:AthenaResultSet.fetchone()does not count the row inrownumberTestAthenaArrowResultSet::test_fetch_across_empty_batches,TestAthenaResultSet::test_fetchall_across_pagesAthenaResultSet.fetchmany()without a size takesarraysize + 1rowsTestAthenaS3FSResultSet::test_reader_null_conversionAthenaResultSet._take_rows()takes one row fewerTestAthenaArrowResultSet::test_fetch_across_empty_batches,TestAthenaS3FSResultSet::test_reader_null_conversionAthenaResultSet._fetch()never requests the next pageTestAthenaResultSet::test_fetch_after_failed_fetch,TestAthenaResultSet::test_fetchall_across_pagesCursorIterator.__next__()skips a rowTestAthenaArrowResultSet::test_fetch_across_empty_batchesAthenaDictResultSetbuilds tuples instead of dictsTestAthenaAioResultSet::test_async_iteration,TestAthenaAioResultSet::test_fetchmany_across_pagesAthenaPandasResultSet.fetchone()does not count the rowTestAthenaPandasResultSet::test_fetch_across_chunks,TestAthenaPandasResultSet::test_fetch_after_chunks_run_outAthenaPandasResultSet._take_rows()takes one row fewerTestAthenaPandasResultSet::test_fetch_across_chunksAthenaPolarsResultSet.fetchone()does not count the rowTestAthenaPolarsResultSet::test_fetch_across_chunks,TestAthenaPolarsResultSet::test_fetch_after_chunks_run_outAthenaPolarsResultSet._take_rows()takes one row fewerTestAthenaPolarsResultSet::test_fetch_across_chunksAthenaArrowResultSet._fetch()buffers only the first record batchTestAthenaArrowResultSet::test_fetch_across_empty_batchesAthenaS3FSResultSet._fetch()drops the first row of each batchTestAthenaS3FSResultSet::test_csv_open_options,TestAthenaS3FSResultSet::test_reader_closes_at_end_of_resultsWithAsyncFetch.fetchall()runsfetchmany()in the threadTestWithAsyncFetch::test_cancelled_fetch_detaches_result_set,TestWithAsyncFetch::test_fetch_runs_result_set_fetchAthenaAioResultSet.fetchone()does not count the rowTestAthenaAioResultSet::test_fetchall_across_pages,TestAthenaAioResultSet::test_fetchone_across_pagesAthenaAioResultSet._async_take_rows()takes one row fewerTestAthenaAioResultSet::test_fetchmany_across_pagesAthenaAioResultSet._async_fetch()never requests the next pageTestAthenaAioResultSet::test_fetch_after_cancelled_fetch,TestAthenaAioResultSet::test_fetch_after_failed_fetchWithResultSet.close()raisesTestWithAsyncFetch::test_context_exit_closes_off_event_loop,TestCursor::test_open_closeAsyncCursor.close()raisesTestAsyncCursor::test_open_closeBreaking the delegation in
WithFetch.fetchmany()orWithFetch.fetchall(), or a query execution property ofWithResultSet, failed no offline test. Those methods are shared by every synchronous cursor, andTestCursorchecks them against Athena; the removed copies ran the same methods.🤖 Generated with Claude Code