Skip to content

Commit 10c544f

Browse files
committed
Merge #46: Add fuzz subcommand
9a24e67 cargo-rbmt: add fuzz subcommand (Nick Johnson) Pull request description: Initially only supporting the dynamic listing of targets, but that might still be super helpful for the CI jobs. The command is following a similar style of `integration` where it is assuming some sort of `fuzz` crate in the workspace. Related to #5 ACKs for top commit: tcharding: ACK 9a24e67 Tree-SHA512: 017f6fd156c72eccf1e1eaf351491d45942f5f95e810ed65c5996901ac44058ec32ac1d185890d7dd38b2a35867b75ac5a1e3cde599b89bdf4347930a3e528dd
2 parents 3dc756f + 9a24e67 commit 10c544f

File tree

4 files changed

+136
-14
lines changed

4 files changed

+136
-14
lines changed

cargo-rbmt/src/environment.rs

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -105,10 +105,8 @@ pub fn get_packages(
105105
}
106106

107107
if !invalid_packages.is_empty() {
108-
let mut error_msg = format!(
109-
"Package not found in workspace: {}",
110-
invalid_packages.join(", ")
111-
);
108+
let mut error_msg =
109+
format!("Package not found in workspace: {}", invalid_packages.join(", "));
112110

113111
error_msg.push_str("\n\nAvailable packages:");
114112
for name in &available_names {
@@ -119,10 +117,8 @@ pub fn get_packages(
119117
}
120118

121119
// Filter to only requested packages.
122-
let package_info: Vec<(String, PathBuf)> = all_packages
123-
.into_iter()
124-
.filter(|(name, _)| packages.iter().any(|p| p == name))
125-
.collect();
120+
let package_info: Vec<(String, PathBuf)> =
121+
all_packages.into_iter().filter(|(name, _)| packages.iter().any(|p| p == name)).collect();
126122

127123
Ok(package_info)
128124
}

cargo-rbmt/src/fuzz.rs

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
//! Fuzz test tasks for workspaces with honggfuzz fuzz targets.
2+
3+
use std::path::Path;
4+
5+
use serde::Deserialize;
6+
use xshell::Shell;
7+
8+
use crate::environment::{quiet_println, CONFIG_FILE_PATH};
9+
use crate::quiet_cmd;
10+
11+
/// Default package name for fuzz targets.
12+
const FUZZ_PACKAGE: &str = "fuzz";
13+
14+
/// Fuzz configuration loaded from rbmt.toml.
15+
#[derive(Debug, Deserialize, Default)]
16+
#[serde(default)]
17+
struct Config {
18+
fuzz: FuzzConfig,
19+
}
20+
21+
/// Fuzz-specific configuration.
22+
#[derive(Debug, Deserialize, Default)]
23+
#[serde(default)]
24+
struct FuzzConfig {
25+
/// Package name containing fuzz targets (defaults to [`FUZZ_PACKAGE`]).
26+
package: Option<String>,
27+
}
28+
29+
impl FuzzConfig {
30+
/// Load fuzz configuration from workspace root.
31+
fn load(workspace_root: &Path) -> Result<Self, Box<dyn std::error::Error>> {
32+
let config_path = workspace_root.join(CONFIG_FILE_PATH);
33+
34+
if !config_path.exists() {
35+
return Ok(Self::default());
36+
}
37+
38+
let contents = std::fs::read_to_string(&config_path)?;
39+
let config: Config = toml::from_str(&contents)?;
40+
Ok(config.fuzz)
41+
}
42+
43+
/// Get the package name (defaults to [`FUZZ_PACKAGE`]).
44+
fn package_name(&self) -> &str { self.package.as_deref().unwrap_or(FUZZ_PACKAGE) }
45+
}
46+
47+
/// Discover all fuzz targets using cargo metadata.
48+
///
49+
/// Targets are discovered by querying cargo metadata for all binary targets
50+
/// in the specified fuzz package.
51+
fn discover_fuzz_targets(
52+
sh: &Shell,
53+
package_name: &str,
54+
) -> Result<Vec<String>, Box<dyn std::error::Error>> {
55+
let metadata = quiet_cmd!(sh, "cargo metadata --format-version 1 --no-deps").read()?;
56+
let json: serde_json::Value = serde_json::from_str(&metadata)?;
57+
58+
let mut targets = Vec::new();
59+
60+
// Find binary targets in the specified fuzz package.
61+
if let Some(packages) = json["packages"].as_array() {
62+
for package in packages {
63+
if package["name"].as_str() == Some(package_name) {
64+
if let Some(package_targets) = package["targets"].as_array() {
65+
for target in package_targets {
66+
// Filter for binary targets only.
67+
let Some(kinds) = target["kind"].as_array() else {
68+
continue;
69+
};
70+
let Some(name) = target["name"].as_str() else {
71+
continue;
72+
};
73+
74+
if kinds.iter().any(|k| k.as_str() == Some("bin")) {
75+
targets.push(name.to_string());
76+
}
77+
}
78+
}
79+
break; // Found the package, no need to continue.
80+
}
81+
}
82+
}
83+
84+
// Sort for consistent output.
85+
targets.sort();
86+
87+
Ok(targets)
88+
}
89+
90+
/// List discovered fuzz targets.
91+
pub fn list(sh: &Shell) -> Result<(), Box<dyn std::error::Error>> {
92+
let workspace_root = sh.current_dir();
93+
let config = FuzzConfig::load(&workspace_root)?;
94+
let package_name = config.package_name();
95+
96+
let targets = discover_fuzz_targets(sh, package_name)?;
97+
98+
if targets.is_empty() {
99+
quiet_println("No fuzz targets found");
100+
} else {
101+
for target in targets {
102+
println!("{}", target);
103+
}
104+
}
105+
106+
Ok(())
107+
}
108+
109+
/// Run fuzz tests for the workspace.
110+
pub fn run(_sh: &Shell) { quiet_println("Fuzz execution not yet implemented"); }

cargo-rbmt/src/main.rs

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ mod api;
22
mod bench;
33
mod docs;
44
mod environment;
5+
mod fuzz;
56
mod integration;
67
mod lint;
78
mod lock;
@@ -60,6 +61,12 @@ enum Commands {
6061
},
6162
/// Run bitcoin core integration tests.
6263
Integration,
64+
/// Run fuzz tests.
65+
Fuzz {
66+
/// List available fuzz targets instead of running them.
67+
#[arg(long)]
68+
list: bool,
69+
},
6370
/// Update Cargo-minimal.lock and Cargo-recent.lock files.
6471
Lock,
6572
/// Run pre-release readiness checks.
@@ -80,8 +87,8 @@ fn main() {
8087
configure_log_level(&sh);
8188
change_to_repo_root(&sh);
8289

83-
// Restore the specified lock file before running any command (except Lock and Integration).
84-
if !matches!(cli.command, Commands::Lock | Commands::Integration) {
90+
// Restore the specified lock file before running any command (except Lock, Integration, and Fuzz).
91+
if !matches!(cli.command, Commands::Lock | Commands::Integration | Commands::Fuzz { .. }) {
8592
if let Err(e) = cli.lock_file.restore(&sh) {
8693
eprintln!("Error restoring lock file: {}", e);
8794
process::exit(1);
@@ -115,17 +122,25 @@ fn main() {
115122
eprintln!("Error running bench tests: {}", e);
116123
process::exit(1);
117124
},
118-
Commands::Test { toolchain, no_debug_assertions } => {
125+
Commands::Test { toolchain, no_debug_assertions } =>
119126
if let Err(e) = test::run(&sh, toolchain, no_debug_assertions, &cli.packages) {
120127
eprintln!("Error running tests: {}", e);
121128
process::exit(1);
122-
}
123-
}
129+
},
124130
Commands::Integration =>
125131
if let Err(e) = integration::run(&sh, &cli.packages) {
126132
eprintln!("Error running integration tests: {}", e);
127133
process::exit(1);
128134
},
135+
Commands::Fuzz { list } =>
136+
if list {
137+
if let Err(e) = fuzz::list(&sh) {
138+
eprintln!("Error listing fuzz targets: {}", e);
139+
process::exit(1);
140+
}
141+
} else {
142+
fuzz::run(&sh);
143+
},
129144
Commands::Lock =>
130145
if let Err(e) = lock::run(&sh) {
131146
eprintln!("Error updating lock files: {}", e);

cargo-rbmt/src/test.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -275,7 +275,8 @@ fn loop_features<S: AsRef<str>>(
275275
.chain(additional.iter().map(std::convert::AsRef::as_ref))
276276
.collect::<Vec<_>>()
277277
.join(" "),
278-
None => additional.iter().map(std::convert::AsRef::as_ref).collect::<Vec<_>>().join(" "),
278+
None =>
279+
additional.iter().map(std::convert::AsRef::as_ref).collect::<Vec<_>>().join(" "),
279280
}
280281
}
281282

0 commit comments

Comments
 (0)