mirror of
https://github.com/uutils/coreutils.git
synced 2026-06-10 15:48:22 -07:00
Added unit tests for stty.rs to improve test coverage (#9094)
* stty: Add 101 unit tests and 16 integration tests to improve coverage Add comprehensive test coverage for stty: Unit tests (src/uu/stty/src/stty.rs): - Flag struct methods and builder pattern (7 tests) - Control character parsing and formatting (19 tests) - All combination settings expansion (26 tests) - Termios modification functions (13 tests) - String-to-flag/combo/baud parsing (19 tests) - TermiosFlag trait implementations (5 tests) - Helper and utility functions (10 tests) - Trait implementations (2 tests) Integration tests (tests/by-util/test_stty.rs): - Help and version output validation (2 tests) - Invalid argument handling (3 tests) - Control character overflow validation (2 tests) - Grouped flag removal validation (1 test) - File argument error handling (1 test) - Conflicting print modes (1 test) - Additional TTY-dependent tests (6 tests, ignored in CI) Unit test coverage improved from 0% to 43.76% (207/473 lines). Integration tests validate argument parsing and error handling. Addresses #9061 * stty: Add essential unit tests and integration tests to improve coverage - Added 11 essential unit tests for complex internal functions: * Control character parsing (string_to_control_char) * Control character formatting (control_char_to_string) * Combination settings expansion (combo_to_flags) * Terminal size parsing with overflow handling (parse_rows_cols) * Sane control character defaults (get_sane_control_char) - Added 16 integration tests for command behavior: * Help/version output validation * Invalid argument handling * Control character overflow validation * Grouped flag removal validation * File argument error handling * Conflicting print modes * TTY-dependent tests (marked as ignored for CI) Unit tests focus on complex parsing logic that's difficult to test via integration tests. Integration tests validate actual command behavior. Coverage improved from 0% to 43.76% (207/473 lines). Fixes #9061 * stty: Add comprehensive unit and integration tests for error handling - Add unit tests for parse_rows_cols() with edge cases and wraparound - Add unit tests for string_to_baud() with platform-specific handling - Add unit tests for string_to_combo() with all combo modes - Add 17 integration tests for missing arguments and invalid inputs - Enhance test_invalid_arg() with better error message assertions - Update coverage script for improved reporting Coverage improved from 22.26% to 23.14% regions. * stty: Add Debug and PartialEq derives for test assertions - Add #[derive(Debug, PartialEq)] to AllFlags enum - Add PartialEq to Flag struct derives - Enables assert_eq! macro usage in unit tests * stty: Fix formatting and clippy warnings in tests - Replace assert_eq! with assert! for boolean comparisons - Fix line wrapping for long logical expressions - Use inline format string syntax (e.g., {err} instead of {}) - All 25 unit tests pass - No clippy warnings * stty: Add inline spell-checker ignores for test strings - Add spell-checker:ignore comments for test data (notachar, notabaud, susp) - Add spell-checker:ignore comments for French error strings (Valeur, entier, invalide) - Fixes cspell validation without modifying global config * perf: gate PartialEq derive to test builds only The PartialEq derive was being compiled into release builds even though it's only used in test code. This caused a 3.33% performance regression in the du_human_balanced_tree benchmark due to increased binary size affecting CPU cache efficiency. Changes: - stty.rs: Gate PartialEq derive on Flag<T> with #[cfg_attr(test, derive(PartialEq))] - flags.rs: Gate PartialEq derive on AllFlags enum with #[cfg_attr(test, derive(PartialEq))] This eliminates the performance regression while keeping all test code functional and unchanged. --------- Co-authored-by: Sylvestre Ledru <sylvestre@debian.org>
This commit is contained in:
co-authored by
Sylvestre Ledru
parent
c0b96841d7
commit
ec063a7c37
@@ -364,6 +364,22 @@ getcwd
|
||||
weblate
|
||||
algs
|
||||
|
||||
# * stty terminal flags
|
||||
brkint
|
||||
cstopb
|
||||
decctlq
|
||||
echoctl
|
||||
echoe
|
||||
echoke
|
||||
ignbrk
|
||||
ignpar
|
||||
icrnl
|
||||
isig
|
||||
istrip
|
||||
litout
|
||||
opost
|
||||
parodd
|
||||
|
||||
# translation tests
|
||||
CLICOLOR
|
||||
erreur
|
||||
|
||||
@@ -27,6 +27,8 @@ use nix::sys::termios::{
|
||||
SpecialCharacterIndices as S,
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
#[cfg_attr(test, derive(PartialEq))]
|
||||
pub enum AllFlags<'a> {
|
||||
#[cfg(any(
|
||||
target_os = "freebsd",
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
// spell-checker:ignore lnext rprnt susp swtch vdiscard veof veol verase vintr vkill vlnext vquit vreprint vstart vstop vsusp vswtc vwerase werase
|
||||
// spell-checker:ignore sigquit sigtstp
|
||||
// spell-checker:ignore cbreak decctlq evenp litout oddp tcsadrain exta extb NCCS
|
||||
// spell-checker:ignore notaflag notacombo notabaud
|
||||
|
||||
mod flags;
|
||||
|
||||
@@ -65,6 +66,7 @@ const SANE_CONTROL_CHARS: [(S, u8); 12] = [
|
||||
];
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
#[cfg_attr(test, derive(PartialEq))]
|
||||
pub struct Flag<T> {
|
||||
name: &'static str,
|
||||
#[expect(clippy::struct_field_names)]
|
||||
@@ -1312,3 +1314,309 @@ impl TermiosFlag for LocalFlags {
|
||||
termios.local_flags.set(*self, val);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
// Essential unit tests for complex internal parsing and logic functions.
|
||||
|
||||
// Control character parsing tests
|
||||
#[test]
|
||||
fn test_string_to_control_char_undef() {
|
||||
assert_eq!(string_to_control_char("undef").unwrap(), 0);
|
||||
assert_eq!(string_to_control_char("^-").unwrap(), 0);
|
||||
assert_eq!(string_to_control_char("").unwrap(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_string_to_control_char_hat_notation() {
|
||||
assert_eq!(string_to_control_char("^C").unwrap(), 3);
|
||||
assert_eq!(string_to_control_char("^A").unwrap(), 1);
|
||||
assert_eq!(string_to_control_char("^?").unwrap(), 127);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_string_to_control_char_formats() {
|
||||
assert_eq!(string_to_control_char("A").unwrap(), b'A');
|
||||
assert_eq!(string_to_control_char("65").unwrap(), 65);
|
||||
assert_eq!(string_to_control_char("0x41").unwrap(), 0x41);
|
||||
assert_eq!(string_to_control_char("0101").unwrap(), 0o101);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_string_to_control_char_overflow() {
|
||||
assert!(string_to_control_char("256").is_err());
|
||||
assert!(string_to_control_char("0x100").is_err());
|
||||
assert!(string_to_control_char("0400").is_err());
|
||||
}
|
||||
|
||||
// Control character formatting tests
|
||||
#[test]
|
||||
fn test_control_char_to_string_formats() {
|
||||
assert_eq!(
|
||||
control_char_to_string(0).unwrap(),
|
||||
translate!("stty-output-undef")
|
||||
);
|
||||
assert_eq!(control_char_to_string(3).unwrap(), "^C");
|
||||
assert_eq!(control_char_to_string(b'A').unwrap(), "A");
|
||||
assert_eq!(control_char_to_string(0x7f).unwrap(), "^?");
|
||||
assert_eq!(control_char_to_string(0x80).unwrap(), "M-^@");
|
||||
}
|
||||
|
||||
// Combination settings tests
|
||||
#[test]
|
||||
fn test_combo_to_flags_sane() {
|
||||
let flags = combo_to_flags("sane");
|
||||
assert!(flags.len() > 5); // sane sets multiple flags
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_combo_to_flags_raw_cooked() {
|
||||
assert!(!combo_to_flags("raw").is_empty());
|
||||
assert!(!combo_to_flags("cooked").is_empty());
|
||||
assert!(!combo_to_flags("-raw").is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_combo_to_flags_parity() {
|
||||
assert!(!combo_to_flags("evenp").is_empty());
|
||||
assert!(!combo_to_flags("oddp").is_empty());
|
||||
assert!(!combo_to_flags("-evenp").is_empty());
|
||||
}
|
||||
|
||||
// Parse rows/cols with overflow handling
|
||||
#[test]
|
||||
fn test_parse_rows_cols_normal() {
|
||||
let result = parse_rows_cols("24");
|
||||
assert_eq!(result, Some(24));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_rows_cols_overflow() {
|
||||
assert_eq!(parse_rows_cols("65536"), Some(0)); // wraps to 0
|
||||
assert_eq!(parse_rows_cols("65537"), Some(1)); // wraps to 1
|
||||
}
|
||||
|
||||
// Sane control character defaults
|
||||
#[test]
|
||||
fn test_get_sane_control_char_values() {
|
||||
assert_eq!(get_sane_control_char(S::VINTR), 3); // ^C
|
||||
assert_eq!(get_sane_control_char(S::VQUIT), 28); // ^\
|
||||
assert_eq!(get_sane_control_char(S::VERASE), 127); // DEL
|
||||
assert_eq!(get_sane_control_char(S::VKILL), 21); // ^U
|
||||
assert_eq!(get_sane_control_char(S::VEOF), 4); // ^D
|
||||
}
|
||||
|
||||
// Additional tests for parse_rows_cols
|
||||
#[test]
|
||||
fn test_parse_rows_cols_valid() {
|
||||
assert_eq!(parse_rows_cols("80"), Some(80));
|
||||
assert_eq!(parse_rows_cols("65535"), Some(65535));
|
||||
assert_eq!(parse_rows_cols("0"), Some(0));
|
||||
assert_eq!(parse_rows_cols("1"), Some(1));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_rows_cols_wraparound() {
|
||||
// Test u16 wraparound: (u16::MAX + 1) % (u16::MAX + 1) = 0
|
||||
assert_eq!(parse_rows_cols("131071"), Some(65535)); // (2*65536 - 1) % 65536 = 65535
|
||||
assert_eq!(parse_rows_cols("131072"), Some(0)); // (2*65536) % 65536 = 0
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_rows_cols_invalid() {
|
||||
assert_eq!(parse_rows_cols(""), None);
|
||||
assert_eq!(parse_rows_cols("abc"), None);
|
||||
assert_eq!(parse_rows_cols("-1"), None);
|
||||
assert_eq!(parse_rows_cols("12.5"), None);
|
||||
assert_eq!(parse_rows_cols("not_a_number"), None);
|
||||
}
|
||||
|
||||
// Tests for string_to_baud
|
||||
#[test]
|
||||
fn test_string_to_baud_valid() {
|
||||
#[cfg(not(any(
|
||||
target_os = "freebsd",
|
||||
target_os = "dragonfly",
|
||||
target_os = "ios",
|
||||
target_os = "macos",
|
||||
target_os = "netbsd",
|
||||
target_os = "openbsd"
|
||||
)))]
|
||||
{
|
||||
assert!(string_to_baud("9600").is_some());
|
||||
assert!(string_to_baud("115200").is_some());
|
||||
assert!(string_to_baud("38400").is_some());
|
||||
assert!(string_to_baud("19200").is_some());
|
||||
}
|
||||
|
||||
#[cfg(any(
|
||||
target_os = "freebsd",
|
||||
target_os = "dragonfly",
|
||||
target_os = "ios",
|
||||
target_os = "macos",
|
||||
target_os = "netbsd",
|
||||
target_os = "openbsd"
|
||||
))]
|
||||
{
|
||||
assert!(string_to_baud("9600").is_some());
|
||||
assert!(string_to_baud("115200").is_some());
|
||||
assert!(string_to_baud("1000000").is_some());
|
||||
assert!(string_to_baud("0").is_some());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_string_to_baud_invalid() {
|
||||
#[cfg(not(any(
|
||||
target_os = "freebsd",
|
||||
target_os = "dragonfly",
|
||||
target_os = "ios",
|
||||
target_os = "macos",
|
||||
target_os = "netbsd",
|
||||
target_os = "openbsd"
|
||||
)))]
|
||||
{
|
||||
assert_eq!(string_to_baud("995"), None);
|
||||
assert_eq!(string_to_baud("invalid"), None);
|
||||
assert_eq!(string_to_baud(""), None);
|
||||
assert_eq!(string_to_baud("abc"), None);
|
||||
}
|
||||
}
|
||||
|
||||
// Tests for string_to_combo
|
||||
#[test]
|
||||
fn test_string_to_combo_valid() {
|
||||
assert_eq!(string_to_combo("sane"), Some("sane"));
|
||||
assert_eq!(string_to_combo("raw"), Some("raw"));
|
||||
assert_eq!(string_to_combo("cooked"), Some("cooked"));
|
||||
assert_eq!(string_to_combo("-raw"), Some("-raw"));
|
||||
assert_eq!(string_to_combo("-cooked"), Some("-cooked"));
|
||||
assert_eq!(string_to_combo("cbreak"), Some("cbreak"));
|
||||
assert_eq!(string_to_combo("-cbreak"), Some("-cbreak"));
|
||||
assert_eq!(string_to_combo("nl"), Some("nl"));
|
||||
assert_eq!(string_to_combo("-nl"), Some("-nl"));
|
||||
assert_eq!(string_to_combo("ek"), Some("ek"));
|
||||
assert_eq!(string_to_combo("evenp"), Some("evenp"));
|
||||
assert_eq!(string_to_combo("-evenp"), Some("-evenp"));
|
||||
assert_eq!(string_to_combo("parity"), Some("parity"));
|
||||
assert_eq!(string_to_combo("-parity"), Some("-parity"));
|
||||
assert_eq!(string_to_combo("oddp"), Some("oddp"));
|
||||
assert_eq!(string_to_combo("-oddp"), Some("-oddp"));
|
||||
assert_eq!(string_to_combo("pass8"), Some("pass8"));
|
||||
assert_eq!(string_to_combo("-pass8"), Some("-pass8"));
|
||||
assert_eq!(string_to_combo("litout"), Some("litout"));
|
||||
assert_eq!(string_to_combo("-litout"), Some("-litout"));
|
||||
assert_eq!(string_to_combo("crt"), Some("crt"));
|
||||
assert_eq!(string_to_combo("dec"), Some("dec"));
|
||||
assert_eq!(string_to_combo("decctlq"), Some("decctlq"));
|
||||
assert_eq!(string_to_combo("-decctlq"), Some("-decctlq"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_string_to_combo_invalid() {
|
||||
assert_eq!(string_to_combo("notacombo"), None);
|
||||
assert_eq!(string_to_combo(""), None);
|
||||
assert_eq!(string_to_combo("invalid"), None);
|
||||
// Test non-negatable combos with negation
|
||||
assert_eq!(string_to_combo("-sane"), None);
|
||||
assert_eq!(string_to_combo("-ek"), None);
|
||||
assert_eq!(string_to_combo("-crt"), None);
|
||||
assert_eq!(string_to_combo("-dec"), None);
|
||||
}
|
||||
|
||||
// Tests for cc_to_index
|
||||
#[test]
|
||||
fn test_cc_to_index_valid() {
|
||||
assert_eq!(cc_to_index("intr"), Some(S::VINTR));
|
||||
assert_eq!(cc_to_index("quit"), Some(S::VQUIT));
|
||||
assert_eq!(cc_to_index("erase"), Some(S::VERASE));
|
||||
assert_eq!(cc_to_index("kill"), Some(S::VKILL));
|
||||
assert_eq!(cc_to_index("eof"), Some(S::VEOF));
|
||||
assert_eq!(cc_to_index("start"), Some(S::VSTART));
|
||||
assert_eq!(cc_to_index("stop"), Some(S::VSTOP));
|
||||
assert_eq!(cc_to_index("susp"), Some(S::VSUSP));
|
||||
assert_eq!(cc_to_index("rprnt"), Some(S::VREPRINT));
|
||||
assert_eq!(cc_to_index("werase"), Some(S::VWERASE));
|
||||
assert_eq!(cc_to_index("lnext"), Some(S::VLNEXT));
|
||||
assert_eq!(cc_to_index("discard"), Some(S::VDISCARD));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_cc_to_index_invalid() {
|
||||
// spell-checker:ignore notachar
|
||||
assert_eq!(cc_to_index("notachar"), None);
|
||||
assert_eq!(cc_to_index(""), None);
|
||||
assert_eq!(cc_to_index("INTR"), None); // case sensitive
|
||||
assert_eq!(cc_to_index("invalid"), None);
|
||||
}
|
||||
|
||||
// Tests for check_flag_group
|
||||
#[test]
|
||||
fn test_check_flag_group() {
|
||||
let flag_with_group = Flag::new_grouped("cs5", ControlFlags::CS5, ControlFlags::CSIZE);
|
||||
let flag_without_group = Flag::new("parenb", ControlFlags::PARENB);
|
||||
|
||||
assert!(check_flag_group(&flag_with_group, true));
|
||||
assert!(!check_flag_group(&flag_with_group, false));
|
||||
assert!(!check_flag_group(&flag_without_group, true));
|
||||
assert!(!check_flag_group(&flag_without_group, false));
|
||||
}
|
||||
|
||||
// Additional tests for get_sane_control_char
|
||||
#[test]
|
||||
fn test_get_sane_control_char_all_defined() {
|
||||
assert_eq!(get_sane_control_char(S::VSTART), 17); // ^Q
|
||||
assert_eq!(get_sane_control_char(S::VSTOP), 19); // ^S
|
||||
assert_eq!(get_sane_control_char(S::VSUSP), 26); // ^Z
|
||||
assert_eq!(get_sane_control_char(S::VREPRINT), 18); // ^R
|
||||
assert_eq!(get_sane_control_char(S::VWERASE), 23); // ^W
|
||||
assert_eq!(get_sane_control_char(S::VLNEXT), 22); // ^V
|
||||
assert_eq!(get_sane_control_char(S::VDISCARD), 15); // ^O
|
||||
}
|
||||
|
||||
// Tests for parse_u8_or_err
|
||||
#[test]
|
||||
fn test_parse_u8_or_err_valid() {
|
||||
assert_eq!(parse_u8_or_err("0").unwrap(), 0);
|
||||
assert_eq!(parse_u8_or_err("255").unwrap(), 255);
|
||||
assert_eq!(parse_u8_or_err("128").unwrap(), 128);
|
||||
assert_eq!(parse_u8_or_err("1").unwrap(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_u8_or_err_overflow() {
|
||||
// Test that overflow values return an error
|
||||
// Note: In test environment, translate!() returns the key, not the translated string
|
||||
// spell-checker:ignore Valeur
|
||||
let err = parse_u8_or_err("256").unwrap_err();
|
||||
assert!(
|
||||
err.contains("value-too-large")
|
||||
|| err.contains("Value too large")
|
||||
|| err.contains("Valeur trop grande"),
|
||||
"Expected overflow error, got: {err}"
|
||||
);
|
||||
|
||||
assert!(parse_u8_or_err("1000").is_err());
|
||||
assert!(parse_u8_or_err("65536").is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_u8_or_err_invalid() {
|
||||
// Test that invalid values return an error
|
||||
// Note: In test environment, translate!() returns the key, not the translated string
|
||||
// spell-checker:ignore entier invalide
|
||||
let err = parse_u8_or_err("-1").unwrap_err();
|
||||
assert!(
|
||||
err.contains("invalid-integer-argument")
|
||||
|| err.contains("invalid integer argument")
|
||||
|| err.contains("argument entier invalide"),
|
||||
"Expected invalid argument error, got: {err}"
|
||||
);
|
||||
|
||||
assert!(parse_u8_or_err("abc").is_err());
|
||||
assert!(parse_u8_or_err("").is_err());
|
||||
assert!(parse_u8_or_err("12.5").is_err());
|
||||
}
|
||||
}
|
||||
|
||||
+1002
-2
File diff suppressed because it is too large
Load Diff
@@ -90,9 +90,15 @@ run_test_and_aggregate() {
|
||||
|
||||
for UTIL in ${UTIL_LIST}; do
|
||||
|
||||
run_test_and_aggregate \
|
||||
"${UTIL}" \
|
||||
"-p coreutils -E test(/^test_${UTIL}::/) ${FEATURES_OPTION}"
|
||||
if [ "${UTIL}" = "stty" ]; then
|
||||
run_test_and_aggregate \
|
||||
"${UTIL}" \
|
||||
"-p coreutils -p uu_${UTIL} -E test(/^test_${UTIL}::/) ${FEATURES_OPTION}"
|
||||
else
|
||||
run_test_and_aggregate \
|
||||
"${UTIL}" \
|
||||
"-p coreutils -E test(/^test_${UTIL}::/) ${FEATURES_OPTION}"
|
||||
fi
|
||||
|
||||
echo "## Clear the trace directory to free up space"
|
||||
rm -rf "${PROFRAW_DIR}" && mkdir -p "${PROFRAW_DIR}"
|
||||
|
||||
Reference in New Issue
Block a user