main.rs raw
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, PathBuf};
8 use std::process::{Command, ExitCode};
9 use std::str;
10
11 /// A type for a complete and readable error message.
12 type AppError = String;
13 type AppResult = Result<(), AppError>;
14
15 const LLVM_PROFDATA: &str = "llvm-profdata";
16 const LLVM_COV: &str = "llvm-cov";
17 const GIT: &str = "git";
18
19 fn exit_help(err: &str) -> AppError {
20 format!(
21 r#"
22 Error: {err}
23
24 Usage: program ./build_dir boost_unittest_filter
25
26 Refer to the devtools/README.md for more details."#
27 )
28 }
29
30 fn sanity_check(test_exe: &Path) -> AppResult {
31 for tool in [LLVM_PROFDATA, LLVM_COV, GIT] {
32 let output = Command::new(tool).arg("--help").output();
33 match output {
34 Ok(output) if output.status.success() => {}
35 _ => Err(exit_help(&format!("The tool {tool} is not installed")))?,
36 }
37 }
38 if !test_exe.exists() {
39 Err(exit_help(&format!(
40 "Test executable ({}) not found",
41 test_exe.display()
42 )))?;
43 }
44 Ok(())
45 }
46
47 fn app() -> AppResult {
48 // Parse args
49 let args = env::args().collect::<Vec<_>>();
50 let build_dir = args.get(1).ok_or(exit_help("Must set build dir"))?;
51 if build_dir == "--help" {
52 Err(exit_help("--help requested"))?;
53 }
54 let filter = args
55 .get(2)
56 // Require filter for now. In the future it could be optional and the tool could provide a
57 // default filter.
58 .ok_or(exit_help("Must set boost test filter"))?;
59 if args.get(3).is_some() {
60 Err(exit_help("Too many args"))?;
61 }
62
63 let build_dir = Path::new(build_dir);
64 let test_exe = build_dir.join("bin/test_bitcoin");
65
66 sanity_check(&test_exe)?;
67
68 deterministic_coverage(build_dir, &test_exe, filter)
69 }
70
71 fn deterministic_coverage(build_dir: &Path, test_exe: &Path, filter: &str) -> AppResult {
72 let profraw_file = build_dir.join("test_det_cov.profraw");
73 let profdata_file = build_dir.join("test_det_cov.profdata");
74 let run_single = |run_id: char| -> Result<PathBuf, AppError> {
75 println!("Run with id {run_id}");
76 let cov_txt_path = build_dir.join(format!("test_det_cov.show.{run_id}.txt"));
77 if !Command::new(test_exe)
78 .env("LLVM_PROFILE_FILE", &profraw_file)
79 .env("BOOST_TEST_RUN_FILTERS", filter)
80 .env("RANDOM_CTX_SEED", "21")
81 .status()
82 .map_err(|e| format!("test failed with {e}"))?
83 .success()
84 {
85 Err("test failed".to_string())?;
86 }
87 if !Command::new(LLVM_PROFDATA)
88 .arg("merge")
89 .arg("--sparse")
90 .arg(&profraw_file)
91 .arg("-o")
92 .arg(&profdata_file)
93 .status()
94 .map_err(|e| format!("{LLVM_PROFDATA} merge failed with {e}"))?
95 .success()
96 {
97 Err(format!("{LLVM_PROFDATA} merge failed. This can be a sign of compiling without code coverage support."))?;
98 }
99 let cov_file = File::create(&cov_txt_path)
100 .map_err(|e| format!("Failed to create coverage txt file ({e})"))?;
101 if !Command::new(LLVM_COV)
102 .args([
103 "show",
104 "--show-line-counts-or-regions",
105 "--show-branches=count",
106 "--show-expansions",
107 "--show-instantiation-summary",
108 "-Xdemangler=llvm-cxxfilt",
109 &format!("--instr-profile={}", profdata_file.display()),
110 ])
111 .arg(test_exe)
112 .stdout(cov_file)
113 .status()
114 .map_err(|e| format!("{LLVM_COV} show failed with {e}"))?
115 .success()
116 {
117 Err(format!("{LLVM_COV} show failed"))?;
118 }
119 Ok(cov_txt_path)
120 };
121 let check_diff = |a: &Path, b: &Path| -> AppResult {
122 let same = Command::new(GIT)
123 .args(["--no-pager", "diff", "--no-index"])
124 .arg(a)
125 .arg(b)
126 .status()
127 .map_err(|e| format!("{GIT} diff failed with {e}"))?
128 .success();
129 if !same {
130 Err("The coverage was not deterministic between runs.".to_string())?;
131 }
132 Ok(())
133 };
134 let r0 = run_single('a')?;
135 let r1 = run_single('b')?;
136 check_diff(&r0, &r1)?;
137 println!("✨ The coverage was deterministic across two runs. ✨");
138 Ok(())
139 }
140
141 fn main() -> ExitCode {
142 match app() {
143 Ok(()) => ExitCode::SUCCESS,
144 Err(err) => {
145 eprintln!("⚠️\n{err}");
146 ExitCode::FAILURE
147 }
148 }
149 }
150