diff --git a/fuzz/Cargo.lock b/fuzz/Cargo.lock index 47ccd31be..403691a73 100644 --- a/fuzz/Cargo.lock +++ b/fuzz/Cargo.lock @@ -292,6 +292,19 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "120133d4db2ec47efe2e26502ee984747630c67f51974fca0b6c1340cf2368d3" +[[package]] +name = "console" +version = "0.15.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea3c6ecd8059b57859df5c69830340ed3c41d30e3da0c1cbed90a96ac853041b" +dependencies = [ + "encode_unicode", + "libc", + "once_cell", + "unicode-width 0.2.0", + "windows-sys", +] + [[package]] name = "const-random" version = "0.1.18" @@ -450,6 +463,12 @@ version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" +[[package]] +name = "encode_unicode" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" + [[package]] name = "errno" version = "0.3.10" @@ -1355,6 +1374,7 @@ dependencies = [ name = "uucore-fuzz" version = "0.0.0" dependencies = [ + "console", "libc", "libfuzzer-sys", "rand 0.9.0", diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml index c3d5fd851..29bd9d558 100644 --- a/fuzz/Cargo.toml +++ b/fuzz/Cargo.toml @@ -8,6 +8,7 @@ edition = "2021" cargo-fuzz = true [dependencies] +console = "0.15.0" libfuzzer-sys = "0.4.7" libc = "0.2.153" tempfile = "3.15.0" diff --git a/fuzz/fuzz_targets/fuzz_cksum.rs b/fuzz/fuzz_targets/fuzz_cksum.rs index 47be18c9e..c14457ab2 100644 --- a/fuzz/fuzz_targets/fuzz_cksum.rs +++ b/fuzz/fuzz_targets/fuzz_cksum.rs @@ -11,7 +11,8 @@ use uu_cksum::uumain; mod fuzz_common; use crate::fuzz_common::{ compare_result, generate_and_run_uumain, generate_random_file, generate_random_string, - run_gnu_cmd, CommandResult, + pretty_print::{print_or_empty, print_test_begin}, + replace_fuzz_binary_name, run_gnu_cmd, CommandResult, }; use rand::Rng; use std::env::temp_dir; @@ -129,13 +130,15 @@ fuzz_target!(|_data: &[u8]| { if let Ok(checksum_file_path) = generate_checksum_file(algo, &file_path, &selected_digest_opts) { + print_test_begin(format!("cksum {:?}", args)); + if let Ok(content) = fs::read_to_string(&checksum_file_path) { - println!("File content: {checksum_file_path}={content}"); + println!("File content ({})", checksum_file_path); + print_or_empty(&content); } else { eprintln!("Error reading the checksum file."); } - println!("args: {:?}", args); - let rust_result = generate_and_run_uumain(&args, uumain, None); + let mut rust_result = generate_and_run_uumain(&args, uumain, None); let gnu_result = match run_gnu_cmd(CMD_PATH, &args[1..], false, None) { Ok(result) => result, @@ -151,6 +154,9 @@ fuzz_target!(|_data: &[u8]| { } }; + // Lower the number of false positives caused by binary names + replace_fuzz_binary_name("cksum", &mut rust_result); + compare_result( "cksum", &format!("{:?}", &args[1..]), diff --git a/fuzz/fuzz_targets/fuzz_common.rs b/fuzz/fuzz_targets/fuzz_common/mod.rs similarity index 88% rename from fuzz/fuzz_targets/fuzz_common.rs rename to fuzz/fuzz_targets/fuzz_common/mod.rs index 70cb6c807..9e72d4019 100644 --- a/fuzz/fuzz_targets/fuzz_common.rs +++ b/fuzz/fuzz_targets/fuzz_common/mod.rs @@ -3,11 +3,14 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. +use console::Style; use libc::STDIN_FILENO; use libc::{close, dup, dup2, pipe, STDERR_FILENO, STDOUT_FILENO}; +use pretty_print::{ + print_diff, print_end_with_status, print_or_empty, print_section, print_with_style, +}; use rand::prelude::IndexedRandom; use rand::Rng; -use similar::TextDiff; use std::env::temp_dir; use std::ffi::OsString; use std::fs::File; @@ -18,6 +21,8 @@ use std::sync::atomic::Ordering; use std::sync::{atomic::AtomicBool, Once}; use std::{io, thread}; +pub mod pretty_print; + /// Represents the result of running a command, including its standard output, /// standard error, and exit code. pub struct CommandResult { @@ -315,8 +320,8 @@ pub fn compare_result( gnu_result: &CommandResult, fail_on_stderr_diff: bool, ) { - println!("Test Type: {}", test_type); - println!("Input: {}", input); + print_section(format!("Compare result for: {} {}", test_type, input)); + if let Some(pipe) = pipe_input { println!("Pipe: {}", pipe); } @@ -326,49 +331,58 @@ pub fn compare_result( if rust_result.stdout.trim() != gnu_result.stdout.trim() { discrepancies.push("stdout differs"); - println!("Rust stdout: {}", rust_result.stdout); - println!("GNU stdout: {}", gnu_result.stdout); + println!("Rust stdout:",); + print_or_empty(rust_result.stdout.as_str()); + println!("GNU stdout:",); + print_or_empty(gnu_result.stdout.as_ref()); print_diff(&rust_result.stdout, &gnu_result.stdout); should_panic = true; } + if rust_result.stderr.trim() != gnu_result.stderr.trim() { discrepancies.push("stderr differs"); - println!("Rust stderr: {}", rust_result.stderr); - println!("GNU stderr: {}", gnu_result.stderr); + println!("Rust stderr:",); + print_or_empty(rust_result.stderr.as_str()); + println!("GNU stderr:",); + print_or_empty(gnu_result.stderr.as_str()); print_diff(&rust_result.stderr, &gnu_result.stderr); if fail_on_stderr_diff { should_panic = true; } } + if rust_result.exit_code != gnu_result.exit_code { discrepancies.push("exit code differs"); - println!("Rust exit code: {}", rust_result.exit_code); - println!("GNU exit code: {}", gnu_result.exit_code); + println!( + "Different exit code: (Rust: {}, GNU: {})", + rust_result.exit_code, gnu_result.exit_code + ); should_panic = true; } if discrepancies.is_empty() { - println!("All outputs and exit codes matched."); + print_end_with_status("Same behavior", true); } else { - println!("Discrepancy detected: {}", discrepancies.join(", ")); + print_with_style( + format!("Discrepancies detected: {}", discrepancies.join(", ")), + Style::new().red(), + ); if should_panic { - panic!("Test failed for {}: {}", test_type, input); + print_end_with_status( + format!("Test failed and will panic for: {} {}", test_type, input), + false, + ); + panic!("Test failed for: {} {}", test_type, input); } else { - println!( - "Test completed with discrepancies for {}: {}", - test_type, input + print_end_with_status( + format!( + "Test completed with discrepancies for: {} {}", + test_type, input + ), + false, ); } } -} - -/// When we have different outputs, print the diff -fn print_diff(rust_output: &str, gnu_output: &str) { - println!("Diff="); - let diff = TextDiff::from_lines(rust_output, gnu_output); - for change in diff.iter_all_changes() { - print!("{}{}", change.tag(), change); - } println!(); } @@ -414,3 +428,10 @@ pub fn generate_random_file() -> Result { Ok(file_path.to_str().unwrap().to_string()) } + +pub fn replace_fuzz_binary_name(cmd: &str, result: &mut CommandResult) { + let fuzz_bin_name = format!("fuzz/target/x86_64-unknown-linux-gnu/release/fuzz_{cmd}"); + + result.stdout = result.stdout.replace(&fuzz_bin_name, cmd); + result.stderr = result.stderr.replace(&fuzz_bin_name, cmd); +} diff --git a/fuzz/fuzz_targets/fuzz_common/pretty_print.rs b/fuzz/fuzz_targets/fuzz_common/pretty_print.rs new file mode 100644 index 000000000..c0dd71150 --- /dev/null +++ b/fuzz/fuzz_targets/fuzz_common/pretty_print.rs @@ -0,0 +1,69 @@ +// This file is part of the uutils coreutils package. +// +// For the full copyright and license information, please view the LICENSE +// file that was distributed with this source code. + +use std::fmt; + +use console::{style, Style}; +use similar::TextDiff; + +pub fn print_section(s: S) { + println!("{}", style(format!("=== {}", s)).bold()); +} + +pub fn print_subsection(s: S) { + println!("{}", style(format!("--- {}", s)).bright()); +} + +pub fn print_test_begin(msg: S) { + println!( + "{} {} {}", + style("===").bold(), // Kind of gray + style("TEST").black().on_yellow().bold(), + style(msg).bold() + ); +} + +pub fn print_end_with_status(msg: S, ok: bool) { + let ok = if ok { + style(" OK ").black().on_green().bold() + } else { + style(" KO ").black().on_red().bold() + }; + + println!( + "{} {} {}", + style("===").bold(), // Kind of gray + ok, + style(msg).bold() + ); +} + +pub fn print_or_empty(s: &str) { + let to_print = if s.is_empty() { "(empty)" } else { s }; + + println!("{}", style(to_print).dim()); +} + +pub fn print_with_style(msg: S, style: Style) { + println!("{}", style.apply_to(msg)); +} + +pub fn print_diff<'a, 'b>(got: &'a str, expected: &'b str) { + let diff = TextDiff::from_lines(got, expected); + + print_subsection("START diff"); + + for change in diff.iter_all_changes() { + let (sign, style) = match change.tag() { + similar::ChangeTag::Equal => (" ", Style::new().dim()), + similar::ChangeTag::Delete => ("-", Style::new().red()), + similar::ChangeTag::Insert => ("+", Style::new().green()), + }; + print!("{}{}", style.apply_to(sign).bold(), style.apply_to(change)); + } + + print_subsection("END diff"); + println!(); +}