mirror of
https://github.com/uutils/coreutils.git
synced 2026-06-10 15:48:22 -07:00
1751 lines
46 KiB
Rust
1751 lines
46 KiB
Rust
// 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.
|
|
// spell-checker:ignore parenb parmrk ixany iuclc onlcr ofdel icanon noflsh econl igpar ispeed ospeed NCCS nonhex gstty notachar cbreak evenp oddp CSIZE
|
|
|
|
use uutests::util::{expected_result, pty_path};
|
|
use uutests::{at_and_ts, new_ucmd, unwrap_or_return};
|
|
|
|
/// Normalize stderr by replacing the full binary path with just the utility name
|
|
/// This allows comparison between GNU (which shows "stty" or "gstty") and ours (which shows full path)
|
|
fn normalize_stderr(stderr: &str) -> String {
|
|
// Replace patterns like "Try 'gstty --help'" or "Try '/path/to/stty --help'" with "Try 'stty --help'"
|
|
let re = regex::Regex::new(r"Try '[^']*(?:g)?stty --help'").unwrap();
|
|
re.replace_all(stderr, "Try 'stty --help'").to_string()
|
|
}
|
|
|
|
#[test]
|
|
fn test_invalid_arg() {
|
|
new_ucmd!()
|
|
.arg("--definitely-invalid")
|
|
.fails_with_code(1)
|
|
.stderr_contains("invalid argument")
|
|
.stderr_contains("--definitely-invalid");
|
|
}
|
|
|
|
#[test]
|
|
#[cfg(unix)]
|
|
fn test_basic() {
|
|
let (path, _controller, _replica) = pty_path();
|
|
new_ucmd!()
|
|
.args(&["--file", &path])
|
|
.succeeds()
|
|
.stdout_contains("speed");
|
|
}
|
|
|
|
#[test]
|
|
#[cfg(unix)]
|
|
fn test_all_flag() {
|
|
let (path, _controller, _replica) = pty_path();
|
|
let result = new_ucmd!().args(&["--all", "--file", &path]).succeeds();
|
|
|
|
for flag in ["parenb", "parmrk", "ixany", "onlcr", "icanon", "noflsh"] {
|
|
result.stdout_contains(flag);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
#[cfg(unix)]
|
|
fn test_sane() {
|
|
let (path, _controller, _replica) = pty_path();
|
|
|
|
new_ucmd!()
|
|
.args(&["--file", &path, "intr", "^A"])
|
|
.succeeds();
|
|
new_ucmd!()
|
|
.args(&["--file", &path])
|
|
.succeeds()
|
|
.stdout_contains("intr = ^A");
|
|
new_ucmd!().args(&["--file", &path, "sane"]).succeeds();
|
|
new_ucmd!()
|
|
.args(&["--file", &path])
|
|
.succeeds()
|
|
.stdout_str_check(|s| !s.contains("intr = ^A"));
|
|
}
|
|
|
|
#[test]
|
|
fn save_and_setting() {
|
|
new_ucmd!()
|
|
.args(&["--save", "nl0"])
|
|
.fails()
|
|
.stderr_contains("when specifying an output style, modes may not be set");
|
|
}
|
|
|
|
#[test]
|
|
fn all_and_setting() {
|
|
new_ucmd!()
|
|
.args(&["--all", "nl0"])
|
|
.fails()
|
|
.stderr_contains("when specifying an output style, modes may not be set");
|
|
}
|
|
|
|
#[test]
|
|
fn all_and_print_setting() {
|
|
new_ucmd!()
|
|
.args(&["--all", "size"])
|
|
.fails()
|
|
.stderr_contains("when specifying an output style, modes may not be set");
|
|
}
|
|
|
|
#[test]
|
|
fn save_and_all() {
|
|
new_ucmd!()
|
|
.args(&["--save", "--all"])
|
|
.fails()
|
|
.stderr_contains(
|
|
"the options for verbose and stty-readable output styles are mutually exclusive",
|
|
);
|
|
|
|
new_ucmd!()
|
|
.args(&["--all", "--save"])
|
|
.fails()
|
|
.stderr_contains(
|
|
"the options for verbose and stty-readable output styles are mutually exclusive",
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn no_mapping() {
|
|
new_ucmd!()
|
|
.args(&["intr"])
|
|
.fails()
|
|
.stderr_contains("missing argument to 'intr'");
|
|
}
|
|
|
|
#[test]
|
|
fn invalid_mapping() {
|
|
new_ucmd!()
|
|
.args(&["intr", "cc"])
|
|
.fails()
|
|
.stderr_contains("invalid integer argument: 'cc'");
|
|
|
|
new_ucmd!()
|
|
.args(&["intr", "256"])
|
|
.fails()
|
|
.stderr_contains("invalid integer argument: '256': Value too large for defined data type");
|
|
|
|
new_ucmd!()
|
|
.args(&["intr", "0x100"])
|
|
.fails()
|
|
.stderr_contains(
|
|
"invalid integer argument: '0x100': Value too large for defined data type",
|
|
);
|
|
|
|
new_ucmd!()
|
|
.args(&["intr", "0400"])
|
|
.fails()
|
|
.stderr_contains("invalid integer argument: '0400': Value too large for defined data type");
|
|
}
|
|
|
|
#[test]
|
|
fn invalid_setting() {
|
|
new_ucmd!()
|
|
.args(&["-econl"])
|
|
.fails()
|
|
.stderr_contains("invalid argument '-econl'");
|
|
|
|
new_ucmd!()
|
|
.args(&["igpar"])
|
|
.fails()
|
|
.stderr_contains("invalid argument 'igpar'");
|
|
}
|
|
|
|
#[test]
|
|
fn invalid_baud_setting() {
|
|
#[cfg(not(any(
|
|
target_os = "freebsd",
|
|
target_os = "dragonfly",
|
|
target_os = "ios",
|
|
target_os = "macos",
|
|
target_os = "netbsd",
|
|
target_os = "openbsd"
|
|
)))]
|
|
new_ucmd!()
|
|
.args(&["100"])
|
|
.fails()
|
|
.stderr_contains("invalid argument '100'");
|
|
|
|
new_ucmd!()
|
|
.args(&["-1"])
|
|
.fails()
|
|
.stderr_contains("invalid argument '-1'");
|
|
|
|
new_ucmd!()
|
|
.args(&["ispeed"])
|
|
.fails()
|
|
.stderr_contains("missing argument to 'ispeed'");
|
|
|
|
new_ucmd!()
|
|
.args(&["ospeed"])
|
|
.fails()
|
|
.stderr_contains("missing argument to 'ospeed'");
|
|
|
|
#[cfg(not(any(
|
|
target_os = "freebsd",
|
|
target_os = "dragonfly",
|
|
target_os = "ios",
|
|
target_os = "macos",
|
|
target_os = "netbsd",
|
|
target_os = "openbsd"
|
|
)))]
|
|
new_ucmd!()
|
|
.args(&["ispeed", "995"])
|
|
.fails()
|
|
.stderr_contains("invalid ispeed '995'");
|
|
|
|
#[cfg(not(any(
|
|
target_os = "freebsd",
|
|
target_os = "dragonfly",
|
|
target_os = "ios",
|
|
target_os = "macos",
|
|
target_os = "netbsd",
|
|
target_os = "openbsd"
|
|
)))]
|
|
new_ucmd!()
|
|
.args(&["ospeed", "995"])
|
|
.fails()
|
|
.stderr_contains("invalid ospeed '995'");
|
|
|
|
for speed in &[
|
|
"9599..", "9600..", "9600.5.", "9600.50.", "9600.0.", "++9600", "0x2580", "96E2", "9600,0",
|
|
"9600.0 ",
|
|
] {
|
|
new_ucmd!().args(&["ispeed", speed]).fails();
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
#[cfg(unix)]
|
|
fn valid_baud_formats() {
|
|
let (path, _controller, _replica) = pty_path();
|
|
for speed in &[" +9600", "9600.49", "9600.50", "9599.51", " 9600."] {
|
|
new_ucmd!()
|
|
.args(&["--file", &path, "ispeed", speed])
|
|
.succeeds();
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
#[ignore = "Fails because cargo test does not run in a tty"]
|
|
fn set_mapping() {
|
|
new_ucmd!().args(&["intr", "'"]).succeeds();
|
|
new_ucmd!()
|
|
.args(&["--all"])
|
|
.succeeds()
|
|
.stdout_contains("intr = '");
|
|
|
|
new_ucmd!().args(&["intr", "undef"]).succeeds();
|
|
new_ucmd!()
|
|
.args(&["--all"])
|
|
.succeeds()
|
|
.stdout_contains("intr = <undef>");
|
|
|
|
new_ucmd!().args(&["intr", "^-"]).succeeds();
|
|
new_ucmd!()
|
|
.args(&["--all"])
|
|
.succeeds()
|
|
.stdout_contains("intr = <undef>");
|
|
|
|
new_ucmd!().args(&["intr", ""]).succeeds();
|
|
new_ucmd!()
|
|
.args(&["--all"])
|
|
.succeeds()
|
|
.stdout_contains("intr = <undef>");
|
|
|
|
new_ucmd!().args(&["intr", "^C"]).succeeds();
|
|
new_ucmd!()
|
|
.args(&["--all"])
|
|
.succeeds()
|
|
.stdout_contains("intr = ^C");
|
|
}
|
|
|
|
#[test]
|
|
fn row_column_sizes() {
|
|
new_ucmd!()
|
|
.args(&["rows", "-1"])
|
|
.fails()
|
|
.stderr_contains("invalid integer argument: '-1'");
|
|
|
|
new_ucmd!()
|
|
.args(&["columns", "-1"])
|
|
.fails()
|
|
.stderr_contains("invalid integer argument: '-1'");
|
|
|
|
// overflow the u32 used for row/col counts
|
|
new_ucmd!()
|
|
.args(&["cols", "4294967296"])
|
|
.fails()
|
|
.stderr_contains("invalid integer argument: '4294967296'");
|
|
|
|
new_ucmd!()
|
|
.args(&["rows", ""])
|
|
.fails()
|
|
.stderr_contains("invalid integer argument: ''");
|
|
|
|
new_ucmd!()
|
|
.args(&["columns"])
|
|
.fails()
|
|
.stderr_contains("missing argument to 'columns'");
|
|
|
|
new_ucmd!()
|
|
.args(&["rows"])
|
|
.fails()
|
|
.stderr_contains("missing argument to 'rows'");
|
|
}
|
|
|
|
#[test]
|
|
#[cfg(unix)]
|
|
fn test_row_column_hex_octal() {
|
|
let (path, _controller, _replica) = pty_path();
|
|
let (_at, ts) = at_and_ts!();
|
|
|
|
// Test various numeric formats: hex (0x1E), octal (036), uppercase hex (0X1E), decimal (30), and zero
|
|
let test_cases = [
|
|
("rows", "0x1E"), // hexadecimal = 30
|
|
("rows", "0x1e"), // lowercase hexadecimal = 30
|
|
("rows", "0X1e"), // upper and lowercase hexadecimal = 30
|
|
("rows", "036"), // octal = 30
|
|
("cols", "0X1E"), // uppercase hex = 30
|
|
("columns", "30"), // decimal = 30
|
|
("rows", "0"), // zero (not octal prefix)
|
|
];
|
|
|
|
for (setting, value) in test_cases {
|
|
let result = ts.ucmd().args(&["--file", &path, setting, value]).run();
|
|
let exp_result =
|
|
unwrap_or_return!(expected_result(&ts, &["--file", &path, setting, value]));
|
|
let normalized_stderr = normalize_stderr(result.stderr_str());
|
|
|
|
result
|
|
.stdout_is(exp_result.stdout_str())
|
|
.code_is(exp_result.code());
|
|
assert_eq!(normalized_stderr, exp_result.stderr_str());
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
#[cfg(any(target_os = "linux", target_os = "android"))]
|
|
fn line() {
|
|
new_ucmd!()
|
|
.args(&["line"])
|
|
.fails()
|
|
.stderr_contains("missing argument to 'line'");
|
|
|
|
new_ucmd!()
|
|
.args(&["line", "-1"])
|
|
.fails()
|
|
.stderr_contains("invalid integer argument: '-1'");
|
|
|
|
new_ucmd!()
|
|
.args(&["line", "256"])
|
|
.fails()
|
|
.stderr_contains("invalid integer argument: '256'");
|
|
}
|
|
|
|
#[test]
|
|
fn min_and_time() {
|
|
new_ucmd!()
|
|
.args(&["min"])
|
|
.fails()
|
|
.stderr_contains("missing argument to 'min'");
|
|
|
|
new_ucmd!()
|
|
.args(&["time"])
|
|
.fails()
|
|
.stderr_contains("missing argument to 'time'");
|
|
|
|
new_ucmd!()
|
|
.args(&["min", "-1"])
|
|
.fails()
|
|
.stderr_contains("invalid integer argument: '-1'");
|
|
|
|
new_ucmd!()
|
|
.args(&["time", "-1"])
|
|
.fails()
|
|
.stderr_contains("invalid integer argument: '-1'");
|
|
|
|
new_ucmd!()
|
|
.args(&["min", "256"])
|
|
.fails()
|
|
.stderr_contains("invalid integer argument: '256': Value too large for defined data type");
|
|
|
|
new_ucmd!()
|
|
.args(&["time", "256"])
|
|
.fails()
|
|
.stderr_contains("invalid integer argument: '256': Value too large for defined data type");
|
|
}
|
|
|
|
#[test]
|
|
fn non_negatable_combo() {
|
|
new_ucmd!()
|
|
.args(&["-dec"])
|
|
.fails()
|
|
.stderr_contains("invalid argument '-dec'");
|
|
new_ucmd!()
|
|
.args(&["-crt"])
|
|
.fails()
|
|
.stderr_contains("invalid argument '-crt'");
|
|
new_ucmd!()
|
|
.args(&["-ek"])
|
|
.fails()
|
|
.stderr_contains("invalid argument '-ek'");
|
|
}
|
|
|
|
#[test]
|
|
fn help_output() {
|
|
new_ucmd!()
|
|
.arg("--help")
|
|
.succeeds()
|
|
.stdout_contains("Usage:")
|
|
.stdout_contains("stty");
|
|
}
|
|
|
|
#[test]
|
|
fn version_output() {
|
|
new_ucmd!()
|
|
.arg("--version")
|
|
.succeeds()
|
|
.stdout_contains("stty");
|
|
}
|
|
|
|
#[test]
|
|
fn invalid_control_char_names() {
|
|
// Test invalid control character names
|
|
new_ucmd!()
|
|
.args(&["notachar", "^C"])
|
|
.fails()
|
|
.stderr_contains("invalid argument 'notachar'");
|
|
}
|
|
|
|
#[test]
|
|
fn control_char_overflow_hex() {
|
|
// Test hex overflow for control characters
|
|
new_ucmd!()
|
|
.args(&["erase", "0xFFF"])
|
|
.fails()
|
|
.stderr_contains("Value too large for defined data type");
|
|
}
|
|
|
|
#[test]
|
|
fn control_char_overflow_octal() {
|
|
// Test octal overflow for control characters
|
|
new_ucmd!()
|
|
.args(&["kill", "0777"])
|
|
.fails()
|
|
.stderr_contains("Value too large for defined data type");
|
|
}
|
|
|
|
#[test]
|
|
fn multiple_invalid_args() {
|
|
// Test multiple invalid arguments
|
|
new_ucmd!()
|
|
.args(&["invalid1", "invalid2"])
|
|
.fails()
|
|
.stderr_contains("invalid argument");
|
|
}
|
|
|
|
#[test]
|
|
#[ignore = "Fails because cargo test does not run in a tty"]
|
|
fn negatable_combo_settings() {
|
|
// These should fail without TTY but validate the argument parsing
|
|
// Testing that negatable combos are recognized (even if they fail later)
|
|
new_ucmd!().args(&["-cbreak"]).fails();
|
|
|
|
new_ucmd!().args(&["-evenp"]).fails();
|
|
|
|
new_ucmd!().args(&["-oddp"]).fails();
|
|
}
|
|
|
|
#[test]
|
|
fn grouped_flag_removal() {
|
|
// Test that removing a grouped flag is invalid
|
|
// cs7 is part of CSIZE group, removing it should fail
|
|
new_ucmd!()
|
|
.args(&["-cs7"])
|
|
.fails()
|
|
.stderr_contains("invalid argument '-cs7'");
|
|
|
|
new_ucmd!()
|
|
.args(&["-cs8"])
|
|
.fails()
|
|
.stderr_contains("invalid argument '-cs8'");
|
|
}
|
|
|
|
#[test]
|
|
#[ignore = "Fails because cargo test does not run in a tty"]
|
|
fn baud_rate_validation() {
|
|
// Test various baud rate formats
|
|
#[cfg(any(
|
|
target_os = "freebsd",
|
|
target_os = "dragonfly",
|
|
target_os = "ios",
|
|
target_os = "macos",
|
|
target_os = "netbsd",
|
|
target_os = "openbsd"
|
|
))]
|
|
{
|
|
// BSD accepts numeric baud rates
|
|
new_ucmd!().args(&["9600"]).fails(); // Fails due to no TTY, but validates parsing
|
|
}
|
|
|
|
// Test ispeed/ospeed with valid baud rates
|
|
new_ucmd!().args(&["ispeed", "9600"]).fails(); // Fails due to no TTY
|
|
new_ucmd!().args(&["ospeed", "115200"]).fails(); // Fails due to no TTY
|
|
}
|
|
|
|
#[test]
|
|
#[ignore = "Fails because cargo test does not run in a tty"]
|
|
fn combination_setting_validation() {
|
|
// Test that combination settings are recognized
|
|
new_ucmd!().args(&["sane"]).fails(); // Fails due to no TTY, but validates parsing
|
|
new_ucmd!().args(&["raw"]).fails();
|
|
new_ucmd!().args(&["cooked"]).fails();
|
|
new_ucmd!().args(&["cbreak"]).fails();
|
|
}
|
|
|
|
#[test]
|
|
#[ignore = "Fails because cargo test does not run in a tty"]
|
|
fn control_char_hat_notation() {
|
|
// Test various hat notation formats
|
|
new_ucmd!().args(&["intr", "^?"]).fails(); // Fails due to no TTY
|
|
new_ucmd!().args(&["quit", "^\\"]).fails();
|
|
new_ucmd!().args(&["erase", "^H"]).fails();
|
|
}
|
|
|
|
#[test]
|
|
#[ignore = "Fails because cargo test does not run in a tty"]
|
|
fn special_settings() {
|
|
// Test special settings that require arguments
|
|
new_ucmd!().args(&["speed"]).fails(); // Fails due to no TTY but validates it's recognized
|
|
|
|
new_ucmd!().args(&["size"]).fails(); // Fails due to no TTY but validates it's recognized
|
|
}
|
|
|
|
#[test]
|
|
fn file_argument() {
|
|
// Test --file argument with non-existent file
|
|
new_ucmd!()
|
|
.args(&["--file", "/nonexistent/device"])
|
|
.fails()
|
|
.stderr_contains("No such file or directory");
|
|
}
|
|
|
|
#[test]
|
|
fn conflicting_print_modes() {
|
|
// Test more conflicting option combinations
|
|
new_ucmd!()
|
|
.args(&["--save", "speed"])
|
|
.fails()
|
|
.stderr_contains("when specifying an output style, modes may not be set");
|
|
|
|
new_ucmd!()
|
|
.args(&["--all", "speed"])
|
|
.fails()
|
|
.stderr_contains("when specifying an output style, modes may not be set");
|
|
}
|
|
|
|
// Additional integration tests to increase coverage
|
|
|
|
#[test]
|
|
#[cfg(unix)]
|
|
#[ignore = "Fails because cargo test does not run in a tty"]
|
|
fn test_save_format() {
|
|
// Test --save flag outputs settings in save format
|
|
let result = new_ucmd!()
|
|
.terminal_simulation(true)
|
|
.args(&["--save"])
|
|
.succeeds();
|
|
// Save format should contain colon-separated fields
|
|
result.stdout_contains(":");
|
|
// Should contain speed information
|
|
let stdout = result.stdout_str();
|
|
assert!(
|
|
stdout.split(':').count() > 1,
|
|
"Save format should have multiple colon-separated fields"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
#[cfg(unix)]
|
|
#[ignore = "Fails because cargo test does not run in a tty"]
|
|
fn test_set_control_flags() {
|
|
// Test setting parenb flag and verify it's set
|
|
new_ucmd!()
|
|
.terminal_simulation(true)
|
|
.args(&["parenb"])
|
|
.succeeds();
|
|
new_ucmd!()
|
|
.terminal_simulation(true)
|
|
.args(&["--all"])
|
|
.succeeds()
|
|
.stdout_contains("parenb");
|
|
|
|
// Test unsetting parenb flag and verify it's unset
|
|
new_ucmd!()
|
|
.terminal_simulation(true)
|
|
.args(&["-parenb"])
|
|
.succeeds();
|
|
new_ucmd!()
|
|
.terminal_simulation(true)
|
|
.args(&["--all"])
|
|
.succeeds()
|
|
.stdout_contains("-parenb");
|
|
|
|
// Test setting parodd flag
|
|
new_ucmd!()
|
|
.terminal_simulation(true)
|
|
.args(&["parodd"])
|
|
.succeeds();
|
|
new_ucmd!()
|
|
.terminal_simulation(true)
|
|
.args(&["--all"])
|
|
.succeeds()
|
|
.stdout_contains("parodd");
|
|
|
|
// Test setting cstopb flag
|
|
new_ucmd!()
|
|
.terminal_simulation(true)
|
|
.args(&["cstopb"])
|
|
.succeeds();
|
|
new_ucmd!()
|
|
.terminal_simulation(true)
|
|
.args(&["--all"])
|
|
.succeeds()
|
|
.stdout_contains("cstopb");
|
|
}
|
|
|
|
// Tests for saved state parsing and restoration
|
|
#[test]
|
|
#[cfg(unix)]
|
|
fn test_save_and_restore() {
|
|
let (path, _controller, _replica) = pty_path();
|
|
let saved = new_ucmd!()
|
|
.args(&["--save", "--file", &path])
|
|
.succeeds()
|
|
.stdout_move_str();
|
|
|
|
let saved = saved.trim();
|
|
assert!(saved.contains(':'));
|
|
|
|
new_ucmd!().args(&["--file", &path, saved]).succeeds();
|
|
}
|
|
|
|
#[test]
|
|
#[cfg(unix)]
|
|
#[ignore = "Fails because cargo test does not run in a tty"]
|
|
fn test_set_input_flags() {
|
|
// Test setting ignbrk flag and verify
|
|
new_ucmd!()
|
|
.terminal_simulation(true)
|
|
.args(&["ignbrk"])
|
|
.succeeds();
|
|
new_ucmd!()
|
|
.terminal_simulation(true)
|
|
.args(&["--all"])
|
|
.succeeds()
|
|
.stdout_contains("ignbrk");
|
|
|
|
// Test setting brkint flag and verify
|
|
new_ucmd!()
|
|
.terminal_simulation(true)
|
|
.args(&["brkint"])
|
|
.succeeds();
|
|
new_ucmd!()
|
|
.terminal_simulation(true)
|
|
.args(&["--all"])
|
|
.succeeds()
|
|
.stdout_contains("brkint");
|
|
|
|
// Test setting ignpar flag and verify
|
|
new_ucmd!()
|
|
.terminal_simulation(true)
|
|
.args(&["ignpar"])
|
|
.succeeds();
|
|
new_ucmd!()
|
|
.terminal_simulation(true)
|
|
.args(&["--all"])
|
|
.succeeds()
|
|
.stdout_contains("ignpar");
|
|
}
|
|
|
|
#[test]
|
|
#[cfg(unix)]
|
|
fn test_save_with_g_flag() {
|
|
let (path, _controller, _replica) = pty_path();
|
|
let saved = new_ucmd!()
|
|
.args(&["-g", "--file", &path])
|
|
.succeeds()
|
|
.stdout_move_str();
|
|
|
|
let saved = saved.trim();
|
|
assert!(saved.contains(':'));
|
|
|
|
new_ucmd!().args(&["--file", &path, saved]).succeeds();
|
|
}
|
|
|
|
#[test]
|
|
#[cfg(unix)]
|
|
#[ignore = "Fails because cargo test does not run in a tty"]
|
|
fn test_set_output_flags() {
|
|
// Test setting opost flag and verify
|
|
new_ucmd!()
|
|
.terminal_simulation(true)
|
|
.args(&["opost"])
|
|
.succeeds();
|
|
new_ucmd!()
|
|
.terminal_simulation(true)
|
|
.args(&["--all"])
|
|
.succeeds()
|
|
.stdout_contains("opost");
|
|
|
|
// Test unsetting opost flag and verify
|
|
new_ucmd!()
|
|
.terminal_simulation(true)
|
|
.args(&["-opost"])
|
|
.succeeds();
|
|
new_ucmd!()
|
|
.terminal_simulation(true)
|
|
.args(&["--all"])
|
|
.succeeds()
|
|
.stdout_contains("-opost");
|
|
|
|
// Test setting onlcr flag and verify
|
|
new_ucmd!()
|
|
.terminal_simulation(true)
|
|
.args(&["onlcr"])
|
|
.succeeds();
|
|
new_ucmd!()
|
|
.terminal_simulation(true)
|
|
.args(&["--all"])
|
|
.succeeds()
|
|
.stdout_contains("onlcr");
|
|
}
|
|
|
|
#[test]
|
|
#[cfg(unix)]
|
|
#[ignore = "Fails because cargo test does not run in a tty"]
|
|
fn test_set_local_flags() {
|
|
// Test setting isig flag and verify
|
|
new_ucmd!()
|
|
.terminal_simulation(true)
|
|
.args(&["isig"])
|
|
.succeeds();
|
|
new_ucmd!()
|
|
.terminal_simulation(true)
|
|
.args(&["--all"])
|
|
.succeeds()
|
|
.stdout_contains("isig");
|
|
|
|
// Test setting icanon flag and verify
|
|
new_ucmd!()
|
|
.terminal_simulation(true)
|
|
.args(&["icanon"])
|
|
.succeeds();
|
|
new_ucmd!()
|
|
.terminal_simulation(true)
|
|
.args(&["--all"])
|
|
.succeeds()
|
|
.stdout_contains("icanon");
|
|
|
|
// Test setting echo flag and verify
|
|
new_ucmd!()
|
|
.terminal_simulation(true)
|
|
.args(&["echo"])
|
|
.succeeds();
|
|
new_ucmd!()
|
|
.terminal_simulation(true)
|
|
.args(&["--all"])
|
|
.succeeds()
|
|
.stdout_contains("echo");
|
|
}
|
|
|
|
#[test]
|
|
#[cfg(unix)]
|
|
#[ignore = "Fails because cargo test does not run in a tty"]
|
|
fn test_combo_cbreak() {
|
|
// Test cbreak combination setting - should disable icanon
|
|
new_ucmd!()
|
|
.terminal_simulation(true)
|
|
.args(&["cbreak"])
|
|
.succeeds();
|
|
new_ucmd!()
|
|
.terminal_simulation(true)
|
|
.args(&["--all"])
|
|
.succeeds()
|
|
.stdout_contains("-icanon");
|
|
|
|
// Test -cbreak should enable icanon
|
|
new_ucmd!()
|
|
.terminal_simulation(true)
|
|
.args(&["-cbreak"])
|
|
.succeeds();
|
|
new_ucmd!()
|
|
.terminal_simulation(true)
|
|
.args(&["--all"])
|
|
.succeeds()
|
|
.stdout_contains("icanon");
|
|
}
|
|
|
|
#[test]
|
|
#[cfg(unix)]
|
|
#[ignore = "Fails because cargo test does not run in a tty"]
|
|
fn test_combo_nl() {
|
|
// Test nl combination setting - should disable icrnl and onlcr
|
|
new_ucmd!()
|
|
.terminal_simulation(true)
|
|
.args(&["nl"])
|
|
.succeeds();
|
|
let result = new_ucmd!()
|
|
.terminal_simulation(true)
|
|
.args(&["--all"])
|
|
.succeeds();
|
|
result.stdout_contains("-icrnl");
|
|
result.stdout_contains("-onlcr");
|
|
}
|
|
|
|
#[test]
|
|
#[cfg(unix)]
|
|
#[ignore = "Fails because cargo test does not run in a tty"]
|
|
fn test_combo_ek() {
|
|
// Test ek combination setting (erase and kill) - should set erase and kill to defaults
|
|
new_ucmd!()
|
|
.terminal_simulation(true)
|
|
.args(&["ek"])
|
|
.succeeds();
|
|
let result = new_ucmd!().terminal_simulation(true).succeeds();
|
|
// Should show erase and kill characters
|
|
result.stdout_contains("erase");
|
|
result.stdout_contains("kill");
|
|
}
|
|
|
|
#[test]
|
|
#[cfg(unix)]
|
|
#[ignore = "Fails because cargo test does not run in a tty"]
|
|
fn test_combo_litout() {
|
|
// Test litout combination setting - should disable parenb, istrip, opost
|
|
new_ucmd!()
|
|
.terminal_simulation(true)
|
|
.args(&["litout"])
|
|
.succeeds();
|
|
let result = new_ucmd!()
|
|
.terminal_simulation(true)
|
|
.args(&["--all"])
|
|
.succeeds();
|
|
result.stdout_contains("-parenb");
|
|
result.stdout_contains("-istrip");
|
|
result.stdout_contains("-opost");
|
|
}
|
|
|
|
#[test]
|
|
#[cfg(unix)]
|
|
#[ignore = "Fails because cargo test does not run in a tty"]
|
|
fn test_combo_pass8() {
|
|
// Test pass8 combination setting - should disable parenb, istrip, set cs8
|
|
new_ucmd!()
|
|
.terminal_simulation(true)
|
|
.args(&["pass8"])
|
|
.succeeds();
|
|
let result = new_ucmd!()
|
|
.terminal_simulation(true)
|
|
.args(&["--all"])
|
|
.succeeds();
|
|
result.stdout_contains("-parenb");
|
|
result.stdout_contains("-istrip");
|
|
result.stdout_contains("cs8");
|
|
}
|
|
|
|
#[test]
|
|
#[cfg(unix)]
|
|
fn test_save_restore_after_change() {
|
|
let (path, _controller, _replica) = pty_path();
|
|
let saved = new_ucmd!()
|
|
.args(&["--save", "--file", &path])
|
|
.succeeds()
|
|
.stdout_move_str();
|
|
|
|
let saved = saved.trim();
|
|
|
|
new_ucmd!()
|
|
.args(&["--file", &path, "intr", "^A"])
|
|
.succeeds();
|
|
|
|
new_ucmd!().args(&["--file", &path, saved]).succeeds();
|
|
|
|
new_ucmd!()
|
|
.args(&["--file", &path])
|
|
.succeeds()
|
|
.stdout_str_check(|s| !s.contains("intr = ^A"));
|
|
}
|
|
|
|
// These tests both validate what we expect each input to return and their error codes
|
|
// and also use the GNU coreutils results to validate our results match expectations
|
|
#[test]
|
|
#[cfg(unix)]
|
|
fn test_saved_state_valid_formats() {
|
|
let (path, _controller, _replica) = pty_path();
|
|
let (_at, ts) = at_and_ts!();
|
|
|
|
// Generate valid saved state from the actual terminal
|
|
let saved = unwrap_or_return!(expected_result(&ts, &["-g", "--file", &path])).stdout_move_str();
|
|
let saved = saved.trim();
|
|
|
|
let result = ts.ucmd().args(&["--file", &path, saved]).run();
|
|
|
|
result.success().no_stderr();
|
|
|
|
let exp_result = unwrap_or_return!(expected_result(&ts, &["--file", &path, saved]));
|
|
let normalized_stderr = normalize_stderr(result.stderr_str());
|
|
result
|
|
.stdout_is(exp_result.stdout_str())
|
|
.code_is(exp_result.code());
|
|
assert_eq!(normalized_stderr, exp_result.stderr_str());
|
|
}
|
|
|
|
#[test]
|
|
#[cfg(unix)]
|
|
#[ignore = "Fails because cargo test does not run in a tty"]
|
|
fn test_combo_decctlq() {
|
|
// Test decctlq combination setting - should enable ixany
|
|
new_ucmd!()
|
|
.terminal_simulation(true)
|
|
.args(&["decctlq"])
|
|
.succeeds();
|
|
new_ucmd!()
|
|
.terminal_simulation(true)
|
|
.args(&["--all"])
|
|
.succeeds()
|
|
.stdout_contains("ixany");
|
|
}
|
|
|
|
#[test]
|
|
#[cfg(unix)]
|
|
#[ignore = "Fails because cargo test does not run in a tty"]
|
|
fn test_combo_dec() {
|
|
// Test dec combination setting - should set multiple flags
|
|
new_ucmd!()
|
|
.terminal_simulation(true)
|
|
.args(&["dec"])
|
|
.succeeds();
|
|
let result = new_ucmd!()
|
|
.terminal_simulation(true)
|
|
.args(&["--all"])
|
|
.succeeds();
|
|
// dec sets echoe, echoctl, echoke
|
|
result.stdout_contains("echoe");
|
|
}
|
|
|
|
#[test]
|
|
#[cfg(unix)]
|
|
#[ignore = "Fails because cargo test does not run in a tty"]
|
|
fn test_combo_crt() {
|
|
// Test crt combination setting - should set echoe
|
|
new_ucmd!()
|
|
.terminal_simulation(true)
|
|
.args(&["crt"])
|
|
.succeeds();
|
|
new_ucmd!()
|
|
.terminal_simulation(true)
|
|
.args(&["--all"])
|
|
.succeeds()
|
|
.stdout_contains("echoe");
|
|
}
|
|
|
|
#[test]
|
|
#[cfg(unix)]
|
|
#[ignore = "Fails because cargo test does not run in a tty"]
|
|
fn test_multiple_settings() {
|
|
// Test setting multiple flags at once and verify all are set
|
|
new_ucmd!()
|
|
.terminal_simulation(true)
|
|
.args(&["parenb", "parodd", "cs7"])
|
|
.succeeds();
|
|
let result = new_ucmd!()
|
|
.terminal_simulation(true)
|
|
.args(&["--all"])
|
|
.succeeds();
|
|
result.stdout_contains("parenb");
|
|
result.stdout_contains("parodd");
|
|
result.stdout_contains("cs7");
|
|
}
|
|
|
|
#[test]
|
|
#[cfg(unix)]
|
|
#[ignore = "Fails because cargo test does not run in a tty"]
|
|
fn test_set_all_control_chars() {
|
|
// Test setting intr control character and verify
|
|
new_ucmd!()
|
|
.terminal_simulation(true)
|
|
.args(&["intr", "^C"])
|
|
.succeeds();
|
|
new_ucmd!()
|
|
.terminal_simulation(true)
|
|
.succeeds()
|
|
.stdout_contains("intr = ^C");
|
|
|
|
// Test setting quit control character and verify
|
|
new_ucmd!()
|
|
.terminal_simulation(true)
|
|
.args(&["quit", "^\\"])
|
|
.succeeds();
|
|
new_ucmd!()
|
|
.terminal_simulation(true)
|
|
.succeeds()
|
|
.stdout_contains("quit = ^\\");
|
|
|
|
// Test setting erase control character and verify
|
|
new_ucmd!()
|
|
.terminal_simulation(true)
|
|
.args(&["erase", "^?"])
|
|
.succeeds();
|
|
new_ucmd!()
|
|
.terminal_simulation(true)
|
|
.succeeds()
|
|
.stdout_contains("erase = ^?");
|
|
|
|
// Test setting kill control character and verify
|
|
new_ucmd!()
|
|
.terminal_simulation(true)
|
|
.args(&["kill", "^U"])
|
|
.succeeds();
|
|
new_ucmd!()
|
|
.terminal_simulation(true)
|
|
.succeeds()
|
|
.stdout_contains("kill = ^U");
|
|
}
|
|
|
|
#[test]
|
|
#[cfg(unix)]
|
|
#[ignore = "Fails because cargo test does not run in a tty"]
|
|
fn test_print_size() {
|
|
// Test size print setting - should output "rows <num>; columns <num>;"
|
|
let result = new_ucmd!()
|
|
.terminal_simulation(true)
|
|
.args(&["size"])
|
|
.succeeds();
|
|
result.stdout_contains("rows");
|
|
result.stdout_contains("columns");
|
|
}
|
|
|
|
#[test]
|
|
#[cfg(unix)]
|
|
#[ignore = "Fails because cargo test does not run in a tty"]
|
|
fn test_print_speed() {
|
|
// Test speed print setting - should output a numeric speed
|
|
let result = new_ucmd!()
|
|
.terminal_simulation(true)
|
|
.args(&["speed"])
|
|
.succeeds();
|
|
// Speed should be a number (common speeds: 9600, 38400, 115200, etc.)
|
|
let stdout = result.stdout_str();
|
|
assert!(
|
|
stdout.trim().parse::<u32>().is_ok(),
|
|
"Speed should be a numeric value"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
#[cfg(unix)]
|
|
#[ignore = "Fails because cargo test does not run in a tty"]
|
|
fn test_set_rows_cols() {
|
|
// Test setting rows and verify
|
|
new_ucmd!()
|
|
.terminal_simulation(true)
|
|
.args(&["rows", "24"])
|
|
.succeeds();
|
|
new_ucmd!()
|
|
.terminal_simulation(true)
|
|
.args(&["size"])
|
|
.succeeds()
|
|
.stdout_contains("rows 24");
|
|
|
|
// Test setting cols and verify
|
|
new_ucmd!()
|
|
.terminal_simulation(true)
|
|
.args(&["cols", "80"])
|
|
.succeeds();
|
|
new_ucmd!()
|
|
.terminal_simulation(true)
|
|
.args(&["size"])
|
|
.succeeds()
|
|
.stdout_contains("columns 80");
|
|
|
|
// Test setting both rows and cols together
|
|
new_ucmd!()
|
|
.terminal_simulation(true)
|
|
.args(&["rows", "50", "cols", "100"])
|
|
.succeeds();
|
|
let result = new_ucmd!()
|
|
.terminal_simulation(true)
|
|
.args(&["size"])
|
|
.succeeds();
|
|
result.stdout_contains("rows 50");
|
|
result.stdout_contains("columns 100");
|
|
}
|
|
|
|
#[test]
|
|
#[cfg(unix)]
|
|
#[ignore = "Fails because cargo test does not run in a tty"]
|
|
fn test_character_size_settings() {
|
|
// Test cs5 setting and verify
|
|
new_ucmd!()
|
|
.terminal_simulation(true)
|
|
.args(&["cs5"])
|
|
.succeeds();
|
|
new_ucmd!()
|
|
.terminal_simulation(true)
|
|
.args(&["--all"])
|
|
.succeeds()
|
|
.stdout_contains("cs5");
|
|
|
|
// Test cs7 setting and verify
|
|
new_ucmd!()
|
|
.terminal_simulation(true)
|
|
.args(&["cs7"])
|
|
.succeeds();
|
|
new_ucmd!()
|
|
.terminal_simulation(true)
|
|
.args(&["--all"])
|
|
.succeeds()
|
|
.stdout_contains("cs7");
|
|
|
|
// Test cs8 setting and verify
|
|
new_ucmd!()
|
|
.terminal_simulation(true)
|
|
.args(&["cs8"])
|
|
.succeeds();
|
|
new_ucmd!()
|
|
.terminal_simulation(true)
|
|
.args(&["--all"])
|
|
.succeeds()
|
|
.stdout_contains("cs8");
|
|
}
|
|
|
|
#[test]
|
|
#[cfg(unix)]
|
|
#[ignore = "Fails because cargo test does not run in a tty"]
|
|
fn test_baud_rate_settings() {
|
|
// Test setting ispeed and verify
|
|
new_ucmd!()
|
|
.terminal_simulation(true)
|
|
.args(&["ispeed", "9600"])
|
|
.succeeds();
|
|
new_ucmd!()
|
|
.terminal_simulation(true)
|
|
.args(&["speed"])
|
|
.succeeds()
|
|
.stdout_contains("9600");
|
|
|
|
// Test setting both ispeed and ospeed
|
|
new_ucmd!()
|
|
.terminal_simulation(true)
|
|
.args(&["ispeed", "38400", "ospeed", "38400"])
|
|
.succeeds();
|
|
new_ucmd!()
|
|
.terminal_simulation(true)
|
|
.args(&["speed"])
|
|
.succeeds()
|
|
.stdout_contains("38400");
|
|
}
|
|
|
|
#[test]
|
|
#[cfg(unix)]
|
|
#[ignore = "Fails because cargo test does not run in a tty"]
|
|
fn test_min_time_settings() {
|
|
// Test min setting and verify
|
|
new_ucmd!()
|
|
.terminal_simulation(true)
|
|
.args(&["min", "1"])
|
|
.succeeds();
|
|
new_ucmd!()
|
|
.terminal_simulation(true)
|
|
.args(&["--all"])
|
|
.succeeds()
|
|
.stdout_contains("min = 1");
|
|
|
|
// Test time setting and verify
|
|
new_ucmd!()
|
|
.terminal_simulation(true)
|
|
.args(&["time", "10"])
|
|
.succeeds();
|
|
new_ucmd!()
|
|
.terminal_simulation(true)
|
|
.args(&["--all"])
|
|
.succeeds()
|
|
.stdout_contains("time = 10");
|
|
}
|
|
|
|
#[test]
|
|
#[cfg(unix)]
|
|
#[ignore = "Fails because cargo test does not run in a tty"]
|
|
fn test_complex_scenario() {
|
|
// Test a complex scenario with multiple settings and verify all are applied
|
|
new_ucmd!()
|
|
.terminal_simulation(true)
|
|
.args(&["sane", "rows", "24", "cols", "80", "intr", "^C"])
|
|
.succeeds();
|
|
|
|
// Verify all settings were applied
|
|
let size_result = new_ucmd!()
|
|
.terminal_simulation(true)
|
|
.args(&["size"])
|
|
.succeeds();
|
|
size_result.stdout_contains("rows 24");
|
|
size_result.stdout_contains("columns 80");
|
|
|
|
let result = new_ucmd!().terminal_simulation(true).succeeds();
|
|
result.stdout_contains("intr = ^C");
|
|
}
|
|
|
|
#[test]
|
|
#[cfg(unix)]
|
|
#[ignore = "Fails because cargo test does not run in a tty"]
|
|
fn test_raw_mode() {
|
|
// Test raw mode setting
|
|
new_ucmd!()
|
|
.terminal_simulation(true)
|
|
.args(&["raw"])
|
|
.succeeds();
|
|
// Verify raw mode is set by checking output
|
|
let result = new_ucmd!()
|
|
.terminal_simulation(true)
|
|
.args(&["--all"])
|
|
.succeeds();
|
|
result.stdout_contains("-icanon");
|
|
}
|
|
|
|
#[test]
|
|
#[cfg(unix)]
|
|
#[ignore = "Fails because cargo test does not run in a tty"]
|
|
fn test_cooked_mode() {
|
|
// Test cooked mode setting (opposite of raw)
|
|
new_ucmd!()
|
|
.terminal_simulation(true)
|
|
.args(&["cooked"])
|
|
.succeeds();
|
|
// Verify cooked mode is set
|
|
let result = new_ucmd!()
|
|
.terminal_simulation(true)
|
|
.args(&["--all"])
|
|
.succeeds();
|
|
result.stdout_contains("icanon");
|
|
}
|
|
|
|
#[test]
|
|
#[cfg(unix)]
|
|
fn test_saved_state_invalid_formats() {
|
|
let (path, _controller, _replica) = pty_path();
|
|
let (_at, ts) = at_and_ts!();
|
|
|
|
let num_cc = libc::NCCS;
|
|
|
|
// Build test strings with platform-specific counts
|
|
let cc_zeros = vec!["0"; num_cc].join(":");
|
|
let cc_with_invalid = if num_cc > 0 {
|
|
let mut parts = vec!["1c"; num_cc];
|
|
parts[0] = "100"; // First control char > 255
|
|
parts.join(":")
|
|
} else {
|
|
String::new()
|
|
};
|
|
let cc_with_space = if num_cc > 0 {
|
|
let mut parts = vec!["1c"; num_cc];
|
|
parts[0] = "1c "; // Space in hex
|
|
parts.join(":")
|
|
} else {
|
|
String::new()
|
|
};
|
|
let cc_with_nonhex = if num_cc > 0 {
|
|
let mut parts = vec!["1c"; num_cc];
|
|
parts[0] = "xyz"; // Non-hex
|
|
parts.join(":")
|
|
} else {
|
|
String::new()
|
|
};
|
|
let cc_with_empty = if num_cc > 0 {
|
|
let mut parts = vec!["1c"; num_cc];
|
|
parts[0] = ""; // Empty
|
|
parts.join(":")
|
|
} else {
|
|
String::new()
|
|
};
|
|
|
|
// Cannot test single value since it would be interpreted as baud rate
|
|
let invalid_states = vec![
|
|
"500:5:4bf".to_string(), // fewer than expected parts
|
|
"500:5:4bf:8a3b".to_string(), // only 4 parts
|
|
format!("500:5:{}:8a3b:{}", cc_zeros, "extra"), // too many parts
|
|
format!("500::4bf:8a3b:{}", cc_zeros), // empty hex value in flags
|
|
format!("500:5:4bf:8a3b:{}", cc_with_empty), // empty hex value in cc
|
|
format!("500:5:4bf:8a3b:{}", cc_with_nonhex), // non-hex characters
|
|
format!("500:5:4bf:8a3b:{}", cc_with_space), // space in hex value
|
|
format!("500:5:4bf:8a3b:{}", cc_with_invalid), // control char > 255
|
|
];
|
|
|
|
for state in &invalid_states {
|
|
let result = ts.ucmd().args(&["--file", &path, state]).run();
|
|
|
|
result.failure().stderr_contains("invalid argument");
|
|
|
|
let exp_result = unwrap_or_return!(expected_result(&ts, &["--file", &path, state]));
|
|
let normalized_stderr = normalize_stderr(result.stderr_str());
|
|
let exp_normalized_stderr = normalize_stderr(exp_result.stderr_str());
|
|
result
|
|
.stdout_is(exp_result.stdout_str())
|
|
.code_is(exp_result.code());
|
|
assert_eq!(normalized_stderr, exp_normalized_stderr);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
#[cfg(unix)]
|
|
#[ignore = "Fails because cargo test does not run in a tty"]
|
|
fn test_parity_settings() {
|
|
// Test evenp setting and verify (should set parenb and cs7)
|
|
new_ucmd!()
|
|
.terminal_simulation(true)
|
|
.args(&["evenp"])
|
|
.succeeds();
|
|
let result = new_ucmd!()
|
|
.terminal_simulation(true)
|
|
.args(&["--all"])
|
|
.succeeds();
|
|
result.stdout_contains("parenb");
|
|
result.stdout_contains("cs7");
|
|
|
|
// Test oddp setting and verify (should set parenb, parodd, and cs7)
|
|
new_ucmd!()
|
|
.terminal_simulation(true)
|
|
.args(&["oddp"])
|
|
.succeeds();
|
|
let result = new_ucmd!()
|
|
.terminal_simulation(true)
|
|
.args(&["--all"])
|
|
.succeeds();
|
|
result.stdout_contains("parenb");
|
|
result.stdout_contains("parodd");
|
|
result.stdout_contains("cs7");
|
|
}
|
|
|
|
// Additional integration tests for missing coverage
|
|
|
|
#[test]
|
|
fn missing_arg_ispeed() {
|
|
// Test missing argument for ispeed
|
|
new_ucmd!()
|
|
.args(&["ispeed"])
|
|
.fails()
|
|
.stderr_contains("missing argument")
|
|
.stderr_contains("ispeed");
|
|
}
|
|
|
|
#[test]
|
|
fn missing_arg_ospeed() {
|
|
// Test missing argument for ospeed
|
|
new_ucmd!()
|
|
.args(&["ospeed"])
|
|
.fails()
|
|
.stderr_contains("missing argument")
|
|
.stderr_contains("ospeed");
|
|
}
|
|
|
|
#[test]
|
|
fn missing_arg_line() {
|
|
// Test missing argument for line
|
|
new_ucmd!()
|
|
.args(&["line"])
|
|
.fails()
|
|
.stderr_contains("missing argument")
|
|
.stderr_contains("line");
|
|
}
|
|
|
|
#[test]
|
|
fn missing_arg_min() {
|
|
// Test missing argument for min
|
|
new_ucmd!()
|
|
.args(&["min"])
|
|
.fails()
|
|
.stderr_contains("missing argument")
|
|
.stderr_contains("min");
|
|
}
|
|
|
|
#[test]
|
|
fn missing_arg_time() {
|
|
// Test missing argument for time
|
|
new_ucmd!()
|
|
.args(&["time"])
|
|
.fails()
|
|
.stderr_contains("missing argument")
|
|
.stderr_contains("time");
|
|
}
|
|
|
|
#[test]
|
|
fn missing_arg_rows() {
|
|
// Test missing argument for rows
|
|
new_ucmd!()
|
|
.args(&["rows"])
|
|
.fails()
|
|
.stderr_contains("missing argument")
|
|
.stderr_contains("rows");
|
|
}
|
|
|
|
#[test]
|
|
fn missing_arg_cols() {
|
|
// Test missing argument for cols
|
|
new_ucmd!()
|
|
.args(&["cols"])
|
|
.fails()
|
|
.stderr_contains("missing argument")
|
|
.stderr_contains("cols");
|
|
}
|
|
|
|
#[test]
|
|
fn missing_arg_columns() {
|
|
// Test missing argument for columns
|
|
new_ucmd!()
|
|
.args(&["columns"])
|
|
.fails()
|
|
.stderr_contains("missing argument")
|
|
.stderr_contains("columns");
|
|
}
|
|
|
|
#[test]
|
|
fn missing_arg_control_char() {
|
|
// Test missing argument for control character
|
|
new_ucmd!()
|
|
.args(&["intr"])
|
|
.fails()
|
|
.stderr_contains("missing argument")
|
|
.stderr_contains("intr");
|
|
|
|
new_ucmd!()
|
|
.args(&["erase"])
|
|
.fails()
|
|
.stderr_contains("missing argument")
|
|
.stderr_contains("erase");
|
|
}
|
|
|
|
#[test]
|
|
fn invalid_integer_rows() {
|
|
// Test invalid integer for rows
|
|
new_ucmd!()
|
|
.args(&["rows", "abc"])
|
|
.fails()
|
|
.stderr_contains("invalid integer argument");
|
|
|
|
new_ucmd!()
|
|
.args(&["rows", "-1"])
|
|
.fails()
|
|
.stderr_contains("invalid integer argument");
|
|
}
|
|
|
|
#[test]
|
|
fn invalid_integer_cols() {
|
|
// Test invalid integer for cols
|
|
new_ucmd!()
|
|
.args(&["cols", "xyz"])
|
|
.fails()
|
|
.stderr_contains("invalid integer argument");
|
|
|
|
new_ucmd!()
|
|
.args(&["columns", "12.5"])
|
|
.fails()
|
|
.stderr_contains("invalid integer argument");
|
|
}
|
|
|
|
#[test]
|
|
fn invalid_min_value() {
|
|
// Test invalid min value
|
|
new_ucmd!()
|
|
.args(&["min", "256"])
|
|
.fails()
|
|
.stderr_contains("Value too large");
|
|
|
|
new_ucmd!()
|
|
.args(&["min", "-1"])
|
|
.fails()
|
|
.stderr_contains("invalid integer argument");
|
|
}
|
|
|
|
#[test]
|
|
fn invalid_time_value() {
|
|
// Test invalid time value
|
|
new_ucmd!()
|
|
.args(&["time", "1000"])
|
|
.fails()
|
|
.stderr_contains("Value too large");
|
|
|
|
new_ucmd!()
|
|
.args(&["time", "abc"])
|
|
.fails()
|
|
.stderr_contains("invalid integer argument");
|
|
}
|
|
|
|
#[test]
|
|
fn invalid_baud_rate() {
|
|
// Test invalid baud rate for ispeed (non-numeric string)
|
|
// spell-checker:ignore notabaud
|
|
new_ucmd!()
|
|
.args(&["ispeed", "notabaud"])
|
|
.fails()
|
|
.stderr_contains("invalid ispeed");
|
|
|
|
// On non-BSD systems, test invalid numeric baud rate
|
|
// On BSD systems, any u32 is accepted, so we skip this test
|
|
#[cfg(not(any(
|
|
target_os = "freebsd",
|
|
target_os = "dragonfly",
|
|
target_os = "ios",
|
|
target_os = "macos",
|
|
target_os = "netbsd",
|
|
target_os = "openbsd"
|
|
)))]
|
|
{
|
|
new_ucmd!()
|
|
.args(&["ospeed", "999999999"])
|
|
.fails()
|
|
.stderr_contains("invalid ospeed");
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn control_char_multiple_chars_error() {
|
|
// Test that control characters with multiple chars fail
|
|
new_ucmd!()
|
|
.args(&["intr", "ABC"])
|
|
.fails()
|
|
.stderr_contains("invalid integer argument");
|
|
}
|
|
|
|
#[test]
|
|
fn control_char_decimal_overflow() {
|
|
// Test decimal overflow for control characters
|
|
new_ucmd!()
|
|
.args(&["quit", "256"])
|
|
.fails()
|
|
.stderr_contains("Value too large");
|
|
|
|
// spell-checker:ignore susp
|
|
new_ucmd!()
|
|
.args(&["susp", "1000"])
|
|
.fails()
|
|
.stderr_contains("Value too large");
|
|
}
|
|
|
|
#[test]
|
|
#[cfg(unix)]
|
|
#[ignore = "Fails because the implementation of print state is not correctly printing flags on certain platforms"]
|
|
fn test_saved_state_with_control_chars() {
|
|
let (path, _controller, _replica) = pty_path();
|
|
let (_at, ts) = at_and_ts!();
|
|
|
|
// Build a valid saved state with platform-specific number of control characters
|
|
let num_cc = libc::NCCS;
|
|
let cc_values: Vec<String> = (1..=num_cc).map(|_| format!("{:x}", 0)).collect();
|
|
let saved_state = format!("500:5:4bf:8a3b:{}", cc_values.join(":"));
|
|
|
|
ts.ucmd().args(&["--file", &path, &saved_state]).succeeds();
|
|
|
|
let result = ts.ucmd().args(&["-g", "--file", &path]).run();
|
|
|
|
result.success().stdout_contains(":");
|
|
|
|
let exp_result = unwrap_or_return!(expected_result(&ts, &["-g", "--file", &path]));
|
|
result
|
|
.stdout_is(exp_result.stdout_str())
|
|
.stderr_is(exp_result.stderr_str())
|
|
.code_is(exp_result.code());
|
|
}
|
|
|
|
// Per POSIX, stty uses stdin for TTY operations. When stdin is a pipe, it should fail.
|
|
#[test]
|
|
#[cfg(unix)]
|
|
fn test_stdin_not_tty_fails() {
|
|
// ENOTTY error message varies by platform/libc:
|
|
// - glibc: "Inappropriate ioctl for device"
|
|
// - musl: "Not a tty"
|
|
// - Android: "Not a typewriter"
|
|
#[cfg(target_os = "android")]
|
|
let expected_error = "standard input: Not a typewriter";
|
|
#[cfg(all(not(target_os = "android"), target_env = "musl"))]
|
|
let expected_error = "standard input: Not a tty";
|
|
#[cfg(all(not(target_os = "android"), not(target_env = "musl")))]
|
|
let expected_error = "standard input: Inappropriate ioctl for device";
|
|
|
|
new_ucmd!()
|
|
.pipe_in("")
|
|
.fails()
|
|
.stderr_contains(expected_error);
|
|
}
|
|
|
|
// Test that stty uses stdin for TTY operations per POSIX.
|
|
// Verifies: output redirection (#8012), save/restore pattern (#8608), stdin redirection (#8848)
|
|
#[test]
|
|
#[cfg(unix)]
|
|
fn test_stty_uses_stdin() {
|
|
use std::fs::File;
|
|
use std::process::Stdio;
|
|
|
|
let (path, _controller, _replica) = pty_path();
|
|
|
|
// Output redirection: stty > file (stdin is still TTY)
|
|
let stdin = File::open(&path).unwrap();
|
|
new_ucmd!()
|
|
.set_stdin(stdin)
|
|
.set_stdout(Stdio::piped())
|
|
.succeeds()
|
|
.stdout_contains("speed");
|
|
|
|
// Save/restore: stty $(stty -g) pattern
|
|
let stdin = File::open(&path).unwrap();
|
|
let saved = new_ucmd!()
|
|
.arg("-g")
|
|
.set_stdin(stdin)
|
|
.set_stdout(Stdio::piped())
|
|
.succeeds()
|
|
.stdout_str()
|
|
.trim()
|
|
.to_string();
|
|
assert!(saved.contains(':'), "Expected colon-separated saved state");
|
|
|
|
let stdin = File::open(&path).unwrap();
|
|
new_ucmd!().arg(&saved).set_stdin(stdin).succeeds();
|
|
|
|
// Stdin redirection: stty rows 30 cols 100 < /dev/pts/N
|
|
let stdin = File::open(&path).unwrap();
|
|
new_ucmd!()
|
|
.args(&["rows", "30", "cols", "100"])
|
|
.set_stdin(stdin)
|
|
.succeeds();
|
|
|
|
let stdin = File::open(&path).unwrap();
|
|
new_ucmd!()
|
|
.arg("--all")
|
|
.set_stdin(stdin)
|
|
.succeeds()
|
|
.stdout_contains("rows 30")
|
|
.stdout_contains("columns 100");
|
|
}
|
|
|
|
#[test]
|
|
#[cfg(unix)]
|
|
fn test_ispeed_ospeed_valid_speeds() {
|
|
let (path, _controller, _replica) = pty_path();
|
|
let (_at, ts) = at_and_ts!();
|
|
|
|
// Test various valid baud rates for both ispeed and ospeed
|
|
let test_cases = [
|
|
("ispeed", "50"),
|
|
("ispeed", "9600"),
|
|
("ispeed", "19200"),
|
|
("ospeed", "1200"),
|
|
("ospeed", "9600"),
|
|
("ospeed", "38400"),
|
|
];
|
|
|
|
for (arg, speed) in test_cases {
|
|
let result = ts.ucmd().args(&["--file", &path, arg, speed]).run();
|
|
let exp_result = unwrap_or_return!(expected_result(&ts, &["--file", &path, arg, speed]));
|
|
let normalized_stderr = normalize_stderr(result.stderr_str());
|
|
|
|
result
|
|
.stdout_is(exp_result.stdout_str())
|
|
.code_is(exp_result.code());
|
|
assert_eq!(normalized_stderr, exp_result.stderr_str());
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
#[cfg(all(
|
|
unix,
|
|
not(any(
|
|
target_os = "freebsd",
|
|
target_os = "dragonfly",
|
|
target_os = "ios",
|
|
target_os = "macos",
|
|
target_os = "netbsd",
|
|
target_os = "openbsd"
|
|
))
|
|
))]
|
|
#[ignore = "Issue: #9547"]
|
|
fn test_ispeed_ospeed_invalid_speeds() {
|
|
let (path, _controller, _replica) = pty_path();
|
|
let (_at, ts) = at_and_ts!();
|
|
|
|
// Test invalid speed values (non-standard baud rates)
|
|
let test_cases = [
|
|
("ispeed", "12345"),
|
|
("ospeed", "99999"),
|
|
("ispeed", "abc"),
|
|
("ospeed", "xyz"),
|
|
];
|
|
|
|
for (arg, speed) in test_cases {
|
|
let result = ts.ucmd().args(&["--file", &path, arg, speed]).run();
|
|
let exp_result = unwrap_or_return!(expected_result(&ts, &["--file", &path, arg, speed]));
|
|
let normalized_stderr = normalize_stderr(result.stderr_str());
|
|
|
|
result
|
|
.stdout_is(exp_result.stdout_str())
|
|
.code_is(exp_result.code());
|
|
assert_eq!(normalized_stderr, exp_result.stderr_str());
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
#[cfg(unix)]
|
|
fn test_columns_env_wrapping() {
|
|
use std::process::Stdio;
|
|
let (path, _controller, _replica) = pty_path();
|
|
|
|
// Must pipe output so stty uses COLUMNS env instead of actual terminal size
|
|
for (columns, max_len) in [(20, 20), (40, 40), (50, 50)] {
|
|
let result = new_ucmd!()
|
|
.args(&["--all", "--file", &path])
|
|
.env("COLUMNS", columns.to_string())
|
|
.set_stdout(Stdio::piped())
|
|
.succeeds();
|
|
|
|
for line in result.stdout_str().lines() {
|
|
assert!(
|
|
line.len() <= max_len,
|
|
"Line exceeds COLUMNS={columns}: '{line}'"
|
|
);
|
|
}
|
|
}
|
|
|
|
// Wide columns should allow longer lines
|
|
let result = new_ucmd!()
|
|
.args(&["--all", "--file", &path])
|
|
.env("COLUMNS", "200")
|
|
.set_stdout(Stdio::piped())
|
|
.succeeds();
|
|
let has_long_line = result.stdout_str().lines().any(|line| line.len() > 80);
|
|
assert!(
|
|
has_long_line,
|
|
"Expected at least one line longer than 80 chars with COLUMNS=200"
|
|
);
|
|
|
|
// Invalid values should fall back to default
|
|
for invalid in ["invalid", "0", "-10"] {
|
|
new_ucmd!()
|
|
.args(&["--all", "--file", &path])
|
|
.env("COLUMNS", invalid)
|
|
.set_stdout(Stdio::piped())
|
|
.succeeds();
|
|
}
|
|
|
|
// Without --all flag
|
|
let result = new_ucmd!()
|
|
.args(&["--file", &path])
|
|
.env("COLUMNS", "30")
|
|
.set_stdout(Stdio::piped())
|
|
.succeeds();
|
|
for line in result.stdout_str().lines() {
|
|
assert!(
|
|
line.len() <= 30,
|
|
"Line exceeds COLUMNS=30 without --all: '{line}'"
|
|
);
|
|
}
|
|
}
|