main.rs
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 }