Skip to content

Upload test results and artifacts also when tests fail - #575

Open
urrsk wants to merge 13 commits into
UniversalRobots:masterfrom
urrsk:ci-artifacts
Open

urrsk wants to merge 13 commits into
UniversalRobots:masterfrom
urrsk:ci-artifacts

Conversation

@urrsk

@urrsk urrsk commented Sep 16, 2026

Copy link
Copy Markdown
Member

Summary

GitHub Actions stops a job at the first failed step, so a red ctest used to take the
Codecov upload and every debug artifact down with it — exactly the runs where flake
statistics and logs are most useful. This makes the reporting steps run regardless.

  • Upload Codecov test results (JUnit) and debug artifacts even when ctest / pytest
    fail, using if: ${{ !cancelled() }} instead of the implicit success().
  • Emit JUnit from the jobs that produced no report at all before: --output-junit is now
    passed to ctest on Windows, macOS, and Alpine.
  • Tag each Codecov upload with one flag and a distinct name:. Integration uses
    <model>-<version> (e.g. ur5e-10.14.0), robot_model_check uses
    check_version_<model>-<version>, platforms use unit-windows /
    unit-macos-arm64 / unit-macos-x86_64 / unit-alpine, Python uses
    python_scripts, ROS uses ros-<distro>-<repo>. Codecov rejects comma-separated
    flags on a single upload.
  • Attach junit.xml as a GitHub artifact from integration, model-check, Python, Windows,
    macOS, Alpine, and ROS industrial CI. PolyScope X also uploads test_artifacts/.
  • Coverage XML is uploaded only from a successful full integration ctest, and no longer
    depends on the Codecov test-results upload succeeding. robot_model_check uploads
    JUnit but no gcov — that one-test report was pulling project coverage down.
  • Set BASEDIR=/tmp/ici_ws for industrial CI so colcon/catkin test_results XML is
    bind-mounted out of the ICI container. The downstream ROS driver's reports were
    previously unreachable from the runner. They are flattened once into ros_junit/ and
    used for both the artifact and the Codecov upload.

Reports are uploaded exactly as the test runners write them. No new scripts, no changes
outside .github/.

Codecov Test Analytics

Worth recording, because it is the one goal this PR does not achieve.

Only the four pytest cases appear under Codecov Tests for this branch. The CTest and ROS
uploads are accepted and then dropped server-side:

  • The reports themselves are complete and valid. Running Codecov's own parser
    (test-results-parser, the Rust library their worker imports) over the uploaded
    artifacts reads 374 tests per platform job and 646 per integration job, with the
    failure counts correct.
  • The CLI reports success for every one of them
    (Found N test_results files, Upload queued for processing complete), but the commit
    ends up with a single test-results upload record — the pytest one.
  • master already ingests raw ctest --output-junit output, so the CTest dialect is
    not the obstacle either.

So this is not a report-format problem and not something the workflow files can fix.
Getting C++ and ROS cases into Test Analytics needs the branch to run inside this
repository with the org CODECOV_TOKEN rather than as a fork PR, or a Codecov support
ticket. Either way it is out of scope here; the JUnit is meanwhile available as GitHub
artifacts on every run.

Test plan

  • A failing integration ctest still runs the Codecov test-results step and attaches
    junit.xml plus URSim / test artifacts.
    Verified on run_tests (10.12.0-ur5e): ctest exited 8, JUnit and debug artifacts
    uploaded. Coverage is intentionally skipped on that path.
  • A passing run still uploads Codecov reports and does not skip run examples.
    Verified on run_tests (3.14.3-ur5). run examples keeps its original position
    before gcovr, so example coverage is still measured.
  • Codecov uses separate flags for Windows, macOS (arm64/x86_64), Alpine, and each
    integration matrix cell. One flag per upload, check_model stays on
    check_version_*.
  • robot_model_check uploads JUnit on failure, and URSim docker logs only when the
    job fails. Coverage XML is no longer generated there.
  • Windows, macOS, and Alpine produce and upload a JUnit report (374 tests each).
  • Industrial CI collects the downstream driver's test_results XML and uploads it as
    an artifact.
  • Cancelled jobs do not upload half-written artifacts (!cancelled(), not always()).
  • CTest and ROS cases appear under Codecov Tests. Blocked, see above.

Copilot AI balanced review requested due to automatic review settings September 16, 2026 09:03
@urrsk urrsk added the CI label Sep 16, 2026
@codecov

codecov Bot commented Sep 16, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 88.68%. Comparing base (5062a24) to head (004d201).
⚠️ Report is 1 commits behind head on master.
✅ All tests successful. No failed tests found.

Additional details and impacted files
@@             Coverage Diff             @@
##           master     #575       +/-   ##
===========================================
+ Coverage   17.95%   88.68%   +70.73%     
===========================================
  Files         121        2      -119     
  Lines        7259      442     -6817     
  Branches     3227        0     -3227     
===========================================
- Hits         1303      392      -911     
+ Misses       5840       50     -5790     
+ Partials      116        0      -116     
Flag Coverage Δ
check_version_ur10-3.15.8 ?
check_version_ur10e-10.11.0 ?
check_version_ur10e-5.15.2 ?
check_version_ur12e-10.12.1 ?
check_version_ur12e-5.25.1 ?
check_version_ur15-10.12.1 ?
check_version_ur15-5.25.1 ?
check_version_ur16e-10.12.1 ?
check_version_ur16e-5.25.1 ?
check_version_ur18-10.12.1 ?
check_version_ur18-5.25.1 ?
check_version_ur20-10.12.1 ?
check_version_ur20-5.25.1 ?
check_version_ur3-3.14.3 ?
check_version_ur30-10.12.1 ?
check_version_ur30-5.25.1 ?
check_version_ur3e-10.11.0 ?
check_version_ur3e-5.9.4 ?
check_version_ur5-3.15.8 ?
check_version_ur5e-10.11.0 ?
check_version_ur5e-5.12.8 ?
check_version_ur7e-10.11.0 ?
check_version_ur7e-5.22.2 ?
check_version_ur8long-10.12.1 ?
check_version_ur8long-5.25.1 ?
python_scripts 75.90% <ø> (ø)
start_ursim 91.64% <ø> (-1.30%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟢 Approval recommended

The workflows consistently preserve diagnostics after failures while avoiding uploads for cancelled jobs.

Pull request overview

Adds failure-resilient CI reporting across supported platforms.

Changes:

  • Generates and uploads JUnit results after test failures.
  • Adds distinct Codecov flags for job classes and environments.
  • Preserves coverage, logs, and debug artifacts unless cancelled.
File summaries
File Description
.github/workflows/ci.yml Extends integration and model-check reporting and artifacts.
.github/workflows/win-build.yml Adds Windows JUnit reporting.
.github/workflows/mac-build.yml Adds per-architecture macOS JUnit reporting.
.github/workflows/alpine-build.yml Adds Alpine dependencies and JUnit reporting.
.github/workflows/test_python_scripts.yml Adds Python JUnit and failure-resilient coverage uploads.
.github/codecov.yml Enables flag carryforward.
Review details
  • Files reviewed: 6/6 changed files
  • Comments generated: 0
  • Review effort level: Balanced

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Copilot AI review requested due to automatic review settings September 16, 2026 09:56

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Changes recommended

The ROS Codecov glob cannot discover external results, and report failures can incorrectly skip example tests.

Get a fresh assessment by requesting another Copilot review.

Review details

Suppressed comments (1)

Previously missed (1) — in code that hasn't changed since the last review.

.github/workflows/ci.yml:175

  • Moving this after the post-processing steps makes it depend on every earlier Codecov/gcovr step through GitHub Actions' implicit success() condition. Consequently, a passing ctest followed by a transient Codecov failure (fail_ci_if_error: true) now skips the examples, whereas they previously ran immediately after a successful test. Move this step before report uploads, or give the test step an ID and condition this specifically on that outcome.
  • Files reviewed: 7/7 changed files
  • Comments generated: 1
  • Review effort level: Balanced

Comment thread .github/workflows/industrial-ci.yml Outdated
@urrsk
urrsk requested a review from urfeex September 16, 2026 10:05

@urfeex urfeex left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

A couple of thoughts:

  • This branch seems to mix the ctest coverage and the check_model coverage into one model-version tag. I would prefer keeping them separate
  • I can't find the test reports on Codecov. That might be normal for PRs, but I don't know
  • Project coverage drops by almost 3 percent
  • Codecov complains about multiple tags: "Multiple flags detected. Please ensure one flag per upload."

Copilot AI review requested due to automatic review settings September 16, 2026 12:52
@urrsk

urrsk commented Sep 16, 2026

Copy link
Copy Markdown
Member Author

Thanks, addressed in af9cb99:

  • One flag per upload. Codecov was rejecting integration,ur5e-10.12.0-style comma lists (Multiple flags detected). Each upload now has a single flag.
  • check_model vs integration stay separate. Integration keeps <model>-<version> (e.g. ur5e-10.14.0). check_model is back on check_version_<model>-<version>, so the one-test coverage report is not mixed into the integration tag.
  • Coverage drop. That ~3% was the check_model 12% reports (and a failed ctest partial gcovr) landing on the same flags as full integration runs. Coverage XML is now uploaded only when ctest succeeded; JUnit/test analytics still upload on failure.
  • Test reports on Codecov. Test analytics is a separate Codecov product (Tests tab), not the coverage flags table. With one flag per upload the JUnit reports should process; they often only show up after the upload is processed, and fork PRs can be delayed. GitHub still has the *_junit artifacts if you want to inspect the XML directly.

Also copied ROS JUnit into the workspace before the Codecov action (absolute /tmp/... globs were outside its search root) and run examples only if the test step itself succeeded, so a Codecov hiccup cannot skip them.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Changes recommended

Downstream ROS results are omitted, reporting failures can still skip examples, and the PR description no longer matches the workflow behavior.

Get a fresh assessment by requesting another Copilot review.

Review details
  • Files reviewed: 7/7 changed files
  • Comments generated: 4
  • Review effort level: Balanced

Comment thread .github/workflows/ci.yml Outdated
Comment thread .github/workflows/industrial-ci.yml Outdated
Comment thread .github/workflows/ci.yml
Comment thread .github/workflows/ci.yml Outdated
Copilot AI review requested due to automatic review settings September 16, 2026 13:21

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔵 Needs a closer look

The model-check job still uploads partial coverage from its single-test run, contrary to the stated coverage goal.

Review details

Suppressed comments (1)

Previously missed (1) — in code that hasn't changed since the last review.

.github/workflows/ci.yml:380

  • This condition still uploads coverage from robot_model_check, even though this job runs only PrimaryClientTest.test_robot_type at line 342. A successful one-test run therefore still contributes its partial gcov report to Codecov, contradicting the PR's stated goal that one-test runs must not affect project coverage. Remove the gcov generation/upload steps from this job (or otherwise exclude this report from project coverage).
  • Files reviewed: 7/7 changed files
  • Comments generated: 0 new
  • Review effort level: Balanced

Copilot AI review requested due to automatic review settings September 17, 2026 06:42

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔵 Needs a closer look

Cross-platform and ROS matrix validation remains in progress, so end-to-end behavior is not fully established.

Review details
  • Files reviewed: 7/7 changed files
  • Comments generated: 0 new
  • Review effort level: Balanced

Copilot AI review requested due to automatic review settings September 17, 2026 08:19

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Changes recommended

Coverage sequencing, duplicate ROS discovery, and stale model-check coverage carryforward can produce incomplete or inaccurate Codecov results.

Get a fresh assessment by requesting another Copilot review.

Review details

Suppressed comments (2)

Previously missed (2) — in code that hasn't changed since the last review.

.github/codecov.yml:8

  • Global carryforward also applies to the historical check_version_* coverage flags whose uploads this PR removes because they depress project coverage. When those coverage reports are absent on future commits, Codecov will reuse their previous one-test snapshots, defeating that removal. Keep carryforward disabled for every check_version_* flag (or scope it only to the coverage flags that should persist).
    .github/workflows/ci.yml:170
  • These conditions still inherit GitHub Actions' implicit success(). If the preceding test-results upload fails (it has fail_ci_if_error: true), all three coverage steps are skipped even though ctest passed. Add an explicit status check and gate each dependent step on its direct predecessor so a Codecov test-results outage cannot suppress valid coverage.
  • Files reviewed: 8/8 changed files
  • Comments generated: 1
  • Review effort level: Balanced

Comment thread .github/workflows/industrial-ci.yml Outdated
Copilot AI review requested due to automatic review settings September 17, 2026 09:43

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔵 Needs a closer look

Cross-platform workflow behavior and ROS ingestion still require the pending end-to-end Codecov verification.

Review details
  • Files reviewed: 8/8 changed files
  • Comments generated: 0 new
  • Review effort level: Balanced

Copilot AI review requested due to automatic review settings September 17, 2026 10:18

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Changes recommended

Coverage can be suppressed by a Codecov failure, and multi-suite JUnit duration is calculated incorrectly.

Get a fresh assessment by requesting another Copilot review.

Review details

Suppressed comments (1)

Previously missed (1) — in code that hasn't changed since the last review.

.github/workflows/ci.yml:170

  • These three conditions do not contain a status-check function, so GitHub implicitly prepends success(). If the preceding test-results Codecov upload fails while ctest succeeded, all coverage work is skipped despite steps.test.outcome == 'success'. Add !cancelled() to each condition, as done for run examples, so a reporting outage cannot suppress valid coverage generation/upload.
  • Files reviewed: 9/9 changed files
  • Comments generated: 1
  • Review effort level: Balanced

Comment thread scripts/prepare_junit_for_codecov.py Outdated
Copilot AI review requested due to automatic review settings September 17, 2026 10:59
Copilot AI review requested due to automatic review settings September 22, 2026 09:10
@urrsk

urrsk commented Sep 22, 2026

Copy link
Copy Markdown
Member Author

@urfeex thanks — your review turned out to be right on every point, including the one I
spent the longest getting wrong. Where each stands now:

1. ctest and check_model coverage mixed into one model-version tag. Fixed.
robot_model_check uses check_version_<model>-<version> and no longer uploads gcov at
all; a single PrimaryClientTest.test_robot_type run was contributing an ~11% report
over the whole library. It still uploads its JUnit.

2. Codecov complains about multiple flags per upload. Fixed. Every upload carries
exactly one flag plus a distinct name:.

3. Project coverage drops by almost 3 percent. Cause found and removed. It was a
165-line Python helper this branch added, of which 86 lines were uncovered — the numbers
line up exactly (+284 lines, +6 hits). That script is gone, so this should be roughly
neutral now. Related: I had also added a global carryforward: true, which Copilot
correctly flagged; it was replaying the obsolete ~11% check-model reports into every new
commit, so it has been dropped too. .github/codecov.yml is now unchanged from master
and this PR touches only .github/workflows.

4. "I can't find the test reports on Codecov." You were right to be suspicious, and
this is the one thing the PR does not achieve. Only the four pytest cases ever appear.
I chased it for several commits on the theory that CTest's JUnit dialect needed
rewriting, which was wrong:

  • Codecov's own parser (test-results-parser, the library their worker imports) reads
    our reports unmodified — 374 cases per platform job, 646 per integration job, failure
    counts intact.
  • The CLI reports success for every upload (Found N test_results files,
    Upload queued for processing complete), but the commit ends up with a single
    test-results upload record: the pytest one. The rest are accepted and dropped
    server-side.
  • master already ingests raw ctest --output-junit output — ~313k test rows — so the
    format is not the obstacle.

The difference I cannot rule out from outside Codecov is that this is a fork PR, where
CODECOV_TOKEN is empty and uploads go through the tokenless path. pytest survives it;
the later uploads do not. So the remaining options are to run the branch inside this
repository with the org token, or to raise it with Codecov support. I have left it out of
scope and said so in the description, rather than keep reshaping XML that was never the
problem.

Net effect: the PR is now workflow-only, 183/32 across 6 files, down from 400/35 across 9
including that script. What it does deliver is the original goal — JUnit and debug
artifacts survive a red ctest — plus JUnit from Windows, macOS and Alpine, which
produced no report at all before, and the ROS driver's test_results XML, which could
not previously escape the industrial_ci container. Windows, Alpine and macOS are green on
the latest run.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot review overview

🔵 Needs a closer look

Coverage upload can run after its generating gcovr step fails.

Review effort: Balanced
Findings: None

Resolved since last review (7)
Previously missed (1)

In code that hasn't changed since last review

Medium severity Gate coverage upload on gcovr step success

.github/​workflows/​ci.yml:169

Because this condition contains cancelled(), it bypasses the implicit success() check and remains true even when the preceding gcovr step fails, provided CTest passed. That can upload a partial coverage.xml (or add a misleading missing-report failure). Give the generator an ID and gate this upload on its outcome; this still keeps coverage independent of the earlier test-results upload.

Copilot AI review requested due to automatic review settings September 22, 2026 09:25
@urrsk

urrsk commented Sep 22, 2026

Copy link
Copy Markdown
Member Author

Addressed in 275d2ab — this one was a real regression I had introduced, and it was
present in one more place than the review found.

The !cancelled() on the coverage steps was there to decouple them from the preceding
test-results upload, which carries fail_ci_if_error: true and can fail on its own on a
tokenless fork upload. But !cancelled() suppresses the implicit success() for the
whole chain, so it also decoupled the coverage steps from each other. Each step is now
gated on the outcome of whatever produces its input, which keeps the intended
independence from the test-results upload:

      - name: run examples
        id: examples
        if: ${{ !cancelled() && steps.test.outcome == 'success' }}
      - name: install gcovr
        if: ${{ !cancelled() && steps.examples.outcome == 'success' }}
      - name: gcovr
        id: gcovr
        if: ${{ !cancelled() && steps.examples.outcome == 'success' }}
      - name: Upload coverage reports to Codecov with GitHub Action
        if: ${{ !cancelled() && steps.gcovr.outcome == 'success' }}

That closes three holes rather than one. A failed gcovr no longer uploads a partial
coverage.xml (the point raised); a failed apt-get install gcovr no longer surfaces as
a misleading missing-report Codecov failure; and a failed run_examples.sh no longer
uploads coverage that is missing the examples' contribution, which matters because the
examples are deliberately ordered before gcovr so that they are counted. Gating on
steps.gcovr.outcome also subsumes the previous steps.test.outcome check, since a
failed ctest leaves gcovr skipped rather than failed.

Also fixed, not flagged: test_python_scripts.yml had the same defect in a worse
form. I had put a bare if: ${{ !cancelled() }} on its coverage upload where master
relied on the implicit success(), so a failing pytest run uploaded its understated
coverage under the python_scripts flag. It now gates on the pytest step's outcome.
Uploading coverage from a failed test run was never a goal of this PR — only test results
and debug artifacts.

robot_model_check needs no equivalent change; it no longer produces or uploads coverage
at all.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot review overview

🟢 Approval recommended

The reporting and coverage conditions are consistent with the stated failure-handling requirements.

Review effort: Balanced
Findings: None

@urrsk urrsk changed the title Upload test results and artifacts when tests fail Upload test results and artifacts also when tests fail Sep 22, 2026
GitHub Actions skipped later steps after a red ctest, so flake stats and debug artifacts were missing.
Tag each upload with job and environment flags so Windows, macOS, Alpine, and integration runs stay distinct.
Expose colcon/catkin JUnit from the ICI container via BASEDIR so Codecov and GitHub artifacts still get the reports after a red downstream driver run.
Keep integration and check_model coverage on separate flags, and stop
attaching two flags to one report. That mixing was dragging project
coverage down and hiding test analytics.
Codecov's JUnit parser reads ctest --output-junit and colcon output as-is,
so the rewriting script was not needed to get reports ingested. Drop it and
point the test-results uploads at the reports the test runners produce.
Carryforward kept replaying the one-test robot_model_check coverage reports
this branch stops uploading, so those ~11% reports would have gone on
depressing project coverage anyway. Coverage was already skipped after a
failed ctest before this branch, so carryforward is not needed to keep the
existing behaviour.
Adding !cancelled() to the coverage steps was meant to decouple them from the
preceding test-results upload, which can fail on its own. It also decoupled
them from each other, so a failed run_examples.sh or gcovr would still upload
whatever coverage.xml happened to be on disk, or fail the job with a missing
report. Chain the steps on the outcome of the step that produces their input
instead, which keeps the intended independence from the test-results upload.
Comment thread .github/workflows/industrial-ci.yml
@urrsk
urrsk requested a review from urfeex September 22, 2026 10:24
The ROS industrial_ci job no longer collects JUnit XML or sends it to
Codecov. Drop the host workspace mount that existed only for that upload.
Copilot AI review requested due to automatic review settings September 22, 2026 11:57

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot review overview

🟡 Changes recommended

The ROS industrial workflow does not implement the promised JUnit collection, artifact, or Codecov upload.

Get a fresh assessment by requesting another Copilot review.

Review effort: Balanced
Findings: 1 Medium severity

Open (1)

Comment thread .github/workflows/industrial-ci.yml
@mergify

mergify Bot commented Sep 22, 2026

Copy link
Copy Markdown
Contributor

Tick the box to add this pull request to the merge queue (same as @mergifyio queue).

  • Queue this pull request

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants