mirror of
https://github.com/ghostty-org/ghostty.git
synced 2026-04-06 07:38:21 +00:00
core: add tests for ghostty.h
* ensure that `ghostty.h` compiles during basic Zig tests * ensure that non-exhaustive enums are kept synchronized between `ghostty.h` and their respective Zig counterpart. * adjust some enums that varied from established conventions
This commit is contained in:
@@ -292,6 +292,7 @@ pub fn build(b: *std.Build) !void {
|
||||
// Crash on x86_64 without this
|
||||
.use_llvm = true,
|
||||
});
|
||||
test_exe.root_module.addIncludePath(b.path("include"));
|
||||
if (config.emit_test_exe) b.installArtifact(test_exe);
|
||||
_ = try deps.add(test_exe);
|
||||
|
||||
|
||||
@@ -586,9 +586,9 @@ typedef enum {
|
||||
// apprt.action.Fullscreen
|
||||
typedef enum {
|
||||
GHOSTTY_FULLSCREEN_NATIVE,
|
||||
GHOSTTY_FULLSCREEN_NON_NATIVE,
|
||||
GHOSTTY_FULLSCREEN_NON_NATIVE_VISIBLE_MENU,
|
||||
GHOSTTY_FULLSCREEN_NON_NATIVE_PADDED_NOTCH,
|
||||
GHOSTTY_FULLSCREEN_MACOS_NON_NATIVE,
|
||||
GHOSTTY_FULLSCREEN_MACOS_NON_NATIVE_VISIBLE_MENU,
|
||||
GHOSTTY_FULLSCREEN_MACOS_NON_NATIVE_PADDED_NOTCH,
|
||||
} ghostty_action_fullscreen_e;
|
||||
|
||||
// apprt.action.FloatWindow
|
||||
@@ -718,7 +718,7 @@ typedef struct {
|
||||
|
||||
// renderer.Health
|
||||
typedef enum {
|
||||
GHOSTTY_RENDERER_HEALTH_OK,
|
||||
GHOSTTY_RENDERER_HEALTH_HEALTHY,
|
||||
GHOSTTY_RENDERER_HEALTH_UNHEALTHY,
|
||||
} ghostty_action_renderer_health_e;
|
||||
|
||||
|
||||
@@ -7,13 +7,13 @@ extension FullscreenMode {
|
||||
case GHOSTTY_FULLSCREEN_NATIVE:
|
||||
.native
|
||||
|
||||
case GHOSTTY_FULLSCREEN_NON_NATIVE:
|
||||
case GHOSTTY_FULLSCREEN_MACOS_NON_NATIVE:
|
||||
.nonNative
|
||||
|
||||
case GHOSTTY_FULLSCREEN_NON_NATIVE_VISIBLE_MENU:
|
||||
case GHOSTTY_FULLSCREEN_MACOS_NON_NATIVE_VISIBLE_MENU:
|
||||
.nonNativeVisibleMenu
|
||||
|
||||
case GHOSTTY_FULLSCREEN_NON_NATIVE_PADDED_NOTCH:
|
||||
case GHOSTTY_FULLSCREEN_MACOS_NON_NATIVE_PADDED_NOTCH:
|
||||
.nonNativePaddedNotch
|
||||
|
||||
default:
|
||||
|
||||
@@ -678,7 +678,7 @@ extension Ghostty {
|
||||
guard let healthAny = notification.userInfo?["health"] else { return }
|
||||
guard let health = healthAny as? ghostty_action_renderer_health_e else { return }
|
||||
DispatchQueue.main.async { [weak self] in
|
||||
self?.healthy = health == GHOSTTY_RENDERER_HEALTH_OK
|
||||
self?.healthy = health == GHOSTTY_RENDERER_HEALTH_HEALTHY
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ const input = @import("../input.zig");
|
||||
const renderer = @import("../renderer.zig");
|
||||
const terminal = @import("../terminal/main.zig");
|
||||
const CoreSurface = @import("../Surface.zig");
|
||||
const lib = @import("../lib/main.zig");
|
||||
|
||||
/// The target for an action. This is generally the thing that had focus
|
||||
/// while the action was made but the concept of "focus" is not guaranteed
|
||||
@@ -19,6 +20,10 @@ pub const Target = union(Key) {
|
||||
pub const Key = enum(c_int) {
|
||||
app,
|
||||
surface,
|
||||
|
||||
test "ghostty_h Target.Key" {
|
||||
try lib.checkGhosttyHEnum(Key, "GHOSTTY_TARGET_");
|
||||
}
|
||||
};
|
||||
|
||||
// Sync with: ghostty_target_u
|
||||
@@ -401,6 +406,10 @@ pub const Action = union(Key) {
|
||||
search_selected,
|
||||
readonly,
|
||||
copy_title_to_clipboard,
|
||||
|
||||
test "ghostty_h Action.Key" {
|
||||
try lib.checkGhosttyHEnum(Key, "GHOSTTY_ACTION_");
|
||||
}
|
||||
};
|
||||
|
||||
/// Sync with: ghostty_action_u
|
||||
@@ -482,6 +491,10 @@ pub const SplitDirection = enum(c_int) {
|
||||
down,
|
||||
left,
|
||||
up,
|
||||
|
||||
test "ghostty_h SplitDirection" {
|
||||
try lib.checkGhosttyHEnum(SplitDirection, "GHOSTTY_SPLIT_DIRECTION_");
|
||||
}
|
||||
};
|
||||
|
||||
// This is made extern (c_int) to make interop easier with our embedded
|
||||
@@ -494,6 +507,10 @@ pub const GotoSplit = enum(c_int) {
|
||||
left,
|
||||
down,
|
||||
right,
|
||||
|
||||
test "ghostty_h GotoSplit" {
|
||||
try lib.checkGhosttyHEnum(GotoSplit, "GHOSTTY_GOTO_SPLIT_");
|
||||
}
|
||||
};
|
||||
|
||||
// This is made extern (c_int) to make interop easier with our embedded
|
||||
@@ -501,6 +518,10 @@ pub const GotoSplit = enum(c_int) {
|
||||
pub const GotoWindow = enum(c_int) {
|
||||
previous,
|
||||
next,
|
||||
|
||||
test "ghostty_h GotoWindow" {
|
||||
try lib.checkGhosttyHEnum(GotoWindow, "GHOSTTY_GOTO_WINDOW_");
|
||||
}
|
||||
};
|
||||
|
||||
/// The amount to resize the split by and the direction to resize it in.
|
||||
@@ -513,6 +534,10 @@ pub const ResizeSplit = extern struct {
|
||||
down,
|
||||
left,
|
||||
right,
|
||||
|
||||
test "ghostty_h ResizeSplit.Direction" {
|
||||
try lib.checkGhosttyHEnum(Direction, "GHOSTTY_RESIZE_SPLIT_");
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
@@ -528,6 +553,11 @@ pub const GotoTab = enum(c_int) {
|
||||
next = -2,
|
||||
last = -3,
|
||||
_,
|
||||
|
||||
// TODO: check non-exhaustive enums
|
||||
// test "ghostty_h GotoTab" {
|
||||
// try lib.checkGhosttyHEnum(GotoTab, "GHOSTTY_GOTO_TAB_");
|
||||
// }
|
||||
};
|
||||
|
||||
/// The fullscreen mode to toggle to if we're moving to fullscreen.
|
||||
@@ -539,18 +569,30 @@ pub const Fullscreen = enum(c_int) {
|
||||
macos_non_native,
|
||||
macos_non_native_visible_menu,
|
||||
macos_non_native_padded_notch,
|
||||
|
||||
test "ghostty_h Fullscreen" {
|
||||
try lib.checkGhosttyHEnum(Fullscreen, "GHOSTTY_FULLSCREEN_");
|
||||
}
|
||||
};
|
||||
|
||||
pub const FloatWindow = enum(c_int) {
|
||||
on,
|
||||
off,
|
||||
toggle,
|
||||
|
||||
test "ghostty_h FloatWindow" {
|
||||
try lib.checkGhosttyHEnum(FloatWindow, "GHOSTTY_FLOAT_WINDOW_");
|
||||
}
|
||||
};
|
||||
|
||||
pub const SecureInput = enum(c_int) {
|
||||
on,
|
||||
off,
|
||||
toggle,
|
||||
|
||||
test "ghostty_h SecureInput" {
|
||||
try lib.checkGhosttyHEnum(SecureInput, "GHOSTTY_SECURE_INPUT_");
|
||||
}
|
||||
};
|
||||
|
||||
/// The inspector mode to toggle to if we're toggling the inspector.
|
||||
@@ -558,27 +600,47 @@ pub const Inspector = enum(c_int) {
|
||||
toggle,
|
||||
show,
|
||||
hide,
|
||||
|
||||
test "ghostty_h Inspector" {
|
||||
try lib.checkGhosttyHEnum(Inspector, "GHOSTTY_INSPECTOR_");
|
||||
}
|
||||
};
|
||||
|
||||
pub const QuitTimer = enum(c_int) {
|
||||
start,
|
||||
stop,
|
||||
|
||||
test "ghostty_h QuitTimer" {
|
||||
try lib.checkGhosttyHEnum(QuitTimer, "GHOSTTY_QUIT_TIMER_");
|
||||
}
|
||||
};
|
||||
|
||||
pub const Readonly = enum(c_int) {
|
||||
off,
|
||||
on,
|
||||
|
||||
test "ghostty_h Readonly" {
|
||||
try lib.checkGhosttyHEnum(Readonly, "GHOSTTY_READONLY_");
|
||||
}
|
||||
};
|
||||
|
||||
pub const MouseVisibility = enum(c_int) {
|
||||
visible,
|
||||
hidden,
|
||||
|
||||
test "ghostty_h MouseVisibility" {
|
||||
try lib.checkGhosttyHEnum(MouseVisibility, "GHOSTTY_MOUSE_");
|
||||
}
|
||||
};
|
||||
|
||||
/// Whether to prompt for the surface title or tab title.
|
||||
pub const PromptTitle = enum(c_int) {
|
||||
surface,
|
||||
tab,
|
||||
|
||||
test "ghostty_h PromptTitle" {
|
||||
try lib.checkGhosttyHEnum(PromptTitle, "GHOSTTY_PROMPT_TITLE_");
|
||||
}
|
||||
};
|
||||
|
||||
pub const MouseOverLink = struct {
|
||||
@@ -782,6 +844,11 @@ pub const ColorKind = enum(c_int) {
|
||||
|
||||
// 0+ values indicate a palette index
|
||||
_,
|
||||
|
||||
// TODO: check non-non-exhaustive enums
|
||||
// test "ghostty_h ColorKind" {
|
||||
// try lib.checkGhosttyHEnum(ColorKind, "GHOSTTY_COLOR_KIND_");
|
||||
// }
|
||||
};
|
||||
|
||||
pub const ReloadConfig = extern struct {
|
||||
@@ -832,6 +899,10 @@ pub const OpenUrl = struct {
|
||||
|
||||
/// The URL is known to contain HTML content.
|
||||
html,
|
||||
|
||||
test "ghostty_h OpenUrl.Kind" {
|
||||
try lib.checkGhosttyHEnum(Kind, "GHOSTTY_ACTION_OPEN_URL_KIND_");
|
||||
}
|
||||
};
|
||||
|
||||
// Sync with: ghostty_action_open_url_s
|
||||
@@ -858,6 +929,10 @@ pub const CloseTabMode = enum(c_int) {
|
||||
other,
|
||||
/// Close all tabs to the right of the current tab.
|
||||
right,
|
||||
|
||||
test "ghostty_h CloseTabMode" {
|
||||
try lib.checkGhosttyHEnum(CloseTabMode, "GHOSTTY_ACTION_CLOSE_TAB_MODE_");
|
||||
}
|
||||
};
|
||||
|
||||
pub const CommandFinished = struct {
|
||||
@@ -922,3 +997,7 @@ pub const SearchSelected = struct {
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
test {
|
||||
_ = std.testing.refAllDeclsRecursive(@This());
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
const std = @import("std");
|
||||
const Allocator = std.mem.Allocator;
|
||||
const assert = @import("../quirks.zig").inlineAssert;
|
||||
const lib = @import("../lib/main.zig");
|
||||
|
||||
pub const Errors = error{
|
||||
/// The IPC failed. If a function returns this error, it's expected that
|
||||
@@ -22,6 +23,10 @@ pub const Target = union(Key) {
|
||||
pub const Key = enum(c_int) {
|
||||
class,
|
||||
detect,
|
||||
|
||||
test "ghostty_h Target.Key" {
|
||||
try lib.checkGhosttyHEnum(Key, "GHOSTTY_IPC_TARGET_");
|
||||
}
|
||||
};
|
||||
|
||||
// Sync with: ghostty_ipc_target_u
|
||||
@@ -106,8 +111,12 @@ pub const Action = union(enum) {
|
||||
};
|
||||
|
||||
/// Sync with: ghostty_ipc_action_tag_e
|
||||
pub const Key = enum(c_uint) {
|
||||
pub const Key = enum(c_int) {
|
||||
new_window,
|
||||
|
||||
test "ghostty_h Action.Key" {
|
||||
try lib.checkGhosttyHEnum(Key, "GHOSTTY_IPC_ACTION_");
|
||||
}
|
||||
};
|
||||
|
||||
/// Sync with: ghostty_ipc_action_u
|
||||
|
||||
@@ -91,3 +91,55 @@ test "abi by removing a key" {
|
||||
try testing.expectEqual(2, @intFromEnum(T.d));
|
||||
}
|
||||
}
|
||||
|
||||
/// Verify that for every key in enum T, there is a matching declaration in
|
||||
/// `ghostty.h` with the correct value.
|
||||
pub fn checkGhosttyHEnum(comptime T: type, comptime prefix: []const u8) !void {
|
||||
const info = @typeInfo(T);
|
||||
|
||||
try std.testing.expect(info == .@"enum");
|
||||
try std.testing.expect(info.@"enum".tag_type == c_int);
|
||||
|
||||
@setEvalBranchQuota(1000000);
|
||||
|
||||
const c = @cImport({
|
||||
@cInclude("ghostty.h");
|
||||
});
|
||||
|
||||
var set: std.EnumSet(T) = .initFull();
|
||||
|
||||
const c_decls = @typeInfo(c).@"struct".decls;
|
||||
const enum_fields = info.@"enum".fields;
|
||||
|
||||
inline for (enum_fields) |field| {
|
||||
const upper_name = comptime u: {
|
||||
var buf: [128]u8 = undefined;
|
||||
break :u std.ascii.upperString(&buf, field.name);
|
||||
};
|
||||
|
||||
inline for (c_decls) |decl| {
|
||||
if (!comptime std.mem.startsWith(u8, decl.name, prefix)) continue;
|
||||
|
||||
const suffix = decl.name[prefix.len..];
|
||||
|
||||
if (!comptime std.mem.eql(u8, suffix, upper_name)) continue;
|
||||
|
||||
std.testing.expectEqual(field.value, @field(c, decl.name)) catch |e| {
|
||||
std.log.err(@typeName(T) ++ " key " ++ field.name ++ " does not have the same backing int as " ++ decl.name, .{});
|
||||
return e;
|
||||
};
|
||||
|
||||
set.remove(@enumFromInt(field.value));
|
||||
}
|
||||
}
|
||||
|
||||
std.testing.expect(set.count() == 0) catch |e| {
|
||||
var it = set.iterator();
|
||||
while (it.next()) |v| {
|
||||
var buf: [128]u8 = undefined;
|
||||
const n = std.ascii.upperString(&buf, @tagName(v));
|
||||
std.log.err("ghostty.h is missing value for {s}{s}, {t}", .{ prefix, n, v });
|
||||
}
|
||||
return e;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ const unionpkg = @import("union.zig");
|
||||
|
||||
pub const allocator = @import("allocator.zig");
|
||||
pub const Enum = enumpkg.Enum;
|
||||
pub const checkGhosttyHEnum = enumpkg.checkGhosttyHEnum;
|
||||
pub const String = types.String;
|
||||
pub const Struct = @import("struct.zig").Struct;
|
||||
pub const Target = @import("target.zig").Target;
|
||||
|
||||
@@ -31,6 +31,7 @@ pub const ScreenSize = size.ScreenSize;
|
||||
pub const GridSize = size.GridSize;
|
||||
pub const Padding = size.Padding;
|
||||
pub const cursorStyle = cursor.style;
|
||||
pub const lib = @import("lib/main.zig");
|
||||
|
||||
/// The implementation to use for the renderer. This is comptime chosen
|
||||
/// so that every build has exactly one renderer implementation.
|
||||
@@ -44,8 +45,12 @@ pub const Renderer = switch (build_config.renderer) {
|
||||
/// renderers even if some states aren't reachable so that our API users
|
||||
/// can use the same enum for all renderers.
|
||||
pub const Health = enum(c_int) {
|
||||
healthy = 0,
|
||||
unhealthy = 1,
|
||||
healthy,
|
||||
unhealthy,
|
||||
|
||||
test "ghostty_h Health" {
|
||||
try lib.checkGhosttyHEnum(Health, "GHOSTTY_RENDERER_HEALTH_");
|
||||
}
|
||||
};
|
||||
|
||||
test {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
const std = @import("std");
|
||||
const build_options = @import("terminal_options");
|
||||
const lib = @import("../lib/main.zig");
|
||||
|
||||
/// The possible cursor shapes. Not all app runtimes support these shapes.
|
||||
/// The shapes are always based on the W3C supported cursor styles so we
|
||||
@@ -63,6 +64,10 @@ pub const MouseShape = enum(c_int) {
|
||||
.none => void,
|
||||
};
|
||||
};
|
||||
|
||||
test "ghostty_h MouseShape" {
|
||||
try lib.checkGhosttyHEnum(MouseShape, "GHOSTTY_MOUSE_SHAPE_");
|
||||
}
|
||||
};
|
||||
|
||||
const string_map = std.StaticStringMap(MouseShape).initComptime(.{
|
||||
@@ -131,3 +136,7 @@ test "cursor shape from string" {
|
||||
const testing = std.testing;
|
||||
try testing.expectEqual(MouseShape.default, MouseShape.fromString("default").?);
|
||||
}
|
||||
|
||||
test {
|
||||
_ = std.testing.refAllDeclsRecursive(@This());
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ const LibEnum = @import("../lib/enum.zig").Enum;
|
||||
const kitty_color = @import("kitty/color.zig");
|
||||
const parsers = @import("osc/parsers.zig");
|
||||
const encoding = @import("osc/encoding.zig");
|
||||
const lib = @import("../lib/main.zig");
|
||||
|
||||
pub const color = parsers.color;
|
||||
pub const semantic_prompt = parsers.semantic_prompt;
|
||||
@@ -197,6 +198,10 @@ pub const Command = union(Key) {
|
||||
@"error",
|
||||
indeterminate,
|
||||
pause,
|
||||
|
||||
test "ghostty_h Command.ProgressReport.State" {
|
||||
try lib.checkGhosttyHEnum(State, "GHOSTTY_PROGRESS_STATE_");
|
||||
}
|
||||
};
|
||||
|
||||
state: State,
|
||||
|
||||
Reference in New Issue
Block a user