blob: 35cc7ab7396a6991d38df54ed1162f62d076b3b5 [file] [log] [blame] [edit]
use std::os::unix::io::{AsFd, AsRawFd};
use tempfile::tempfile;
use nix::errno::Errno;
use nix::fcntl;
use nix::pty::openpty;
use nix::sys::termios::{self, tcgetattr, BaudRate, LocalFlags, OutputFlags};
use nix::unistd::{read, write};
/// Helper function analogous to `std::io::Write::write_all`, but for `Fd`s
fn write_all<Fd: AsFd>(f: Fd, buf: &[u8]) {
let mut len = 0;
while len < buf.len() {
len += write(f.as_fd(), &buf[len..]).unwrap();
}
}
#[test]
fn test_baudrate_try_from() {
assert_eq!(Ok(BaudRate::B0), BaudRate::try_from(libc::B0));
#[cfg(not(target_os = "haiku"))]
BaudRate::try_from(999999999).expect_err("assertion failed");
#[cfg(target_os = "haiku")]
BaudRate::try_from(99).expect_err("assertion failed");
}
// Test tcgetattr on a terminal
#[test]
fn test_tcgetattr_pty() {
// openpty uses ptname(3) internally
let _m = crate::PTSNAME_MTX.lock();
let pty = openpty(None, None).expect("openpty failed");
termios::tcgetattr(&pty.slave).unwrap();
}
// Test tcgetattr on something that isn't a terminal
#[test]
fn test_tcgetattr_enotty() {
let file = tempfile().unwrap();
assert_eq!(termios::tcgetattr(&file).err(), Some(Errno::ENOTTY));
}
// Test modifying output flags
#[test]
fn test_output_flags() {
// openpty uses ptname(3) internally
let _m = crate::PTSNAME_MTX.lock();
// Open one pty to get attributes for the second one
let mut termios = {
let pty = openpty(None, None).expect("openpty failed");
tcgetattr(&pty.slave).expect("tcgetattr failed")
};
// Make sure postprocessing '\r' isn't specified by default or this test is useless.
assert!(!termios
.output_flags
.contains(OutputFlags::OPOST | OutputFlags::OCRNL));
// Specify that '\r' characters should be transformed to '\n'
// OPOST is specified to enable post-processing
termios
.output_flags
.insert(OutputFlags::OPOST | OutputFlags::OCRNL);
// Open a pty
let pty = openpty(None, &termios).unwrap();
// Write into the master
let string = "foofoofoo\r";
write_all(&pty.master, string.as_bytes());
// Read from the slave verifying that the output has been properly transformed
let mut buf = [0u8; 10];
crate::read_exact(&pty.slave, &mut buf);
let transformed_string = "foofoofoo\n";
assert_eq!(&buf, transformed_string.as_bytes());
}
// Test modifying local flags
#[test]
fn test_local_flags() {
// openpty uses ptname(3) internally
let _m = crate::PTSNAME_MTX.lock();
// Open one pty to get attributes for the second one
let mut termios = {
let pty = openpty(None, None).unwrap();
tcgetattr(&pty.slave).unwrap()
};
// Make sure echo is specified by default or this test is useless.
assert!(termios.local_flags.contains(LocalFlags::ECHO));
// Disable local echo
termios.local_flags.remove(LocalFlags::ECHO);
// Open a new pty with our modified termios settings
let pty = openpty(None, &termios).unwrap();
// Set the master is in nonblocking mode or reading will never return.
let flags = fcntl::fcntl(pty.master.as_raw_fd(), fcntl::F_GETFL).unwrap();
let new_flags =
fcntl::OFlag::from_bits_truncate(flags) | fcntl::OFlag::O_NONBLOCK;
fcntl::fcntl(pty.master.as_raw_fd(), fcntl::F_SETFL(new_flags)).unwrap();
// Write into the master
let string = "foofoofoo\r";
write_all(&pty.master, string.as_bytes());
// Try to read from the master, which should not have anything as echoing was disabled.
let mut buf = [0u8; 10];
let read = read(pty.master.as_raw_fd(), &mut buf).unwrap_err();
assert_eq!(read, Errno::EAGAIN);
}