use qssh::pty::Pty;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use std::process::Command;
use std::time::Duration;
use tokio::time::timeout;
#[cfg(unix)]
#[tokio::test]
async fn test_pty_creation() {
let pty_result = Pty::new();
assert!(pty_result.is_ok());
let mut pty = pty_result.unwrap();
let child = Command::new("/bin/sh")
.arg("-c")
.arg("echo 'PTY test'")
.spawn();
assert!(child.is_ok());
}
#[tokio::test]
async fn test_shell_command_execution() {
let commands = vec![
("echo 'hello world'", "hello world"),
("expr 2 + 2", "4"),
("printf 'test'", "test"),
];
for (cmd, expected) in commands {
let output = Command::new("/bin/sh")
.arg("-c")
.arg(cmd)
.output()
.expect("Failed to execute command");
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(stdout.contains(expected), "Command {} failed, got: {}", cmd, stdout);
}
}
#[tokio::test]
async fn test_shell_io_redirection() {
let test_input = "test input data\n";
let echo_cmd = format!("echo '{}'", test_input.trim());
let output = Command::new("/bin/sh")
.arg("-c")
.arg(&echo_cmd)
.output()
.expect("Failed to execute command");
let stdout = String::from_utf8_lossy(&output.stdout);
assert_eq!(stdout.trim(), test_input.trim());
}
#[tokio::test]
async fn test_shell_environment_variables() {
use std::env;
env::set_var("QSSH_TEST_VAR", "test_value");
let output = Command::new("/bin/sh")
.arg("-c")
.arg("echo $QSSH_TEST_VAR")
.env("QSSH_TEST_VAR", "test_value")
.output()
.expect("Failed to execute command");
let stdout = String::from_utf8_lossy(&output.stdout);
assert_eq!(stdout.trim(), "test_value");
env::remove_var("QSSH_TEST_VAR");
}
#[tokio::test]
async fn test_shell_exit_codes() {
let test_cases = vec![
("exit 0", 0),
("exit 1", 1),
("exit 42", 42),
("false", 1),
("true", 0),
];
for (cmd, expected_code) in test_cases {
let output = Command::new("/bin/sh")
.arg("-c")
.arg(cmd)
.output()
.expect("Failed to execute command");
assert_eq!(
output.status.code(),
Some(expected_code),
"Command {} returned wrong exit code",
cmd
);
}
}
#[tokio::test]
async fn test_shell_signal_handling() {
use tokio::process::Command as TokioCommand;
use tokio::signal;
let mut child = TokioCommand::new("/bin/sh")
.arg("-c")
.arg("sleep 10")
.spawn()
.expect("Failed to spawn process");
tokio::time::sleep(Duration::from_millis(100)).await;
child.kill().await.expect("Failed to kill process");
let status = child.wait().await.expect("Failed to wait for process");
assert!(!status.success());
}
#[cfg(unix)]
#[tokio::test]
async fn test_pty_window_size() {
let pty = Pty::new().unwrap();
assert!(pty.master_fd() > 0);
}
#[tokio::test]
async fn test_shell_pipeline() {
let pipeline = "echo 'hello world' | grep 'world' | wc -l";
let output = Command::new("/bin/sh")
.arg("-c")
.arg(pipeline)
.output()
.expect("Failed to execute pipeline");
let stdout = String::from_utf8_lossy(&output.stdout);
assert_eq!(stdout.trim(), "1");
}
#[tokio::test]
async fn test_shell_error_handling() {
let invalid_commands = vec![
"nonexistentcommand123",
"/invalid/path/to/command",
"$()",
];
for cmd in invalid_commands {
let output = Command::new("/bin/sh")
.arg("-c")
.arg(cmd)
.output()
.expect("Failed to execute command");
assert_ne!(output.status.code(), Some(0), "Command {} should have failed", cmd);
}
}
#[tokio::test]
async fn test_shell_working_directory() {
let output = Command::new("/bin/sh")
.arg("-c")
.arg("cd /tmp && pwd")
.output()
.expect("Failed to execute command");
let stdout = String::from_utf8_lossy(&output.stdout);
assert_eq!(stdout.trim(), "/tmp");
}
#[tokio::test]
async fn test_shell_user_context() {
let output = Command::new("/bin/sh")
.arg("-c")
.arg("whoami")
.output()
.expect("Failed to execute command");
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(!stdout.trim().is_empty());
}
#[tokio::test]
async fn test_concurrent_shell_sessions() {
let mut handles = vec![];
for i in 0..5 {
let handle = tokio::spawn(async move {
let output = Command::new("/bin/sh")
.arg("-c")
.arg(format!("echo 'Session {}'", i))
.output()
.expect("Failed to execute command");
String::from_utf8_lossy(&output.stdout).trim().to_string()
});
handles.push(handle);
}
let mut results = Vec::new();
for handle in handles {
results.push(handle.await);
}
for (i, result) in results.iter().enumerate() {
let output = result.as_ref().unwrap();
assert_eq!(output, &format!("Session {}", i));
}
}
#[tokio::test]
async fn test_shell_timeout() {
let long_command = tokio::process::Command::new("/bin/sh")
.arg("-c")
.arg("sleep 10")
.spawn();
assert!(long_command.is_ok());
let mut child = long_command.unwrap();
let wait_result = timeout(Duration::from_secs(1), child.wait()).await;
assert!(wait_result.is_err());
child.kill().await.ok();
}
#[tokio::test]
async fn test_shell_special_characters() {
let test_cases = vec![
("echo 'test$variable'", "test$variable"),
("echo \"test'with'quotes\"", "test'with'quotes"),
("echo 'test\"with\"doublequotes'", "test\"with\"doublequotes"),
("echo 'test\\with\\backslashes'", "test\\with\\backslashes"),
];
for (cmd, expected) in test_cases {
let output = Command::new("/bin/sh")
.arg("-c")
.arg(cmd)
.output()
.expect("Failed to execute command");
let stdout = String::from_utf8_lossy(&output.stdout);
assert_eq!(stdout.trim(), expected, "Failed for command: {}", cmd);
}
}