Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 4 additions & 8 deletions cargo-rbmt/src/environment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -105,10 +105,8 @@ pub fn get_packages(
}

if !invalid_packages.is_empty() {
let mut error_msg = format!(
"Package not found in workspace: {}",
invalid_packages.join(", ")
);
let mut error_msg =
format!("Package not found in workspace: {}", invalid_packages.join(", "));

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

// Filter to only requested packages.
let package_info: Vec<(String, PathBuf)> = all_packages
.into_iter()
.filter(|(name, _)| packages.iter().any(|p| p == name))
.collect();
let package_info: Vec<(String, PathBuf)> =
all_packages.into_iter().filter(|(name, _)| packages.iter().any(|p| p == name)).collect();

Ok(package_info)
}
Expand Down
110 changes: 110 additions & 0 deletions cargo-rbmt/src/fuzz.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
//! Fuzz test tasks for workspaces with honggfuzz fuzz targets.

use std::path::Path;

use serde::Deserialize;
use xshell::Shell;

use crate::environment::{quiet_println, CONFIG_FILE_PATH};
use crate::quiet_cmd;

/// Default package name for fuzz targets.
const FUZZ_PACKAGE: &str = "fuzz";

/// Fuzz configuration loaded from rbmt.toml.
#[derive(Debug, Deserialize, Default)]
#[serde(default)]
struct Config {
fuzz: FuzzConfig,
}

/// Fuzz-specific configuration.
#[derive(Debug, Deserialize, Default)]
#[serde(default)]
struct FuzzConfig {
/// Package name containing fuzz targets (defaults to [`FUZZ_PACKAGE`]).
package: Option<String>,
}

impl FuzzConfig {
/// Load fuzz configuration from workspace root.
fn load(workspace_root: &Path) -> Result<Self, Box<dyn std::error::Error>> {
let config_path = workspace_root.join(CONFIG_FILE_PATH);

if !config_path.exists() {
return Ok(Self::default());
}

let contents = std::fs::read_to_string(&config_path)?;
let config: Config = toml::from_str(&contents)?;
Ok(config.fuzz)
}

/// Get the package name (defaults to [`FUZZ_PACKAGE`]).
fn package_name(&self) -> &str { self.package.as_deref().unwrap_or(FUZZ_PACKAGE) }
}

/// Discover all fuzz targets using cargo metadata.
///
/// Targets are discovered by querying cargo metadata for all binary targets
/// in the specified fuzz package.
fn discover_fuzz_targets(
sh: &Shell,
package_name: &str,
) -> Result<Vec<String>, Box<dyn std::error::Error>> {
let metadata = quiet_cmd!(sh, "cargo metadata --format-version 1 --no-deps").read()?;
let json: serde_json::Value = serde_json::from_str(&metadata)?;

let mut targets = Vec::new();

// Find binary targets in the specified fuzz package.
if let Some(packages) = json["packages"].as_array() {
for package in packages {
if package["name"].as_str() == Some(package_name) {
if let Some(package_targets) = package["targets"].as_array() {
for target in package_targets {
// Filter for binary targets only.
let Some(kinds) = target["kind"].as_array() else {
continue;
};
let Some(name) = target["name"].as_str() else {
continue;
};

if kinds.iter().any(|k| k.as_str() == Some("bin")) {
targets.push(name.to_string());
}
}
}
break; // Found the package, no need to continue.
}
}
}

// Sort for consistent output.
targets.sort();

Ok(targets)
}

/// List discovered fuzz targets.
pub fn list(sh: &Shell) -> Result<(), Box<dyn std::error::Error>> {
let workspace_root = sh.current_dir();
let config = FuzzConfig::load(&workspace_root)?;
let package_name = config.package_name();

let targets = discover_fuzz_targets(sh, package_name)?;

if targets.is_empty() {
quiet_println("No fuzz targets found");
} else {
for target in targets {
println!("{}", target);
}
}

Ok(())
}

/// Run fuzz tests for the workspace.
pub fn run(_sh: &Shell) { quiet_println("Fuzz execution not yet implemented"); }
25 changes: 20 additions & 5 deletions cargo-rbmt/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ mod api;
mod bench;
mod docs;
mod environment;
mod fuzz;
mod integration;
mod lint;
mod lock;
Expand Down Expand Up @@ -60,6 +61,12 @@ enum Commands {
},
/// Run bitcoin core integration tests.
Integration,
/// Run fuzz tests.
Fuzz {
/// List available fuzz targets instead of running them.
#[arg(long)]
list: bool,
},
/// Update Cargo-minimal.lock and Cargo-recent.lock files.
Lock,
/// Run pre-release readiness checks.
Expand All @@ -80,8 +87,8 @@ fn main() {
configure_log_level(&sh);
change_to_repo_root(&sh);

// Restore the specified lock file before running any command (except Lock and Integration).
if !matches!(cli.command, Commands::Lock | Commands::Integration) {
// Restore the specified lock file before running any command (except Lock, Integration, and Fuzz).
if !matches!(cli.command, Commands::Lock | Commands::Integration | Commands::Fuzz { .. }) {
if let Err(e) = cli.lock_file.restore(&sh) {
eprintln!("Error restoring lock file: {}", e);
process::exit(1);
Expand Down Expand Up @@ -115,17 +122,25 @@ fn main() {
eprintln!("Error running bench tests: {}", e);
process::exit(1);
},
Commands::Test { toolchain, no_debug_assertions } => {
Commands::Test { toolchain, no_debug_assertions } =>
if let Err(e) = test::run(&sh, toolchain, no_debug_assertions, &cli.packages) {
eprintln!("Error running tests: {}", e);
process::exit(1);
}
}
},
Commands::Integration =>
if let Err(e) = integration::run(&sh, &cli.packages) {
eprintln!("Error running integration tests: {}", e);
process::exit(1);
},
Commands::Fuzz { list } =>
if list {
if let Err(e) = fuzz::list(&sh) {
eprintln!("Error listing fuzz targets: {}", e);
process::exit(1);
}
} else {
fuzz::run(&sh);
},
Commands::Lock =>
if let Err(e) = lock::run(&sh) {
eprintln!("Error updating lock files: {}", e);
Expand Down
3 changes: 2 additions & 1 deletion cargo-rbmt/src/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -275,7 +275,8 @@ fn loop_features<S: AsRef<str>>(
.chain(additional.iter().map(std::convert::AsRef::as_ref))
.collect::<Vec<_>>()
.join(" "),
None => additional.iter().map(std::convert::AsRef::as_ref).collect::<Vec<_>>().join(" "),
None =>
additional.iter().map(std::convert::AsRef::as_ref).collect::<Vec<_>>().join(" "),
}
}

Expand Down