Coverage testing - #111
Merged
Merged
Conversation
Add pytest-cov and run it by default via pytest addopts (branch coverage, term-missing + xml reports), scoped to the cppwg package. Upload the coverage.xml report to Codecov from the test-unit workflow. Add a codecov.yml ratchet policy: project coverage may not drop and every patch must be fully covered, so coverage keeps moving toward the 100% goal. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add tests for cppwg.templates.custom.Custom, taking it from 0% to 100%. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add unit tests taking base_info, free_function_info, variable_info, base_writer and package_writer to 100%: custom-generator loading, the free-function namespace lookup (found and not-found paths), config application, the tidy_name replacements, and per-module write delegation. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add tests taking __main__ to 100%: argument parsing (including the -l const/default and value-with-"=" handling), generate() building the generator with combined castxml cflags and rejecting a multi-token --std, and main() wiring up logging (with and without a log file) before delegating to generate(). The generator and root logger are mocked/isolated so no wrappers are produced and handlers do not leak. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add tests taking utils.py from 79% to 99%: convert_to_bool, is_option_ALL, find_classes_in_source(_file), split/parse template helpers (including the unbracketed and empty-name edge cases), find_template_signature_in_source, str_to_num, read_source_file/strip_source, and find_member_function across a class and its bases. The two remaining branches are unreachable defensive guards, to be handled in a later pass. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The variables path created CppVariableInfo with variable_config["name"],
but "name" is never a key in variable_config, so any explicitly listed
variable raised KeyError('name'). Use raw_variable_info["name"], matching
how the free_functions path reads the name from the raw config.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add tests taking package_info_parser from 75% to 98%: module source_locations resolved to full paths (and verify_path raising on a missing path), explicit variables (regression test for the KeyError fix), free-function and variable source_file handling, and a custom_generator CPPWG_SOURCEROOT placeholder resolved and loaded. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add tests taking module_info from 59% to 96%: add_variable, is_decl_in_source_path (with and without source_locations), use_all_* discovery in update_from_ns, update_from_source updating each class then sorting, and the single-class sort_classes early return. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add tests taking class_info from 64% to 92%: signature_arg_types and requires, update_from_source (matching by class name, existing source_file_path, source_file basename, and excluded skip), update_from_ns (direct class, typedef fallback, deferred unresolved instantiation, templated arg-list retention and excluded skip), the C++/Python name builders, and template_has_defaulted_params. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add tests taking package_info from 83% to 89%: parse_exception_entry and the exception_names property, parse_typecaster_entry across its string/non-list/non-str-type/normalization/malformed cases, and resolve_exceptions building the per-exception message expression and header (with the c_str() pointer branch and the missing-class / missing-method errors). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
free_function_writer 48% -> 98% and constructor_writer 37% -> 95%. Add tests for generate_wrapper (default-arg normalization, exclude_default_args, template-parameter substitution, empty-initializer-list expansion) and the remaining constructor exclusion branches (private pure-virtual, abstract base, subclass constructor, artificial copy constructor, iterator and arg-type excludes), plus the constructor writer's __init__ metadata. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
method_writer 25% -> 97%. Add tests for __init__ metadata, the remaining exclusion branches (excluded_methods, private, subclass method), and both generators: generate_wrapper (instance/static/const, default-arg normalization, template-parameter substitution, pointer/reference call policies) and generate_virtual_override_wrapper (pure vs non-pure, const, multi-argument, excluded). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
class_writer 61% -> 72%. Add tests for virtual_overrides: a class with pure-virtual and plain-virtual methods produces the trampoline override class and return-type typedefs (for special-character return types), while a class with no virtual methods yields an empty override block. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The unit tests cannot reach the castxml/pygccxml parsing and generation orchestration (source_parser, generators, and the writers' build/write paths). Measure those from the real generator runs the example workflows already perform: - test-shapes-pip runs cppwg under `coverage run` and uploads with the `shapes` flag. - test-cells-conda enables subprocess coverage (a .pth calling coverage.process_startup() plus COVERAGE_PROCESS_START) so the cppwg that `make pycells_wrappers` spawns self-instruments, then uploads with the `cells` flag. The pipx-based ubuntu cells workflow runs identical code and is left unchanged. - test-unit uploads with the `unit` flag. codecov.yml gains flag_management with carryforward so codecov merges the three sources per commit and holds a flag's coverage steady when its job does not run. Both example jobs install cppwg editable so coverage paths map to the repository. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add unit tests for the paths the example integration runs cannot reach: - CppWrapperGenerator.__init__ error/edge branches (explicit vs searched castxml binary, missing binary, unparsable version, explicit vs found compiler, missing source root, auto-created wrapper root, missing include dir, missing vs default package info), mocking castxml discovery/version. - CppSourceParser.parse_instantiations skip/error branches (unparsable file, no-location, other-file, non-instantiation, and dedup of local explicit instantiations). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add tests for write()'s edge paths that the example runs do not exercise: the __init__ and write() guards against mismatched decls/cpp_names/py_names lists, the struct-with-single-enum path (which writes the hpp/cpp files), and a struct that is not the single-enum pattern registering nothing. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- header_collection_writer: excluded classes skipped, free-function and exception-class headers included, and the early break once every exception header is found. - module_writer: empty exception translator when no exceptions are configured, and excluded classes left out of the decl map. - class_info: extract_templates_from_source early returns (args already set, no source file) and apply_template_instantiations skips (excluded class, class absent from the instantiation map). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
os.path.splitext keeps the leading dot, so collect_source_files compared the
inner extension ".cppwg" against "cppwg" and never matched - generated
wrapper files (e.g. Foo.cppwg.hpp) were never skipped. In practice they were
excluded only because they live under a restricted wrapper_root. Compare
against ".{CPPWG_EXT}" so an in-source generated file is skipped too.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Extract strip_outer_angle_brackets, used by both parse_template_params and template_has_default_param. The latter's inline copy of the strip was effectively one-directional (its signature always arrives bracketed from find_template_signature_in_source), leaving two uncoverable branch halves. The shared helper is tested directly with bracketed and unbracketed input, taking utils.py to 100% without a coverage pragma. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Tests for discover_base_class_instantiations (harvesting base-class instantiations; skipping unresolved, non-target and duplicate bases and a decl whose recursive_bases raises), collect_source_files (skipping generated and restricted files), the no-headers-found error, and the _referenced_instantiations unbalanced-bracket edge. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- package_info_parser: convert_path("") short-circuit and the CPPWG_ALL
classes option skipping the explicit-class loop.
- free_function_writer: a non-numeric default value emitted verbatim.
- method_writer: a pointer return with no configured call policy.
- module_info: discovered decls outside the module's source_locations dropped.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
parse_package_info passed self.source_root (a str) as PackageInfo's second
argument, which is a config dict - so the no-package-info-file path crashed
with AttributeError ('str' object has no attribute 'get'). This path is not
exercised in normal use (a config is always supplied). Pass the source root
as a config entry instead.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- generators: parse_package_info building a default PackageInfo, and log_unknown_classes reporting a source class wrapped nowhere. - class_writer: the include block falling back to the decl's own header when source_file is unset, and bases_block skipping a private base while aliasing a base wrapped in the same module. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- module_writer: the duplicate-wrapper-name ValueError guard and the module-directory makedirs branch. - class_info: apply_template_instantiations filter dropping all args, a merge that adds no new args, and the untrusted-defaulted-params warning. - package_info: _iter_wrapped_arg_return_types honouring every method and constructor exclusion, _build_type_header_map dropping ambiguous names and skipping out-of-location headers, and resolve_auto_includes falling back to the decl's header when source_file is unset. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Reword the goal comment: coverage is ~99% and held by the ratchet; the small remainder is unreachable/defensive branch halves left uncovered rather than pragma'd. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Coverage has reached ~99%; pin the project status target at 99% (rather than the auto ratchet) so it holds there, with patches still required to be fully covered. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Contributor
There was a problem hiding this comment.
Pull request overview
This PR expands Python unit test coverage across the cppwg package and introduces automated coverage reporting/upload in CI to support Issue #20 (“Expand Testing”).
Changes:
- Add extensive unit tests for parsers, info objects, writers, CLI, and utility helpers.
- Enable pytest coverage collection (pytest-cov) and configure Coverage.py/pytest defaults via
pyproject.toml. - Upload coverage reports to Codecov from unit tests and selected example workflows (shapes/cells).
Reviewed changes
Copilot reviewed 29 out of 29 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| tests/test_variable_info.py | Adds unit tests for CppVariableInfo initialization and config application. |
| tests/test_utils.py | Adds broad unit tests for utility functions (parsing, normalization, helpers). |
| tests/test_source_parser.py | Adds tests for explicit-instantiation parsing and error handling in source parsing. |
| tests/test_package_writer.py | Adds tests ensuring package writer delegates per-module writing correctly. |
| tests/test_package_info.py | Adds many tests covering exception/typecaster parsing, discovery, header mapping, and includes logic. |
| tests/test_package_info_parser.py | Adds tests for path resolution/verification and parsing additional module items. |
| tests/test_module_writer.py | Adds tests for exception translator behavior, duplicate stems, and output directory creation. |
| tests/test_module_info.py | Adds tests for variables, source-location filtering, and update/sort behaviors. |
| tests/test_method_writer.py | Adds tests for method wrapper generation and virtual override wrapper generation. |
| tests/test_main.py | Adds tests for CLI arg parsing, generation wiring, and logging behavior in main(). |
| tests/test_header_collection_writer.py | Adds tests for header collection output with exclusions and exception/free-function headers. |
| tests/test_generators.py | Adds tests for generator initialization, CastXML discovery/version handling, and logging of unknown classes. |
| tests/test_free_function_writer.py | Adds tests for free-function wrapper generation (defaults, exclusions, options). |
| tests/test_free_function_info.py | Adds tests for free-function declaration discovery and missing-function errors. |
| tests/test_custom.py | Adds tests for default behavior of the custom template hook base class. |
| tests/test_constructor_writer.py | Adds tests for constructor wrapper exclusion and wrapper generation (defaults, templates). |
| tests/test_class_writer.py | Adds tests for virtual override trampoline generation, instantiation list validation, bases/includes behaviors. |
| tests/test_class_info.py | Adds tests for class-info source/ns update paths, template discovery/merge behaviors, and naming. |
| tests/test_base_writer.py | Adds tests for tidy_name transformations. |
| tests/test_base_info.py | Adds tests for custom generator loading and default instance behavior. |
| pyproject.toml | Adds pytest-cov and configures pytest/coverage defaults and reporting outputs. |
| cppwg/utils/utils.py | Introduces strip_outer_angle_brackets() and refactors template parsing to reuse it. |
| cppwg/parsers/package_info_parser.py | Fixes variable parsing to use the raw variable name when constructing CppVariableInfo. |
| cppwg/info/package_info.py | Fixes generated-wrapper file skipping by correctly matching the “.cppwg” inner extension. |
| cppwg/generators.py | Fixes default PackageInfo construction when no config file is provided. |
| codecov.yml | Adds Codecov configuration (project/patch targets and flag management). |
| .github/workflows/test-unit.yml | Uploads unit-test coverage to Codecov. |
| .github/workflows/test-shapes-pip.yml | Runs the shapes example under Coverage.py and uploads a Codecov report (flagged). |
| .github/workflows/test-cells-conda.yml | Enables subprocess coverage for cells, combines results, and uploads to Codecov (flagged). |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
strip_outer_angle_brackets stripped its input before slicing off the brackets, so a signature like "< a >" left " a " - contradicting the docstring's promise of a whitespace-stripped body. Strip once more before returning and cover the whitespace-inside-brackets case. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
kwabenantim
marked this pull request as ready for review
July 29, 2026 07:16
The flag was only honoured at package level: module_info parsed a fixed key list that omitted it, and the parser dropped it from the module and class config because it was not pre-seeded there. A module- or class-level setting was therefore silently ignored, even though the option is read via hierarchy_attribute and is meant to cascade package -> module -> class. Seed the key (as None, the inherit sentinel) in the module and class config templates and add it to the shared base_info key list, so an explicit True/False is applied at the level it is set while an unset value falls through to the parent. A module-level False now correctly shadows a package-level True. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…xample Add an abstract class hierarchy to the shapes example (AbstractShape<DIM> -> AbstractPolygon<DIM> -> concrete RegularPolygon<DIM>) and enable exclude_inherited_overrides on the primitives module. This exercises generator paths that were previously cold in the examples: constructor skipping for an abstract class with an abstract base, trampoline return-typedef dedup across instantiations, the inherited-override skip, and virtual/pointer return handling. The example still builds and its tests pass. Lifts the shapes-flag coverage of class_writer 51% -> 70% and method_writer 64% -> 77%. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ample Add an abstract mesh chain to the cells example (AbstractMesh -> AbstractSphericalMesh<E,S> -> concrete SphericalMesh<E,S>) and enable exclude_inherited_overrides on the `all` module. This exercises, with two-parameter templates and instantiation discovery, the same generator paths the shapes example now covers: constructor skipping for an abstract class with an abstract base, and skipping of redundant inherited overrides (SphericalMesh's and PottsMesh's Scale, which AbstractMesh already binds). Expose the concrete SphericalMesh in the pycells package (the abstract bases, like AbstractMesh, are not surfaced) and add a test confirming the inherited Scale/GetNumElements remain callable despite their leaf bindings being skipped. The module builds and the example tests pass. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Contributor
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 56 out of 56 changed files in this pull request and generated no new comments.
Comments suppressed due to low confidence (2)
cppwg/parsers/package_info_parser.py:180
- The new class-level exclude_inherited_overrides has the same normalization issue as the module-level setting: a quoted string like "False" remains truthy and flips behavior. Normalize it with utils.convert_to_bool() while keeping None as the inherit/default value.
class_config = {
"exclude_inherited_overrides": None,
"name_override": "",
"source_file": "",
"source_file_path": "",
cppwg/parsers/package_info_parser.py:123
- The new module-level exclude_inherited_overrides value is read straight from YAML but never normalized like the package-level booleans. If a user quotes it (e.g. "False"), it stays a non-empty string and is treated as truthy, incorrectly enabling inherited-override exclusion. Normalize it with utils.convert_to_bool() while preserving None for the inherit/default tri-state.
This issue also appears on line 176 of the same file.
module_config = {
"name": "cppwg_module",
"exclude_inherited_overrides": None,
"external_bases": [],
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.
Fixes #20