Conversation
Adds a codecov project status group holding tests/** to a 99% target, and closes the coverage gap that would otherwise have made that status red on every commit. Coverage of tests/ goes from 96.59% (64 uncovered statements of 1876) to 99.89% (2 of 1881). Seventeen of the 64 uncovered lines were not missing tests at all but a pytest-benchmark tracer artifact. pytest_benchmark's fixture calls sys.settrace(None) around the timed loop and restores the global trace function afterwards; restoring it cannot reinstate f_trace on frames that are already executing, so every statement after a benchmark(...) call in a benchmark test body goes untraced even though it runs. Passing --benchmark-disable in the CI coverage run takes all three benchmark files to 100%; the benchmarked functions still execute once each, and nothing consumes CI timings. Local `pytest` runs are unaffected. The rest of the gap was closed with real tests, not suppression: thirteen error parametrizations added to existing `if isinstance(expectation, Exception)` branches (each exception type observed by running the case, never guessed), a test covering the non-bundled schema download path that the mock_schema_download fixture exists to serve, deletion of the unused clarreo_test_data_dir fixture, and two small helper cleanups. No `# pragma: no cover`, no [tool.coverage.report] exclude_lines, and no codecov ignore entry was added. The two lines still uncovered are branches in the SUDA example loop that the single-event test data file cannot reach. Closes #202 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #289 +/- ##
==========================================
+ Coverage 94.49% 95.75% +1.25%
==========================================
Files 49 49
Lines 4163 4168 +5
==========================================
+ Hits 3934 3991 +57
+ Misses 229 177 -52 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Contributor
There was a problem hiding this comment.
🟡 Changes recommended
Two moderate test-isolation and schema-mocking issues remain unresolved.
Get a fresh assessment by requesting another Copilot review.
Pull request overview
This PR enforces 99% coverage for tests/** and expands test coverage while disabling benchmarks during CI coverage runs.
Changes:
- Adds a dedicated Codecov status for
tests/**. - Adds error-path, validation, and fixture coverage.
- Disables benchmark timing during coverage collection.
File summaries
| File | Summary |
|---|---|
tests/unit/test_xtce/test_validation.py |
Adds non-bundled schema download coverage; schema host policy should be explicit. |
tests/unit/test_xtce/test_validation_security.py |
Improves mocked response stream behavior. |
tests/unit/test_xtce/test_parameter_types.py |
Adds invalid parameter-type cases. |
tests/unit/test_xtce/test_encodings.py |
Adds invalid encoding cases. |
tests/unit/test_xtce/test_definitions.py |
Simplifies coverage assertions. |
tests/unit/test_xtce/test_comparisons.py |
Adds invalid operator coverage. |
tests/unit/test_xtce/test_calibrators.py |
Adds invalid calibrator cases. |
tests/unit/test_common.py |
Exercises the ignored method. |
tests/conftest.py |
Removes an unused fixture and updates schema mocking; the network guard should reject bundled URLs or assert no call. |
codecov.yml |
Adds the 99% tests coverage status. |
.github/workflows/ci.yml |
Disables benchmark timing during coverage collection. |
Review details
Suppressed comments (1)
tests/unit/test_xtce/test_validation.py:88
- This test leaves
allowed_schema_hostsunset, so_resolve_schema_policywill honor anySPP_ALLOWED_SCHEMA_HOSTSvalue from the process environment. A developer or CI environment that omitswww.omg.orgwould make this otherwise valid test fail before the mocked download is reached; pass the intended allowlist explicitly (or clear the environment variable withmonkeypatch) to keep the test hermetic.
result = validate_xtce(io.StringIO(xtce_str), level="schema", raise_on_error=False)
- Files reviewed: 11/11 changed files
- Comments generated: 1
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Comment on lines
+67
to
+71
| Shared by unit and integration tests. Documents that reference the standard OMG schema URL | ||
| are served from the bundled schema without any network call, so this mock only actually | ||
| serves content for non-bundled URLs (see | ||
| ``test_validation.test_schema_validation_downloads_non_bundled_schema``). For documents that | ||
| resolve from the bundle it acts as a guard that no network call is attempted. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Closes #202.
The ask is a codecov status that holds
tests/**to a very high bar. That status is one small block of YAML, but adding it on its own would have been permanently red on every commit (only_pulls: false): coverage oftests/onmaintoday is 96.59% — 64 uncovered statements out of 1876. So the bulk of this PR is closing that gap, and the codecov change is the last step.Before: 96.59% (64 / 1876 uncovered). After: 99.89% (2 / 1881 uncovered).
Measured with
coverage report --include="tests/*" --precision=2on a single Python 3.13 environment. The full suite goes from 481 to 495 passing tests. Library coverage also ticked up as a side effect (92.04% → 92.35%), and the combined number codecov sees went 94% → 95.75%.No coverage suppression of any kind was used. No
# pragma: no cover, no[tool.coverage.report] exclude_linesinpyproject.toml, notests/**entry in codecov'signore:list, and no test was deleted to raise a percentage. This is per @greglucas's request on the previous attempt (#203): "Please add tests to get the coverage to 100%. You shouldn't add any no-pragma comments to ignore coverage on specific lines. I want those lines to get test parameterizations added to increase the coverage, not ignore the current coverage."pyproject.tomlis untouched.1. 17 of the 64 lines were a pytest-benchmark tracer artifact, not missing tests
This is the least obvious part of the diff, so it's worth spelling out.
All three files under
tests/benchmark/reported missing lines — every statement after abenchmark(...)call in a test body, plus the helper functions that are only invoked from inside the timed loop. Those statements plainly do execute; the tests pass and assert on their results.The cause is in
pytest_benchmark/fixture.py. Around the timed loop the fixture does:Disabling tracing for the measurement is deliberate — a tracer would dominate the timing. The problem is the restore.
sys.settrace()sets the global trace function, which CPython consults only when a frame is created. Frames that are already on the stack carry their ownf_trace, and that was cleared bysettrace(None). Restoring the global hook therefore cannot re-arm the already-executing test function's frame. Coverage silently loses the remainder of every benchmark test body.Fix, in
.github/workflows/ci.yml:pytest --color=yes --cov --cov-report=xml --benchmark-disableWith
--benchmark-disable, pytest-benchmark still calls each benchmarked function exactly once (so the tests still genuinely exercise the library) but skips the timed loop and itssettracedance. All three benchmark files go to 100%. Nothing consumes CI benchmark timings — numbers from shared GitHub runners are noise — and localpytestruns are unaffected, so developers still get timings by default. A comment in the workflow explains the flag so it doesn't get "cleaned up" later.COVERAGE_CORE=sysmonalso fixes it (sys.monitoringis immune tosettrace(None)), but it is unavailable on Python 3.10/3.11, which are in the CI matrix, so--benchmark-disableis the portable choice.2. Thirteen error parametrizations, every exception type observed rather than guessed
Thirteen parametrized tests carry an
if isinstance(expectation, Exception):branch that no case ever took. The pattern for filling them in already existed in-repo (test_encodings.pypasses a bareAttributeError()/ValueError()as theexpectationvalue), so these copy it.The previous attempt's main failure mode was asserting exception types that the code did not actually raise. For every case below I wrote the XML, executed it, read the real exception, and only then chose the assertion. Most turned out to be meaningful, typed library errors rather than incidental ones:
test_string_parameter_typeStringParameterTypewith anIntegerDataEncodingValueError: StringParameterType may only be instantiated with a StringDataEncoding encoding.test_integer_parameter_typeValueError: No Data Encoding element found for Parameter Type ...test_float_parameter_typeValueError: No Data Encoding element found ...test_enumerated_parameter_typeEnumerationListValueError: An EnumeratedParameterType must contain an EnumerationList.test_binary_parameter_typeBinaryParameterTypewith anIntegerDataEncodingValueError: BinaryParameterType may only be instantiated with a BinaryDataEncoding encoding.test_boolean_parameter_typeValueError: No Data Encoding element found ...test_absolute_time_parameter_typeValueError: No Data Encoding element found ...test_spline_calibratororder="2"NotImplementedError: Spline calibrators of order > 1 are not implemented.test_integer_data_encodingencoding="notARealEncoding"ValueError: Encoding must be one of ('unsigned', 'signed', 'twosCompliment', 'twosComplement')test_binary_data_encodingSizeInBitscontaining an unrecognized tagValueError: Tried parsing a binary parameter length using Fixed, Dynamic, and DiscreteLookupList but failed.test_boolean_expressionComparisonOperatorof=!=ValueError: Unrecognized operator syntax =!=. Must be one of {...}Two cases have no meaningful library-level error reachable, and I'm flagging them explicitly rather than pretending otherwise:
test_polynomial_calibrator— a<Term>withcoefficient="not-a-number"raisesValueErrorstraight out of the builtinfloat(), not from a library check. Every other malformed-Termshape (e.g. a missingcoefficientattribute) raises a bareKeyError. I picked theValueErrorbecause it is the least brittle of the available options, but it is an incidental exception type, not a designed one.test_polynomial_calibrator_calibrate— calibrating a non-numeric query point raisesTypeError: unsupported operand type(s) for ** or pow(). Again incidental: it comes from the**operator, not from input validation.If the maintainers would rather these raised a typed
space_packet_parser.exceptionserror, that is a library behavior change and belongs in its own ticket; I deliberately kept it out of scope here. The tests assert only the exception type (pytest.raises(type(expectation)), which is what the existing branch does), not the message, so they will not fight a future improvement.3. Dead fixture code in
tests/conftest.py(14 lines)This is exactly the defect class #202 is about.
clarreo_test_data_dir— deleted. Zero usages anywhere in the repo, andtests/test_data/clarreo/does not exist, so the fixture could not have worked if anything had requested it.mock_schema_download— kept, and now actually exercised. Six tests request this fixture, but its innermock_urlopen/MockResponsebodies never ran: since 6.2.0 bundles the OMG schema, documents referencing the standard schema URL resolve offline and no download is attempted. Rather than delete a useful network guard, I addedtest_schema_validation_downloads_non_bundled_schema, which validates a document whosexsi:schemaLocationpoints at a non-bundled path on the allowlistedwww.omg.orghost. That drivesvalidation._download_schemaend-to-end through the publicvalidate_xtceAPI (the existing security tests only reach it through the private_load_schema), including the size cap, the XSD parse, and the write-to-cache-only-after-validation behavior. The cache directory is redirected totmp_pathso the download is genuinely attempted instead of being served from a previous run's cache. For the other six tests the fixture now documents itself as what it actually is: a guard that no network call happens.4. Small cleanups (5 lines)
test_definitions.py— the_flatten_containerhelper usedif cond: raise AssertionError(msg)three times, so theraisestatements only executed on failure. Rewritten asassert cond, msg. Identical semantics, more idiomatic in a test, and the statement now executes on the happy path.test_common.py—TestClass.ignored()existed to demonstrate that methods are excluded fromAttrComparablecomparison, but was never called. Now called with an assertion, which is a slightly stronger statement of intent: the method is callable and irrelevant to equality.test_validation_security.py— the_mock_urlopen_returninghelper'sread()had anif args: ... else: ...split, and nothing in the library ever callsresponse.read()without a size (_download_schemaalways passesMAX_SCHEMA_BYTES + 1), so the no-argument arm was unreachable. The helper now delegates to anio.BytesIO, which reproduces both behaviors of a real response body exactly with no branch of its own.5. The codecov status
Only after all of the above does the new status group go in, under
coverage.status.project:This is additive —
project.default(95% / 5%) still covers the whole repo includingtests/. POSTing the file tohttps://codecov.io/validatereturnsValid!and showspathscompiling to(?s:tests/.*)\Z, i.e. correct recursive matching. Thevalidate-codecov-yamlpre-commit hook is inci.skipbecause it needs network, so it was run locally; it passes.At 99.89% against a 99% target with a 1% threshold, there is real headroom rather than a number that squeaks past.
Every remaining uncovered line
Two lines, both in
tests/integration/test_xtce_based_parsing/test_suda_parsing.py— 2 of 1881 statements, 0.11%:breakthat exits the packet-accumulation loop when the nextscitypeis1, i.e. when a new event begins.p = p_next, the statement immediately after that loop, reachable only via the samebreak.tests/test_data/suda/sciData_2022_130_17_41_53.splcontains a single complete event, so the loop always terminates byStopIterationat end-of-file and never bybreak. The test's own inline comment already says so: "For this example, we only have one full event so we have already hit a StopIteration by this point." Covering these would require fabricating a multi-event SUDA binary file, which would be inventing mission data to move a coverage number — not a real test. They are left uncovered deliberately, and 99.89% clears the 99% target with them.No other line under
tests/is uncovered; the remaining 34 files are all at 100.00%.Verification
uv run pytest --cov --cov-report=term-missing --benchmark-disable→ 495 passed.uv run pytest(no flags, i.e. what a developer runs locally, benchmarks enabled) → 495 passed. Local runs are unaffected by the CI flag.uv run coverage report --include="tests/*" --precision=2→ 1881 statements, 2 missed, 99.89%.uv run pre-commit run --all-files→ all 12 hooks pass, includingprettier,ruff-format,trim trailing whitespace, and the network-dependentvalidate codecov.yml.curl -X POST --data-binary @codecov.yml https://codecov.io/validate→Valid!.Not done, on purpose
project.default(95%) orpatch.default(85%), and no attempt to raise coverage ofspace_packet_parser/**— out of scope for this ticket.[tool.coverage.run] sourcesetting. Bare--covalready measurestests/; pinning a source list would risk quietly dropping test modules from the report.🤖 Generated with Claude Code