Skip to content

Commit 5eb2833

Browse files
committed
add fuzz-coverage subcommand
1 parent bcbaf32 commit 5eb2833

File tree

2 files changed

+77
-7
lines changed

2 files changed

+77
-7
lines changed

src/cli.rs

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -169,14 +169,17 @@ pub(crate) enum Subcommand {
169169
/// Output the environment set by cargo-llvm-cov to build Rust projects.
170170
ShowEnv,
171171

172-
/// Run tests with cargo nextest
172+
/// Run tests with cargo nextest.
173173
Nextest {
174174
archive_file: bool,
175175
},
176176

177-
/// Build and archive tests with cargo nextest
177+
/// Build and archive tests with cargo nextest.
178178
NextestArchive,
179179

180+
/// Generate a coverage report of a fuzz target with cargo fuzz coverage.
181+
FuzzCoverage,
182+
180183
// internal (unstable)
181184
Demangle,
182185
}
@@ -190,10 +193,21 @@ static CARGO_LLVM_COV_SHOW_ENV_USAGE: &str = include_str!("../docs/cargo-llvm-co
190193
static CARGO_LLVM_COV_NEXTEST_USAGE: &str = include_str!("../docs/cargo-llvm-cov-nextest.txt");
191194
static CARGO_LLVM_COV_NEXTEST_ARCHIVE_USAGE: &str =
192195
include_str!("../docs/cargo-llvm-cov-nextest-archive.txt");
196+
static CARGO_LLVM_COV_NEXTEST_FUZZ_COVERAGE_USAGE: &str =
197+
include_str!("../docs/cargo-llvm-cov-fuzz-coverage.txt");
193198

194199
impl Subcommand {
200+
/// Whether command line flags can be forwarded to this subcommand
195201
fn can_passthrough(subcommand: Self) -> bool {
196-
matches!(subcommand, Self::Test | Self::Run | Self::Nextest { .. } | Self::NextestArchive)
202+
// These are the subcommands that themselves call other cargo subcommands.
203+
matches!(
204+
subcommand,
205+
Self::Test
206+
| Self::Run
207+
| Self::Nextest { .. }
208+
| Self::NextestArchive
209+
| Self::FuzzCoverage
210+
)
197211
}
198212

199213
fn help_text(subcommand: Self) -> &'static str {
@@ -206,6 +220,7 @@ impl Subcommand {
206220
Self::ShowEnv => CARGO_LLVM_COV_SHOW_ENV_USAGE,
207221
Self::Nextest { .. } => CARGO_LLVM_COV_NEXTEST_USAGE,
208222
Self::NextestArchive => CARGO_LLVM_COV_NEXTEST_ARCHIVE_USAGE,
223+
Self::FuzzCoverage => CARGO_LLVM_COV_NEXTEST_FUZZ_COVERAGE_USAGE,
209224
Self::Demangle => "", // internal API
210225
}
211226
}
@@ -220,6 +235,7 @@ impl Subcommand {
220235
Self::ShowEnv => "show-env",
221236
Self::Nextest { .. } => "nextest",
222237
Self::NextestArchive => "nextest-archive",
238+
Self::FuzzCoverage => "fuzz-coverage",
223239
Self::Demangle => "demangle",
224240
}
225241
}
@@ -247,6 +263,7 @@ impl FromStr for Subcommand {
247263
"show-env" => Ok(Self::ShowEnv),
248264
"nextest" => Ok(Self::Nextest { archive_file: false }),
249265
"nextest-archive" => Ok(Self::NextestArchive),
266+
"fuzz-coverage" => Ok(Self::FuzzCoverage),
250267
"demangle" => Ok(Self::Demangle),
251268
_ => bail!("unrecognized subcommand {s}"),
252269
}

src/main.rs

Lines changed: 57 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,15 @@ fn try_main() -> Result<()> {
113113
create_dirs(cx)?;
114114
archive_nextest(cx)?;
115115
}
116+
Subcommand::FuzzCoverage => {
117+
let cx = &Context::new(args)?;
118+
clean::clean_partial(cx)?;
119+
create_dirs(cx)?;
120+
run_fuzz_coverage(cx)?;
121+
if !cx.args.cov.no_report {
122+
generate_report(cx)?;
123+
}
124+
}
116125
Subcommand::None | Subcommand::Test => {
117126
let cx = &Context::new(args)?;
118127
clean::clean_partial(cx)?;
@@ -439,6 +448,41 @@ fn archive_nextest(cx: &Context) -> Result<()> {
439448
Ok(())
440449
}
441450

451+
fn run_fuzz_coverage(cx: &Context) -> Result<()> {
452+
let mut cargo = cx.cargo();
453+
454+
set_env(cx, &mut cargo, IsNextest(false))?;
455+
456+
cargo.arg("fuzz").arg("coverage");
457+
458+
if let Some(target) = &cx.args.target {
459+
cargo.arg("--target");
460+
cargo.arg(target);
461+
}
462+
if cx.args.release {
463+
cargo.arg("--release");
464+
}
465+
466+
cargo.arg("--target-dir");
467+
cargo.arg(cx.ws.target_dir.as_str());
468+
469+
for cargo_arg in &cx.args.cargo_args {
470+
cargo.arg(cargo_arg);
471+
}
472+
473+
if !cx.args.rest.is_empty() {
474+
cargo.args(&cx.args.rest);
475+
}
476+
477+
if term::verbose() {
478+
status!("Running", "{cargo}");
479+
}
480+
stdout_to_stderr(cx, &mut cargo);
481+
cargo.run()?;
482+
483+
Ok(())
484+
}
485+
442486
fn run_nextest(cx: &Context) -> Result<()> {
443487
let mut cargo = cx.cargo();
444488

@@ -533,7 +577,15 @@ fn stdout_to_stderr(cx: &Context, cargo: &mut ProcessBuilder) {
533577
}
534578

535579
fn generate_report(cx: &Context) -> Result<()> {
536-
merge_profraw(cx).context("failed to merge profile data")?;
580+
let profraw_path = match cx.args.subcommand {
581+
Subcommand::FuzzCoverage => {
582+
let fuzz_target = cx.args.rest.iter().find(|s| !s.starts_with("--")).unwrap();
583+
let path = std::env::current_dir()?.join(format!("coverage/{fuzz_target}/raw"));
584+
Utf8PathBuf::from(path.to_string_lossy().as_ref())
585+
}
586+
_ => cx.ws.target_dir.clone(),
587+
};
588+
merge_profraw(cx, &profraw_path).context("failed to merge profile data")?;
537589

538590
let object_files = object_files(cx).context("failed to collect object files")?;
539591
let ignore_filename_regex = ignore_filename_regex(cx, &object_files)?;
@@ -650,21 +702,22 @@ fn open_report(cx: &Context, path: &Utf8Path) -> Result<()> {
650702
Ok(())
651703
}
652704

653-
fn merge_profraw(cx: &Context) -> Result<()> {
705+
fn merge_profraw(cx: &Context, profraw_path: &Utf8Path) -> Result<()> {
654706
// Convert raw profile data.
655707
let profraw_files = glob::glob(
656-
Utf8Path::new(&glob::Pattern::escape(cx.ws.target_dir.as_str())).join("*.profraw").as_str(),
708+
Utf8Path::new(&glob::Pattern::escape(profraw_path.as_str())).join("*.profraw").as_str(),
657709
)?
658710
.filter_map(Result::ok)
659711
.collect::<Vec<_>>();
712+
660713
if profraw_files.is_empty() {
661714
if cx.ws.profdata_file.exists() {
662715
return Ok(());
663716
}
664717
warn!(
665718
"not found *.profraw files in {}; this may occur if target directory is accidentally \
666719
cleared, or running report subcommand without running any tests or binaries",
667-
cx.ws.target_dir
720+
profraw_path
668721
);
669722
}
670723
let mut input_files = String::new();

0 commit comments

Comments
 (0)