Skip to content

Commit e73e5b1

Browse files
committed
perf(ci): Parallelize feature testing by crate
- Split feature-checks into parallel jobs (one per crate) - Each crate's features tested in parallel with others - Add run-crate-features and list-crates-with-features commands - no-std checks remain as single job (fast enough)
1 parent efeef23 commit e73e5b1

2 files changed

Lines changed: 124 additions & 4 deletions

File tree

.github/scripts/ci_config.py

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,87 @@ def run_no_std(args):
271271
return 0
272272

273273

274+
def list_crates_with_features(args):
275+
"""List crates that have testable features (for matrix generation)."""
276+
metadata = get_workspace_metadata()
277+
features_config = load_yaml(args.features_file) or {}
278+
279+
crates_with_features = []
280+
281+
for pkg in metadata["packages"]:
282+
crate_name = pkg["name"]
283+
cargo_features = set(pkg.get("features", {}).keys())
284+
285+
if not cargo_features:
286+
continue
287+
288+
crate_config = features_config.get(crate_name, {})
289+
skip_list = set(crate_config.get("skip", []) or [])
290+
features_to_test = cargo_features - skip_list
291+
292+
if features_to_test:
293+
crates_with_features.append(crate_name)
294+
295+
# Output as JSON for GitHub Actions matrix
296+
import json
297+
print(json.dumps(sorted(crates_with_features)))
298+
return 0
299+
300+
301+
def run_crate_features(args):
302+
"""Run feature tests for a single crate."""
303+
metadata = get_workspace_metadata()
304+
features_config = load_yaml(args.features_file) or {}
305+
306+
# Find the crate
307+
pkg = None
308+
for p in metadata["packages"]:
309+
if p["name"] == args.crate:
310+
pkg = p
311+
break
312+
313+
if not pkg:
314+
github_error(f"Crate not found: {args.crate}")
315+
return 1
316+
317+
cargo_features = set(pkg.get("features", {}).keys())
318+
if not cargo_features:
319+
print(f"No features to test for {args.crate}")
320+
return 0
321+
322+
crate_config = features_config.get(args.crate, {})
323+
skip_list = set(crate_config.get("skip", []) or [])
324+
features_to_test = sorted(cargo_features - skip_list)
325+
326+
if not features_to_test:
327+
print(f"All features skipped for {args.crate}")
328+
return 0
329+
330+
failed = []
331+
332+
for feature in features_to_test:
333+
github_group_start(f"{args.crate} ({feature})")
334+
335+
cmd = ["cargo", "test", "-p", args.crate, "--features", feature]
336+
result = subprocess.run(cmd)
337+
338+
github_group_end()
339+
340+
if result.returncode != 0:
341+
failed.append(feature)
342+
github_error(f"Feature test failed: {args.crate} --features {feature}")
343+
344+
if failed:
345+
print("\n" + "=" * 40)
346+
print(f"FAILED FEATURES for {args.crate}:")
347+
for f in failed:
348+
print(f" - {f}")
349+
print("=" * 40)
350+
return 1
351+
352+
return 0
353+
354+
274355
def run_feature_checks(args):
275356
"""Run both feature matrix tests and no-std checks."""
276357
print("=" * 50)
@@ -362,6 +443,11 @@ def main():
362443
subparsers.add_parser("run-no-std", help="Run no-std checks")
363444
subparsers.add_parser("run-feature-checks", help="Run both feature tests and no-std checks")
364445

446+
crate_features_parser = subparsers.add_parser("run-crate-features", help="Run feature tests for a single crate")
447+
crate_features_parser.add_argument("crate", help="Crate name")
448+
449+
subparsers.add_parser("list-crates-with-features", help="List crates that have features to test")
450+
365451
run_group_parser = subparsers.add_parser("run-group", help="Run tests for a group")
366452
run_group_parser.add_argument("group", help="Group name")
367453
run_group_parser.add_argument("--os", default="ubuntu-latest", help="OS name")
@@ -376,6 +462,8 @@ def main():
376462
"run-features": run_features,
377463
"run-no-std": run_no_std,
378464
"run-feature-checks": run_feature_checks,
465+
"run-crate-features": run_crate_features,
466+
"list-crates-with-features": list_crates_with_features,
379467
"run-group": run_group_tests,
380468
}
381469

.github/workflows/rust.yml

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -99,8 +99,40 @@ jobs:
9999
- name: Run tests
100100
run: python .github/scripts/ci_config.py run-group ${{ matrix.group }} --os ${{ matrix.os }}
101101

102-
feature-checks:
103-
name: Feature & No-std Checks
102+
feature-matrix:
103+
name: "features: ${{ matrix.crate }}"
104+
needs: verify-features
105+
runs-on: ubuntu-latest
106+
strategy:
107+
fail-fast: false
108+
matrix:
109+
crate:
110+
- dash-network
111+
- dash-spv
112+
- dashcore
113+
- dashcore-private
114+
- dashcore-test-utils
115+
- dashcore_hashes
116+
- key-wallet
117+
- key-wallet-ffi
118+
- key-wallet-manager
119+
steps:
120+
- uses: actions/checkout@v6
121+
- uses: dtolnay/rust-toolchain@stable
122+
- uses: Swatinem/rust-cache@v2
123+
with:
124+
shared-key: "features-${{ matrix.crate }}"
125+
- uses: actions/setup-python@v5
126+
with:
127+
python-version: "3.12"
128+
cache: "pip"
129+
cache-dependency-path: .github/scripts/requirements.txt
130+
- run: pip install -r .github/scripts/requirements.txt
131+
- name: Test features
132+
run: python .github/scripts/ci_config.py run-crate-features ${{ matrix.crate }}
133+
134+
no-std:
135+
name: No-std Checks
104136
needs: verify-features
105137
runs-on: ubuntu-latest
106138
steps:
@@ -113,8 +145,8 @@ jobs:
113145
cache: "pip"
114146
cache-dependency-path: .github/scripts/requirements.txt
115147
- run: pip install -r .github/scripts/requirements.txt
116-
- name: Run feature tests and no-std checks
117-
run: python .github/scripts/ci_config.py run-feature-checks
148+
- name: Run no-std checks
149+
run: python .github/scripts/ci_config.py run-no-std
118150

119151
integrations_tests:
120152
name: Integration Tests

0 commit comments

Comments
 (0)