test(core): add headless end-to-end tests and fix bugs they found

Adds crates/ghostty-core/tests/e2e_*.rs: 81 tests that drive the real
core Surface, termio, pty and child processes through a fake runtime
(recording apprt actions, in-memory clipboard) with a headless renderer
(ghostty-renderer/src/headless.rs). They cover keys (legacy, DECCKM,
kitty, modifyOtherKeys), keybinding actions and sequences, mouse
selection/reporting/scrolling/links, clipboard incl. OSC 52 and paste
safety, resize, focus, titles/pwd/bell/notifications/progress, child
exit, and IME preedit.

Fixes for issues found by the tests and by the Codex audit
(docs/rewrite/notes/review-core-codex.md, all 9 items):
- replacing a selection with copy-on-select panicked (stale tracked
  handles); global new_window no longer inherits a parent; the child
  environment uses the runtime's env wholesale so GTK's removals stick;
  Flatpak host resources path; locale recovery (ensureLocale) in
  apprt-gtk before GTK init and for CLI actions; failed screen-file
  opens propagate errors; OSC 52 base64 rules, CSI/ESC binding length
  limits and GHOSTTY_LOG parsing match Zig.
- Also includes V2's termio cleanups (tmux viewer wiring in the stream
  handler, Pty slave semantics docs, shared cgroup wait loop).

apprt-gtk gains a debug input replay hook (GHOSTTY_DEBUG_INPUT_SCRIPT)
exercising the real GTK key path. With it, the real app was verified on
Wayland: typing and output, new tab, splits, select-all/copy/paste and
font size all work (docs/rewrite/notes/verification-V1.md). Not yet
exercised at GTK level: mouse, real IM-context IME, clipboard dialogs.

Test Plan:
- cargo test --workspace (2983 passed)
- cargo test -p ghostty-core --tests (81 e2e tests)
This commit is contained in:
ddidderr committed 2026-10-12 03:58:19 +02:00
1 parent d650e5171b
commit be6d706ba5
33 files changed
+3016 -222

No files matched your search

+5 -7
View File
@@ -29,7 +29,7 @@ use std::time::{Duration, Instant};
use ghostty_config::conditional;
use ghostty_config::config::Config;
use ghostty_config::io::ReadableIO;
use ghostty_config::types_a::{Color, CursorStyle as ConfigCursorStyle, TerminalColor};
use ghostty_config::types_a::{Color, CursorStyle, TerminalColor};
use ghostty_config::types_b::{ClipboardAccess, GraphemeWidthMethod, OSCColorReportFormat};
use ghostty_datastruct::blocking_queue::Timeout;
use ghostty_renderer::message::Message as RendererMessage;
@@ -52,9 +52,7 @@ use crate::message::{Message, SizeReport};
use crate::options::Options;
use crate::sinks::{RendererMailbox, SurfaceMailbox};
use crate::state::{SharedState, SharedStateExt, State};
use crate::stream_handler::{
HandlerOptions, HandlerState, TerminalStream, cursor_style_from_config,
};
use crate::stream_handler::{HandlerOptions, HandlerState, TerminalStream};
use crate::wakeup::Wakeup;
/// Errors from termio initialization and thread setup.
@@ -95,7 +93,7 @@ pub enum MutexState {
pub struct DerivedConfig {
pub palette: Palette,
pub image_storage_limit: usize,
pub cursor_style: ConfigCursorStyle,
pub cursor_style: CursorStyle,
pub cursor_blink: Option<bool>,
pub cursor_color: Option<TerminalColor>,
pub foreground: Color,
@@ -376,7 +374,7 @@ impl Termio {
set_image_limits(&mut term, config.image_storage_limit);
// Set our default cursor style
term.screen_mut().cursor.cursor_style = cursor_style_from_config(config.cursor_style);
term.screen_mut().cursor.cursor_style = config.cursor_style;
// Setup our terminal size in pixels for certain requests.
term.width_px = u32::from(term.cols) * size.cell.width;
@@ -391,7 +389,7 @@ impl Termio {
termio_mailbox: mailbox.clone(),
surface_mailbox: Arc::clone(&surface_mailbox),
renderer_wakeup: renderer_wakeup.clone(),
default_cursor_style: cursor_style_from_config(config.cursor_style),
default_cursor_style: config.cursor_style,
default_cursor_blink: config.cursor_blink,
enquiry_response: config.enquiry_response.clone().into_bytes(),
osc_color_report_format: config.osc_color_report_format,