mirror of
https://github.com/ghostty-org/ghostty.git
synced 2026-04-18 21:40:29 +00:00
Fix zig build test-lib-vt (#11778)
- Our `checkGhosttyH` calls need to be guarded on building Ghostty app which has it - Move FileFormatter to its own file to avoid poisoning test refs with Config.zig which pulls in the world - Move WindowPaddingBalance to renderer to avoid pulling in Config.zig - Add a `zig build test-lib-vt` CI job
This commit is contained in:
31
.github/workflows/test.yml
vendored
31
.github/workflows/test.yml
vendored
@@ -105,6 +105,7 @@ jobs:
|
||||
- test-sentry-linux
|
||||
- test-i18n
|
||||
- test-fuzz-libghostty
|
||||
- test-lib-vt
|
||||
- test-macos
|
||||
- pinact
|
||||
- prettier
|
||||
@@ -911,6 +912,36 @@ jobs:
|
||||
- name: Test System Build
|
||||
run: nix develop -c zig build --system ${ZIG_GLOBAL_CACHE_DIR}/p
|
||||
|
||||
test-lib-vt:
|
||||
if: github.repository == 'ghostty-org/ghostty' && needs.skip.outputs.skip != 'true'
|
||||
needs: skip
|
||||
runs-on: namespace-profile-ghostty-md
|
||||
env:
|
||||
ZIG_LOCAL_CACHE_DIR: /zig/local-cache
|
||||
ZIG_GLOBAL_CACHE_DIR: /zig/global-cache
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- name: Setup Cache
|
||||
uses: namespacelabs/nscloud-cache-action@a90bb5d4b27522ce881c6e98eebd7d7e6d1653f9 # v1.4.2
|
||||
with:
|
||||
path: |
|
||||
/nix
|
||||
/zig
|
||||
|
||||
# Install Nix and use that to run our tests so our environment matches exactly.
|
||||
- uses: cachix/install-nix-action@1ca7d21a94afc7c957383a2d217460d980de4934 # v31.10.1
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
- uses: cachix/cachix-action@1eb2ef646ac0255473d23a5907ad7b04ce94065c # v17
|
||||
with:
|
||||
name: ghostty
|
||||
authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}"
|
||||
|
||||
- name: Test
|
||||
run: nix develop -c zig build test-lib-vt
|
||||
|
||||
test-gtk:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
|
||||
@@ -2,6 +2,7 @@ const builtin = @import("builtin");
|
||||
|
||||
const file_load = @import("config/file_load.zig");
|
||||
const formatter = @import("config/formatter.zig");
|
||||
const formatter_file = @import("config/formatter_file.zig");
|
||||
pub const Config = @import("config/Config.zig");
|
||||
pub const conditional = @import("config/conditional.zig");
|
||||
pub const io = @import("config/io.zig");
|
||||
@@ -10,7 +11,7 @@ pub const edit = @import("config/edit.zig");
|
||||
pub const url = @import("config/url.zig");
|
||||
|
||||
pub const ConditionalState = conditional.State;
|
||||
pub const FileFormatter = formatter.FileFormatter;
|
||||
pub const FileFormatter = formatter_file.FileFormatter;
|
||||
pub const entryFormatter = formatter.entryFormatter;
|
||||
pub const formatEntry = formatter.formatEntry;
|
||||
pub const preferredDefaultFilePath = file_load.preferredDefaultFilePath;
|
||||
|
||||
@@ -39,6 +39,7 @@ pub const Path = @import("path.zig").Path;
|
||||
pub const RepeatablePath = @import("path.zig").RepeatablePath;
|
||||
const ClipboardCodepointMap = @import("ClipboardCodepointMap.zig");
|
||||
const KeyRemapSet = @import("../input/key_mods.zig").RemapSet;
|
||||
pub const WindowPaddingBalance = @import("../renderer/size.zig").PaddingBalance;
|
||||
const string = @import("string.zig");
|
||||
|
||||
// We do this instead of importing all of terminal/main.zig to
|
||||
@@ -5245,12 +5246,6 @@ pub const Fullscreen = enum(c_int) {
|
||||
@"non-native-padded-notch",
|
||||
};
|
||||
|
||||
pub const WindowPaddingBalance = enum {
|
||||
false,
|
||||
true,
|
||||
equal,
|
||||
};
|
||||
|
||||
pub const WindowPaddingColor = enum {
|
||||
background,
|
||||
extend,
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
const formatter = @This();
|
||||
|
||||
const std = @import("std");
|
||||
const Allocator = std.mem.Allocator;
|
||||
const help_strings = @import("help_strings");
|
||||
const Config = @import("Config.zig");
|
||||
const Key = @import("key.zig").Key;
|
||||
|
||||
/// Returns a single entry formatter for the given field name and writer.
|
||||
@@ -125,106 +124,6 @@ pub fn formatEntry(
|
||||
@compileError("missing case for type");
|
||||
}
|
||||
|
||||
/// FileFormatter is a formatter implementation that outputs the
|
||||
/// config in a file-like format. This uses more generous whitespace,
|
||||
/// can include comments, etc.
|
||||
pub const FileFormatter = struct {
|
||||
alloc: Allocator,
|
||||
config: *const Config,
|
||||
|
||||
/// Include comments for documentation of each key
|
||||
docs: bool = false,
|
||||
|
||||
/// Only include changed values from the default.
|
||||
changed: bool = false,
|
||||
|
||||
/// Implements std.fmt so it can be used directly with std.fmt.
|
||||
pub fn format(
|
||||
self: FileFormatter,
|
||||
writer: *std.Io.Writer,
|
||||
) std.Io.Writer.Error!void {
|
||||
@setEvalBranchQuota(10_000);
|
||||
|
||||
// If we're change-tracking then we need the default config to
|
||||
// compare against.
|
||||
var default: ?Config = if (self.changed)
|
||||
Config.default(self.alloc) catch return error.WriteFailed
|
||||
else
|
||||
null;
|
||||
defer if (default) |*v| v.deinit();
|
||||
|
||||
inline for (@typeInfo(Config).@"struct".fields) |field| {
|
||||
if (field.name[0] == '_') continue;
|
||||
|
||||
const value = @field(self.config, field.name);
|
||||
const do_format = if (default) |d| format: {
|
||||
const key = @field(Key, field.name);
|
||||
break :format d.changed(self.config, key);
|
||||
} else true;
|
||||
|
||||
if (do_format) {
|
||||
const do_docs = self.docs and @hasDecl(help_strings.Config, field.name);
|
||||
if (do_docs) {
|
||||
const help = @field(help_strings.Config, field.name);
|
||||
var lines = std.mem.splitScalar(u8, help, '\n');
|
||||
while (lines.next()) |line| {
|
||||
try writer.print("# {s}\n", .{line});
|
||||
}
|
||||
}
|
||||
|
||||
formatEntry(
|
||||
field.type,
|
||||
field.name,
|
||||
value,
|
||||
writer,
|
||||
) catch return error.WriteFailed;
|
||||
|
||||
if (do_docs) try writer.print("\n", .{});
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
test "format default config" {
|
||||
const testing = std.testing;
|
||||
const alloc = testing.allocator;
|
||||
var cfg = try Config.default(alloc);
|
||||
defer cfg.deinit();
|
||||
|
||||
var buf: std.Io.Writer.Allocating = .init(alloc);
|
||||
defer buf.deinit();
|
||||
|
||||
// We just make sure this works without errors. We aren't asserting output.
|
||||
const fmt: FileFormatter = .{
|
||||
.alloc = alloc,
|
||||
.config = &cfg,
|
||||
};
|
||||
try fmt.format(&buf.writer);
|
||||
|
||||
//std.log.warn("{s}", .{buf.written()});
|
||||
}
|
||||
|
||||
test "format default config changed" {
|
||||
const testing = std.testing;
|
||||
const alloc = testing.allocator;
|
||||
var cfg = try Config.default(alloc);
|
||||
defer cfg.deinit();
|
||||
cfg.@"font-size" = 42;
|
||||
|
||||
var buf: std.Io.Writer.Allocating = .init(alloc);
|
||||
defer buf.deinit();
|
||||
|
||||
// We just make sure this works without errors. We aren't asserting output.
|
||||
const fmt: FileFormatter = .{
|
||||
.alloc = alloc,
|
||||
.config = &cfg,
|
||||
.changed = true,
|
||||
};
|
||||
try fmt.format(&buf.writer);
|
||||
|
||||
//std.log.warn("{s}", .{buf.written()});
|
||||
}
|
||||
|
||||
test "formatEntry bool" {
|
||||
const testing = std.testing;
|
||||
|
||||
|
||||
110
src/config/formatter_file.zig
Normal file
110
src/config/formatter_file.zig
Normal file
@@ -0,0 +1,110 @@
|
||||
const std = @import("std");
|
||||
const Allocator = std.mem.Allocator;
|
||||
const Config = @import("Config.zig");
|
||||
const Key = @import("key.zig").Key;
|
||||
const help_strings = @import("help_strings");
|
||||
const formatter = @import("formatter.zig");
|
||||
|
||||
// IMPORTANT: This is in a separate file from formatter.zig because it
|
||||
// puts a build-time dependency on Config.zig which brings in too much
|
||||
// into libghostty-vt tests which reference some formattable types.
|
||||
|
||||
/// FileFormatter is a formatter implementation that outputs the
|
||||
/// config in a file-like format. This uses more generous whitespace,
|
||||
/// can include comments, etc.
|
||||
pub const FileFormatter = struct {
|
||||
alloc: Allocator,
|
||||
config: *const Config,
|
||||
|
||||
/// Include comments for documentation of each key
|
||||
docs: bool = false,
|
||||
|
||||
/// Only include changed values from the default.
|
||||
changed: bool = false,
|
||||
|
||||
/// Implements std.fmt so it can be used directly with std.fmt.
|
||||
pub fn format(
|
||||
self: FileFormatter,
|
||||
writer: *std.Io.Writer,
|
||||
) std.Io.Writer.Error!void {
|
||||
@setEvalBranchQuota(10_000);
|
||||
|
||||
// If we're change-tracking then we need the default config to
|
||||
// compare against.
|
||||
var default: ?Config = if (self.changed)
|
||||
Config.default(self.alloc) catch return error.WriteFailed
|
||||
else
|
||||
null;
|
||||
defer if (default) |*v| v.deinit();
|
||||
|
||||
inline for (@typeInfo(Config).@"struct".fields) |field| {
|
||||
if (field.name[0] == '_') continue;
|
||||
|
||||
const value = @field(self.config, field.name);
|
||||
const do_format = if (default) |d| format: {
|
||||
const key = @field(Key, field.name);
|
||||
break :format d.changed(self.config, key);
|
||||
} else true;
|
||||
|
||||
if (do_format) {
|
||||
const do_docs = self.docs and @hasDecl(help_strings.Config, field.name);
|
||||
if (do_docs) {
|
||||
const help = @field(help_strings.Config, field.name);
|
||||
var lines = std.mem.splitScalar(u8, help, '\n');
|
||||
while (lines.next()) |line| {
|
||||
try writer.print("# {s}\n", .{line});
|
||||
}
|
||||
}
|
||||
|
||||
formatter.formatEntry(
|
||||
field.type,
|
||||
field.name,
|
||||
value,
|
||||
writer,
|
||||
) catch return error.WriteFailed;
|
||||
|
||||
if (do_docs) try writer.print("\n", .{});
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
test "format default config" {
|
||||
const testing = std.testing;
|
||||
const alloc = testing.allocator;
|
||||
var cfg = try Config.default(alloc);
|
||||
defer cfg.deinit();
|
||||
|
||||
var buf: std.Io.Writer.Allocating = .init(alloc);
|
||||
defer buf.deinit();
|
||||
|
||||
// We just make sure this works without errors. We aren't asserting output.
|
||||
const fmt: FileFormatter = .{
|
||||
.alloc = alloc,
|
||||
.config = &cfg,
|
||||
};
|
||||
try fmt.format(&buf.writer);
|
||||
|
||||
//std.log.warn("{s}", .{buf.written()});
|
||||
}
|
||||
|
||||
test "format default config changed" {
|
||||
const testing = std.testing;
|
||||
const alloc = testing.allocator;
|
||||
var cfg = try Config.default(alloc);
|
||||
defer cfg.deinit();
|
||||
cfg.@"font-size" = 42;
|
||||
|
||||
var buf: std.Io.Writer.Allocating = .init(alloc);
|
||||
defer buf.deinit();
|
||||
|
||||
// We just make sure this works without errors. We aren't asserting output.
|
||||
const fmt: FileFormatter = .{
|
||||
.alloc = alloc,
|
||||
.config = &cfg,
|
||||
.changed = true,
|
||||
};
|
||||
try fmt.format(&buf.writer);
|
||||
|
||||
//std.log.warn("{s}", .{buf.written()});
|
||||
}
|
||||
@@ -2,7 +2,6 @@ const std = @import("std");
|
||||
const builtin = @import("builtin");
|
||||
const Allocator = std.mem.Allocator;
|
||||
const cimgui = @import("dcimgui");
|
||||
const OptionAsAlt = @import("config.zig").OptionAsAlt;
|
||||
|
||||
pub const Mods = @import("key_mods.zig").Mods;
|
||||
|
||||
|
||||
@@ -95,7 +95,10 @@ test "abi by removing a key" {
|
||||
/// Verify that for every key in enum T, there is a matching declaration in
|
||||
/// `ghostty.h` with the correct value. This should only ever be called inside a `test`
|
||||
/// because the `ghostty.h` module is only available then.
|
||||
pub fn checkGhosttyHEnum(comptime T: type, comptime prefix: []const u8) !void {
|
||||
pub fn checkGhosttyHEnum(
|
||||
comptime T: type,
|
||||
comptime prefix: []const u8,
|
||||
) !void {
|
||||
const info = @typeInfo(T);
|
||||
|
||||
try std.testing.expect(info == .@"enum");
|
||||
|
||||
@@ -1,10 +1,22 @@
|
||||
const std = @import("std");
|
||||
const Allocator = std.mem.Allocator;
|
||||
const configpkg = @import("../config.zig");
|
||||
const terminal_size = @import("../terminal/size.zig");
|
||||
|
||||
const log = std.log.scoped(.renderer_size);
|
||||
|
||||
/// Controls how extra whitespace around the terminal grid is distributed.
|
||||
pub const PaddingBalance = enum {
|
||||
/// No balancing; padding is applied as specified explicitly.
|
||||
false,
|
||||
/// Balances padding but caps the top padding so the first row doesn't
|
||||
/// drift too far from the top of the window. Excess vertical space is
|
||||
/// shifted to the bottom.
|
||||
true,
|
||||
/// Distributes leftover space equally on all sides so the grid is
|
||||
/// centered within the screen.
|
||||
equal,
|
||||
};
|
||||
|
||||
/// All relevant sizes for a rendered terminal. These are all the sizes that
|
||||
/// any functionality should need to know about the terminal in order to
|
||||
/// convert between any coordinate systems.
|
||||
@@ -37,7 +49,7 @@ pub const Size = struct {
|
||||
pub fn balancePadding(
|
||||
self: *Size,
|
||||
explicit: Padding,
|
||||
mode: configpkg.Config.WindowPaddingBalance,
|
||||
mode: PaddingBalance,
|
||||
) void {
|
||||
// This ensure grid() does the right thing
|
||||
self.padding = explicit;
|
||||
|
||||
@@ -43,6 +43,7 @@ pub fn get(
|
||||
}
|
||||
|
||||
return switch (data) {
|
||||
.invalid => .invalid_value,
|
||||
inline else => |comptime_data| getTyped(
|
||||
comptime_data,
|
||||
@ptrCast(@alignCast(out)),
|
||||
|
||||
@@ -110,6 +110,7 @@ pub fn get(
|
||||
}
|
||||
|
||||
return switch (data) {
|
||||
.invalid => .invalid_value,
|
||||
inline else => |comptime_data| getTyped(
|
||||
cell_,
|
||||
comptime_data,
|
||||
|
||||
@@ -259,7 +259,7 @@ test "setopt_from_terminal" {
|
||||
|
||||
// Options should reflect defaults from a fresh terminal
|
||||
try testing.expect(!e.?.opts.cursor_key_application);
|
||||
try testing.expect(!e.?.opts.alt_esc_prefix);
|
||||
try testing.expect(e.?.opts.alt_esc_prefix);
|
||||
try testing.expectEqual(KittyFlags.disabled, e.?.opts.kitty_flags);
|
||||
try testing.expectEqual(OptionAsAlt.false, e.?.opts.macos_option_as_alt);
|
||||
}
|
||||
|
||||
@@ -76,6 +76,7 @@ pub fn commandData(
|
||||
}
|
||||
|
||||
return switch (data) {
|
||||
.invalid => false,
|
||||
inline else => |comptime_data| commandDataTyped(
|
||||
command_,
|
||||
comptime_data,
|
||||
|
||||
@@ -193,6 +193,7 @@ pub fn get(
|
||||
}
|
||||
|
||||
return switch (data) {
|
||||
.invalid => .invalid_value,
|
||||
inline else => |comptime_data| getTyped(
|
||||
state_,
|
||||
comptime_data,
|
||||
@@ -336,11 +337,7 @@ pub fn colors_get(
|
||||
out_colors.cursor_has_value = colors.cursor != null;
|
||||
}
|
||||
|
||||
if (lib.structSizedFieldFits(
|
||||
Colors,
|
||||
out_size,
|
||||
"palette",
|
||||
)) {
|
||||
{
|
||||
const palette_offset = @offsetOf(Colors, "palette");
|
||||
if (out_size > palette_offset) {
|
||||
const available = out_size - palette_offset;
|
||||
@@ -467,6 +464,7 @@ pub fn row_cells_get(
|
||||
}
|
||||
|
||||
return switch (data) {
|
||||
.invalid => .invalid_value,
|
||||
inline else => |comptime_data| rowCellsGetTyped(
|
||||
cells_,
|
||||
comptime_data,
|
||||
@@ -566,6 +564,7 @@ pub fn row_get(
|
||||
}
|
||||
|
||||
return switch (data) {
|
||||
.invalid => .invalid_value,
|
||||
inline else => |comptime_data| rowGetTyped(
|
||||
iterator_,
|
||||
comptime_data,
|
||||
|
||||
@@ -73,6 +73,7 @@ pub fn get(
|
||||
}
|
||||
|
||||
return switch (data) {
|
||||
.invalid => .invalid_value,
|
||||
inline else => |comptime_data| getTyped(
|
||||
row_,
|
||||
comptime_data,
|
||||
|
||||
@@ -196,6 +196,7 @@ pub fn get(
|
||||
}
|
||||
|
||||
return switch (data) {
|
||||
.invalid => .invalid_value,
|
||||
inline else => |comptime_data| getTyped(
|
||||
terminal_,
|
||||
comptime_data,
|
||||
|
||||
@@ -92,6 +92,7 @@ pub const Shape = enum(c_int) {
|
||||
};
|
||||
|
||||
test "ghostty.h MouseShape" {
|
||||
if (comptime build_options.artifact == .lib) return error.SkipZigTest;
|
||||
try lib.checkGhosttyHEnum(Shape, "GHOSTTY_MOUSE_SHAPE_");
|
||||
}
|
||||
};
|
||||
|
||||
@@ -205,6 +205,7 @@ pub const Command = union(Key) {
|
||||
pause,
|
||||
|
||||
test "ghostty.h Command.ProgressReport.State" {
|
||||
if (comptime build_options.artifact == .lib) return error.SkipZigTest;
|
||||
try lib.checkGhosttyHEnum(State, "GHOSTTY_PROGRESS_STATE_");
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user