tests: Add ctypes coverage for lib/arraystats and lib/rowio - #7879
tests: Add ctypes coverage for lib/arraystats and lib/rowio#7879jalpatel11 wants to merge 7 commits into
Conversation
|
If you want to know what's missed, you can try pointing your AI to that work I was refreshing after seeing your previous PR: echoix#674 It is correctly supporting the code coverage in C/C++ based library code, even when called from python. So, you'll be able to see if something is missing. |
|
I couldn’t run the coverage setup since it hasn’t been merged yet, so I went through the source manually and compared the 28 tests against the relevant branches. I found three gaps and pushed a fix.
This brings the suite to 34 tests, all verified against a real GRASS build using |
23d0b5e to
9804b35
Compare
|
I ran an AI review over this PR (Opus 5 though) and had it verify the asserted values by compiling Two things are probably worth acting on:
Details, plus smaller coverage gaps: Full findingsBlocking1. The test passes only because no successful if (cur == R->cur)
R->cur = -1;which compares a slot index ( 2. It passes only interior breaks, so the unbounded 3. Perfectly uniform data is the one input where the residual Coverage gaps
Note (not blocking)
Verified as correctmin/max/mean/stdev, interval, quant, stdev, equiprob, the frequency counts, and the discont chi2/breaks all reproduce. The |
|
So, the tests here are fine. There’s some issues to file in different PRs. (Not here). There’s still some coverage gaps. And testing for a wrong guarantee. Feel free to challenge that with an adversarial model, and your head. |
❗ |
|
In general, I support this kind of low level unit tests on the libraries, and via Python bridge is an added plus. |
|
And if I finish to choose the cleanest implementation of code coverage (for c based code), properly done its about 10-18% impact, and PRs like that really show what is missed or improved by these tests. I’m still learning how to use Claude, and I really like it, as it can finalize work that I started, but get stuck on a small problem and it stalled. So it unblocked me this week. |
test_get_returns_none_when_getrow_fails claimed a failed row was never left half-cached, but that only held for the narrow case it tested (a row that was never successfully cached before). Rowio_get()'s cleanup after a failed getrow() compares a cache slot index against a row number, so once a different row has been cached successfully, this comparison essentially never matches and the cache can return a stale buffer instead of retrying or failing. Narrowed the original test to the case it actually covers and added a second test that reproduces and documents the stale-buffer behavior. The underlying bug belongs in a separate change to lib/rowio itself. Found via code review from echoix on OSGeo#7879.
|
@echoix I fixed the blocking The underlying issue is in For I’m still working through the remaining non-blocking coverage gaps you mentioned, including the |
a0a569d to
1b9b2eb
Compare
1b9b2eb to
ac0bc50
Compare
|
@echoix, I reviewed the rest and closed the remaining gaps. The I also added coverage for the equiprob class-reduction branch and That brings the suite to 41 tests. The |
Add ctypes unit tests for
lib/arraystatsandlib/rowioSummary
As a follow-up to the
lib/datetimetest coverage work in #7871, I reviewed libraries underlib/that have limited or no direct test coverage and already have generatedgrass.lib.*ctypes bindings.This PR adds direct pytest coverage for two self-contained libraries:
lib/arraystatslib/rowioThe tests call the C APIs directly through the existing ctypes bindings. Neither library requires a GRASS session, mapset, project, or real file I/O for the functionality covered here, so these are unit tests rather than integration tests.
The PR adds 28 tests across 3 test files.
lib/arraystats19 tests across 2 files
lib/arraystatsprovides statistical calculations and classification algorithms used by tools such asv.classandd.vect.thematicto classify data into map-legend breaks.The test suite covers:
AS_class_apply_algorithm()AS_option_to_algorithm()Basic statistics
lib_arraystats_basic_stats_ctypes_test.pycoversAS_basic_stats()andAS_eqdrt().One important implementation detail is explicitly covered:
AS_basic_stats()takes the minimum and maximum from the first and last elements of the input rather than scanning the entire array. This means those values are only correct when the input is appropriately ordered, while the sum-based statistics remain correct for unsorted input.Classification
lib_arraystats_classify_ctypes_test.pycovers all five classification algorithms, along with the dispatcher and option parsing helpers.AS_option_to_algorithm()is tested using a minimal zero-initializedOptionstructure, so the test does not need to invokeG_parser()or initialize a GRASS session.Scope
Natural-breaks coverage is limited to a regression-style case for now. The algorithm is complex enough that adding a larger set of manually derived expected values would make the initial test suite unnecessarily difficult to maintain.
Paths that call
G_fatal_error()are also not covered. These paths callexit()by default, which would terminate the pytest process rather than provide an exception that can be asserted in a unit test.lib/rowio9 tests
lib/rowioimplements an in-memory LRU row cache around caller-providedgetrowandputrowcallbacks.The tests use Python closures backed by an in-memory dictionary, so no actual files are required. The file descriptor passed through the C API is treated as an opaque value and passed through to the callbacks.
The tests cover:
Rowio_flush()writing pending dirty rowsRowio_fileno()returning the configured descriptorThe suite also documents two less obvious behaviors found during testing:
Rowio_forget()does not flush a dirty row. If a modified row is forgotten before it is flushed, the modification is discarded.R->curshortcut pointing to the existing buffer. A subsequentRowio_get()for that same row can return the stale buffer without callinggetrow(). Accessing another row first clears the shortcut and restores the normal reload behavior.These tests document the current implementation behavior; this PR does not change it.
lib/statsFindingWhile reviewing additional libraries for potential coverage, I found an issue with the generated ctypes bindings for
lib/stats.The functions declared in
stats.husing thestat_funcfunction-pointer typedef are currently interpreted byctypesgenas data symbols rather than callable functions. The generated bindings therefore use.in_dll()for these symbols.As a result, calling functions such as
c_count,c_sum, orc_avethroughgrass.lib.statscauses the Python interpreter to segfault.I confirmed the root cause by loading the same symbols manually with the correct
CFUNCTYPEsignature. With the correct function signature, the functions execute normally and return the expected results.This is out of scope for this PR since
lib/statsis not part of the test coverage being added here. I will report this separately and follow up with another PR for the binding fix and corresponding test coverage. I wanted to flag it here because the issue affects actual use ofgrass.lib.stats, not just its testability.Testing
The tests were verified against a real GRASS build using the
osgeo/grass-gis:main-alpineDocker image.This verifies that the tests exercise the compiled C implementations through the generated ctypes bindings rather than a mocked or standalone implementation.
Results:
ruffversion from.pre-commit-config.yaml(v0.15.17)AI Disclosure
I used AI assistance (Claude) while drafting and iterating on the tests, including helping verify test cases against a real compiled
lib/datetimebuild.I reviewed the generated tests and validation results myself and made the final decisions about test coverage, expected behavior, and the scope of the changes.