contrib: Make deterministic-coverage error messages more readable #32074

pull maflcko wants to merge 2 commits into bitcoin:master from maflcko:2503-det-cov-err changing 3 files +167 βˆ’115
  1. maflcko commented at 8:07 am on March 15, 2025: member

    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 #31901 (review).

    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.

  2. DrahtBot commented at 8:07 am on March 15, 2025: contributor

    The following sections might be updated with supplementary metadata relevant to reviewers and maintainers.

    Code Coverage & Benchmarks

    For details see: https://corecheck.dev/bitcoin/bitcoin/pulls/32074.

    Reviews

    See the guideline for information on the review process.

    Type Reviewers
    ACK hodlinator, janb84

    If your review is incorrectly listed, please react with πŸ‘Ž to this comment and the bot will ignore it on the next update.

  3. DrahtBot added the label Scripts and tools on Mar 15, 2025
  4. maflcko force-pushed on Mar 15, 2025
  5. maflcko requested review from hodlinator on Mar 15, 2025
  6. in contrib/devtools/deterministic-fuzz-coverage/src/main.rs:35 in faf298b6da outdated
    53         match output {
    54             Ok(output) if output.status.success() => {}
    55-            _ => {
    56-                exit_help(&format!("The tool {} is not installed", tool));
    57-            }
    58+            _ => Err(exit_help(&format!("The tool {} is not installed", tool)))?,
    


    hodlinator commented at 9:34 pm on March 17, 2025:

    info: Would have expected:

    0            _ => return Err(exit_help(&format!("The tool {} is not installed", tool))),
    

    ? feels like returning is an open question, but after Err() it’s always true… maybe this is still idiomatic though. ? in other places implies a possible return, so probably just takes some getting used to.


    maflcko commented at 8:01 pm on March 19, 2025:
    I found Err(...)? nicer as an early-return statement, but no strong opinion
  7. in contrib/devtools/deterministic-fuzz-coverage/src/main.rs:60 in faf298b6da outdated
    90+        Err(exit_help("--help requested"))?;
    91     }
    92-    let corpora_dir = args
    93-        .get(2)
    94-        .unwrap_or_else(|| exit_help("Must set fuzz corpora dir"));
    95+    let corpora_dir = args.get(2).ok_or(exit_help("Must set fuzz corpora dir"))?;
    


    hodlinator commented at 11:20 pm on March 17, 2025:

    nit: Unrelated to this change, but would be nice to avoid “shadowing”.

    0    let corpora_dir = Path::new(args.get(2).ok_or(exit_help("Must set fuzz corpora dir"))?);
    

    maflcko commented at 8:02 pm on March 19, 2025:
    In Rust shadowing is a feature, but happy to change, no strong opinion.

    hodlinator commented at 9:33 pm on March 19, 2025:

    Thread #32074 (review): I like shadowing for if let Some(foo) = foo, in other cases it ups the code complexity in my book.

    But this is not an egregious case. Fine with leaving as-is unless others agree with me.

  8. maflcko commented at 8:49 am on March 18, 2025: member
    @hodlinator I’ll wait for you to give the ack/review on this, and once you are happy that your feedback is fully addressed, I’ll move it out of draft
  9. in contrib/devtools/deterministic-fuzz-coverage/src/main.rs:191 in faf298b6da outdated
    196@@ -175,29 +197,36 @@ fn deterministic_coverage(
    197     //
    198     // Also, This can catch issues where several fuzz inputs are non-deterministic, but the sum of
    199     // their overall coverage trace remains the same across runs and thus remains undetected.
    200+    println!("Check each fuzz input individually ...");
    201     for entry in entries {
    


    hodlinator commented at 8:14 pm on March 18, 2025:

    Unrelated, but while looking for faults with this PR and trying to trigger errors, I got distracted into making this run in parallel. Earlier version triggered OOM-killer and this one is still in a super-unbaked state. (Output should be buffered and sequenced, parallization logic should be refined, file naming uniqueness-approach is bad, etc).

     0diff --git a/contrib/devtools/deterministic-fuzz-coverage/src/main.rs b/contrib/devtools/deterministic-fuzz-coverage/src/main.rs
     1index 423393181c..171afbbed7 100644
     2--- a/contrib/devtools/deterministic-fuzz-coverage/src/main.rs
     3+++ b/contrib/devtools/deterministic-fuzz-coverage/src/main.rs
     4@@ -2,11 +2,13 @@
     5 // Distributed under the MIT software license, see the accompanying
     6 // file COPYING or https://opensource.org/license/mit/.
     7 
     8+use std::collections::VecDeque;
     9 use std::env;
    10 use std::fs::{read_dir, File};
    11 use std::path::{Path, PathBuf};
    12 use std::process::{Command, ExitCode};
    13 use std::str;
    14+use std::thread;
    15 
    16 /// A type for a complete and readable error message.
    17 type AppError = String;
    18@@ -106,8 +108,6 @@ fn deterministic_coverage(
    19     fuzz_target: &str,
    20 ) -> AppResult {
    21     let using_libfuzzer = using_libfuzzer(fuzz_exe)?;
    22-    let profraw_file = build_dir.join("fuzz_det_cov.profraw");
    23-    let profdata_file = build_dir.join("fuzz_det_cov.profdata");
    24     let corpus_dir = corpora_dir.join(fuzz_target);
    25     let mut entries = read_dir(&corpus_dir)
    26         .map_err(|err| {
    27@@ -121,7 +121,13 @@ fn deterministic_coverage(
    28         .collect::<Vec<_>>();
    29     entries.sort_by_key(|entry| entry.file_name());
    30     let run_single = |run_id: u8, entry: &Path| -> Result<PathBuf, AppError> {
    31-        let cov_txt_path = build_dir.join(format!("fuzz_det_cov.show.{run_id}.txt"));
    32+        let file_name = match entry.file_name() {
    33+            Some(f) => Ok(f.to_string_lossy()),
    34+            None => Err("noooo".to_string()),
    35+        }?;
    36+        let cov_txt_path = build_dir.join(format!("fuzz_det_cov.show.{fuzz_target}.{file_name}.{run_id}.txt"));
    37+        let profraw_file = build_dir.join(format!("fuzz_det_cov.{fuzz_target}.{file_name}.profraw"));
    38+        let profdata_file = build_dir.join(format!("fuzz_det_cov.{fuzz_target}.{file_name}.profdata"));
    39         if !{
    40             {
    41                 let mut cmd = Command::new(fuzz_exe);
    42@@ -197,20 +203,44 @@ The coverage was not deterministic between runs.
    43     //
    44     // Also, This can catch issues where several fuzz inputs are non-deterministic, but the sum of
    45     // their overall coverage trace remains the same across runs and thus remains undetected.
    46-    println!("Check each fuzz input individually ...");
    47-    for entry in entries {
    48-        let entry = entry.path();
    49-        if !entry.is_file() {
    50-            Err(format!("{} should be a file", entry.display()))?;
    51+    println!("Check each fuzz input individually, {} inputs...", entries.len());
    52+    thread::scope(|s| -> AppResult {
    53+        use std::thread::ScopedJoinHandle;
    54+        let mut handles = VecDeque::<ScopedJoinHandle<_>>::new();
    55+        for entry in entries {
    56+            let entry = entry.path();
    57+            if !entry.is_file() {
    58+                return Err(format!("{} should be a file", entry.display()));
    59+            }
    60+            let entry = entry.to_path_buf();
    61+            handles.push_back(s.spawn(move || -> AppResult {
    62+                let cov_txt_base = run_single(0, &entry)?;
    63+                let cov_txt_repeat = run_single(1, &entry)?;
    64+                check_diff(
    65+                    &cov_txt_base,
    66+                    &cov_txt_repeat,
    67+                    &format!("The fuzz target input was {}.", entry.display()),
    68+                )
    69+            }));
    70+            if handles.len() >= std::thread::available_parallelism().map_err(|e| format!("Failed checking available_parallelism: {e}"))?.into() {
    71+                match handles.remove(0) {
    72+                    Some(front) => match front.join() {
    73+                        Err(e) => Err(format!("err: {:?}", e)),
    74+                        Ok(result) => result,
    75+                    },
    76+                    None => Ok({})
    77+                }?
    78+            }
    79         }
    80-        let cov_txt_base = run_single(0, &entry)?;
    81-        let cov_txt_repeat = run_single(1, &entry)?;
    82-        check_diff(
    83-            &cov_txt_base,
    84-            &cov_txt_repeat,
    85-            &format!("The fuzz target input was {}.", entry.display()),
    86-        )?;
    87-    }
    88+        for h in handles {
    89+            match h.join() {
    90+                Err(e) => Err(format!("err: {:?}", e)),
    91+                Ok(result) => result,
    92+            }?
    93+        }
    94+
    95+        Ok(())
    96+    })?;
    97     // Finally, check that running over all fuzz inputs in one process is deterministic as well.
    98     // This can catch issues where mutable global state is leaked from one fuzz input execution to
    99     // the next.
    

    maflcko commented at 8:04 pm on March 19, 2025:
    Nice. I think this is sufficient as an approach for a dev-only tool. Happy to push it here, or review your follow-up. Just let me know.

    hodlinator commented at 9:36 pm on March 19, 2025:
    Thread #32074 (review): If you want to implement some version of it I’m happy to review, or we can defer it, not sure when I’ll be able to get it into good shape.

    maflcko commented at 2:16 pm on March 28, 2025:

    If you want to implement some version of it I’m happy to review, or we can defer it, not sure when I’ll be able to get it into good shape.

    Done in #32158. I ended up only taking thread::scope and VecDeque and restructured the rest. Lmk if this sounds good, or if you want to be listed as co-author.

  10. in contrib/devtools/deterministic-fuzz-coverage/src/main.rs:23 in faf298b6da outdated
    20+        Err(err) => {
    21+            eprintln!("{}", err);
    22+            ExitCode::FAILURE
    23+        }
    24+    }
    25+}
    


    hodlinator commented at 8:19 pm on March 18, 2025:

    This is slightly boilerplate-y but it’s a price I’m willing to pay for the rest of the diff.

    Might be less annoying to have at the bottom of the file so it’s out of the way?


    maflcko commented at 7:59 pm on March 19, 2025:
    thx, done
  11. in contrib/devtools/deterministic-fuzz-coverage/src/main.rs:12 in faf298b6da outdated
     9+use std::path::{Path, PathBuf};
    10+use std::process::{Command, ExitCode};
    11 use std::str;
    12 
    13+/// A type for a complete and readable error message.
    14+type AppError = String;
    


    hodlinator commented at 8:28 pm on March 18, 2025:

    Tempting to suggest

    0struct AppError(String);
    

    But it does add a fair amount of noise where it’s instantiated, so I understand we might want to go a bit less type-safe for these tools.


    maflcko commented at 7:57 pm on March 19, 2025:
    Yeah, the string ends up in Err eventually, so it should be type-safe enough for nothing to go wrong. At least I can’t see any risk here.
  12. hodlinator commented at 8:41 pm on March 18, 2025: contributor

    @hodlinator I’ll wait for you to give the ack/review on this, and once you are happy that your feedback is fully addressed, I’ll move it out of draft

    Can’t promise I won’t find any more issues, but this turned out surprisingly well IMO. Thanks for taking the time to work on it!

  13. maflcko force-pushed on Mar 19, 2025
  14. maflcko commented at 8:06 pm on March 19, 2025: member
    rebased with a move-only change, and replied to all feedback
  15. maflcko marked this as ready for review on Mar 19, 2025
  16. in contrib/devtools/deterministic-fuzz-coverage/src/main.rs:142 in fa7b672e20 outdated
    144-        let cov_file = File::create(&cov_txt_path).expect("Failed to create coverage txt file");
    145-        assert!(Command::new(LLVM_COV)
    146+            .map_err(|e| format!("{LLVM_PROFDATA} merge failed with {e}"))?
    147+            .success()
    148+        {
    149+            Err(format!("{LLVM_PROFDATA} merge failed"))?;
    


    hodlinator commented at 10:13 pm on March 19, 2025:

    nit: Tried running without coverage support compiled in:

     0β‚Ώ cargo run --manifest-path ./contrib/devtools/deterministic-fuzz-coverage/Cargo.toml -- $PWD/build_fuzz ../qa-assets/fuzz_corpora/ process_message
     1    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.03s
     2     Running `contrib/devtools/deterministic-fuzz-coverage/target/debug/deterministic-fuzz-coverage /home/hodlinator/bitcoin/build_fuzz ../qa-assets/fuzz_corpora/ process_message`
     3Check if using libFuzzer ...
     4Check each fuzz input individually ...
     5INFO: Running with entropic power schedule (0xFF, 100).
     6INFO: Seed: 1580959840
     7INFO: Loaded 1 modules   (543273 inline 8-bit counters): 543273 [0x559d31989d40, 0x559d31a0e769), 
     8INFO: Loaded 1 PC tables (543273 PCs): 543273 [0x559d31a0e770,0x559d32258a00), 
     9/home/hodlinator/bitcoin/build_fuzz/bin/fuzz: Running 1 inputs 1 time(s) each.
    10Running: ../qa-assets/fuzz_corpora/process_message/00023ad97f6f03d0899bfa81d8ff7d095d8b4ee9
    11Executed ../qa-assets/fuzz_corpora/process_message/00023ad97f6f03d0899bfa81d8ff7d095d8b4ee9 in 2 ms
    12***
    13*** NOTE: fuzzing was not performed, you have only
    14***       executed the target code on a fixed set of inputs.
    15***
    16error: /home/hodlinator/bitcoin/build_fuzz/fuzz_det_cov.profraw: No such file or directory
    17llvm-profdata merge failed
    

    Could change to:

    0            Err(format!("{LLVM_PROFDATA} merge failed. This can be a sign of compiling without code coverage support."))?;
    

    Same for unit tests.


    maflcko commented at 2:59 pm on March 20, 2025:
    thx, done!
  17. maflcko force-pushed on Mar 20, 2025
  18. hodlinator approved
  19. hodlinator commented at 1:18 pm on March 20, 2025: contributor

    ACK fab6fe3c1676202bd9469c3e9f9454911c92602b

    Removes the use of assert!()s, expect(), unwrap_or_else(), error backtraces and exit(1) in favor of more Rust-idiomatic user/environment error handling through Result<>.

    Heavy use of map_err() and ? to enable early returns on error results in quite a small diff and pleasant code.

    Testing

    Tested running fuzz-util on non-existent build-dir with expected error. Tried running fuzz executable without coverage support (see inline comment). Ran with coverage and found deterministic uint160_deserialize and non-deterministic process_message.

    Tested unit test miniscript_tests for determinism successfully.

  20. contrib: Make deterministic-coverage error messages more readable fa751639fb
  21. contrib: Print deterministic-coverage runs fa7a40d952
  22. maflcko force-pushed on Mar 20, 2025
  23. hodlinator approved
  24. hodlinator commented at 3:06 pm on March 20, 2025: contributor

    re-ACK fa7a40d952ad7588f45f6229aeae754b02fdfb55

    See prior ACK for high level take on the change.

    New changes:

  25. fanquake requested review from dergoegge on Mar 21, 2025
  26. fanquake requested review from marcofleon on Mar 21, 2025
  27. in contrib/devtools/README.md:1 in fa7a40d952


    janb84 commented at 1:03 pm on March 21, 2025:
    Perhaps it’s worth adding a note to clarify that the fuzz tool should be allowed to run until it completes. Given the nature of fuzz tests, someone might assume that, unlike fuzzing runs with “runs=1,” this process will continue indefinitely, and thus a few “succeeded against 1 file in xs” messages might seem sufficient.

    maflcko commented at 1:20 pm on March 21, 2025:
    thx, maybe in a follow-up. With two acks, this seems close to merge

    janb84 commented at 1:23 pm on March 21, 2025:

    thx, maybe in a follow-up. With two acks, this seems close to merge

    Agreed ! Lets not invalidate the ACKS over this.

  28. janb84 commented at 1:03 pm on March 21, 2025: contributor

    ACK fa7a40d

    • Code review βœ…
    • Tested several error messages on fuzz tool (no fuzz build, no coverage support, on-existent build-dir) βœ…
    • Tested several error message on unit tool (on-existent build-dir, no coverage support) βœ…
    • Tested for correct working for deterministic Unit test finding: util_string_tests βœ…
    • Tested for correct working for deterministic Fuzz test finding: uint160_deserialize βœ…
    • Tested for correct working for NON-deterministic Unit test finding: validation_block_tests βœ… (All tests are done one macOS based system)
  29. fanquake merged this on Mar 22, 2025
  30. fanquake closed this on Mar 22, 2025

  31. maflcko deleted the branch on Mar 22, 2025
  32. spboy777 commented at 11:34 am on March 22, 2025: none
    A

github-metadata-mirror

This is a metadata mirror of the GitHub repository bitcoin/bitcoin. This site is not affiliated with GitHub. Content is generated from a GitHub metadata backup.
generated: 2025-03-28 15:12 UTC

This site is hosted by @0xB10C
More mirrored repositories can be found on mirror.b10c.me