refactor: move embedded tests into dedicated test directory
This commit is contained in:
@@ -0,0 +1,192 @@
|
||||
use super::*;
|
||||
use std::fs;
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
use std::sync::atomic::{AtomicU64, Ordering};
|
||||
|
||||
static FIXTURE_ID: AtomicU64 = AtomicU64::new(0);
|
||||
|
||||
struct Fixture(PathBuf);
|
||||
|
||||
impl Fixture {
|
||||
fn new() -> Self {
|
||||
let path = std::env::temp_dir().join(format!(
|
||||
"vamp-clipboard-{}-{}",
|
||||
std::process::id(),
|
||||
FIXTURE_ID.fetch_add(1, Ordering::Relaxed)
|
||||
));
|
||||
fs::create_dir(&path).unwrap();
|
||||
Self(path)
|
||||
}
|
||||
|
||||
fn command(&self, name: &str, body: &str) -> PathBuf {
|
||||
let path = self.0.join(name);
|
||||
fs::write(&path, format!("#!/bin/sh\n{body}\n")).unwrap();
|
||||
fs::set_permissions(&path, fs::Permissions::from_mode(0o700)).unwrap();
|
||||
path
|
||||
}
|
||||
|
||||
fn clipboard(&self, text: &[u8]) -> SystemClipboard {
|
||||
let state = self.0.join("state");
|
||||
fs::write(&state, text).unwrap();
|
||||
let state = format!("'{}'", state.to_str().unwrap().replace('\'', "'\\''"));
|
||||
SystemClipboard::with_commands(
|
||||
self.command("read", &format!("exec /bin/cat {state}")),
|
||||
self.command("write", &format!("exec /bin/cat > {state}")),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Fixture {
|
||||
fn drop(&mut self) {
|
||||
fs::remove_dir_all(&self.0).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn construction_does_not_probe_or_spawn() {
|
||||
assert!(SystemClipboard::new().commands.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn round_trips_unicode_whitespace_and_large_pipes_exactly() {
|
||||
let fixture = Fixture::new();
|
||||
let mut clipboard = fixture.clipboard(b"");
|
||||
for text in ["", " héllo\t世界🙂\r\n\n", &"é\n".repeat(300_000)] {
|
||||
clipboard.write(text).unwrap();
|
||||
assert_eq!(fs::read(fixture.0.join("state")).unwrap(), text.as_bytes());
|
||||
assert_eq!(clipboard.read().unwrap(), text);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rejects_oversize_and_invalid_utf8_without_leaking_contents() {
|
||||
let fixture = Fixture::new();
|
||||
let mut clipboard = fixture.clipboard(&[0xff, b'\n']);
|
||||
assert_eq!(
|
||||
clipboard.read().unwrap_err(),
|
||||
"System clipboard is not valid UTF-8"
|
||||
);
|
||||
let maximum = "é".repeat(MAX_CLIPBOARD_BYTES / 2);
|
||||
clipboard.write(&maximum).unwrap();
|
||||
assert_eq!(clipboard.read().unwrap(), maximum);
|
||||
let too_large = format!("{maximum}x");
|
||||
assert_eq!(
|
||||
clipboard.write(&too_large).unwrap_err(),
|
||||
"System clipboard text exceeds 4 MiB"
|
||||
);
|
||||
assert_eq!(
|
||||
fs::read(fixture.0.join("state")).unwrap(),
|
||||
maximum.as_bytes()
|
||||
);
|
||||
fs::write(fixture.0.join("state"), &too_large).unwrap();
|
||||
assert_eq!(
|
||||
clipboard.read().unwrap_err(),
|
||||
"System clipboard text exceeds 4 MiB"
|
||||
);
|
||||
let mut incomplete = vec![b'x'; MAX_CLIPBOARD_BYTES - 1];
|
||||
incomplete.push(0xc3);
|
||||
fs::write(fixture.0.join("state"), incomplete).unwrap();
|
||||
assert_eq!(
|
||||
clipboard.read().unwrap_err(),
|
||||
"System clipboard is not valid UTF-8"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn command_failures_and_stderr_cannot_leak_clipboard_text() {
|
||||
let fixture = Fixture::new();
|
||||
let failed = fixture.command(
|
||||
"failed",
|
||||
"printf 'private clipboard payload' >&2\nprintf 'private clipboard payload'\nexit 23",
|
||||
);
|
||||
let mut clipboard = SystemClipboard::with_commands(failed.clone(), failed);
|
||||
let error = clipboard.read().unwrap_err();
|
||||
assert!(error.contains("failed"));
|
||||
assert!(!error.contains("private clipboard payload"));
|
||||
let error = clipboard.write("private clipboard payload").unwrap_err();
|
||||
assert!(!error.contains("private clipboard payload"));
|
||||
let missing = fixture.0.join("not-an-executable");
|
||||
let mut clipboard = SystemClipboard::with_commands(missing.clone(), missing);
|
||||
assert!(clipboard.read().unwrap_err().contains("Cannot start"));
|
||||
assert!(
|
||||
clipboard
|
||||
.write("secret")
|
||||
.unwrap_err()
|
||||
.contains("Cannot start")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ignored_stderr_does_not_block_large_writes() {
|
||||
let fixture = Fixture::new();
|
||||
let mut clipboard = fixture.clipboard(b"");
|
||||
let state = fixture.0.join("state");
|
||||
let state = format!("'{}'", state.to_str().unwrap().replace('\'', "'\\''"));
|
||||
clipboard
|
||||
.commands
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.writer
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.program = fixture.command(
|
||||
"noisy",
|
||||
&format!("/bin/dd if=/dev/zero bs=65536 count=8 >&2\nexec /bin/cat > {state}"),
|
||||
);
|
||||
let text = "λ\n".repeat(300_000);
|
||||
clipboard.write(&text).unwrap();
|
||||
assert_eq!(clipboard.read().unwrap(), text);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn failed_process_cleanup_kills_and_reaps_a_running_child() {
|
||||
use std::io::Read;
|
||||
use std::os::unix::process::CommandExt;
|
||||
use std::process::{Command, Stdio};
|
||||
|
||||
unsafe extern "C" {
|
||||
fn waitpid(pid: i32, status: *mut i32, options: i32) -> i32;
|
||||
}
|
||||
|
||||
let fixture = Fixture::new();
|
||||
let hung = fixture.command("running", "printf R\nwhile :; do :; done");
|
||||
let mut command = Command::new(hung);
|
||||
command
|
||||
.process_group(0)
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::null());
|
||||
let mut child = command.spawn().unwrap();
|
||||
let pid = child.id() as i32;
|
||||
let mut ready = [0];
|
||||
child
|
||||
.stdout
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.read_exact(&mut ready)
|
||||
.unwrap();
|
||||
assert_eq!(ready, [b'R']);
|
||||
drop(ClipboardProcess(Some(child)));
|
||||
let mut status = 0;
|
||||
// SAFETY: waitpid's output pointer is valid; WNOHANG cannot block this check.
|
||||
assert_eq!(unsafe { waitpid(pid, &mut status, 1) }, -1);
|
||||
assert_eq!(std::io::Error::last_os_error().raw_os_error(), Some(10));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn immediate_injected_timeout_terminates_nonterminating_commands() {
|
||||
let fixture = Fixture::new();
|
||||
let hung = fixture.command("hung", "while :; do :; done");
|
||||
let mut clipboard = SystemClipboard::with_commands(hung.clone(), hung);
|
||||
// No assertion depends on wall-clock speed or a deadline remaining in the future.
|
||||
clipboard.timeout = Duration::ZERO;
|
||||
assert_eq!(
|
||||
clipboard.read().unwrap_err(),
|
||||
"System clipboard command timed out"
|
||||
);
|
||||
assert_eq!(
|
||||
clipboard.write("text").unwrap_err(),
|
||||
"System clipboard command timed out"
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user