Skip to content

Commit af3dee0

Browse files
committedMar 22, 2025··
Merge bitcoin/bitcoin#32074: contrib: Make deterministic-coverage error messages more readable
fa7a40d contrib: Print deterministic-coverage runs (MarcoFalke) fa75163 contrib: Make deterministic-coverage error messages more readable (MarcoFalke) Pull request description: This is almost a "refactor" to tidy up the error messages. Apart from the messages, the behavior of the tools is identical. This was requested in bitcoin/bitcoin#31901 (comment). Previously, the tool would abort the program early on some errors. Now, the tool propagates an `std::result::Result::Err` up to `main` via an early return. Getting rid of the aborts also allows to drop the `RUST_BACKTRACE` env setting. ACKs for top commit: hodlinator: re-ACK fa7a40d janb84: ACK [fa7a40d](bitcoin/bitcoin@fa7a40d) Tree-SHA512: 6c97861306e2fececa14b2d12deafb78995fc2bcf75e4e22773cb0ab4231de78834db9f1f89b30c49d77499433b1c16c1d90b97eb4069c81855bd2a7944b554f
2 parents 8046759 + fa7a40d commit af3dee0

File tree

3 files changed

+167
-115
lines changed

3 files changed

+167
-115
lines changed
 

‎contrib/devtools/README.md

+4-4
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ deterministic-fuzz-coverage
88
A tool to check for non-determinism in fuzz coverage. To get the help, run:
99

1010
```
11-
RUST_BACKTRACE=1 cargo run --manifest-path ./contrib/devtools/deterministic-fuzz-coverage/Cargo.toml -- --help
11+
cargo run --manifest-path ./contrib/devtools/deterministic-fuzz-coverage/Cargo.toml -- --help
1212
```
1313

1414
To execute the tool, compilation has to be done with the build options:
@@ -22,7 +22,7 @@ repository must have been cloned. Finally, a fuzz target has to be picked
2222
before running the tool:
2323

2424
```
25-
RUST_BACKTRACE=1 cargo run --manifest-path ./contrib/devtools/deterministic-fuzz-coverage/Cargo.toml -- $PWD/build_dir $PWD/qa-assets/fuzz_corpora fuzz_target_name
25+
cargo run --manifest-path ./contrib/devtools/deterministic-fuzz-coverage/Cargo.toml -- $PWD/build_dir $PWD/qa-assets/fuzz_corpora fuzz_target_name
2626
```
2727

2828
deterministic-unittest-coverage
@@ -31,7 +31,7 @@ deterministic-unittest-coverage
3131
A tool to check for non-determinism in unit-test coverage. To get the help, run:
3232

3333
```
34-
RUST_BACKTRACE=1 cargo run --manifest-path ./contrib/devtools/deterministic-unittest-coverage/Cargo.toml -- --help
34+
cargo run --manifest-path ./contrib/devtools/deterministic-unittest-coverage/Cargo.toml -- --help
3535
```
3636

3737
To execute the tool, compilation has to be done with the build options:
@@ -43,7 +43,7 @@ To execute the tool, compilation has to be done with the build options:
4343
Both llvm-profdata and llvm-cov must be installed.
4444

4545
```
46-
RUST_BACKTRACE=1 cargo run --manifest-path ./contrib/devtools/deterministic-unittest-coverage/Cargo.toml -- $PWD/build_dir <boost unittest filter>
46+
cargo run --manifest-path ./contrib/devtools/deterministic-unittest-coverage/Cargo.toml -- $PWD/build_dir <boost unittest filter>
4747
```
4848

4949
clang-format-diff.py

‎contrib/devtools/deterministic-fuzz-coverage/src/main.rs

+94-65
Original file line numberDiff line numberDiff line change
@@ -4,113 +4,115 @@
44

55
use std::env;
66
use std::fs::{read_dir, File};
7-
use std::path::Path;
8-
use std::process::{exit, Command};
7+
use std::path::{Path, PathBuf};
8+
use std::process::{Command, ExitCode};
99
use std::str;
1010

11+
/// A type for a complete and readable error message.
12+
type AppError = String;
13+
type AppResult = Result<(), AppError>;
14+
1115
const LLVM_PROFDATA: &str = "llvm-profdata";
1216
const LLVM_COV: &str = "llvm-cov";
1317
const GIT: &str = "git";
1418

15-
fn exit_help(err: &str) -> ! {
16-
eprintln!("Error: {}", err);
17-
eprintln!();
18-
eprintln!("Usage: program ./build_dir ./qa-assets/fuzz_corpora fuzz_target_name");
19-
eprintln!();
20-
eprintln!("Refer to the devtools/README.md for more details.");
21-
exit(1)
19+
fn exit_help(err: &str) -> AppError {
20+
format!(
21+
r#"
22+
Error: {err}
23+
24+
Usage: program ./build_dir ./qa-assets/fuzz_corpora fuzz_target_name
25+
26+
Refer to the devtools/README.md for more details."#
27+
)
2228
}
2329

24-
fn sanity_check(corpora_dir: &Path, fuzz_exe: &Path) {
30+
fn sanity_check(corpora_dir: &Path, fuzz_exe: &Path) -> AppResult {
2531
for tool in [LLVM_PROFDATA, LLVM_COV, GIT] {
2632
let output = Command::new(tool).arg("--help").output();
2733
match output {
2834
Ok(output) if output.status.success() => {}
29-
_ => {
30-
exit_help(&format!("The tool {} is not installed", tool));
31-
}
35+
_ => Err(exit_help(&format!("The tool {} is not installed", tool)))?,
3236
}
3337
}
3438
if !corpora_dir.is_dir() {
35-
exit_help(&format!(
39+
Err(exit_help(&format!(
3640
"Fuzz corpora path ({}) must be a directory",
3741
corpora_dir.display()
38-
));
42+
)))?;
3943
}
4044
if !fuzz_exe.exists() {
41-
exit_help(&format!(
45+
Err(exit_help(&format!(
4246
"Fuzz executable ({}) not found",
4347
fuzz_exe.display()
44-
));
48+
)))?;
4549
}
50+
Ok(())
4651
}
4752

48-
fn main() {
53+
fn app() -> AppResult {
4954
// Parse args
5055
let args = env::args().collect::<Vec<_>>();
51-
let build_dir = args
52-
.get(1)
53-
.unwrap_or_else(|| exit_help("Must set build dir"));
56+
let build_dir = args.get(1).ok_or(exit_help("Must set build dir"))?;
5457
if build_dir == "--help" {
55-
exit_help("--help requested")
58+
Err(exit_help("--help requested"))?;
5659
}
57-
let corpora_dir = args
58-
.get(2)
59-
.unwrap_or_else(|| exit_help("Must set fuzz corpora dir"));
60+
let corpora_dir = args.get(2).ok_or(exit_help("Must set fuzz corpora dir"))?;
6061
let fuzz_target = args
6162
.get(3)
6263
// Require fuzz target for now. In the future it could be optional and the tool could
6364
// iterate over all compiled fuzz targets
64-
.unwrap_or_else(|| exit_help("Must set fuzz target"));
65+
.ok_or(exit_help("Must set fuzz target"))?;
6566
if args.get(4).is_some() {
66-
exit_help("Too many args")
67+
Err(exit_help("Too many args"))?;
6768
}
6869

6970
let build_dir = Path::new(build_dir);
7071
let corpora_dir = Path::new(corpora_dir);
7172
let fuzz_exe = build_dir.join("bin/fuzz");
7273

73-
sanity_check(corpora_dir, &fuzz_exe);
74+
sanity_check(corpora_dir, &fuzz_exe)?;
7475

75-
deterministic_coverage(build_dir, corpora_dir, &fuzz_exe, fuzz_target);
76+
deterministic_coverage(build_dir, corpora_dir, &fuzz_exe, fuzz_target)
7677
}
7778

78-
fn using_libfuzzer(fuzz_exe: &Path) -> bool {
79+
fn using_libfuzzer(fuzz_exe: &Path) -> Result<bool, AppError> {
7980
println!("Check if using libFuzzer ...");
8081
let stderr = Command::new(fuzz_exe)
8182
.arg("-help=1") // Will be interpreted as option (libfuzzer) or as input file
8283
.env("FUZZ", "addition_overflow") // Any valid target
8384
.output()
84-
.expect("fuzz failed")
85+
.map_err(|e| format!("fuzz failed with {e}"))?
8586
.stderr;
86-
let help_output = str::from_utf8(&stderr).expect("The -help=1 output must be valid text");
87-
help_output.contains("libFuzzer")
87+
let help_output = str::from_utf8(&stderr)
88+
.map_err(|e| format!("The libFuzzer -help=1 output must be valid text ({e})"))?;
89+
Ok(help_output.contains("libFuzzer"))
8890
}
8991

9092
fn deterministic_coverage(
9193
build_dir: &Path,
9294
corpora_dir: &Path,
9395
fuzz_exe: &Path,
9496
fuzz_target: &str,
95-
) {
96-
let using_libfuzzer = using_libfuzzer(fuzz_exe);
97+
) -> AppResult {
98+
let using_libfuzzer = using_libfuzzer(fuzz_exe)?;
9799
let profraw_file = build_dir.join("fuzz_det_cov.profraw");
98100
let profdata_file = build_dir.join("fuzz_det_cov.profdata");
99101
let corpus_dir = corpora_dir.join(fuzz_target);
100102
let mut entries = read_dir(&corpus_dir)
101-
.unwrap_or_else(|err| {
103+
.map_err(|err| {
102104
exit_help(&format!(
103105
"The fuzz target's input directory must exist! ({}; {})",
104106
corpus_dir.display(),
105107
err
106108
))
107-
})
109+
})?
108110
.map(|entry| entry.expect("IO error"))
109111
.collect::<Vec<_>>();
110112
entries.sort_by_key(|entry| entry.file_name());
111-
let run_single = |run_id: u8, entry: &Path| {
113+
let run_single = |run_id: u8, entry: &Path| -> Result<PathBuf, AppError> {
112114
let cov_txt_path = build_dir.join(format!("fuzz_det_cov.show.{run_id}.txt"));
113-
assert!({
115+
if !{
114116
{
115117
let mut cmd = Command::new(fuzz_exe);
116118
if using_libfuzzer {
@@ -122,20 +124,26 @@ fn deterministic_coverage(
122124
.env("FUZZ", fuzz_target)
123125
.arg(entry)
124126
.status()
125-
.expect("fuzz failed")
127+
.map_err(|e| format!("fuzz failed with {e}"))?
126128
.success()
127-
});
128-
assert!(Command::new(LLVM_PROFDATA)
129+
} {
130+
Err("fuzz failed".to_string())?;
131+
}
132+
if !Command::new(LLVM_PROFDATA)
129133
.arg("merge")
130134
.arg("--sparse")
131135
.arg(&profraw_file)
132136
.arg("-o")
133137
.arg(&profdata_file)
134138
.status()
135-
.expect("merge failed")
136-
.success());
137-
let cov_file = File::create(&cov_txt_path).expect("Failed to create coverage txt file");
138-
assert!(Command::new(LLVM_COV)
139+
.map_err(|e| format!("{LLVM_PROFDATA} merge failed with {e}"))?
140+
.success()
141+
{
142+
Err(format!("{LLVM_PROFDATA} merge failed. This can be a sign of compiling without code coverage support."))?;
143+
}
144+
let cov_file = File::create(&cov_txt_path)
145+
.map_err(|e| format!("Failed to create coverage txt file ({e})"))?;
146+
if !Command::new(LLVM_COV)
139147
.args([
140148
"show",
141149
"--show-line-counts-or-regions",
@@ -146,27 +154,31 @@ fn deterministic_coverage(
146154
.arg(fuzz_exe)
147155
.stdout(cov_file)
148156
.spawn()
149-
.expect("Failed to execute llvm-cov")
157+
.map_err(|e| format!("{LLVM_COV} show failed with {e}"))?
150158
.wait()
151-
.expect("Failed to execute llvm-cov")
152-
.success());
153-
cov_txt_path
159+
.map_err(|e| format!("{LLVM_COV} show failed with {e}"))?
160+
.success()
161+
{
162+
Err(format!("{LLVM_COV} show failed"))?;
163+
};
164+
Ok(cov_txt_path)
154165
};
155-
let check_diff = |a: &Path, b: &Path, err: &str| {
166+
let check_diff = |a: &Path, b: &Path, err: &str| -> AppResult {
156167
let same = Command::new(GIT)
157168
.args(["--no-pager", "diff", "--no-index"])
158169
.arg(a)
159170
.arg(b)
160171
.status()
161-
.expect("Failed to execute git command")
172+
.map_err(|e| format!("{GIT} diff failed with {e}"))?
162173
.success();
163174
if !same {
164-
eprintln!();
165-
eprintln!("The coverage was not deterministic between runs.");
166-
eprintln!("{}", err);
167-
eprintln!("Exiting.");
168-
exit(1);
175+
Err(format!(
176+
r#"
177+
The coverage was not deterministic between runs.
178+
{err}"#
179+
))?;
169180
}
181+
Ok(())
170182
};
171183
// First, check that each fuzz input is deterministic running by itself in a process.
172184
//
@@ -175,29 +187,46 @@ fn deterministic_coverage(
175187
//
176188
// Also, This can catch issues where several fuzz inputs are non-deterministic, but the sum of
177189
// their overall coverage trace remains the same across runs and thus remains undetected.
190+
println!("Check each fuzz input individually ...");
178191
for entry in entries {
179192
let entry = entry.path();
180-
assert!(entry.is_file());
181-
let cov_txt_base = run_single(0, &entry);
182-
let cov_txt_repeat = run_single(1, &entry);
193+
if !entry.is_file() {
194+
Err(format!("{} should be a file", entry.display()))?;
195+
}
196+
let cov_txt_base = run_single(0, &entry)?;
197+
let cov_txt_repeat = run_single(1, &entry)?;
183198
check_diff(
184199
&cov_txt_base,
185200
&cov_txt_repeat,
186201
&format!("The fuzz target input was {}.", entry.display()),
187-
);
202+
)?;
188203
}
189204
// Finally, check that running over all fuzz inputs in one process is deterministic as well.
190205
// This can catch issues where mutable global state is leaked from one fuzz input execution to
191206
// the next.
207+
println!("Check all fuzz inputs in one go ...");
192208
{
193-
assert!(corpus_dir.is_dir());
194-
let cov_txt_base = run_single(0, &corpus_dir);
195-
let cov_txt_repeat = run_single(1, &corpus_dir);
209+
if !corpus_dir.is_dir() {
210+
Err(format!("{} should be a folder", corpus_dir.display()))?;
211+
}
212+
let cov_txt_base = run_single(0, &corpus_dir)?;
213+
let cov_txt_repeat = run_single(1, &corpus_dir)?;
196214
check_diff(
197215
&cov_txt_base,
198216
&cov_txt_repeat,
199217
&format!("All fuzz inputs in {} were used.", corpus_dir.display()),
200-
);
218+
)?;
201219
}
202220
println!("Coverage test passed for {fuzz_target}.");
221+
Ok(())
222+
}
223+
224+
fn main() -> ExitCode {
225+
match app() {
226+
Ok(()) => ExitCode::SUCCESS,
227+
Err(err) => {
228+
eprintln!("{}", err);
229+
ExitCode::FAILURE
230+
}
231+
}
203232
}

0 commit comments

Comments
 (0)
Please sign in to comment.