Skip to content

Python: add unit tests for rst_code_example_pipeline - #1369

Draft
gusthoff wants to merge 61 commits into
AdaCore:mainfrom
gusthoff:dev/topic/infrastructure/python/rst-pipeline-unit-tests/2026-06-19/main
Draft

Python: add unit tests for rst_code_example_pipeline#1369
gusthoff wants to merge 61 commits into
AdaCore:mainfrom
gusthoff:dev/topic/infrastructure/python/rst-pipeline-unit-tests/2026-06-19/main

Conversation

@gusthoff

Copy link
Copy Markdown
Collaborator

Add a comprehensive pytest-based unit test suite for the
rst_code_example_pipeline package. The package was recently extracted
from frontend/py_modules/code_projects/ into a standalone, installable
Python package but shipped with only a smoke-test suite covering --help
exit codes. This change closes that gap: 344 tests at 92% coverage,
exercising all 12 source modules from pure utility helpers through full
Ada/C compilation, run, and SPARK-proof pipelines.

Changes

  • pyproject.toml: add pytest + pytest-cov as optional [test]
    extras; configure [tool.pytest.ini_options] and [tool.coverage.*]
    with branch = true and fail_under = 90
  • Makefile: new test_rst_pipeline target
  • frontend/python/rst_code_example_pipeline/README.md: new
    "Development / Running the tests" section
  • tests/: 11 new test modules covering all 12 source modules —
    unit tests for pure-Python helpers (colors, fmt_utils, resource,
    checks, blocks, chop), toolchain-dependent modules (toolchain_info,
    toolchain_setup, extract_projects, check_projects,
    check_code_block), and integration tests for real Ada/C compilation,
    run, and SPARK proof paths
  • Strategic # pragma: no cover / # pragma: no branch annotations for
    structurally dead/unreachable branches in three modules

Testing / Validation

  • All 344/344 tests pass (Python 3.12.3, pytest 9.1.1)
  • Coverage: 92% (branch coverage enabled; fail_under = 90)

Notes

  • Global mutable state (verbose, code_block_at, etc.) is reset per
    test via autouse fixtures — no cross-test contamination
  • Diag is independently defined in both extract_projects and
    check_code_block — noted as future cleanup, out of scope here

Co-Authored-By: Claude Sonnet 4.6 noreply@anthropic.com

@gusthoff
gusthoff marked this pull request as draft June 20, 2026 02:04
gusthoff and others added 28 commits July 25, 2026 01:37
Tests cover Colors class ANSI escape sequence attributes, col() with
colors enabled and disabled, printcol() output, the no_colors() context
manager (disable inside, restore outside, nested use), disable_colors(),
CI/non-TTY detection, and adversarial direct __enter__/__exit__ usage.

Documents known limitation: no_colors() uses a bare yield without
try/finally, so _enabled is not restored if an exception propagates
out of the with-block.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add an optional-dependency group `[test]` to pyproject.toml so the
test toolchain can be installed with:

    pip install -e ".[test]"

This installs pytest and pytest-cov without making them mandatory
for users who only need the pipeline itself.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Tests cover init_toolchain_info() populating DEFAULT_VERSION, TOOLCHAINS,
and TOOLCHAIN_PATH; get_toolchain_default_version() auto-initialising and
returning version strings for gnat/gnatprove/gprbuild; re-initialisation
idempotency; KeyError for unknown tool; and state isolation via an autouse
fixture that clears the module-level dicts before and after each test.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Tests cover header() (content, star underline length, return type),
error() (stdout output containing ERROR/loc/msg), simple_error() and
simple_success() (stdout output).  Adversarial cases include empty
string, Unicode with non-ASCII characters, and verifying that no output
goes to stderr.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add [tool.pytest.ini_options] to set testpaths and default addopts
(coverage measurement + term-missing report). Add [tool.coverage.run]
with branch coverage enabled, and [tool.coverage.report] requiring
≥90% coverage and showing missing lines.

Running `pytest` from the package root now measures coverage
automatically without extra command-line flags.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Tests cover reset_toolchain() with no pre-existing symlinks, with symlinks
present, and for idempotency; set_toolchain() with "default" versions (no
symlinks created) and "selected" versions (symlinks created pointing to the
correct version directories); set_toolchain() followed by reset_toolchain();
and adversarial double set_toolchain() without an explicit reset in between.
An isolated_toolchain_path fixture redirects symlink creation into a tmp_path
subdirectory so /opt/ada/selected is not mutated during tests.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Tests cover the Resource constructor (basename storage, content=None,
content=[], single-element, multi-element join), the append() method,
and the content property (always returns str).  Adversarial cases
include append of empty string, append of a line with embedded newline,
a large 1000-element content list, and content=None never returning
None.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add a new target that runs the rst_code_example_pipeline unit test
suite via pytest. Coverage options and testpaths are configured in
the package's pyproject.toml, so the target only needs to cd to
the package directory and invoke pytest.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Tests cover get_project_dir() with simple and dotted project names;
write_project_file() for all four combinations of spark_mode × main_file ×
compiler_switches; ProjectsList construction, add(), JSON round-trip, and
missing-file None return; and analyze_file() with no-check, syntax-only,
manual_chop (C), ConfigBlock, no-button, and no-project (SystemExit) cases.

A work_dir fixture uses monkeypatch.chdir() to isolate tests that write to
the filesystem.  Global module state (verbose, code_block_at, current_config)
is reset before and after each test by an autouse fixture.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Tests cover CodeCheck construction and defaults (timestamp float,
all fields None by default), BlockCheck construction (empty checks dict
regardless of parameter), add_check() accumulation, to_json_file() +
from_json_file() round-trips, and from_json_file() with a nonexistent
file returning None.

Documents known limitation: BlockCheck.__init__ always resets
self.checks to an empty dict, ignoring the 'checks' keyword argument,
so nested CodeCheck entries are lost on a JSON round-trip.

Adversarial cases include overwriting an existing file and passing an
empty JSON object ({}) which raises TypeError.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add a "Development" section to the package README covering:
- how to install test extras with `pip install -e ".[test]"`
- how to run pytest from the package root (plain `pytest`)
- the Ada toolchain (GNAT) requirement for the full suite
- the explicit coverage invocation for reference

No VM-specific or build-system details in the module README.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Tests cover get_blocks() with an empty regex list, with a valid block_info.json,
with a glob pattern, with two projects in separate subdirectories, and with a
block whose project field is None (skipped with ERROR); get_projects() without
a projects list file and with one; cwd side-effect isolation (os.chdir is called
internally — restored by an autouse fixture); a WARNING when projects_list_file
does not exist; the check_block() thin wrapper; and check_projects() integration
with a build dir containing no-check blocks.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Tests cover Block.get_blocks_from_rst() for all attribute combinations
(minimal Ada block, project/main_file, compiler switches, gnat version
selection, language=c, manual_chop, buttons, :code-config: directive,
two consecutive blocks), CodeBlock derived fields (no_check,
syntax_only, run_it, compile_it, prove_it, text_hash/text_hash_short),
CodeBlock JSON round-trip, ConfigBlock construction and update(), and
adversarial paths (empty RST, nonexistent JSON file).

Documents end-of-file behaviour: a block with content but no trailing
paragraph produces a WARNING and is still parsed successfully; a block
with an empty body cannot be processed and triggers exit(1), captured
as SystemExit.

These tests call toolchain_info.get_toolchain_default_version() at
parse time and therefore require the epub VM with the Ada toolchain.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
After the rst_code_example_pipeline package description, add a short
paragraph pointing readers to the package's pytest suite: names the
`make test_rst_pipeline` target and links to the "Development" section
of the package README for full install and run instructions.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Tests cover Diag.__repr__; check_block() with no_check=True (early return,
no subprocess); cache hit with status_ok=True/False/None; force_checks=True
bypassing cache; BUTTONS check failure for empty buttons list; real Ada
syntax check (gcc -gnats) for valid and invalid Ada; real gprbuild compile
for a valid Ada procedure and a procedure with a syntax error; real run
check for a compilable Ada program; BUTTONS check for selected toolchain
with non-"no" button; and check_code_block_json() with a missing file and
with a valid no-check block.

Key fix in _make_block(): changed `buttons = buttons or ["no"]` to
`buttons = ["no"] if buttons is None else buttons` so that passing
`buttons=[]` explicitly is preserved (an empty list is falsy, causing
the `or` form to silently substitute `["no"]`).  Added compile_it and
run_it parameters to _make_block() to allow tests to reach the BUTTONS
check without triggering the compile path that requires a project file.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds test_chop.py in the package test directory covering manual_chop and
cheapo_gnatchop edge cases not present in the existing
frontend/sphinx/tests/test_chop.py.

manual_chop additions: .ads and .adb Ada extensions, empty input, input
with no !filename lines at all, garbage before the first valid file
marker, single filename with no content, fake extensions not matched.

cheapo_gnatchop additions: dotted package body names (Foo.Bar →
foo-bar.adb), dotted procedure names, triple-dotted names, spec-only
files (.ads), empty input, only-garbage input, garbage before the first
declaration, body-before-spec ordering.

Does not cover real_gnatchop (requires the Ada toolchain; already
tested in sphinx/tests/test_chop.py).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Lower fail_under from 90% to 75%: the three toolchain-dependent modules
(check_code_block, check_projects, extract_projects) have large compile/run/prove
code paths that are not exercised by unit tests; together they cap realistic
coverage well below 90%.

Add exclude_lines for "if __name__ == '__main__':" so the CLI entry-point
blocks in check_code_block, check_projects, and extract_projects (approximately
80 lines total) are excluded from the measurement; those blocks are tested via
the --help smoke tests rather than unit tests.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Three locations in check_block() that coverage cannot reach when imported:
- `if __name__ == '__main__':` guard inside check_block() — impossible
  when called as a module; already matched by exclude_lines, pragma is
  belt-and-suspenders
- `if False:` block (35-line dead code, structurally unreachable)
- `if True:` block (branch coverage flags the never-taken false path of
  an always-true condition)

These three pragmas bring overall package coverage from 75.43% to 76.55%.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The isinstance(block, blocks.ConfigBlock) guard at line 251 is inside a
loop over projects[project], which is built exclusively from CodeBlock
instances (ConfigBlocks are filtered out earlier in analyze_file).
The branch is structurally unreachable under normal execution.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The module-level TTY check at line 39 always takes the true branch in a
pytest environment (non-TTY), making the false branch structurally
unreachable without TTY mocking or importlib.reload tricks. pragma: no
branch suppresses the missed-branch coverage arc.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…election)

Add two tests covering the gnatprove and gprbuild version-selection attribute
paths in get_blocks_from_rst() — lines 129 and 133 of blocks.py.  These two
branches (gnatprove_version = ["selected", ...] and gprbuild_version =
["selected", ...]) were not exercised by any existing test; the only
version-selection test used gnat=.  The new tests parse RST blocks with
gnatprove= and gprbuild= attributes and assert the resulting CodeBlock
carries ["selected", <version>] for the respective field.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…e/inactive paths, same-project branch)

Add three groups of tests to test_extract_projects.py:

Diag class (lines 28-40): new TestDiag class with three tests verifying that
__init__ stores all four fields and __repr__ produces "file:line:col: msg"
format, including an edge case with zero/empty values.

analyze_file() coverage-improvement tests (B2, B3) — added to TestAnalyzeFile:
- test_code_block_at_sets_inactive: sets code_block_at=9999 so no block's line
  range matches; all blocks stay inactive and the inner loop hits the continue
  path (lines 188-191, 211).  Asserts no project directory is created.
- test_verbose_prints_headers: sets verbose=True; confirms project name appears
  in stdout (lines 246-248).
- test_second_call_same_project_logs_exists: calls analyze_file() twice on the
  same RST; second call prints "already exists" (lines 234-237).
- test_no_check_verbose_skip: verbose=True with a no-check block; confirms
  "Skipping" appears in stdout (line 344).

Same-project second block (line 218 false branch): new class
TestAnalyzeFileSameProjectTwoBlocks with an RST containing two no-check Ada
blocks sharing project=SameProject; verifies both are processed without error
and two block_info.json files are written.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ock, duplicate project, None-block)

Add reset_cp_globals autouse fixture to reset cp.verbose, cp.all_diagnostics,
cp.max_columns and cp.force_checks before and after each test.  The existing
tests did not reset these globals, so any test that set verbose=True would
have leaked state into subsequent tests.

Add new TestCheckProjectsExtended class with four tests:

- test_get_blocks_from_json_file_returns_none: monkeypatches
  CodeBlock.from_json_file to return None; verifies get_blocks() prints ERROR
  and returns an empty dict (lines 30-32).

- test_get_blocks_duplicate_project: two block_info.json files with the same
  project name; verifies both are accumulated in the list under one key (false
  branch of "if not b.project in projects:" at lines 38-40).

- test_get_projects_verbose: sets cp.verbose=True and calls check_projects();
  verifies the project header appears in stdout (lines 87-88).

- test_check_projects_skips_inactive_block: serialises a block with
  active=False; monkeypatches cp.check_block to track calls; verifies the
  inactive branch (line 93 continue) is taken and check_block is never called.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The bodies of the if __name__ == "__main__": guards in
check_code_block.py, check_projects.py, and extract_projects.py are
CLI entry-point code that cannot be exercised by unit tests.  The
exclude_lines pattern in pyproject.toml already suppresses the guard
line itself, but coverage.py 7.x still counts the body lines as
uncovered.  Adding # pragma: no cover to the guard lines causes
coverage to exclude the entire block body, accurately reflecting
the fact that these paths are tested via the --help smoke tests
(test_smoke.py) and not by the unit test suite.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…piler switches, error handler)

Add TestRealGnatchop class with four tests exercising the real_gnatchop
function (chop.py lines 96-149), which was previously untested because
the Ada toolchain is required:

- test_valid_ada_no_switches_returns_resources: calls real_gnatchop with
  compiler_switches=None on minimal valid Ada; verifies a non-empty list of
  Resource objects is returned (line 118 — the compiler_switches=None branch).
- test_valid_ada_no_switches_basename: confirms gnatchop produces main.adb.
- test_valid_ada_with_compiler_switches: passes compiler_switches=["-gnata"];
  exercises lines 120-125 (the cmd.extend branch).
- test_invalid_input_raises_exception: passes garbage input; gnatchop fails;
  verifies the except CalledProcessError handler (lines 137-144) raises
  Exception with "Could not chop files with gnatchop".

Also update the module docstring to reflect that real_gnatchop is now covered.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
gusthoff and others added 25 commits July 25, 2026 01:37
Add coverage for previously-untested check_block() paths: the
max-columns style-check switch, both directions of the Ada and C
run-expect-failure classes, the C compile-error-expected class, both
outcomes of a gnatprove failure with and without the expect-error
class, the three gnatprove report/flow argument variants, and the
inactive-block warning printed by check_code_block_json().

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Adds three new tests in TestAnalyzeFileIntegration that exercise real Ada
toolchain paths inside analyze_file():

- test_analyze_file_compile_button: RST with a compile_button Ada block;
  real_gnatchop is called, the project file is written, and block_info.json
  is created.  Returns False (no error), covering the compile_it path in
  analyze_file() including prepare_project_block_dir() and to_json_file().
- test_analyze_file_run_button: same setup with a run_button attribute;
  covers the run_it branch (compile_it=True, run_it=True).
- test_analyze_file_prove_button: SPARK Ada body with a prove_button
  attribute; write_project_file() uses spark_mode=True, covering the
  prove_it branch including the SPARK project-file path.

All three tests require the Ada toolchain (gnatchop must be in PATH) and
use the work_dir fixture so each test starts in a fresh temporary directory.

Also removes line-number annotations from pre-existing section-header
comments (line numbers are fragile and describe location rather than
behaviour).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add coverage for previously-untested analyze_file() paths: a
code_block_at value that matches a block's line range, the verbose
messages for an existing vs. not-yet-created projects-list file, a
run button with no explicit main file, combined prove and run
buttons on the same block, a C block with a prove button (wrong
language for proving), and a block with no button keyword at all.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Adds one new test in TestCheckProjectsReturnsTrue that exercises the
check_error=True propagation path in check_projects():

- test_check_projects_returns_true_on_check_error: sets up a block_info.json
  with a CodeBlock that has compile_it=True and source that fails to compile
  (deliberate Ada syntax error).  With force_checks=True, check_projects()
  calls check_block(), which invokes gprbuild, which fails.  check_projects()
  then returns True, confirming that check_error is propagated to the caller.

This test requires the Ada toolchain (gprbuild must be in PATH).  It covers
check_projects.py line 100 (check_error = True), the last previously
uncovered statement in check_projects.py, bringing its coverage to 100%.

Also removes line-number annotations from pre-existing section-header
comments (line numbers are fragile and describe location rather than
behaviour).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add a test confirming that a default compiler switch already present
in an explicit switches= attribute is not appended a second time.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Add a test confirming that a compiler switch not containing "gnat"
is silently dropped before invoking gnatchop, and that chopping
still succeeds.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
All tests pass at 92% coverage. The gate was 75 during development
to allow incremental test authoring; 90 is the target reflecting the achieved
level and prevents coverage regressions going forward.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds three more tests that exercise previously-uncovered defensive and
compatibility paths in check_block():

- A corrupt (non-JSON) previous-check cache file on disk is caught and
  ignored, so a full check runs instead of crashing.
- A block with an unrecognized language value takes neither the Ada
  nor the C branch in cleanup, syntax-check, compile, or run, and
  completes without raising.
- A prove block pinned to a genuinely installed legacy GNATprove
  version builds that version's older-style command line and still
  proves the example cleanly with a real invocation.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Adds unit tests for check_code_block.py, extract_projects.py, blocks.py,
and chop.py covering paths that were reachable without mocking subprocess
calls: the Ada/C run-expect-failure classes in both directions, the
gnatprove failure handler and its extra-argument variants, a C
compile-error-expected class, an inactive-block warning, a code-block-at
line-range match, verbose logging paths, a get-main-filename fallback, a
combined prove-and-run path, a wrong-language-for-prove error, and a
default-compiler-switch deduplication check. Also adds two pragma
annotations for structurally dead code (a module-level constant's always-
true branch, and an unused local helper function) that no test can
meaningfully cover.

366/366 tests pass; coverage rises from 90.94% to 95.92%.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Covers the stale-project-directory path in analyze_file(): if a code
block's per-block directory survives from a prior run but its info
JSON file has since been deleted, the directory is detected as stale,
logged, and removed rather than reused, and a second real run over the
same source completes cleanly afterward.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
All tests pass at 95.92% coverage after adding pragma annotations and
clean-path unit tests for previously-untested branches. The gate was 90,
set when coverage was 92%; 95 reflects the achieved level while leaving a
few points of headroom, since tooling-version bumps alone (coverage,
ipython, pyright) were observed this session to shift the measured
percentage by about a point with no code change.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Add a test covering a switches= attribute value that is present but not
shaped like Compiler(...) (e.g. Foo(-gnata)): the value is silently
discarded and only the default -gnata switch remains, closing a
previously untested parser branch.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Add a test that hides all toolchain binaries from PATH so the version
lookup genuinely fails, confirming the exception handler falls back to
an unknown-version marker instead of crashing the check.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…sly-mocked-looking gaps

Adds tests for check_code_block.py and extract_projects.py covering paths
that a closer look showed didn't actually need mocking: a corrupt
previous-check cache file on disk, a block with an unrecognized language
value, a prove block pinned to a genuinely installed legacy GNATprove
version, and a stale per-block directory left behind after its info file
was deleted. All exercised through real inputs and real toolchain
invocations, matching the style of the rest of the suite.

370/370 tests pass; coverage rises from 95.92% to 96.86%.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Add verbose=True to the Ada and C run-expect-failure tests so the
expected-failure print statement is actually exercised, with an
assertion on the captured message. Add the missing symmetric case for
c-run-expect-failure where the run unexpectedly succeeds. Reword a
docstring to describe the wrong-language prove-button behavior instead
of referencing a source line number.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…ing-toolchain coverage

Adds a test that hides all toolchain binaries from PATH for a check, so
the version-lookup exception handler is exercised for real rather than by
mocking subprocess: the recorded version falls back to an unknown-version
marker instead of the call crashing.

371/371 tests pass; coverage rises from 96.86% to 97.25%.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…he Group 3/4 audit

Adds a test for blocks.py confirming a switches= attribute value that
isn't shaped like a compiler-switches specification is discarded rather
than silently misapplied. Extends two existing run-expect-failure tests
in check_code_block.py to also exercise their verbose-logging output, adds
the missing symmetric C run-unexpectedly-succeeds case, and rewords a
docstring to describe behavior instead of pointing at a source line
number.

374/374 tests pass; coverage rises from 97.25% to 97.72%.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The C cleanup branch set has_error = True on an rm -f failure, but since
that assignment has no nonlocal declaration, it creates a local variable
scoped to the cleanup helper and never affects the check's actual result
-- it has had zero observable effect since this code was first written.
The Ada cleanup branches never attempted to set this flag either, so
removing the dead assignment simply makes both languages consistent with
the design that's already been in place throughout this file's history:
clean-up failures are logged, not treated as check failures.

No behavior change: the removed line never had any effect to begin with.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Cover clean-up failures that occur after a successful Ada compile and
run (gprclean before compiling, gprclean and gnatprove --clean at the
end of the check) and after a successful C compile and run (rm -f):
each is logged, or in one case silently swallowed, but never affects
the check's own result.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Cover the case where chopping a block's source text produces zero
source files: the failure is logged at the point it happens and again
by the surrounding per-block handler that catches it and moves on,
while the overall analysis still completes without raising and
reports no error.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…op-empty coverage

Removes a dead has_error assignment in the C clean-up handler that never
had any observable effect (missing nonlocal declaration made it a no-op
since the file was first written), bringing C in line with the Ada
clean-up branches, which have never treated clean-up failures as fatal.

Adds tests confirming that clean-up-command failures (gprclean before and
after compiling, gnatprove --clean, rm -f) are logged (or, in one case,
silently swallowed) without affecting a check's outcome, and that a
code-chopping failure producing no source files is logged and the
analysis moves on to the next block rather than crashing.

377/377 tests pass; coverage rises from 97.72% to 99.61%.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
All tests pass at 99.61% coverage after closing out the remaining
clean-up-failure and code-chopping-failure gaps. The gate was 95, set
when coverage was 95.92%; 99 reflects the achieved level while leaving a
small buffer for the kind of tooling-version measurement drift observed
earlier in this same effort.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@gusthoff
gusthoff force-pushed the dev/topic/infrastructure/python/rst-pipeline-unit-tests/2026-06-19/main branch from bc3a655 to 953c63d Compare July 25, 2026 01:08
gusthoff and others added 4 commits July 25, 2026 03:18
The pyright type-check workflow only installed the base package, so
pytest could never be resolved once the test suite grew beyond the
original unittest-based smoke tests. Install the [test] extra alongside
the package so the new pytest-based test files type-check cleanly.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…ass-only attributes

get_blocks_from_rst() is typed to return the base Block class, but most
tests then read attributes that only exist on CodeBlock or ConfigBlock.
Add isinstance() narrowing (or, for ConfigBlock's dynamically-set
run_button/prove_button/accumulate_code, a getattr() lookup, since
isinstance narrowing doesn't help there) so each test verifies its own
type expectation instead of relying on pyright accepting an unchecked
attribute access on the base class.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The workflow file and its display name still said "code_projects", the
package's name before it was renamed to rst_code_example_pipeline in an
earlier refactor. Renamed both to match.

More importantly, nothing in CI ever ran the package's pytest suite --
only pyright type-checked it. Add a second job that installs the Ada
toolchain (reusing the same install script and the same versions the
package's own toolchain.ini already specifies, so there's no separate
version list to keep in sync) and runs the existing test_rst_pipeline
Makefile target. Verified locally: 377/377 tests pass at 99.61% coverage,
matching the epub VM's results.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Fixes the pyright type-check workflow, which never installed the test
extras and so could never resolve pytest imports once the test suite
grew past the original unittest-based smoke tests, and separately fixes
36 genuine attribute-narrowing errors in the block-parsing tests (28 via
isinstance narrowing, 8 via getattr for attributes ConfigBlock sets
dynamically).

Also renames the workflow (its name and filename still referenced the
package's name from before an earlier, unrelated refactor) and adds a
job that actually runs the pytest suite in CI -- previously nothing did,
only the type-checker ran. The new job installs the real Ada toolchain
using the repository's existing installer script, which reads its
version list from the package's own bundled configuration file, so there
is no separate list of versions to keep in sync.

377/377 tests pass at 99.61% coverage; pyright reports zero errors.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant