Skip to content

feat: link JUnit XML properties to sphinx-needs fields#135

Merged
patdhlk merged 4 commits into
useblocks:masterfrom
cpolzer:feature/linking-by-property
May 20, 2026
Merged

feat: link JUnit XML properties to sphinx-needs fields#135
patdhlk merged 4 commits into
useblocks:masterfrom
cpolzer:feature/linking-by-property

Conversation

@cpolzer
Copy link
Copy Markdown
Contributor

@cpolzer cpolzer commented May 12, 2026

Summary

This PR adds support for JUnit XML <properties> elements, enabling requirement traceability by mapping test case and test suite properties to sphinx-needs fields.

The entire properties feature is opt-in. If you don't set tr_extra_options or tr_property_link_types, the extension behaves exactly as it did before this PR.

Use Case

Many test frameworks (e.g., pytest with custom markers, Maven Surefire, JUnit 5) can embed key-value properties in JUnit XML output. These properties often contain requirement IDs, priorities, or categories. Previously, sphinx-test-reports ignored this data. Now you can surface it directly in your documentation and link tests back to requirements.

Example JUnit XML

<testcase classname="auth.tests" name="test_login" time="0.42">
  <properties>
    <property name="verifies" value="REQ_AUTH_001,REQ_AUTH_002"/>
    <property name="priority" value="high"/>
  </properties>
</testcase>

New Configuration Options

Option Type Description
tr_extra_options list[str] Which JUnit property names become sphinx-needs fields
tr_property_link_types dict[str, str] Maps a property name to a sphinx-needs link field (e.g. "links")

Example conf.py

# Make these JUnit properties available as need fields
tr_extra_options = ["verifies", "priority", "category"]

# Map the "verifies" property to sphinx-needs links
tr_property_link_types = {
    "verifies": "links",  # comma-separated IDs → need links
}

What Changed

  • Parser (junitparser.py): Extracts <properties> from both <testcase> and <testsuite> elements with a safe guard against empty <properties/>.
  • Test Case Directive (directives/test_case.py): Flattens allowed properties into top-level fields; processes tr_property_link_types to build semicolon-separated sphinx-needs link values.
  • Test Suite Directive (directives/test_suite.py): Same property flattening and link mapping at the suite level.
  • Field Registration (test_reports.py): Automatically registers tr_extra_options as sphinx-needs fields on startup. Duplicate registration is handled gracefully (logs debug, no error).
  • Config (config.py): Added "properties" to the default JUnit parser options list.
  • Tests (test_properties.py): 384 lines of tests covering parser extraction, empty properties, integration build, DOM rendering, link creation, and backward compatibility.

Backward Compatibility

  • Fully backward compatible. If tr_extra_options and tr_property_link_types are not set, behavior is identical to before.
  • Property flattening is filtered through the tr_extra_options allowlist, so unknown properties never leak into add_need().

Test Coverage

  • Parser unit tests for property extraction
  • Empty <properties/> handling
  • Integration build with real JUnit XML
  • HTML DOM assertions for rendered fields
  • Link creation and merging
  • Backward compatibility (no config → no change)

~chrstian polzer and others added 3 commits May 12, 2026 19:12
Replace deprecated add_extra_option() with add_field() for sphinx-needs >= 8.0.0,
using a version-gated shim that falls back to add_extra_option() for older versions.

Also adds sphinx-needs 8.0.0 to the CI and nox test matrices, fixes the warning
check in tests to also inspect stderr (where Sphinx writes warnings), adds a test
asserting the test-file need node renders correctly, and removes the broken taplo
pre-commit hook (taplo 0.9.3 PyPI package fails to build on this platform).
…inks

Add support for JUnit XML <properties> elements at both testsuite and
testcase level, enabling requirement traceability by mapping property
values to sphinx-needs fields and links.

- Parse <properties>/<property> from testcase and testsuite elements
- Add tr_extra_options config to declare which properties become fields
- Add tr_property_link_types config to map properties to sphinx-needs links
- Register extra options as sphinx-needs fields with duplicate-safe handling
- Guard against empty <properties/> elements without children
- Filter property flattening through tr_extra_options allowlist
- Add tests for parser extraction, empty properties, integration build,
  DOM field rendering, link creation, and backward compatibility
@cpolzer cpolzer changed the title feat: link JUnit XML properties to sphinx-needs fields and requirement links feat: link JUnit XML properties to sphinx-needs fields May 12, 2026
Copy link
Copy Markdown
Collaborator

@patdhlk patdhlk left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the PR. In addition to the duplication of the file we need some documentation. I see no changes to docs/ or README. The user-facing PR description is good, but those config options should be in the rendered docs.

Comment on lines +168 to +194
allowed_extras = set(getattr(self.app.config, "tr_extra_options", []))
case_properties = case.get("properties", {})
for prop_name, prop_value in case_properties.items():
if prop_name in allowed_extras and prop_name not in case:
case[prop_name] = prop_value

# Process tr_property_link_types: map property values to sphinx-needs links
tr_property_link_types = getattr(self.app.config, "tr_property_link_types", {})
for prop_name, link_field in tr_property_link_types.items():
prop_value = case_properties.get(prop_name, "")
if prop_value:
# Convert comma-separated IDs to semicolon-separated (sphinx-needs link format)
link_ids = ";".join(
id_val.strip() for id_val in prop_value.split(",") if id_val.strip()
)
if link_field == "links":
existing = self.test_links
else:
existing = self.extra_options.get(link_field, "")
if existing:
merged = existing + ";" + link_ids
else:
merged = link_ids
if link_field == "links":
self.test_links = merged
else:
self.extra_options[link_field] = merged
Copy link
Copy Markdown
Collaborator

@patdhlk patdhlk May 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@cpolzer same code/logic as in test_suite.py L76-L110

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@patdhlk moved to "test_common .py" to deduplicate.

Extract the duplicated tr_property_link_types loop from test_case.py
and test_suite.py into _apply_property_links() on TestCommonDirective.

Add tr_property_link_types section to docs/configuration.rst and expand
the tr_extra_options section to cover JUnit <properties> mapping.
@cpolzer
Copy link
Copy Markdown
Contributor Author

cpolzer commented May 20, 2026

Thanks for the PR. In addition to the duplication of the file we need some documentation. I see no changes to docs/ or README. The user-facing PR description is good, but those config options should be in the rendered docs.

@patdhlk added user facing docs - sphinx , skipped readme it has no configuration content as of now.

@patdhlk patdhlk self-requested a review May 20, 2026 09:02
Copy link
Copy Markdown
Collaborator

@patdhlk patdhlk left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM 🚀

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