Skip to content

Commit 83e42ed

Browse files
committed
Merge #30: cargo-rbmt: add integration command for corepc conventions
2d63432 cargo-rbmt: add integration command for corepc conventions (Nick Johnson) Pull request description: This codifies corepc integration testing which I am working on over in [rust-psbt](tcharding/rust-psbt#41), which is based on the structure in rust-miniscript. ACKs for top commit: tcharding: ACK 2d63432 Tree-SHA512: 0ee5d3a4036d8938cfceb81e85763cc14e05aae691f75a6dda113a88484e32ebf12d336cce78178584fc73e5450d6adebe2e12a78d5a660fc0cf67c41d92271a
2 parents 95a2fd3 + 2d63432 commit 83e42ed

File tree

3 files changed

+173
-4
lines changed

3 files changed

+173
-4
lines changed

cargo-rbmt/README.md

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22

33
Maintainer tools for Rust-based projects in the Bitcoin domain. Built with [xshell](https://github.com/matklad/xshell).
44

5+
## Environment Variables
6+
7+
* `RBMT_LOG_LEVEL=quiet` - Suppress verbose output and reduce cargo noise.
8+
59
## Configuration
610

711
Configuration for `rbmt` is stored in `rbmt.toml`. The file can live at both the workspace root (e.g. `$ROOT/rbmt.toml`) as well as per-package (e.g. `$ROOT/$PACKAGE/rbmt.toml`) within a repository.
@@ -57,9 +61,17 @@ exact_features = [
5761
features_with_no_std = ["serde", "rand"]
5862
```
5963

60-
### Environment Variables
64+
### Integration
6165

62-
* `RBMT_LOG_LEVEL=quiet` - Suppress verbose output and reduce cargo noise.
66+
The `integration` command is designed to work with the [`corepc`](https://github.com/rust-bitcoin/corepc) integration testing framework, which provides Bitcoin Core binaries and testing infrastructure.
67+
68+
```toml
69+
[integration]
70+
# Integration tests package name, defaults to "bitcoind-tests".
71+
package = "bitcoind-tests"
72+
# Versions to test. If omitted, tests all discovered versions from Cargo.toml.
73+
versions = ["29_0", "28_2", "27_2"]
74+
```
6375

6476
## Lock Files
6577

cargo-rbmt/src/integration.rs

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
//! Integration test tasks for packages with bitcoind-tests or similar test packages.
2+
3+
use crate::environment::{get_crate_dirs, quiet_println, CONFIG_FILE_PATH};
4+
use crate::quiet_cmd;
5+
use serde::Deserialize;
6+
use std::path::{Path, PathBuf};
7+
use xshell::{cmd, Shell};
8+
9+
/// Integration test configuration loaded from rbmt.toml.
10+
#[derive(Debug, Deserialize, Default)]
11+
#[serde(default)]
12+
struct Config {
13+
integration: IntegrationConfig,
14+
}
15+
16+
/// Integration-specific configuration.
17+
#[derive(Debug, Deserialize, Default)]
18+
#[serde(default)]
19+
struct IntegrationConfig {
20+
/// Package name containing integration tests (defaults to "bitcoind-tests").
21+
package: Option<String>,
22+
23+
/// Bitcoind versions to test (runs each individually).
24+
/// If not specified, discovers all version features from Cargo.toml.
25+
///
26+
/// # Examples
27+
///
28+
/// `["29_0", "28_2", "27_2"]`
29+
versions: Option<Vec<String>>,
30+
}
31+
32+
impl IntegrationConfig {
33+
/// Load integration configuration from a crate directory.
34+
fn load(crate_dir: &Path) -> Result<Self, Box<dyn std::error::Error>> {
35+
let config_path = crate_dir.join(CONFIG_FILE_PATH);
36+
37+
if !config_path.exists() {
38+
return Ok(IntegrationConfig::default());
39+
}
40+
41+
let contents = std::fs::read_to_string(&config_path)?;
42+
let config: Config = toml::from_str(&contents)?;
43+
Ok(config.integration)
44+
}
45+
46+
/// Get the package name (defaults to "bitcoind-tests").
47+
fn package_name(&self) -> &str {
48+
self.package.as_deref().unwrap_or("bitcoind-tests")
49+
}
50+
}
51+
52+
/// Run integration tests for all crates with integration test packages.
53+
///
54+
/// # Arguments
55+
///
56+
/// * `packages` - Optional filter for specific package names.
57+
pub fn run(sh: &Shell, packages: &[String]) -> Result<(), Box<dyn std::error::Error>> {
58+
let crate_dirs = get_crate_dirs(sh, packages)?;
59+
quiet_println(&format!(
60+
"Looking for integration tests in {} crate(s)",
61+
crate_dirs.len()
62+
));
63+
64+
for crate_dir in &crate_dirs {
65+
let config = IntegrationConfig::load(Path::new(crate_dir))?;
66+
let integration_dir = PathBuf::from(crate_dir).join(config.package_name());
67+
68+
if !integration_dir.exists() {
69+
continue;
70+
}
71+
72+
if !integration_dir.join("Cargo.toml").exists() {
73+
continue;
74+
}
75+
76+
quiet_println(&format!(
77+
"Running integration tests for crate: {}",
78+
crate_dir
79+
));
80+
81+
let _dir = sh.push_dir(&integration_dir);
82+
83+
let available_versions = discover_version_features(sh, &integration_dir)?;
84+
if available_versions.is_empty() {
85+
quiet_println(" No version features found in Cargo.toml");
86+
continue;
87+
}
88+
89+
let versions_to_test: Vec<String> = if let Some(config_versions) = &config.versions {
90+
// Filter available versions by config.
91+
let mut filtered = Vec::new();
92+
for requested in config_versions {
93+
if available_versions.contains(requested) {
94+
filtered.push(requested.clone());
95+
} else {
96+
return Err(format!(
97+
"Requested version '{}' not found in available versions: {}",
98+
requested,
99+
available_versions.join(", ")
100+
)
101+
.into());
102+
}
103+
}
104+
filtered
105+
} else {
106+
// No config, test all available versions.
107+
available_versions
108+
};
109+
110+
// Run tests for each version.
111+
for version in &versions_to_test {
112+
quiet_println(&format!(" Testing with version: {}", version));
113+
quiet_cmd!(sh, "cargo test --features={version}").run()?;
114+
}
115+
}
116+
117+
Ok(())
118+
}
119+
120+
/// Discover all features from the integration package using cargo metadata.
121+
fn discover_version_features(
122+
sh: &Shell,
123+
integration_dir: &Path,
124+
) -> Result<Vec<String>, Box<dyn std::error::Error>> {
125+
let _dir = sh.push_dir(integration_dir);
126+
let metadata = cmd!(sh, "cargo metadata --format-version 1 --no-deps").read()?;
127+
let json: serde_json::Value = serde_json::from_str(&metadata)?;
128+
129+
let mut features = Vec::new();
130+
131+
// Find the package in the metadata and extract its features.
132+
if let Some(packages) = json["packages"].as_array() {
133+
// Should only be one package since we're in the integration test directory.
134+
if let Some(package) = packages.first() {
135+
if let Some(package_features) = package["features"].as_object() {
136+
for feature_name in package_features.keys() {
137+
features.push(feature_name.clone());
138+
}
139+
}
140+
}
141+
}
142+
143+
// Sort for consistent output.
144+
features.sort();
145+
146+
Ok(features)
147+
}

cargo-rbmt/src/main.rs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
mod bench;
22
mod docs;
33
mod environment;
4+
mod integration;
45
mod lint;
56
mod lock;
67
mod test;
@@ -46,6 +47,8 @@ enum Commands {
4647
#[arg(value_enum)]
4748
toolchain: Toolchain,
4849
},
50+
/// Run bitcoin core integration tests.
51+
Integration,
4952
/// Update Cargo-minimal.lock and Cargo-recent.lock files.
5053
Lock,
5154
}
@@ -64,9 +67,10 @@ fn main() {
6467
configure_log_level(&sh);
6568
change_to_repo_root(&sh);
6669

67-
// Restore the specified lock file before running any command (except Lock itself).
70+
// Restore the specified lock file before running any command (except Lock and Integration).
71+
// Integration tests use their own lock files in the integration package directory.
6872
if let Some(lock_file) = cli.lock_file {
69-
if !matches!(cli.command, Commands::Lock) {
73+
if !matches!(cli.command, Commands::Lock | Commands::Integration) {
7074
if let Err(e) = lock::restore_lock_file(&sh, lock_file) {
7175
eprintln!("Error restoring lock file: {}", e);
7276
process::exit(1);
@@ -105,6 +109,12 @@ fn main() {
105109
process::exit(1);
106110
}
107111
}
112+
Commands::Integration => {
113+
if let Err(e) = integration::run(&sh, &cli.packages) {
114+
eprintln!("Error running integration tests: {}", e);
115+
process::exit(1);
116+
}
117+
}
108118
Commands::Lock => {
109119
if let Err(e) = lock::run(&sh) {
110120
eprintln!("Error updating lock files: {}", e);

0 commit comments

Comments
 (0)