test: verify IME, clipboard dialogs, quick terminal and Flatpak paths
Exercises the runtime paths that had never run (details in docs/rewrite/notes/verification-VR.md): - IME: replay steps drive the surface's real GtkIMContext handlers with a real preedit + attributes; preedit renders under the cursor and commits reach the pty, incl. dead-key and compose orderings. - Paste and OSC 52 read/write confirmation dialogs: allow, deny and remember flows. - Quick terminal on niri via wlr-layer-shell: positions, sizes, toggle and autohide. Fixes a real bug: link order could put libwayland-client ahead of libgtk4-layer-shell, silently disabling the quick terminal; apprt-gtk now loads libwayland-client via wayland-sys dlopen. - Flatpak host commands against a zbus mock of org.freedesktop.Flatpak.Development under dbus-run-session: spawn, exit status, cwd/env/fd forwarding and SIGHUP on stop, both directly and through termio Exec (flatpak-info path seam added). - Horizontal-scroll tab switching thresholds and reset. tests/gtk_replay.rs runs the real binary against the session display (skips without one). Test Plan: - cargo test --test gtk_replay (needs a Wayland/X11 display) - cargo test -p ghostty-os --test flatpak_mock; -p ghostty-termio --test flatpak_exec
This commit is contained in:
1 parent
a714fd9a65
commit
42cb8796da
7 files changed
+1567
-12
No files matched your search
@@ -0,0 +1,130 @@
|
||||
#![cfg(unix)]
|
||||
#![allow(clippy::unwrap_used)]
|
||||
//! `FlatpakHostCommand` against a mock `org.freedesktop.Flatpak.Development`
|
||||
//! DBus service on a private session bus (`dbus-run-session`).
|
||||
|
||||
mod common {
|
||||
pub mod flatpak_mock;
|
||||
}
|
||||
|
||||
use std::{
|
||||
io::Read,
|
||||
os::fd::OwnedFd,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
use common::flatpak_mock::Service;
|
||||
use ghostty_os::flatpak::{self, Error, FlatpakHostCommand};
|
||||
|
||||
fn devnull() -> OwnedFd {
|
||||
std::fs::OpenOptions::new()
|
||||
.read(true)
|
||||
.write(true)
|
||||
.open("/dev/null")
|
||||
.unwrap()
|
||||
.into()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn host_command_spawn_exit_status_and_signal() {
|
||||
if !common::flatpak_mock::under_dbus_session("host_command_spawn_exit_status_and_signal") {
|
||||
return;
|
||||
}
|
||||
|
||||
// The injection seam: pretend /.flatpak-info exists.
|
||||
let info = std::env::temp_dir().join(format!("flatpak-info-{}", std::process::id()));
|
||||
std::fs::write(&info, "[Application]\nname=com.mitchellh.ghostty\n").unwrap();
|
||||
assert!(flatpak::set_flatpak_info_path(&info));
|
||||
assert!(flatpak::is_flatpak());
|
||||
|
||||
let svc = Service::start();
|
||||
|
||||
// 1. Spawn returns the host pid; stdout goes through the passed fd;
|
||||
// cwd and env arrive; the exit status is propagated.
|
||||
{
|
||||
let (r, w) = ghostty_os::pipe::pipe().unwrap();
|
||||
let mut cmd = FlatpakHostCommand::new(
|
||||
vec![
|
||||
"/bin/sh".into(),
|
||||
"-c".into(),
|
||||
"echo \"$FOO:$(pwd)\"; exit 7".into(),
|
||||
],
|
||||
devnull(),
|
||||
w,
|
||||
devnull(),
|
||||
);
|
||||
cmd.cwd = Some("/".into());
|
||||
let mut env = ghostty_os::env::EnvMap::default();
|
||||
env.insert("FOO", "bar");
|
||||
cmd.env = Some(env);
|
||||
let pid = cmd.spawn().unwrap();
|
||||
assert!(pid > 1);
|
||||
assert_eq!(cmd.wait(), Ok(7));
|
||||
// wait() resets the state: a second wait is an error.
|
||||
assert_eq!(cmd.wait(), Err(Error::FlatpakMustBeStarted));
|
||||
drop(cmd); // closes our write end so the read below sees EOF
|
||||
let mut out = String::new();
|
||||
std::fs::File::from(r).read_to_string(&mut out).unwrap();
|
||||
assert_eq!(out, "bar:/\n");
|
||||
|
||||
let spawned = svc.spawned();
|
||||
assert_eq!(spawned.len(), 1);
|
||||
assert_eq!(spawned[0].pid, pid);
|
||||
assert_eq!(spawned[0].argv[0], "/bin/sh");
|
||||
assert_eq!(spawned[0].cwd, "/");
|
||||
assert_eq!(spawned[0].env.get("FOO").map(String::as_str), Some("bar"));
|
||||
assert_eq!(spawned[0].fds, vec![0, 1, 2]);
|
||||
}
|
||||
|
||||
// 2. Exit code 0 and the async wait path.
|
||||
{
|
||||
let mut cmd = FlatpakHostCommand::new(
|
||||
vec!["/bin/sh".into(), "-c".into(), ":".into()],
|
||||
devnull(),
|
||||
devnull(),
|
||||
devnull(),
|
||||
);
|
||||
cmd.cwd = Some("/".into());
|
||||
cmd.spawn().unwrap();
|
||||
let (tx, rx) = std::sync::mpsc::channel();
|
||||
cmd.wait_callback(move |r| tx.send(r).unwrap()).unwrap();
|
||||
assert_eq!(rx.recv_timeout(Duration::from_secs(10)).unwrap(), Ok(0));
|
||||
}
|
||||
|
||||
// 3. Spawn failure on the host is reported.
|
||||
{
|
||||
let mut cmd = FlatpakHostCommand::new(
|
||||
vec!["/nonexistent/binary".into()],
|
||||
devnull(),
|
||||
devnull(),
|
||||
devnull(),
|
||||
);
|
||||
cmd.cwd = Some("/".into());
|
||||
assert_eq!(cmd.spawn(), Err(Error::FlatpakSpawnFail));
|
||||
}
|
||||
|
||||
// 4. SIGHUP to the process group (what Exec::stop sends) reaches the
|
||||
// host command, which then exits; the signal handle works after the
|
||||
// command moved into the exit watcher.
|
||||
{
|
||||
let mut cmd = FlatpakHostCommand::new(
|
||||
vec!["/bin/sleep".into(), "30".into()],
|
||||
devnull(),
|
||||
devnull(),
|
||||
devnull(),
|
||||
);
|
||||
cmd.cwd = Some("/".into());
|
||||
let pid = cmd.spawn().unwrap();
|
||||
let handle = cmd.signal_handle();
|
||||
let (tx, rx) = std::sync::mpsc::channel();
|
||||
cmd.wait_callback(move |r| tx.send(r).unwrap()).unwrap();
|
||||
let start = Instant::now();
|
||||
assert_eq!(handle.signal(1, true), Ok(()));
|
||||
let r = rx.recv_timeout(Duration::from_secs(10)).unwrap();
|
||||
assert!(r.is_ok(), "{r:?}");
|
||||
assert!(start.elapsed() < Duration::from_secs(10));
|
||||
assert_eq!(svc.signals(), vec![(pid, 1, true)]);
|
||||
}
|
||||
|
||||
let _ = std::fs::remove_file(&info);
|
||||
}
|
||||
Reference in new issue
Block a user