Skip to content

test(dtypes): pin the silent-coercion thresholds - #502

Merged
kevincostner17 merged 2 commits into
mainfrom
test/dtypes-thresholds
Sep 20, 2026
Merged

kevincostner17 merged 2 commits into
mainfrom
test/dtypes-thresholds

Conversation

@kevincostner17

Copy link
Copy Markdown
Contributor

What

The last of four mutation lanes on steps/dtypes.py. 19 tests, killing all 19 mutants that survived the full suite. No equivalence claim was needed.

Candidates 22
Killed by the full suite (not gaps) 3
Real gaps 19
Killed by these tests 19

The 3 already-covered mutants got no tests. The lane had drafted and verified kills for all three before stage 1 finished, then removed them once the confirmation showed they were covered.

Why these matter

These are the thresholds that decide when a column's type is silently rewritten, and how much collateral damage that is allowed to cause:

  • the parse share required before a whole column converts (cmp#10, #13, #24)
  • the contamination-share floor, both boundaries (cmp#30, #31)
  • the bad-cell budget before a conversion is abandoned (cmp#32, #36)
  • the coerced-cells report cap (cmp#33)
  • the post-semantic numeric retry (cmp#34, #35)
  • the plain and mixed datetime screens (bool#12, #14, #15, #16)

Each is pinned at the value, just below it and just above it — 3 bad cells in 10 warns while 4 stays silent; exactly COERCED_CELLS_CAP is not reported as truncated while cap+1 is.

One worth naming on its own: len(nonnull) > 0 could become >= 0, and .all() on an empty series is True — so an all-null column would have been called integral. Now pinned by test_a_column_with_no_parsed_values_is_never_called_integral.

A defect pinned, not fixed

A column whose values carry different UTC offsets is reported as a successful datetime conversion that did not happen:

vals = ["2021-01-05 00:00:00+01:00", "2021-01-06 00:00:00+02:00",
        "2021-01-07 00:00:00+03:00", "2021-01-08 00:00:00+04:00"]
suggest_conversion(pd.Series(vals), CleanConfig())
# -> target='datetime'   result_dtype=object   n_coerced=0
# fix_dtypes: column dtype stays object
# report action: "converted to object"

Values with differing offsets cannot share one tz-aware datetime64, so pandas returns an object-dtype Series of Timestamps and _try_datetime accepts it. Three problems in one: the declared target is not the delivered result; the audit record contradicts itself"converted to object" read plainly says no conversion happened, which is squarely the audit-completeness contract; and _record_coerced's day/month ambiguity note, gated on is_datetime64_any_dtype, can never fire for such a column.

pd.to_datetime(..., utc=True) yields a real datetime64[ns, UTC]. The right behaviour is to convert with utc=True and say so, or decline the column — claiming "datetime" and delivering object is not defensible. Pinned as test_mixed_utc_offsets_are_reported_as_a_conversion_to_object, marked as recording rather than endorsing.

Verification

  • py3.12 / pandas 2.3.3 — 19 passed
  • py3.9 / pandas 1.5.3 — 16 passed, 3 skipped behind PANDAS_MAJOR >= 2, matching the source branch's own gate
  • Ordering swept over 8 pytest-randomly seeds with all four dtypes test files together — 105 passed on each. An order-dependent test nearly landed earlier in this series, so this is checked rather than assumed.
  • Full suite — 7389 passed, 22 skipped, 0 failed, coverage 95.08%
  • ruff check . clean

Sample-vs-column screens compute their drawn positions at runtime through sample_series rather than hard-coding indices, so a pandas RNG change adapts instead of breaking.

Scope

Tests only — no src/ change. No behaviour change, no changelog entry, no compatibility impact.

The last of four mutation lanes on steps/dtypes.py. 19 tests, killing all
19 mutants that survived the full suite. No equivalence claim was needed.

These are the thresholds that decide when a column's type is silently
rewritten, and how much collateral damage that is allowed to cause: the
parse share required to convert, the contamination-share floor, the
bad-cell budget before a conversion is abandoned, the coerced-cells report
cap, the post-semantic retry, and the datetime screens. Each is pinned at
the value, just below it and just above it -- 3 bad cells in 10 warns while
4 stays silent, exactly the cap is not "truncated" while cap+1 is.

One worth naming: len(nonnull) > 0 could become >= 0, and .all() on an
empty series is True, so an all-null column would have been called
integral.

22 mutants were candidates; the full suite killed 3 of them, which got no
tests. The lane had drafted and verified kills for all three before Stage 1
finished and removed them once the confirmation showed they were already
covered.

Sample-vs-column screens compute their drawn positions at runtime through
sample_series rather than hard-coding indices, so a pandas RNG change
adapts instead of breaking.

Also pins a defect, not fixed: a column whose values carry different UTC
offsets is reported as target="datetime" while the result is object dtype,
and the audit record reads "converted to object" -- a description that, read
plainly, says no conversion happened. It also silently disarms the day/month
ambiguity note, which is gated on is_datetime64_any_dtype.

Verified on py3.12/pandas 2.3.3 (19 passed) and py3.9/pandas 1.5.3 (16
passed, 3 skipped behind PANDAS_MAJOR >= 2, matching the source branch's own
gate). Ordering swept over 8 pytest-randomly seeds against all four dtypes
test files together: 105 passed each. Full suite 7389 passed.

Tests only; no src change.
@coderabbitai

coderabbitai Bot commented Sep 20, 2026

Copy link
Copy Markdown
Contributor

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Advanced

Run ID: 84852431-c42c-42c2-8427-90ced6b89a5f


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@kevincostner17
kevincostner17 merged commit e4a8819 into main Sep 20, 2026
19 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant