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:
1 parent
7cdc0e9055
commit
2f58f0214d
33 files changed
+3016
-222
No files matched your search
@@ -26,21 +26,29 @@ impl Default for Logging {
|
||||
}
|
||||
|
||||
impl Logging {
|
||||
/// Parse the `GHOSTTY_LOG` value (comma separated field names, an
|
||||
/// optional `no-` prefix disables; Zig: `cli.args.parsePackedStruct`).
|
||||
/// Invalid values yield the default.
|
||||
/// Parse the `GHOSTTY_LOG` value (Zig: `cli.args.parsePackedStruct`):
|
||||
/// a standalone boolean (`1/t/T/true/0/f/F/false`) sets every field,
|
||||
/// otherwise a comma separated list of field names where a `no-` prefix
|
||||
/// disables. Fields not mentioned keep their default (stderr on). Any
|
||||
/// invalid value (including an empty part) yields the default.
|
||||
#[must_use]
|
||||
pub fn parse(v: &str) -> Logging {
|
||||
let mut out = Logging { stderr: false };
|
||||
match v {
|
||||
"1" | "t" | "T" | "true" => return Logging { stderr: true },
|
||||
"0" | "f" | "F" | "false" => return Logging { stderr: false },
|
||||
_ => {}
|
||||
}
|
||||
let mut out = Logging::default();
|
||||
for part in v.split(',') {
|
||||
let part = part.trim();
|
||||
let part = part.trim_matches([' ', '\t']);
|
||||
let (name, value) = match part.strip_prefix("no-") {
|
||||
Some(n) => (n, false),
|
||||
None => (part, true),
|
||||
};
|
||||
match name {
|
||||
"stderr" => out.stderr = value,
|
||||
"macos" | "" => {}
|
||||
// The only other field (macOS unified logging) is a no-op.
|
||||
"macos" => {}
|
||||
_ => return Logging::default(),
|
||||
}
|
||||
}
|
||||
@@ -87,11 +95,16 @@ pub struct GlobalState {
|
||||
static STATE: OnceLock<GlobalState> = OnceLock::new();
|
||||
|
||||
/// Options for [`init`].
|
||||
#[derive(Debug, Clone, Copy, Default)]
|
||||
#[derive(Clone, Copy, Default)]
|
||||
pub struct InitOptions {
|
||||
/// A CLI action is being executed: disable stderr logging by default
|
||||
/// since the action writes to stderr/stdout itself.
|
||||
pub action: bool,
|
||||
|
||||
/// The runtime-specific resources directory discovery (Zig:
|
||||
/// `apprt.runtime.resourcesDir`, e.g. GTK's Flatpak host path lookup).
|
||||
/// `None` uses the generic `ghostty_os::resourcesdir::resources_dir`.
|
||||
pub resources_dir: Option<fn() -> std::io::Result<ResourcesDir>>,
|
||||
}
|
||||
|
||||
/// The global state. If [`init`] was never called, a default state (no
|
||||
@@ -132,7 +145,10 @@ pub fn init(opts: InitOptions) -> &'static GlobalState {
|
||||
|
||||
// Find our resources directory once for the app so every launch
|
||||
// hereafter can use this cached value.
|
||||
let resources_dir = match ghostty_os::resourcesdir::resources_dir() {
|
||||
let discover = opts
|
||||
.resources_dir
|
||||
.unwrap_or(ghostty_os::resourcesdir::resources_dir);
|
||||
let resources_dir = match discover() {
|
||||
Ok(d) => d,
|
||||
Err(err) => {
|
||||
log::warn!("failed to find resources dir err={err}");
|
||||
@@ -224,7 +240,14 @@ mod tests {
|
||||
fn logging_parse() {
|
||||
assert!(Logging::parse("stderr").stderr);
|
||||
assert!(!Logging::parse("no-stderr").stderr);
|
||||
assert!(!Logging::parse("macos").stderr);
|
||||
// Unmentioned fields keep their default (stderr on).
|
||||
assert!(Logging::parse("macos").stderr);
|
||||
assert!(!Logging::parse("false").stderr);
|
||||
assert!(!Logging::parse("0").stderr);
|
||||
assert!(Logging::parse("true").stderr);
|
||||
// Empty / invalid values yield the default.
|
||||
assert!(Logging::parse("").stderr);
|
||||
assert!(Logging::parse("stderr,").stderr);
|
||||
assert_eq!(Logging::parse("garbage"), Logging::default());
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user