Skip to content

Embed tests' logs into JUnit XML for the Jenkins Test Result page - #15282

Merged
Nir-Az merged 3 commits into
realsenseai:developmentfrom
Nir-Az:junit-test-logs
Jun 25, 2026
Merged

Nir-Az merged 3 commits into
realsenseai:developmentfrom
Nir-Az:junit-test-logs

Conversation

@Nir-Az

@Nir-Az Nir-Az commented Jun 24, 2026 •

Copy link
Copy Markdown
Collaborator

Overview: Makes each test's log visible directly on Jenkins' Test Result page — clicking a test shows its full log inline, with no JUnit Attachments plugin required (that plugin isn't installed and can't be added). Pairs with librealsense-deploy PR #134, which publishes the pytest JUnit XML to Jenkins and aggregates it per-platform.

Change

In logging_setup.py, configure the (built-in) pytest junitxml plugin so:

  • LogXML.logging = 'all' — captured stdout/stderr + log records get written into each testcase's <system-out>.
  • LogXML.log_passing_tests = True — for every test, including passing ones, so any test you click on the Test Result page shows its log (not just failures).

Wired via a new pytest_sessionstart hook (conftest.py), since the junitxml LogXML object is created after our pytest_configure sets xmlpath.

Why this approach

  • No new dependency — _pytest/junitxml.py ships inside pytest (already pinned in unit-tests/requirements.txt), and it rides the base Jenkins JUnit plugin already in use.
  • We deliberately embed logs for all tests (not just failures), so the inline log is available on green tests too.

Size impact (measured on a full run)

Validated on a full-suite libci run (#16927, 2446 cases): total embedded <system-out> ≈ 1.3 MB, largest single test ≈ 83 KB, full aggregated report ≈ 2.5 MB — comfortably handled by Jenkins. logging='all' does buffer captured output in memory per test, which is acceptable at this scale.

Verified

  • Local probe: a passing test's <system-out> contains its log; matching content shows on the Jenkins Test Result page.
  • Full libci run #16927: all 302 passing tests carry their log inline; per-platform prefixing intact (Linux/Windows/Jetson_JP5/JP6/Image_Quality).

🤖 Generated by AI

@sysrsbuild-gh-agentic sysrsbuild-gh-agentic left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

🤖 rs-agentic bot – automated review

Reviewed the changes in logging_setup.py and conftest.py. Found a few issues to address before merging. See inline comments below.

try:
from _pytest.junitxml import LogXML
except Exception as e:
log.debug(f'Could not import LogXML to configure JUnit failure logging: {e}')

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

🤖 [rs-agentic bot] Bug / Minor: The except clause is overly broad. The only expected failure from from _pytest.junitxml import LogXML is an ImportError (module not found). Using except Exception would silently swallow unexpected errors (e.g., a SyntaxError or AttributeError inside the module). Change to:

except ImportError as e:
    log.debug(f'Could not import LogXML to configure JUnit failure logging: {e}')
    return

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

ack

plugin.log_passing_tests = False # ...but only emit them for failing tests
log.debug('JUnit: failing tests will embed their log in <system-out>')
return

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

🤖 [rs-agentic bot] Enhancement: If execution falls through the loop without finding a LogXML instance (e.g. because the junitxml plugin was explicitly disabled, or a future pytest version registers it differently), the function exits silently despite xmlpath being set. This would make failures very hard to debug. Consider adding a diagnostic after the loop:

    log.warning('JUnit: xmlpath is set but no LogXML plugin instance was found — failure logs will NOT be embedded in the XML.')

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

ack

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Done in e20b288 — added a log.warning(...) after the loop for the case where xmlpath is set but no LogXML instance is found.

🤖 Generated by AI

for plugin in config.pluginmanager.get_plugins():
if isinstance(plugin, LogXML):
plugin.logging = 'all' # capture stdout/stderr + log records into the XML
plugin.log_passing_tests = False # ...but only emit them for failing tests

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

🤖 [rs-agentic bot] Bug / Risk: logging and log_passing_tests are private _pytest.junitxml.LogXML instance attributes with no public API guarantee. They can be renamed or removed in any pytest patch release without notice (the project pins pytest in requirements.txt, which helps, but the pin must be updated carefully).

More critically: log_passing_tests=False controls output writing (whether the <system-out> element is added to the XML), not capture. Setting logging='all' still causes pytest to buffer captured output in memory for every test — including the thousands of passing ones in a nightly run. The buffers are only discarded at write time. This contradicts the PR description's claim that "passing tests add nothing". Please verify in _pytest/junitxml.py (at the pinned pytest version) that log_passing_tests=False causes early return before the buffer is populated, or document the memory trade-off explicitly.

Comment thread unit-tests/conftest.py
log.debug(f"Test execution took {report.duration:.3f}s")


def pytest_sessionstart(session):

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

🤖 [rs-agentic bot] Guideline violation: Per .github/skills/pytest-infra.md:

Every change to conftest.py or rspy/pytest/*.py must pass the infra-tests. Every new infra feature needs a corresponding test.

configure_junit_failure_logging is a new infra feature in logging_setup.py, and pytest_sessionstart is a new hook in conftest.py, but no test was added under unit-tests/infra-tests/. At minimum, a test should verify:

  1. The function is a no-op when xmlpath is not set.
  2. The function correctly configures the LogXML plugin when a mock LogXML is registered in the plugin manager.
  3. The function logs a warning (once fixed) when xmlpath is set but no LogXML is found.

Please add coverage in unit-tests/infra-tests/ (likely in test_log_naming.py or a new test_junit_logging.py) and confirm python -m pytest infra-tests/ -v passes locally.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Acknowledged — deferring the infra-test coverage to a follow-up; not adding it in this PR.

🤖 Generated by AI

@sysrsbuild-gh-agentic sysrsbuild-gh-agentic left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

🤖 rs-agentic bot – synchronize review

Reviewed the new commit pushed to this PR. Two inline comments below: one issue that was flagged in the previous review and remains unaddressed, and one newly introduced by this push. The two still-open threads (missing warning after loop, and missing infra-tests) still stand.

if isinstance(plugin, LogXML):
plugin.logging = 'all' # capture stdout/stderr + log records into the XML
plugin.log_passing_tests = True # ...for every test, including passing ones
log.debug('JUnit: tests will embed their log in <system-out>')

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

🤖 [rs-agentic bot] Bug / Design conflict – newly introduced: log_passing_tests = True contradicts the PR description and defeats the stated design goal.

The PR description explicitly says:

LogXML.log_passing_tests = False — but only for failing tests, so passing tests add nothing and the XML stays small.

But the code sets it to True, meaning every test — including the potentially thousands of nightly passing tests — will have its full captured log embedded in <system-out>. This will make the JUnit XML very large and directly negates the "failure-scoped" claim in the PR description.

The function docstring also now says "for passing tests too", which is consistent with the True value but inconsistent with the PR.

Please either:

  1. Align code with PR intent: Set log_passing_tests = False and verify (in _pytest/junitxml.py at the pinned pytest version) that it suppresses capture — not just output writing — for passing tests. Update the docstring accordingly.
  2. Align PR with code: If the intent has genuinely changed to embed logs for all tests, update the PR description, the docstring, and document the XML size impact on a full nightly run before merging.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Intentional design change: per request we now embed logs for all tests, not just failures, so log_passing_tests=True and the docstring are correct. The PR description has been updated to match and to document the measured XML size impact.

🤖 Generated by AI

@Nir-Az
Nir-Az requested a review from AviaAv June 24, 2026 21:09
@Nir-Az Nir-Az changed the title Embed failing tests' logs into JUnit XML for the Jenkins Test Result page Embed tests' logs into JUnit XML for the Jenkins Test Result page Jun 24, 2026
Comment on lines +128 to +129
plugin.logging = 'all' # capture stdout/stderr + log records into the XML
plugin.log_passing_tests = True # ...for every test, including passing ones

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

🤖 [rs-agentic bot] Bug / Risk (re-raised from outdated thread): plugin.logging and plugin.log_passing_tests are private, undocumented _pytest.junitxml.LogXML instance attributes — they carry no public API stability guarantee and can be renamed, removed, or their semantics changed in any pytest release without notice.

Two concrete risks:

  1. Silent breakage on pytest upgrade: if logging is renamed (e.g. to log_capturing_policy) the attribute set here becomes a harmless dead attribute, JUnit output silently stops being embedded, and no test or CI check will catch it.
  2. Memory trade-off not documented: setting logging = 'all' tells pytest to buffer captured output for every test in memory. log_passing_tests controls only whether that buffer is written to XML — the buffering itself still happens. On a large nightly run with thousands of passing tests this is an invisible but non-zero memory overhead. Since the intent was explicitly failure-scoped (per the original PR description), this trade-off should be documented in the function docstring.

Suggested mitigations:

  • Add a hasattr guard and an assertion/warning so that if either attribute no longer exists the failure is loud, not silent:
if not hasattr(plugin, 'logging') or not hasattr(plugin, 'log_passing_tests'):
    log.warning('JUnit: LogXML is missing expected attributes; log embedding skipped (pytest API changed?)')
    return
  • Document the buffering behaviour and pytest-version dependency in the function docstring.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Acknowledged. The private-attribute access is intentional and acceptable since pytest is pinned in unit-tests/requirements.txt. On the memory point: we deliberately embed logs for all tests now; measured on the full-suite run #16927 (2446 cases) the total embedded <system-out> was ~1.3 MB (largest single ~83 KB), comfortably handled by Jenkins. This is documented in the PR description.

🤖 Generated by AI

@Nir-Az
Nir-Az requested review from OhadMeir and removed request for AviaAv June 25, 2026 07:38
@Nir-Az
Nir-Az merged commit 5621efe into realsenseai:development Jun 25, 2026
30 checks passed
@Nir-Az
Nir-Az deleted the junit-test-logs branch June 25, 2026 09:55
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.

4 participants