From 2e0e88c3acf6b86965bb7ed60a23662d439d74f7 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Wed, 8 Nov 2023 12:46:24 +0100 Subject: [PATCH 1/6] fuzz: verify the various steps when opening/closing fd --- fuzz/fuzz_targets/fuzz_common.rs | 49 ++++++++++++++++++++++++++------ 1 file changed, 40 insertions(+), 9 deletions(-) diff --git a/fuzz/fuzz_targets/fuzz_common.rs b/fuzz/fuzz_targets/fuzz_common.rs index a94963ef024..86b8e561827 100644 --- a/fuzz/fuzz_targets/fuzz_common.rs +++ b/fuzz/fuzz_targets/fuzz_common.rs @@ -3,9 +3,10 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. -use libc::{dup, dup2, STDOUT_FILENO}; +use libc::{close, dup, dup2, pipe, STDOUT_FILENO}; use std::ffi::OsString; use std::io; +use std::io::Write; use std::process::Command; use std::sync::atomic::Ordering; use std::sync::{atomic::AtomicBool, Once}; @@ -38,18 +39,43 @@ where { let uumain_exit_status; + // Duplicate the stdout file descriptor let original_stdout_fd = unsafe { dup(STDOUT_FILENO) }; - println!("Running test {:?}", &args[1..]); + if original_stdout_fd == -1 { + return ("Failed to duplicate STDOUT_FILENO".to_string(), -1); + } + println!("Running test {:?}", &args[0..]); let mut pipe_fds = [-1; 2]; - unsafe { libc::pipe(pipe_fds.as_mut_ptr()) }; - { - unsafe { dup2(pipe_fds[1], STDOUT_FILENO) }; - uumain_exit_status = uumain_function(args.to_owned().into_iter()); - unsafe { dup2(original_stdout_fd, STDOUT_FILENO) }; - unsafe { libc::close(original_stdout_fd) }; + if unsafe { pipe(pipe_fds.as_mut_ptr()) } == -1 { + return ("Failed to create a pipe".to_string(), -1); + } + + // Redirect stdout to the pipe + unsafe { + if dup2(pipe_fds[1], STDOUT_FILENO) == -1 { + close(pipe_fds[0]); + close(pipe_fds[1]); + return ( + "Failed to redirect STDOUT_FILENO to the pipe".to_string(), + -1, + ); + } } - unsafe { libc::close(pipe_fds[1]) }; + + uumain_exit_status = uumain_function(args.to_owned().into_iter()); + + // Restore the original stdout + unsafe { + if dup2(original_stdout_fd, STDOUT_FILENO) == -1 { + return ( + "Failed to restore the original STDOUT_FILENO".to_string(), + -1, + ); + } + close(original_stdout_fd); + } + unsafe { close(pipe_fds[1]) }; let mut captured_output = Vec::new(); let mut read_buffer = [0; 1024]; @@ -61,6 +87,11 @@ where read_buffer.len(), ) }; + + if bytes_read == -1 { + eprintln!("Failed to read from the pipe"); + break; + } if bytes_read <= 0 { break; } From 5fed5443e43a167d11a334163009e6538ec766f8 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Wed, 8 Nov 2023 12:46:46 +0100 Subject: [PATCH 2/6] fuzz: flush after calling uumain - was failing with printf --- fuzz/fuzz_targets/fuzz_common.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/fuzz/fuzz_targets/fuzz_common.rs b/fuzz/fuzz_targets/fuzz_common.rs index 86b8e561827..e30e24ddd3e 100644 --- a/fuzz/fuzz_targets/fuzz_common.rs +++ b/fuzz/fuzz_targets/fuzz_common.rs @@ -64,6 +64,7 @@ where } uumain_exit_status = uumain_function(args.to_owned().into_iter()); + io::stdout().flush().unwrap(); // Restore the original stdout unsafe { From 064ad7200c80a45a3a19e83a8becf73689af153a Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Wed, 8 Nov 2023 23:06:11 +0100 Subject: [PATCH 3/6] fuzzing function should also return stderr --- fuzz/fuzz_targets/fuzz_common.rs | 137 ++++++++++++++++++++----------- 1 file changed, 90 insertions(+), 47 deletions(-) diff --git a/fuzz/fuzz_targets/fuzz_common.rs b/fuzz/fuzz_targets/fuzz_common.rs index e30e24ddd3e..9afc2cc8332 100644 --- a/fuzz/fuzz_targets/fuzz_common.rs +++ b/fuzz/fuzz_targets/fuzz_common.rs @@ -3,10 +3,11 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. -use libc::{close, dup, dup2, pipe, STDOUT_FILENO}; +use libc::{close, dup, dup2, pipe, STDERR_FILENO, STDOUT_FILENO}; use std::ffi::OsString; use std::io; use std::io::Write; +use std::os::fd::RawFd; use std::process::Command; use std::sync::atomic::Ordering; use std::sync::{atomic::AtomicBool, Once}; @@ -33,57 +34,90 @@ pub fn is_gnu_cmd(cmd_path: &str) -> Result<(), std::io::Error> { } } -pub fn generate_and_run_uumain(args: &[OsString], uumain_function: F) -> (String, i32) +pub fn generate_and_run_uumain(args: &[OsString], uumain_function: F) -> (String, String, i32) where F: FnOnce(std::vec::IntoIter) -> i32, { let uumain_exit_status; - // Duplicate the stdout file descriptor + // Duplicate the stdout and stderr file descriptors let original_stdout_fd = unsafe { dup(STDOUT_FILENO) }; - if original_stdout_fd == -1 { - return ("Failed to duplicate STDOUT_FILENO".to_string(), -1); + let original_stderr_fd = unsafe { dup(STDERR_FILENO) }; + if original_stdout_fd == -1 || original_stderr_fd == -1 { + return ( + "Failed to duplicate STDOUT_FILENO or STDERR_FILENO".to_string(), + "".to_string(), + -1, + ); } println!("Running test {:?}", &args[0..]); - let mut pipe_fds = [-1; 2]; - - if unsafe { pipe(pipe_fds.as_mut_ptr()) } == -1 { - return ("Failed to create a pipe".to_string(), -1); + let mut pipe_stdout_fds = [-1; 2]; + let mut pipe_stderr_fds = [-1; 2]; + + // Create pipes for stdout and stderr + if unsafe { pipe(pipe_stdout_fds.as_mut_ptr()) } == -1 + || unsafe { pipe(pipe_stderr_fds.as_mut_ptr()) } == -1 + { + return ("Failed to create pipes".to_string(), "".to_string(), -1); } - // Redirect stdout to the pipe - unsafe { - if dup2(pipe_fds[1], STDOUT_FILENO) == -1 { - close(pipe_fds[0]); - close(pipe_fds[1]); - return ( - "Failed to redirect STDOUT_FILENO to the pipe".to_string(), - -1, - ); + // Redirect stdout and stderr to their respective pipes + if unsafe { dup2(pipe_stdout_fds[1], STDOUT_FILENO) } == -1 + || unsafe { dup2(pipe_stderr_fds[1], STDERR_FILENO) } == -1 + { + unsafe { + close(pipe_stdout_fds[0]); + close(pipe_stdout_fds[1]); + close(pipe_stderr_fds[0]); + close(pipe_stderr_fds[1]); } + return ( + "Failed to redirect STDOUT_FILENO or STDERR_FILENO".to_string(), + "".to_string(), + -1, + ); } uumain_exit_status = uumain_function(args.to_owned().into_iter()); io::stdout().flush().unwrap(); - - // Restore the original stdout + io::stderr().flush().unwrap(); + + // Restore the original stdout and stderr + if unsafe { dup2(original_stdout_fd, STDOUT_FILENO) } == -1 + || unsafe { dup2(original_stderr_fd, STDERR_FILENO) } == -1 + { + return ( + "Failed to restore the original STDOUT_FILENO or STDERR_FILENO".to_string(), + "".to_string(), + -1, + ); + } unsafe { - if dup2(original_stdout_fd, STDOUT_FILENO) == -1 { - return ( - "Failed to restore the original STDOUT_FILENO".to_string(), - -1, - ); - } close(original_stdout_fd); + close(original_stderr_fd); } - unsafe { close(pipe_fds[1]) }; + unsafe { close(pipe_stdout_fds[1]) }; + unsafe { close(pipe_stderr_fds[1]) }; + + let captured_stdout = read_from_fd(pipe_stdout_fds[0]).trim().to_string(); + let captured_stderr = read_from_fd(pipe_stderr_fds[0]).to_string(); + let captured_stderr = captured_stderr + .splitn(2, ':') + .nth(1) + .unwrap_or("") + .trim() + .to_string(); + + (captured_stdout, captured_stderr, uumain_exit_status) +} +fn read_from_fd(fd: RawFd) -> String { let mut captured_output = Vec::new(); let mut read_buffer = [0; 1024]; loop { let bytes_read = unsafe { libc::read( - pipe_fds[0], + fd, read_buffer.as_mut_ptr() as *mut libc::c_void, read_buffer.len(), ) @@ -93,29 +127,30 @@ where eprintln!("Failed to read from the pipe"); break; } - if bytes_read <= 0 { + if bytes_read == 0 { break; } captured_output.extend_from_slice(&read_buffer[..bytes_read as usize]); } - unsafe { libc::close(pipe_fds[0]) }; - - let my_output = String::from_utf8_lossy(&captured_output) - .to_string() - .trim() - .to_owned(); + unsafe { libc::close(fd) }; - (my_output, uumain_exit_status) + String::from_utf8_lossy(&captured_output).into_owned() } pub fn run_gnu_cmd( cmd_path: &str, args: &[OsString], check_gnu: bool, -) -> Result<(String, i32), io::Error> { +) -> Result<(String, String, i32), (String, String, i32)> { if check_gnu { - is_gnu_cmd(cmd_path)?; // Check if it's a GNU implementation + match is_gnu_cmd(cmd_path) { + Ok(_) => {} // if the check passes, do nothing + Err(e) => { + // Convert the io::Error into the function's error type + return Err((String::new(), e.to_string(), -1)); + } + } } let mut command = Command::new(cmd_path); @@ -123,17 +158,25 @@ pub fn run_gnu_cmd( command.arg(arg); } - let output = command.output()?; + let output = match command.output() { + Ok(output) => output, + Err(e) => return Err((String::new(), e.to_string(), -1)), + }; let exit_code = output.status.code().unwrap_or(-1); + + // Here we get stdout and stderr as Strings + let stdout = String::from_utf8_lossy(&output.stdout).to_string(); + let stderr = String::from_utf8_lossy(&output.stderr).to_string(); + let stderr = stderr + .splitn(2, ':') + .nth(1) + .unwrap_or("") + .trim() + .to_string(); + if output.status.success() || !check_gnu { - Ok(( - String::from_utf8_lossy(&output.stdout).to_string(), - exit_code, - )) + Ok((stdout, stderr, exit_code)) } else { - Err(io::Error::new( - io::ErrorKind::Other, - format!("GNU command execution failed with exit code {}", exit_code), - )) + Err((stdout, stderr, exit_code)) } } From 8ab20c4673dec1bc54bd8bc9a20177f6fffde16a Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Wed, 8 Nov 2023 23:31:41 +0100 Subject: [PATCH 4/6] fuzz: adjust the fuzzers with the new arg --- fuzz/fuzz_targets/fuzz_expr.rs | 33 ++++++++++++++++++++++++--------- fuzz/fuzz_targets/fuzz_test.rs | 29 +++++++++++++++++++++-------- 2 files changed, 45 insertions(+), 17 deletions(-) diff --git a/fuzz/fuzz_targets/fuzz_expr.rs b/fuzz/fuzz_targets/fuzz_expr.rs index c2217c48abc..ee65745bf2a 100644 --- a/fuzz/fuzz_targets/fuzz_expr.rs +++ b/fuzz/fuzz_targets/fuzz_expr.rs @@ -84,7 +84,7 @@ fuzz_target!(|_data: &[u8]| { let mut args = vec![OsString::from("expr")]; args.extend(expr.split_whitespace().map(OsString::from)); - let (rust_output, uumain_exit_code) = generate_and_run_uumain(&args, uumain); + let (rust_stdout, rust_stderr, uumain_exit_code) = generate_and_run_uumain(&args, uumain); // Use C locale to avoid false positives, like in https://github.com/uutils/coreutils/issues/5378, // because uutils expr doesn't support localization yet @@ -93,28 +93,43 @@ fuzz_target!(|_data: &[u8]| { // Run GNU expr with the provided arguments and compare the output match run_gnu_cmd(CMD_PATH, &args[1..], true) { - Ok((gnu_output, gnu_exit_code)) => { - let gnu_output = gnu_output.trim().to_owned(); + Ok((gnu_stdout, gnu_stderr, gnu_exit_code)) => { + let gnu_stdout = gnu_stdout.trim().to_owned(); if uumain_exit_code != gnu_exit_code { println!("Expression: {}", expr); + + println!("GNU stderr: {}", gnu_stderr); + println!("Rust stderr: {}", rust_stderr); + println!("Rust code: {}", uumain_exit_code); println!("GNU code: {}", gnu_exit_code); panic!("Different error codes"); } - if rust_output == gnu_output { + if rust_stdout == gnu_stdout { println!( "Outputs matched for expression: {} => Result: {}", - expr, rust_output + expr, rust_stdout ); } else { println!("Expression: {}", expr); - println!("Rust output: {}", rust_output); - println!("GNU output: {}", gnu_output); + println!("Rust output: {}", rust_stdout); + println!("GNU output: {}", gnu_stdout); panic!("Different output between Rust & GNU"); } } - Err(_) => { - println!("GNU expr execution failed for expression: {}", expr); + + Err((_gnu_stdout, gnu_stderr, _gnu_exit_code)) => { + if rust_stderr == gnu_stderr { + println!( + "GNU execution failed for input: {} stderr: {}", + expr, rust_stderr + ); + } else { + println!("Input: {}", expr); + println!("Rust stderr: {}", rust_stderr); + println!("GNU stderr: {}", gnu_stderr); + panic!("Different stderr between Rust & GNU"); + } } } }); diff --git a/fuzz/fuzz_targets/fuzz_test.rs b/fuzz/fuzz_targets/fuzz_test.rs index 4805a41af16..4c4834bdbaf 100644 --- a/fuzz/fuzz_targets/fuzz_test.rs +++ b/fuzz/fuzz_targets/fuzz_test.rs @@ -204,19 +204,22 @@ fuzz_target!(|_data: &[u8]| { args.push(OsString::from(generate_test_arg())); } - let (rust_output, uumain_exit_status) = generate_and_run_uumain(&args, uumain); + let (rust_stdout, rust_stderr, uumain_exit_status) = generate_and_run_uumain(&args, uumain); // Run GNU test with the provided arguments and compare the output match run_gnu_cmd(CMD_PATH, &args[1..], false) { - Ok((gnu_output, gnu_exit_status)) => { - let gnu_output = gnu_output.trim().to_owned(); + Ok((gnu_stdout, gnu_stderr, gnu_exit_status)) => { + let gnu_stdout = gnu_stdout.trim().to_owned(); println!("gnu_exit_status {}", gnu_exit_status); println!("uumain_exit_status {}", uumain_exit_status); - if rust_output != gnu_output || uumain_exit_status != gnu_exit_status { + if rust_stdout != gnu_stdout || uumain_exit_status != gnu_exit_status { println!("Discrepancy detected!"); println!("Test: {:?}", &args[1..]); - println!("My output: {}", rust_output); - println!("GNU output: {}", gnu_output); + println!("Rust output: {}", rust_stdout); + println!("GNU output: {}", gnu_stdout); + + println!("Rust stderr: {}", rust_stderr); + println!("GNU stderr: {}", gnu_stderr); println!("My exit status: {}", uumain_exit_status); println!("GNU exit status: {}", gnu_exit_status); panic!(); @@ -227,8 +230,18 @@ fuzz_target!(|_data: &[u8]| { ); } } - Err(_) => { - println!("GNU test execution failed for expression {:?}", &args[1..]); + Err((_gnu_stdout, gnu_stderr, _gnu_exit_code)) => { + if rust_stderr == gnu_stderr { + println!( + "GNU execution failed for input: {:?} stderr: {}", + args, rust_stderr + ); + } else { + println!("Input: {:?}", args); + println!("Rust stderr: {}", rust_stderr); + println!("GNU stderr: {}", gnu_stderr); + panic!("Different stderr between Rust & GNU"); + } } } }); From 104e707b07057d81d7be33b09d72b5d3f618b1d2 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Thu, 9 Nov 2023 00:04:14 +0100 Subject: [PATCH 5/6] fuzzing: provide a better error management --- fuzz/fuzz_targets/fuzz_common.rs | 66 ++++++++++++++++++++++++++++---- fuzz/fuzz_targets/fuzz_expr.rs | 59 ++++++++-------------------- fuzz/fuzz_targets/fuzz_test.rs | 58 ++++++++-------------------- 3 files changed, 92 insertions(+), 91 deletions(-) diff --git a/fuzz/fuzz_targets/fuzz_common.rs b/fuzz/fuzz_targets/fuzz_common.rs index 9afc2cc8332..4d796866697 100644 --- a/fuzz/fuzz_targets/fuzz_common.rs +++ b/fuzz/fuzz_targets/fuzz_common.rs @@ -38,8 +38,6 @@ pub fn generate_and_run_uumain(args: &[OsString], uumain_function: F) -> (Str where F: FnOnce(std::vec::IntoIter) -> i32, { - let uumain_exit_status; - // Duplicate the stdout and stderr file descriptors let original_stdout_fd = unsafe { dup(STDOUT_FILENO) }; let original_stderr_fd = unsafe { dup(STDERR_FILENO) }; @@ -78,7 +76,8 @@ where ); } - uumain_exit_status = uumain_function(args.to_owned().into_iter()); + let uumain_exit_status = uumain_function(args.to_owned().into_iter()); + io::stdout().flush().unwrap(); io::stderr().flush().unwrap(); @@ -102,8 +101,8 @@ where let captured_stdout = read_from_fd(pipe_stdout_fds[0]).trim().to_string(); let captured_stderr = read_from_fd(pipe_stderr_fds[0]).to_string(); let captured_stderr = captured_stderr - .splitn(2, ':') - .nth(1) + .split_once(':') + .map(|x| x.1) .unwrap_or("") .trim() .to_string(); @@ -168,8 +167,8 @@ pub fn run_gnu_cmd( let stdout = String::from_utf8_lossy(&output.stdout).to_string(); let stderr = String::from_utf8_lossy(&output.stderr).to_string(); let stderr = stderr - .splitn(2, ':') - .nth(1) + .split_once(':') + .map(|x| x.1) .unwrap_or("") .trim() .to_string(); @@ -180,3 +179,56 @@ pub fn run_gnu_cmd( Err((stdout, stderr, exit_code)) } } + +pub fn compare_result( + test_type: &str, + input: &str, + rust_stdout: &str, + gnu_stdout: &str, + rust_stderr: &str, + gnu_stderr: &str, + rust_exit_code: i32, + gnu_exit_code: i32, + fail_on_stderr_diff: bool, +) { + println!("Test Type: {}", test_type); + println!("Input: {}", input); + + let mut discrepancies = Vec::new(); + let mut should_panic = false; + + if rust_stdout.trim() != gnu_stdout.trim() { + discrepancies.push("stdout differs"); + println!("Rust stdout: {}", rust_stdout); + println!("GNU stdout: {}", gnu_stdout); + should_panic = true; + } + if rust_stderr.trim() != gnu_stderr.trim() { + discrepancies.push("stderr differs"); + println!("Rust stderr: {}", rust_stderr); + println!("GNU stderr: {}", gnu_stderr); + if fail_on_stderr_diff { + should_panic = true; + } + } + if rust_exit_code != gnu_exit_code { + discrepancies.push("exit code differs"); + println!("Rust exit code: {}", rust_exit_code); + println!("GNU exit code: {}", gnu_exit_code); + should_panic = true; + } + + if discrepancies.is_empty() { + println!("All outputs and exit codes matched."); + } else { + println!("Discrepancy detected: {}", discrepancies.join(", ")); + if should_panic { + panic!("Test failed for {}: {}", test_type, input); + } else { + println!( + "Test completed with discrepancies for {}: {}", + test_type, input + ); + } + } +} diff --git a/fuzz/fuzz_targets/fuzz_expr.rs b/fuzz/fuzz_targets/fuzz_expr.rs index ee65745bf2a..4f0ad3c4ada 100644 --- a/fuzz/fuzz_targets/fuzz_expr.rs +++ b/fuzz/fuzz_targets/fuzz_expr.rs @@ -13,7 +13,7 @@ use rand::Rng; use std::{env, ffi::OsString}; mod fuzz_common; -use crate::fuzz_common::{generate_and_run_uumain, run_gnu_cmd}; +use crate::fuzz_common::{compare_result, generate_and_run_uumain, run_gnu_cmd}; static CMD_PATH: &str = "expr"; @@ -84,52 +84,25 @@ fuzz_target!(|_data: &[u8]| { let mut args = vec![OsString::from("expr")]; args.extend(expr.split_whitespace().map(OsString::from)); - let (rust_stdout, rust_stderr, uumain_exit_code) = generate_and_run_uumain(&args, uumain); - // Use C locale to avoid false positives, like in https://github.com/uutils/coreutils/issues/5378, // because uutils expr doesn't support localization yet // TODO remove once uutils expr supports localization env::set_var("LC_COLLATE", "C"); - // Run GNU expr with the provided arguments and compare the output - match run_gnu_cmd(CMD_PATH, &args[1..], true) { - Ok((gnu_stdout, gnu_stderr, gnu_exit_code)) => { - let gnu_stdout = gnu_stdout.trim().to_owned(); - if uumain_exit_code != gnu_exit_code { - println!("Expression: {}", expr); - - println!("GNU stderr: {}", gnu_stderr); - println!("Rust stderr: {}", rust_stderr); - - println!("Rust code: {}", uumain_exit_code); - println!("GNU code: {}", gnu_exit_code); - panic!("Different error codes"); - } - if rust_stdout == gnu_stdout { - println!( - "Outputs matched for expression: {} => Result: {}", - expr, rust_stdout - ); - } else { - println!("Expression: {}", expr); - println!("Rust output: {}", rust_stdout); - println!("GNU output: {}", gnu_stdout); - panic!("Different output between Rust & GNU"); - } - } + let (rust_stdout, rust_stderr, uumain_exit_code) = generate_and_run_uumain(&args, uumain); - Err((_gnu_stdout, gnu_stderr, _gnu_exit_code)) => { - if rust_stderr == gnu_stderr { - println!( - "GNU execution failed for input: {} stderr: {}", - expr, rust_stderr - ); - } else { - println!("Input: {}", expr); - println!("Rust stderr: {}", rust_stderr); - println!("GNU stderr: {}", gnu_stderr); - panic!("Different stderr between Rust & GNU"); - } - } - } + let (gnu_stdout, gnu_stderr, gnu_exit_code) = + run_gnu_cmd(CMD_PATH, &args[1..], false).unwrap_or_else(|e| e); + + compare_result( + "expr", + &format!("{:?}", &args[1..]), + &rust_stdout, + &gnu_stdout, + &rust_stderr, + &gnu_stderr, + uumain_exit_code, + gnu_exit_code, + false, // Set to true if you want to fail on stderr diff + ); }); diff --git a/fuzz/fuzz_targets/fuzz_test.rs b/fuzz/fuzz_targets/fuzz_test.rs index 4c4834bdbaf..d112126663e 100644 --- a/fuzz/fuzz_targets/fuzz_test.rs +++ b/fuzz/fuzz_targets/fuzz_test.rs @@ -13,7 +13,7 @@ use rand::Rng; use std::ffi::OsString; mod fuzz_common; -use crate::fuzz_common::{generate_and_run_uumain, run_gnu_cmd}; +use crate::fuzz_common::{compare_result, generate_and_run_uumain, run_gnu_cmd}; #[derive(PartialEq, Debug, Clone)] enum ArgType { @@ -204,44 +204,20 @@ fuzz_target!(|_data: &[u8]| { args.push(OsString::from(generate_test_arg())); } - let (rust_stdout, rust_stderr, uumain_exit_status) = generate_and_run_uumain(&args, uumain); - - // Run GNU test with the provided arguments and compare the output - match run_gnu_cmd(CMD_PATH, &args[1..], false) { - Ok((gnu_stdout, gnu_stderr, gnu_exit_status)) => { - let gnu_stdout = gnu_stdout.trim().to_owned(); - println!("gnu_exit_status {}", gnu_exit_status); - println!("uumain_exit_status {}", uumain_exit_status); - if rust_stdout != gnu_stdout || uumain_exit_status != gnu_exit_status { - println!("Discrepancy detected!"); - println!("Test: {:?}", &args[1..]); - println!("Rust output: {}", rust_stdout); - println!("GNU output: {}", gnu_stdout); - - println!("Rust stderr: {}", rust_stderr); - println!("GNU stderr: {}", gnu_stderr); - println!("My exit status: {}", uumain_exit_status); - println!("GNU exit status: {}", gnu_exit_status); - panic!(); - } else { - println!( - "Outputs and exit statuses matched for expression {:?}", - &args[1..] - ); - } - } - Err((_gnu_stdout, gnu_stderr, _gnu_exit_code)) => { - if rust_stderr == gnu_stderr { - println!( - "GNU execution failed for input: {:?} stderr: {}", - args, rust_stderr - ); - } else { - println!("Input: {:?}", args); - println!("Rust stderr: {}", rust_stderr); - println!("GNU stderr: {}", gnu_stderr); - panic!("Different stderr between Rust & GNU"); - } - } - } + let (rust_stdout, rust_stderr, uumain_exit_code) = generate_and_run_uumain(&args, uumain); + + let (gnu_stdout, gnu_stderr, gnu_exit_code) = + run_gnu_cmd(CMD_PATH, &args[1..], false).unwrap_or_else(|e| e); + + compare_result( + "test", + &format!("{:?}", &args[1..]), + &rust_stdout, + &gnu_stdout, + &rust_stderr, + &gnu_stderr, + uumain_exit_code, + gnu_exit_code, + false, // Set to true if you want to fail on stderr diff + ); }); From 2746c199cf58cb8e4e9837fee299de5453396087 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Fri, 10 Nov 2023 16:01:38 +0100 Subject: [PATCH 6/6] use a command result structure --- fuzz/fuzz_targets/fuzz_common.rs | 90 +++++++++++++++++++++++--------- fuzz/fuzz_targets/fuzz_expr.rs | 34 +++++++----- fuzz/fuzz_targets/fuzz_test.rs | 32 ++++++++---- 3 files changed, 109 insertions(+), 47 deletions(-) diff --git a/fuzz/fuzz_targets/fuzz_common.rs b/fuzz/fuzz_targets/fuzz_common.rs index 4d796866697..0fe2306e751 100644 --- a/fuzz/fuzz_targets/fuzz_common.rs +++ b/fuzz/fuzz_targets/fuzz_common.rs @@ -12,6 +12,19 @@ use std::process::Command; use std::sync::atomic::Ordering; use std::sync::{atomic::AtomicBool, Once}; +/// Represents the result of running a command, including its standard output, +/// standard error, and exit code. +pub struct CommandResult { + /// The standard output (stdout) of the command as a string. + pub stdout: String, + + /// The standard error (stderr) of the command as a string. + pub stderr: String, + + /// The exit code of the command. + pub exit_code: i32, +} + static CHECK_GNU: Once = Once::new(); static IS_GNU: AtomicBool = AtomicBool::new(false); @@ -34,7 +47,7 @@ pub fn is_gnu_cmd(cmd_path: &str) -> Result<(), std::io::Error> { } } -pub fn generate_and_run_uumain(args: &[OsString], uumain_function: F) -> (String, String, i32) +pub fn generate_and_run_uumain(args: &[OsString], uumain_function: F) -> CommandResult where F: FnOnce(std::vec::IntoIter) -> i32, { @@ -42,11 +55,11 @@ where let original_stdout_fd = unsafe { dup(STDOUT_FILENO) }; let original_stderr_fd = unsafe { dup(STDERR_FILENO) }; if original_stdout_fd == -1 || original_stderr_fd == -1 { - return ( - "Failed to duplicate STDOUT_FILENO or STDERR_FILENO".to_string(), - "".to_string(), - -1, - ); + return CommandResult { + stdout: "Failed to duplicate STDOUT_FILENO or STDERR_FILENO".to_string(), + stderr: "".to_string(), + exit_code: -1, + }; } println!("Running test {:?}", &args[0..]); let mut pipe_stdout_fds = [-1; 2]; @@ -56,7 +69,11 @@ where if unsafe { pipe(pipe_stdout_fds.as_mut_ptr()) } == -1 || unsafe { pipe(pipe_stderr_fds.as_mut_ptr()) } == -1 { - return ("Failed to create pipes".to_string(), "".to_string(), -1); + return CommandResult { + stdout: "Failed to create pipes".to_string(), + stderr: "".to_string(), + exit_code: -1, + }; } // Redirect stdout and stderr to their respective pipes @@ -69,11 +86,11 @@ where close(pipe_stderr_fds[0]); close(pipe_stderr_fds[1]); } - return ( - "Failed to redirect STDOUT_FILENO or STDERR_FILENO".to_string(), - "".to_string(), - -1, - ); + return CommandResult { + stdout: "Failed to redirect STDOUT_FILENO or STDERR_FILENO".to_string(), + stderr: "".to_string(), + exit_code: -1, + }; } let uumain_exit_status = uumain_function(args.to_owned().into_iter()); @@ -85,18 +102,19 @@ where if unsafe { dup2(original_stdout_fd, STDOUT_FILENO) } == -1 || unsafe { dup2(original_stderr_fd, STDERR_FILENO) } == -1 { - return ( - "Failed to restore the original STDOUT_FILENO or STDERR_FILENO".to_string(), - "".to_string(), - -1, - ); + return CommandResult { + stdout: "Failed to restore the original STDOUT_FILENO or STDERR_FILENO".to_string(), + stderr: "".to_string(), + exit_code: -1, + }; } unsafe { close(original_stdout_fd); close(original_stderr_fd); + + close(pipe_stdout_fds[1]); + close(pipe_stderr_fds[1]); } - unsafe { close(pipe_stdout_fds[1]) }; - unsafe { close(pipe_stderr_fds[1]) }; let captured_stdout = read_from_fd(pipe_stdout_fds[0]).trim().to_string(); let captured_stderr = read_from_fd(pipe_stderr_fds[0]).to_string(); @@ -107,7 +125,11 @@ where .trim() .to_string(); - (captured_stdout, captured_stderr, uumain_exit_status) + CommandResult { + stdout: captured_stdout, + stderr: captured_stderr, + exit_code: uumain_exit_status, + } } fn read_from_fd(fd: RawFd) -> String { @@ -141,13 +163,17 @@ pub fn run_gnu_cmd( cmd_path: &str, args: &[OsString], check_gnu: bool, -) -> Result<(String, String, i32), (String, String, i32)> { +) -> Result { if check_gnu { match is_gnu_cmd(cmd_path) { Ok(_) => {} // if the check passes, do nothing Err(e) => { // Convert the io::Error into the function's error type - return Err((String::new(), e.to_string(), -1)); + return Err(CommandResult { + stdout: String::new(), + stderr: e.to_string(), + exit_code: -1, + }); } } } @@ -159,7 +185,13 @@ pub fn run_gnu_cmd( let output = match command.output() { Ok(output) => output, - Err(e) => return Err((String::new(), e.to_string(), -1)), + Err(e) => { + return Err(CommandResult { + stdout: String::new(), + stderr: e.to_string(), + exit_code: -1, + }); + } }; let exit_code = output.status.code().unwrap_or(-1); @@ -174,9 +206,17 @@ pub fn run_gnu_cmd( .to_string(); if output.status.success() || !check_gnu { - Ok((stdout, stderr, exit_code)) + Ok(CommandResult { + stdout, + stderr, + exit_code, + }) } else { - Err((stdout, stderr, exit_code)) + Err(CommandResult { + stdout, + stderr, + exit_code, + }) } } diff --git a/fuzz/fuzz_targets/fuzz_expr.rs b/fuzz/fuzz_targets/fuzz_expr.rs index 4f0ad3c4ada..90daa107fc8 100644 --- a/fuzz/fuzz_targets/fuzz_expr.rs +++ b/fuzz/fuzz_targets/fuzz_expr.rs @@ -13,8 +13,8 @@ use rand::Rng; use std::{env, ffi::OsString}; mod fuzz_common; +use crate::fuzz_common::CommandResult; use crate::fuzz_common::{compare_result, generate_and_run_uumain, run_gnu_cmd}; - static CMD_PATH: &str = "expr"; fn generate_random_string(max_length: usize) -> String { @@ -88,21 +88,31 @@ fuzz_target!(|_data: &[u8]| { // because uutils expr doesn't support localization yet // TODO remove once uutils expr supports localization env::set_var("LC_COLLATE", "C"); - - let (rust_stdout, rust_stderr, uumain_exit_code) = generate_and_run_uumain(&args, uumain); - - let (gnu_stdout, gnu_stderr, gnu_exit_code) = - run_gnu_cmd(CMD_PATH, &args[1..], false).unwrap_or_else(|e| e); + let rust_result = generate_and_run_uumain(&args, uumain); + + let gnu_result = match run_gnu_cmd(CMD_PATH, &args[1..], false) { + Ok(result) => result, + Err(error_result) => { + eprintln!("Failed to run GNU command:"); + eprintln!("Stderr: {}", error_result.stderr); + eprintln!("Exit Code: {}", error_result.exit_code); + CommandResult { + stdout: String::new(), + stderr: error_result.stderr, + exit_code: error_result.exit_code, + } + } + }; compare_result( "expr", &format!("{:?}", &args[1..]), - &rust_stdout, - &gnu_stdout, - &rust_stderr, - &gnu_stderr, - uumain_exit_code, - gnu_exit_code, + &rust_result.stdout, + &gnu_result.stdout, + &rust_result.stderr, + &gnu_result.stderr, + rust_result.exit_code, + gnu_result.exit_code, false, // Set to true if you want to fail on stderr diff ); }); diff --git a/fuzz/fuzz_targets/fuzz_test.rs b/fuzz/fuzz_targets/fuzz_test.rs index d112126663e..0f2328c55db 100644 --- a/fuzz/fuzz_targets/fuzz_test.rs +++ b/fuzz/fuzz_targets/fuzz_test.rs @@ -13,6 +13,7 @@ use rand::Rng; use std::ffi::OsString; mod fuzz_common; +use crate::fuzz_common::CommandResult; use crate::fuzz_common::{compare_result, generate_and_run_uumain, run_gnu_cmd}; #[derive(PartialEq, Debug, Clone)] @@ -204,20 +205,31 @@ fuzz_target!(|_data: &[u8]| { args.push(OsString::from(generate_test_arg())); } - let (rust_stdout, rust_stderr, uumain_exit_code) = generate_and_run_uumain(&args, uumain); - - let (gnu_stdout, gnu_stderr, gnu_exit_code) = - run_gnu_cmd(CMD_PATH, &args[1..], false).unwrap_or_else(|e| e); + let rust_result = generate_and_run_uumain(&args, uumain); + + let gnu_result = match run_gnu_cmd(CMD_PATH, &args[1..], false) { + Ok(result) => result, + Err(error_result) => { + eprintln!("Failed to run GNU command:"); + eprintln!("Stderr: {}", error_result.stderr); + eprintln!("Exit Code: {}", error_result.exit_code); + CommandResult { + stdout: String::new(), + stderr: error_result.stderr, + exit_code: error_result.exit_code, + } + } + }; compare_result( "test", &format!("{:?}", &args[1..]), - &rust_stdout, - &gnu_stdout, - &rust_stderr, - &gnu_stderr, - uumain_exit_code, - gnu_exit_code, + &rust_result.stdout, + &gnu_result.stdout, + &rust_result.stderr, + &gnu_result.stderr, + rust_result.exit_code, + gnu_result.exit_code, false, // Set to true if you want to fail on stderr diff ); });