From 53a3d7df319a67fc9f29c54328112d2eaa15ddfe Mon Sep 17 00:00:00 2001 From: xdustinface Date: Sat, 14 Mar 2026 20:48:37 +0700 Subject: [PATCH 1/2] ci: integrate Codecov test analytics via `cargo-nextest` Switch CI test runner from `cargo test` to `cargo-nextest` to produce JUnit XML reports. Results are uploaded to Codecov's new test analytics feature for tracking test duration trends, failure rates, and flaky tests. --- .config/nextest.toml | 5 ++++ .github/scripts/ci_config.py | 23 +++++++++++++++++-- .github/workflows/build-and-test.yml | 11 +++++++++ .../transaction_router/tests/coinbase.rs | 1 + 4 files changed, 38 insertions(+), 2 deletions(-) create mode 100644 .config/nextest.toml diff --git a/.config/nextest.toml b/.config/nextest.toml new file mode 100644 index 000000000..6a109e171 --- /dev/null +++ b/.config/nextest.toml @@ -0,0 +1,5 @@ +[profile.ci] +fail-fast = false + +[profile.ci.junit] +path = "junit.xml" diff --git a/.github/scripts/ci_config.py b/.github/scripts/ci_config.py index 7c56715c8..c1a6dc1c0 100755 --- a/.github/scripts/ci_config.py +++ b/.github/scripts/ci_config.py @@ -11,6 +11,7 @@ import argparse import json import os +import shutil import subprocess import sys from pathlib import Path @@ -118,6 +119,9 @@ def run_group_tests(args): if coverage and args.group not in no_coverage: github_output("crate_flags", args.group) + junit_dir = Path("target/test-results") + junit_dir.mkdir(parents=True, exist_ok=True) + for crate in crates: # Skip dash-fuzz on Windows if args.os == "windows-latest" and crate == "dash-fuzz": @@ -127,17 +131,32 @@ def run_group_tests(args): github_group_start(f"Testing {crate}") if coverage and args.group not in no_coverage: - cmd = ["cargo", "llvm-cov", "--no-report", "-p", crate, "--all-features"] + cmd = [ + "cargo", "llvm-cov", "nextest", + "--no-report", "-p", crate, "--all-features", + "--profile", "ci", + ] else: - cmd = ["cargo", "test", "-p", crate, "--all-features"] + cmd = [ + "cargo", "nextest", "run", + "-p", crate, "--all-features", + "--profile", "ci", + ] result = subprocess.run(cmd) + # Collect JUnit XML per crate for Codecov test analytics + src = Path("target/nextest/ci/junit.xml") + if src.exists(): + shutil.copy(src, junit_dir / f"junit-{crate}.xml") + github_group_end() if result.returncode != 0: failed.append(crate) github_error(f"Test failed for {crate} on {args.os}") + github_output("junit_dir", str(junit_dir)) + if failed: print("\n" + "=" * 40) print(f"FAILED TESTS ({args.group} on {args.os}):") diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 65ee36f76..aa699b3b1 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -42,6 +42,9 @@ jobs: with: shared-key: "test-${{ inputs.os }}-${{ matrix.group }}" + - name: Install cargo-nextest + uses: taiki-e/install-action@nextest + - name: Install cargo-llvm-cov if: inputs.coverage uses: taiki-e/install-action@cargo-llvm-cov @@ -85,6 +88,14 @@ jobs: token: ${{ secrets.CODECOV_TOKEN }} fail_ci_if_error: true + - name: Upload test results to Codecov + if: ${{ !cancelled() }} + uses: codecov/test-results-action@v1 + with: + files: target/test-results/junit-*.xml + flags: ${{ matrix.group }} + token: ${{ secrets.CODECOV_TOKEN }} + - name: Upload failed dashd test logs if: failure() && (matrix.group == 'spv' || matrix.group == 'ffi') uses: actions/upload-artifact@v4 diff --git a/key-wallet/src/transaction_checking/transaction_router/tests/coinbase.rs b/key-wallet/src/transaction_checking/transaction_router/tests/coinbase.rs index acd2c81c1..23c6ff72f 100644 --- a/key-wallet/src/transaction_checking/transaction_router/tests/coinbase.rs +++ b/key-wallet/src/transaction_checking/transaction_router/tests/coinbase.rs @@ -252,6 +252,7 @@ async fn test_update_state_flag_behavior() { // First check with update_state = false let result1 = + managed_wallet_info.check_core_transaction(&tx, context, &mut wallet, false).await; assert!(result1.is_relevant); From 7cdd7e847aef318a2d1b266cc2db931b95cf3093 Mon Sep 17 00:00:00 2001 From: xdustinface Date: Sun, 15 Mar 2026 17:25:43 +0700 Subject: [PATCH 2/2] fix: resolve CI failures in `cargo-nextest` migration - Serialize `dashd_sync` integration tests via nextest test-groups (nextest runs each test as a separate process concurrently, causing dashd resource contention and crashes) - Add `--no-tests=pass` to nextest commands so crates with zero tests (like `dash-fuzz`) don't fail - Output explicit JUnit file list instead of glob pattern to avoid Windows path expansion issues with codecov CLI --- .config/nextest.toml | 9 +++++++++ .github/scripts/ci_config.py | 9 ++++++++- .github/workflows/build-and-test.yml | 4 ++-- .../transaction_router/tests/coinbase.rs | 1 - 4 files changed, 19 insertions(+), 4 deletions(-) diff --git a/.config/nextest.toml b/.config/nextest.toml index 6a109e171..9fabc4519 100644 --- a/.config/nextest.toml +++ b/.config/nextest.toml @@ -1,5 +1,14 @@ +[test-groups] +dashd-integration = { max-threads = 1 } + [profile.ci] fail-fast = false [profile.ci.junit] path = "junit.xml" + +# dashd integration tests each start their own dashd process; +# running them concurrently causes resource contention and crashes. +[[profile.ci.overrides]] +filter = 'binary(dashd_sync)' +test-group = 'dashd-integration' diff --git a/.github/scripts/ci_config.py b/.github/scripts/ci_config.py index c1a6dc1c0..66e0606ed 100755 --- a/.github/scripts/ci_config.py +++ b/.github/scripts/ci_config.py @@ -135,12 +135,14 @@ def run_group_tests(args): "cargo", "llvm-cov", "nextest", "--no-report", "-p", crate, "--all-features", "--profile", "ci", + "--no-tests=pass", ] else: cmd = [ "cargo", "nextest", "run", "-p", crate, "--all-features", "--profile", "ci", + "--no-tests=pass", ] result = subprocess.run(cmd) @@ -155,7 +157,12 @@ def run_group_tests(args): failed.append(crate) github_error(f"Test failed for {crate} on {args.os}") - github_output("junit_dir", str(junit_dir)) + junit_files = sorted(junit_dir.glob("junit-*.xml")) + if junit_files: + github_output( + "junit_files", + ",".join(str(f).replace("\\", "/") for f in junit_files), + ) if failed: print("\n" + "=" * 40) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index aa699b3b1..32cd4708b 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -89,10 +89,10 @@ jobs: fail_ci_if_error: true - name: Upload test results to Codecov - if: ${{ !cancelled() }} + if: ${{ !cancelled() && steps.tests.outputs.junit_files }} uses: codecov/test-results-action@v1 with: - files: target/test-results/junit-*.xml + files: ${{ steps.tests.outputs.junit_files }} flags: ${{ matrix.group }} token: ${{ secrets.CODECOV_TOKEN }} diff --git a/key-wallet/src/transaction_checking/transaction_router/tests/coinbase.rs b/key-wallet/src/transaction_checking/transaction_router/tests/coinbase.rs index 23c6ff72f..acd2c81c1 100644 --- a/key-wallet/src/transaction_checking/transaction_router/tests/coinbase.rs +++ b/key-wallet/src/transaction_checking/transaction_router/tests/coinbase.rs @@ -252,7 +252,6 @@ async fn test_update_state_flag_behavior() { // First check with update_state = false let result1 = - managed_wallet_info.check_core_transaction(&tx, context, &mut wallet, false).await; assert!(result1.is_relevant);