Ports the macOS-specific Zig code behind cfg(target_os = "macos"): - renderer: a GraphicsApi trait with generic::Renderer<G>; Metal backend (all renderer/metal/* files, shaders.metal verbatim, runtime library compile, IOSurface layer, CVDisplayLink), shadertoy -> MSL via naga; OpenGL remains the backend elsewhere. - font: CoreText face, shaper and discovery with build.rs backend selection (coretext default; coretext-freetype / coretext-harfbuzz / coretext-noshape features), no fontconfig/freetype/harfbuzz on Apple. - input: KeymapDarwin via UCKeyTranslate, option-as-alt, mac keycodes. - os: macos, mach, cf_release_thread, NSProcessInfo args and macOS branches; config: macOS defaults, keybinds, Application Support paths. - termio/core: login(1) wrapping, openpty/execvpe, Darwin child exit handling, os_log logging. Compile-checked for aarch64/x86_64-apple-darwin (clippy -D warnings clean) using the cross setup in docs/rewrite/notes/macos-M1.md; cannot run here. The embedded runtime/C API (embedded.zig) is the next step. Also includes the cross-platform integration edits shared by the crash, verification, Windows and macOS work (Cargo target-gating, thread crash-context hooks, apprt-gtk surface/IME refactor, termio/os cfg gates), a cfg fix so ghostty-config builds on wasm32, and an allow for the intentional recursion in the crash overflow test. Test Plan: - cargo test --workspace (3224 passed) - cargo clippy --workspace --all-targets -- -D warnings - cargo check --target aarch64-apple-darwin -p ghostty-renderer (with the documented cross env)
102 lines
2.8 KiB
Rust
102 lines
2.8 KiB
Rust
//! Integration test: run a real child attached to a pty.
|
|
|
|
#![cfg(unix)]
|
|
#![allow(clippy::unwrap_used)]
|
|
|
|
use std::{
|
|
fs::File,
|
|
io::Read,
|
|
os::fd::{AsRawFd, OwnedFd},
|
|
};
|
|
|
|
use ghostty_termio::{Command, Pty, Winsize, command::Exit, pty::child_pre_exec};
|
|
|
|
fn dup(fd: &OwnedFd) -> OwnedFd {
|
|
fd.try_clone().unwrap()
|
|
}
|
|
|
|
/// Read from the master until the child side hangs up (EIO on Linux).
|
|
fn read_until_hangup(master: OwnedFd) -> String {
|
|
let mut f = File::from(master);
|
|
let mut out = Vec::new();
|
|
let mut buf = [0u8; 4096];
|
|
loop {
|
|
match f.read(&mut buf) {
|
|
Ok(0) | Err(_) => break,
|
|
Ok(n) => out.extend_from_slice(&buf[..n]),
|
|
}
|
|
}
|
|
String::from_utf8_lossy(&out).into_owned()
|
|
}
|
|
|
|
fn run_on_pty(script: &str, size: Winsize) -> (String, Exit) {
|
|
let mut pty = Pty::open(size).unwrap();
|
|
let slave = pty.slave.take().unwrap();
|
|
|
|
let (master_raw, _) = pty.raw_fds();
|
|
let slave_raw = slave.as_raw_fd();
|
|
|
|
let mut cmd = Command::new("/bin/sh", &["/bin/sh", "-c", script]);
|
|
cmd.stdin = Some(dup(&slave));
|
|
cmd.stdout = Some(dup(&slave));
|
|
cmd.stderr = Some(dup(&slave));
|
|
cmd.os_pre_exec = Some(Box::new(move || {
|
|
// Make the slave our controlling terminal in a new session; this
|
|
// also closes the master/slave descriptors in the child.
|
|
match child_pre_exec(master_raw, slave_raw) {
|
|
Ok(()) => None,
|
|
Err(_) => Some(1),
|
|
}
|
|
}));
|
|
cmd.start().unwrap();
|
|
|
|
// Close our copies of the slave so we see a hangup when the child exits.
|
|
drop(cmd.stdin.take());
|
|
drop(cmd.stdout.take());
|
|
drop(cmd.stderr.take());
|
|
drop(slave);
|
|
|
|
let exit = cmd.wait(true).unwrap();
|
|
let out = read_until_hangup(pty.master);
|
|
(out, exit)
|
|
}
|
|
|
|
#[test]
|
|
fn echo_and_stty_size_on_pty() {
|
|
let size = Winsize {
|
|
ws_row: 31,
|
|
ws_col: 97,
|
|
ws_xpixel: 0,
|
|
ws_ypixel: 0,
|
|
};
|
|
let (out, exit) = run_on_pty("echo hi; stty size", size);
|
|
assert_eq!(exit, Exit::Exited(0));
|
|
// A pty translates \n to \r\n.
|
|
assert_eq!(out, "hi\r\n31 97\r\n");
|
|
}
|
|
|
|
#[test]
|
|
fn child_has_controlling_terminal() {
|
|
// `tty` succeeds only with a terminal on stdin; /dev/tty only opens when
|
|
// we have a controlling terminal (TIOCSCTTY).
|
|
let (out, exit) = run_on_pty(
|
|
"tty >/dev/null && echo tty; echo x > /dev/tty && echo ctty",
|
|
Winsize::default(),
|
|
);
|
|
assert_eq!(exit, Exit::Exited(0));
|
|
assert!(out.contains("tty\r\n"), "{out:?}");
|
|
assert!(out.contains("ctty\r\n"), "{out:?}");
|
|
}
|
|
|
|
#[test]
|
|
fn resize_is_visible_to_child() {
|
|
let size = Winsize {
|
|
ws_row: 10,
|
|
ws_col: 20,
|
|
ws_xpixel: 0,
|
|
ws_ypixel: 0,
|
|
};
|
|
let (out, _) = run_on_pty("stty size", size);
|
|
assert_eq!(out, "10 20\r\n");
|
|
}
|