Skip to content

test: reach 100% coverage and enforce it in CI - #194

Merged
SemyonSinchenko merged 3 commits into
MrPowers:mainfrom
slavlotski:add-pytests-to-get-100%-coverage
Aug 20, 2026
Merged

test: reach 100% coverage and enforce it in CI#194
SemyonSinchenko merged 3 commits into
MrPowers:mainfrom
slavlotski:add-pytests-to-get-100%-coverage

Conversation

@slavlotski

Copy link
Copy Markdown
Contributor

Brings the test suite to 100% statement and branch coverage, and adds the tooling to keep it there.

main current branch
Statements 739 (59 missed) 736 (0 missed)
Branches 304 (37 partial) 302 (0 partial)
Coverage 89% 100%
Tests 185 passed 252 passed, 1 xfailed

The small drop in totals comes from the flatten.py import change described below.

Tests added

Two new files:

  • tests/test_chispa.py — the Chispa class had no direct coverage: default-format fallback, passing a FormattingConfig through untouched, converting a legacy dataclass, and both outcomes of assert_df_equality.
  • tests/test_number_helpers.pynan_safe_equality / nan_safe_approx_equality, including the isnan TypeError path and the boundary where the difference exactly equals the precision.

Extended:

  • test_row_comparer.py — rewritten as parametrized cases with named ids. Adds missing-row handling, nan-vs-non-nan, and nested list/tuple comparison at differing lengths.
  • test_schema_comparer.pyprint_schema_diff in both output formats plus its unknown-format error, empty schemas, and tree-diff colouring for differing leaf types, nullability, and map key types.
  • test_dataframe_comparer.py_contains_map_type across nesting shapes, the zero-precision fallbacks in assert_approx_df_equality, and custom format objects reaching the output.
  • test_rows_comparer.pyassert_generic_rows_equality with default and custom formats.
  • test_deprecated.py — the two deprecated *_ignore_nullable helpers now assert their DeprecationWarning.
  • test_formats.py / test_formatting_config.py — error paths for non-dict input, a list where a colour string is expected, and an invalid format type.

Assertions target observable behaviour — the actual ANSI codes in the error message, the specific tree line — rather than just "it raised".

Source changes

Kept to the minimum needed to measure coverage — no logic is touched.

  • chispa/flatten.py: the pyspark type imports moved out of the TYPE_CHECKING block to runtime imports, so the lines execute under coverage.
  • Two # pragma: no branch markers on final elif arms whose fall-through is impossible (flatten.py map handling, _compare_datatypes map dispatch), each with the reason inline. schema_comparer.py is otherwise byte-identical to main — the marker is a comment; the branch it silences is unreachable because the enclosing if has already established the type is one of the three complex kinds, and the two preceding arms rule out the other two.

No behaviour changes.

Tooling

  • Coverage settings moved from the Make recipe into pyproject.toml ([tool.coverage.run] with branch = true, [tool.coverage.report] with fail_under = 100), so every entry point that passes --cov gets the same configuration.
  • make test now reports term-missing, so a shortfall names the lines.
  • New coverage CI job running make test. It pins pyspark-version: 4.0.2 — the composite action defaults to 3.5.1, which no test job covers, and 4.0.2 is both exercised by the test matrix and shares its cache key with the Python 3.11 leg.

The nine-leg test matrix keeps running plain pytest tests. A pyspark-version quirk should surface as a test failure on the leg that has it, not as nine red coverage checks.

For reviewers

One xfail is intentional. it_throws_when_duplicate_column_names_hide_a_mismatch is marked xfail(strict=True) and documents a real bug: assert_basic_rows_equality zips fields positionally but looks them up by name, so when a DataFrame has duplicate column names every lookup returns the first match and genuine differences go unreported. The test is written to pass once that is fixed. The same pattern exists in assert_generic_rows_equality and is not yet marked.

Two further bugs found but deliberately left alone — both reachable from the public API, both invisible to coverage, both better as their own PR:

  1. assert_df_equality / assert_approx_df_equality mutate the caller's transforms list. Passing the same list to two calls with ignore_column_order=True leaves it holding two sort transforms.
  2. assert_basic_rows_equality raises AttributeError: 'NoneType' object has no attribute 'split' instead of DataFramesNotEqualError when rows have different field counts, because zip_longest yields a None field name that is then used as a key.

Three tests call private helpers with a None side on purpose. describe_compare_array_types, describe_compare_map_types, and the both-missing case in describe_are_fields_shallow_equal pass arguments no public call path produces — the callers pair two same-kind types, and zip_longest never yields two Nones. Those guards are therefore dead defensive code. They are covered here rather than deleted to keep this PR to tests; deleting them instead would remove ~16 statements and ~10 branches and reach 100% by subtraction. Happy to do that here if reviewers prefer.

Verification

make test

Locally: 252 passed, 1 xfailed, 100% coverage; pre-commit run -a and mypy chispa clean. Note the local run is Python 3.14 / pyspark 4.0.0

@SemyonSinchenko
SemyonSinchenko merged commit 297605e into MrPowers:main Aug 20, 2026
12 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.

2 participants