Skip to content

Commit fa579d6

Browse files
author
MarcoFalke
committed
contrib: Add deterministic-unittest-coverage
This replaces the bash script with a tool based on clang/llvm tools.
1 parent fa3940b commit fa579d6

File tree

5 files changed

+158
-151
lines changed

5 files changed

+158
-151
lines changed

Diff for: contrib/devtools/README.md

+21
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,27 @@ before running the tool:
2525
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
2626
```
2727

28+
deterministic-unittest-coverage
29+
===========================
30+
31+
A tool to check for non-determinism in unit-test coverage. To get the help, run:
32+
33+
```
34+
RUST_BACKTRACE=1 cargo run --manifest-path ./contrib/devtools/deterministic-unittest-coverage/Cargo.toml -- --help
35+
```
36+
37+
To execute the tool, compilation has to be done with the build options:
38+
39+
```
40+
-DCMAKE_C_COMPILER='clang' -DCMAKE_CXX_COMPILER='clang++' -DCMAKE_CXX_FLAGS='-fprofile-instr-generate -fcoverage-mapping'
41+
```
42+
43+
Both llvm-profdata and llvm-cov must be installed.
44+
45+
```
46+
RUST_BACKTRACE=1 cargo run --manifest-path ./contrib/devtools/deterministic-unittest-coverage/Cargo.toml -- $PWD/build_dir <boost unittest filter>
47+
```
48+
2849
clang-format-diff.py
2950
===================
3051

Diff for: contrib/devtools/deterministic-unittest-coverage/Cargo.lock

+7
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
[package]
2+
name = "deterministic-unittest-coverage"
3+
version = "0.1.0"
4+
edition = "2021"
5+
6+
[dependencies]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
// Copyright (c) The Bitcoin Core developers
2+
// Distributed under the MIT software license, see the accompanying
3+
// file COPYING or https://opensource.org/license/mit/.
4+
5+
use std::env;
6+
use std::fs::File;
7+
use std::path::Path;
8+
use std::process::{exit, Command};
9+
use std::str;
10+
11+
const LLVM_PROFDATA: &str = "llvm-profdata";
12+
const LLVM_COV: &str = "llvm-cov";
13+
const GIT: &str = "git";
14+
15+
fn exit_help(err: &str) -> ! {
16+
eprintln!("Error: {}", err);
17+
eprintln!();
18+
eprintln!("Usage: program ./build_dir boost_unittest_filter");
19+
eprintln!();
20+
eprintln!("Refer to the devtools/README.md for more details.");
21+
exit(1)
22+
}
23+
24+
fn sanity_check(test_exe: &Path) {
25+
for tool in [LLVM_PROFDATA, LLVM_COV, GIT] {
26+
let output = Command::new(tool).arg("--help").output();
27+
match output {
28+
Ok(output) if output.status.success() => {}
29+
_ => {
30+
exit_help(&format!("The tool {} is not installed", tool));
31+
}
32+
}
33+
}
34+
if !test_exe.exists() {
35+
exit_help(&format!(
36+
"Test executable ({}) not found",
37+
test_exe.display()
38+
));
39+
}
40+
}
41+
42+
fn main() {
43+
// Parse args
44+
let args = env::args().collect::<Vec<_>>();
45+
let build_dir = args
46+
.get(1)
47+
.unwrap_or_else(|| exit_help("Must set build dir"));
48+
if build_dir == "--help" {
49+
exit_help("--help requested")
50+
}
51+
let filter = args
52+
.get(2)
53+
// Require filter for now. In the future it could be optional and the tool could provide a
54+
// default filter.
55+
.unwrap_or_else(|| exit_help("Must set boost test filter"));
56+
if args.get(3).is_some() {
57+
exit_help("Too many args")
58+
}
59+
60+
let build_dir = Path::new(build_dir);
61+
let test_exe = build_dir.join("src/test/test_bitcoin");
62+
63+
sanity_check(&test_exe);
64+
65+
deterministic_coverage(build_dir, &test_exe, filter);
66+
}
67+
68+
fn deterministic_coverage(build_dir: &Path, test_exe: &Path, filter: &str) {
69+
let profraw_file = build_dir.join("test_det_cov.profraw");
70+
let profdata_file = build_dir.join("test_det_cov.profdata");
71+
let run_single = |run_id: u8| {
72+
let cov_txt_path = build_dir.join(format!("test_det_cov.show.{run_id}.txt"));
73+
assert!(Command::new(test_exe)
74+
.env("LLVM_PROFILE_FILE", &profraw_file)
75+
.env("BOOST_TEST_RUN_FILTERS", filter)
76+
.env("RANDOM_CTX_SEED", "21")
77+
.status()
78+
.expect("test failed")
79+
.success());
80+
assert!(Command::new(LLVM_PROFDATA)
81+
.arg("merge")
82+
.arg("--sparse")
83+
.arg(&profraw_file)
84+
.arg("-o")
85+
.arg(&profdata_file)
86+
.status()
87+
.expect("merge failed")
88+
.success());
89+
let cov_file = File::create(&cov_txt_path).expect("Failed to create coverage txt file");
90+
assert!(Command::new(LLVM_COV)
91+
.args([
92+
"show",
93+
"--show-line-counts-or-regions",
94+
"--show-branches=count",
95+
"--show-expansions",
96+
&format!("--instr-profile={}", profdata_file.display()),
97+
])
98+
.arg(test_exe)
99+
.stdout(cov_file)
100+
.status()
101+
.expect("llvm-cov failed")
102+
.success());
103+
cov_txt_path
104+
};
105+
let check_diff = |a: &Path, b: &Path| {
106+
let same = Command::new(GIT)
107+
.args(["--no-pager", "diff", "--no-index"])
108+
.arg(a)
109+
.arg(b)
110+
.status()
111+
.expect("Failed to execute git command")
112+
.success();
113+
if !same {
114+
eprintln!();
115+
eprintln!("The coverage was not deterministic between runs.");
116+
eprintln!("Exiting.");
117+
exit(1);
118+
}
119+
};
120+
let r0 = run_single(0);
121+
let r1 = run_single(1);
122+
check_diff(&r0, &r1);
123+
println!("The coverage was deterministic across two runs.");
124+
}

Diff for: contrib/devtools/test_deterministic_coverage.sh

-151
This file was deleted.

0 commit comments

Comments
 (0)