From 039b1e7250e917e7fbc1c1f2dfb9f92d3d848c36 Mon Sep 17 00:00:00 2001 From: Pierre Tachoire Date: Tue, 4 Feb 2025 16:36:53 +0100 Subject: [PATCH 1/2] zig fmt --- build.zig.zon | 18 +- src/server/server.zig | 2 +- support/autobahn/client/build.zig.zon | 12 +- support/autobahn/server/build.zig.zon | 12 +- support/autobahn/server/main.zig | 2 +- test_runner.zig | 527 +++++++++++++------------- 6 files changed, 283 insertions(+), 290 deletions(-) diff --git a/build.zig.zon b/build.zig.zon index 876719b..ec11737 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -1,11 +1,11 @@ .{ - .name = "websocket", - .version = "0.1.0", - .dependencies = .{}, - .paths = .{ - "readme.md", - "build.zig", - "build.zig.zon", - "src", - }, + .name = "websocket", + .version = "0.1.0", + .dependencies = .{}, + .paths = .{ + "readme.md", + "build.zig", + "build.zig.zon", + "src", + }, } diff --git a/src/server/server.zig b/src/server/server.zig index 25e5a5b..37b6826 100644 --- a/src/server/server.zig +++ b/src/server/server.zig @@ -1712,7 +1712,7 @@ test "tests:beforeAll" { .port = 9292, .address = "127.0.0.1", }); - test_thread = try test_server.listenInNewThread(.{.ws_host = "127.0.0.1", .ws_port = 9292}); + test_thread = try test_server.listenInNewThread(.{ .ws_host = "127.0.0.1", .ws_port = 9292 }); } test "tests:afterAll" { diff --git a/support/autobahn/client/build.zig.zon b/support/autobahn/client/build.zig.zon index 94dbee3..1ac354b 100644 --- a/support/autobahn/client/build.zig.zon +++ b/support/autobahn/client/build.zig.zon @@ -1,10 +1,8 @@ .{ - .name = "autobahn_test_client", - .paths = .{""}, - .version = "0.0.0", - .dependencies = .{ - .websocket = .{ - .path = "../../../" + .name = "autobahn_test_client", + .paths = .{""}, + .version = "0.0.0", + .dependencies = .{ + .websocket = .{ .path = "../../../" }, }, - }, } diff --git a/support/autobahn/server/build.zig.zon b/support/autobahn/server/build.zig.zon index 534706b..2ed8c25 100644 --- a/support/autobahn/server/build.zig.zon +++ b/support/autobahn/server/build.zig.zon @@ -1,8 +1,8 @@ .{ - .name = "autobahn_test_server", - .paths = .{""}, - .version = "0.0.0", - .dependencies = .{ - .websocket = .{.path = "../../../"}, - }, + .name = "autobahn_test_server", + .paths = .{""}, + .version = "0.0.0", + .dependencies = .{ + .websocket = .{ .path = "../../../" }, + }, } diff --git a/support/autobahn/server/main.zig b/support/autobahn/server/main.zig index aac824d..963d294 100644 --- a/support/autobahn/server/main.zig +++ b/support/autobahn/server/main.zig @@ -94,7 +94,7 @@ const Handler = struct { if (std.unicode.utf8ValidateSlice(data)) { try self.conn.writeText(data); } else { - self.conn.close(.{.code = 1007}) catch {}; + self.conn.close(.{ .code = 1007 }) catch {}; } }, } diff --git a/test_runner.zig b/test_runner.zig index 3403cf3..3cf194f 100644 --- a/test_runner.zig +++ b/test_runner.zig @@ -6,11 +6,9 @@ // .root_source_file = b.path("src/main.zig"), // }); -pub const std_options = std.Options{ - .log_scope_levels = &[_]std.log.ScopeLevel{ - .{.scope = .websocket, .level = .warn}, - } -}; +pub const std_options = std.Options{ .log_scope_levels = &[_]std.log.ScopeLevel{ + .{ .scope = .websocket, .level = .warn }, +} }; const std = @import("std"); const builtin = @import("builtin"); @@ -23,286 +21,283 @@ const BORDER = "=" ** 80; var current_test: ?[]const u8 = null; pub fn main() !void { - var mem: [8192]u8 = undefined; - var fba = std.heap.FixedBufferAllocator.init(&mem); - - const allocator = fba.allocator(); - - const env = Env.init(allocator); - defer env.deinit(allocator); - - var slowest = SlowTracker.init(allocator, 5); - defer slowest.deinit(); - - var pass: usize = 0; - var fail: usize = 0; - var skip: usize = 0; - var leak: usize = 0; - - const printer = Printer.init(); - printer.fmt("\r\x1b[0K", .{}); // beginning of line and clear to end of line - - - for (builtin.test_functions) |t| { - if (isSetup(t)) { - t.func() catch |err| { - printer.status(.fail, "\nsetup \"{s}\" failed: {}\n", .{t.name, err}); - return err; - }; - } - } - - for (builtin.test_functions) |t| { - if (isSetup(t) or isTeardown(t)) { - continue; - } - - var status = Status.pass; - slowest.startTiming(); - - - const is_unnamed_test = isUnnamed(t); - if (env.filter) |f| { - if (!is_unnamed_test and std.mem.indexOf(u8, t.name, f) == null) { - continue; - } - } - - const friendly_name = blk: { - const name = t.name; - var it = std.mem.splitScalar(u8, name, '.'); - while (it.next()) |value| { - if (std.mem.eql(u8, value, "test")) { - const rest = it.rest(); - break :blk if (rest.len > 0) rest else name; - } - } - break :blk name; - }; - - current_test = friendly_name; - std.testing.allocator_instance = .{}; - const result = t.func(); - current_test = null; - - const ns_taken = slowest.endTiming(friendly_name); - - if (std.testing.allocator_instance.deinit() == .leak) { - leak += 1; - printer.status(.fail, "\n{s}\n\"{s}\" - Memory Leak\n{s}\n", .{BORDER, friendly_name, BORDER}); - } - - if (result) |_| { - pass += 1; - } else |err| switch (err) { - error.SkipZigTest => { - skip += 1; - status = .skip; - }, - else => { - status = .fail; - fail += 1; - printer.status(.fail, "\n{s}\n\"{s}\" - {s}\n{s}\n", .{BORDER, friendly_name, @errorName(err), BORDER}); - if (@errorReturnTrace()) |trace| { - std.debug.dumpStackTrace(trace.*); - } - if (env.fail_first) { - break; - } - } - } - - if (env.verbose) { - const ms = @as(f64, @floatFromInt(ns_taken)) / 1_000_000.0; - printer.status(status, "{s} ({d:.2}ms)\n", .{friendly_name, ms}); - } else { - printer.status(status, ".", .{}); - } - } - - - for (builtin.test_functions) |t| { - if (isTeardown(t)) { - t.func() catch |err| { - printer.status(.fail, "\nteardown \"{s}\" failed: {}\n", .{t.name, err}); - return err; - }; - } - } - - const total_tests = pass + fail; - const status = if (fail == 0) Status.pass else Status.fail; - printer.status(status, "\n{d} of {d} test{s} passed\n", .{pass, total_tests, if (total_tests != 1) "s" else ""}); - if (skip > 0) { - printer.status(.skip, "{d} test{s} skipped\n", .{skip, if (skip != 1) "s" else ""}); - } - if (leak > 0) { - printer.status(.fail, "{d} test{s} leaked\n", .{leak, if (leak != 1) "s" else ""}); - } - printer.fmt("\n", .{}); - try slowest.display(printer); - printer.fmt("\n", .{}); - std.posix.exit(if (fail == 0) 0 else 1); + var mem: [8192]u8 = undefined; + var fba = std.heap.FixedBufferAllocator.init(&mem); + + const allocator = fba.allocator(); + + const env = Env.init(allocator); + defer env.deinit(allocator); + + var slowest = SlowTracker.init(allocator, 5); + defer slowest.deinit(); + + var pass: usize = 0; + var fail: usize = 0; + var skip: usize = 0; + var leak: usize = 0; + + const printer = Printer.init(); + printer.fmt("\r\x1b[0K", .{}); // beginning of line and clear to end of line + + for (builtin.test_functions) |t| { + if (isSetup(t)) { + t.func() catch |err| { + printer.status(.fail, "\nsetup \"{s}\" failed: {}\n", .{ t.name, err }); + return err; + }; + } + } + + for (builtin.test_functions) |t| { + if (isSetup(t) or isTeardown(t)) { + continue; + } + + var status = Status.pass; + slowest.startTiming(); + + const is_unnamed_test = isUnnamed(t); + if (env.filter) |f| { + if (!is_unnamed_test and std.mem.indexOf(u8, t.name, f) == null) { + continue; + } + } + + const friendly_name = blk: { + const name = t.name; + var it = std.mem.splitScalar(u8, name, '.'); + while (it.next()) |value| { + if (std.mem.eql(u8, value, "test")) { + const rest = it.rest(); + break :blk if (rest.len > 0) rest else name; + } + } + break :blk name; + }; + + current_test = friendly_name; + std.testing.allocator_instance = .{}; + const result = t.func(); + current_test = null; + + const ns_taken = slowest.endTiming(friendly_name); + + if (std.testing.allocator_instance.deinit() == .leak) { + leak += 1; + printer.status(.fail, "\n{s}\n\"{s}\" - Memory Leak\n{s}\n", .{ BORDER, friendly_name, BORDER }); + } + + if (result) |_| { + pass += 1; + } else |err| switch (err) { + error.SkipZigTest => { + skip += 1; + status = .skip; + }, + else => { + status = .fail; + fail += 1; + printer.status(.fail, "\n{s}\n\"{s}\" - {s}\n{s}\n", .{ BORDER, friendly_name, @errorName(err), BORDER }); + if (@errorReturnTrace()) |trace| { + std.debug.dumpStackTrace(trace.*); + } + if (env.fail_first) { + break; + } + }, + } + + if (env.verbose) { + const ms = @as(f64, @floatFromInt(ns_taken)) / 1_000_000.0; + printer.status(status, "{s} ({d:.2}ms)\n", .{ friendly_name, ms }); + } else { + printer.status(status, ".", .{}); + } + } + + for (builtin.test_functions) |t| { + if (isTeardown(t)) { + t.func() catch |err| { + printer.status(.fail, "\nteardown \"{s}\" failed: {}\n", .{ t.name, err }); + return err; + }; + } + } + + const total_tests = pass + fail; + const status = if (fail == 0) Status.pass else Status.fail; + printer.status(status, "\n{d} of {d} test{s} passed\n", .{ pass, total_tests, if (total_tests != 1) "s" else "" }); + if (skip > 0) { + printer.status(.skip, "{d} test{s} skipped\n", .{ skip, if (skip != 1) "s" else "" }); + } + if (leak > 0) { + printer.status(.fail, "{d} test{s} leaked\n", .{ leak, if (leak != 1) "s" else "" }); + } + printer.fmt("\n", .{}); + try slowest.display(printer); + printer.fmt("\n", .{}); + std.posix.exit(if (fail == 0) 0 else 1); } const Printer = struct { - out: std.fs.File.Writer, - - fn init() Printer { - return .{ - .out = std.io.getStdErr().writer(), - }; - } - - fn fmt(self: Printer, comptime format: []const u8, args: anytype) void { - std.fmt.format(self.out, format, args) catch unreachable; - } - - fn status(self: Printer, s: Status, comptime format: []const u8, args: anytype) void { - const color = switch (s) { - .pass => "\x1b[32m", - .fail => "\x1b[31m", - .skip => "\x1b[33m", - else => "", - }; - const out = self.out; - out.writeAll(color) catch @panic("writeAll failed?!"); - std.fmt.format(out, format, args) catch @panic("std.fmt.format failed?!"); - self.fmt("\x1b[0m", .{}); - } + out: std.fs.File.Writer, + + fn init() Printer { + return .{ + .out = std.io.getStdErr().writer(), + }; + } + + fn fmt(self: Printer, comptime format: []const u8, args: anytype) void { + std.fmt.format(self.out, format, args) catch unreachable; + } + + fn status(self: Printer, s: Status, comptime format: []const u8, args: anytype) void { + const color = switch (s) { + .pass => "\x1b[32m", + .fail => "\x1b[31m", + .skip => "\x1b[33m", + else => "", + }; + const out = self.out; + out.writeAll(color) catch @panic("writeAll failed?!"); + std.fmt.format(out, format, args) catch @panic("std.fmt.format failed?!"); + self.fmt("\x1b[0m", .{}); + } }; const Status = enum { - pass, - fail, - skip, - text, + pass, + fail, + skip, + text, }; const SlowTracker = struct { - const SlowestQueue = std.PriorityDequeue(TestInfo, void, compareTiming); - max: usize, - slowest: SlowestQueue, - timer: std.time.Timer, - - fn init(allocator: Allocator, count: u32) SlowTracker { - const timer = std.time.Timer.start() catch @panic("failed to start timer"); - var slowest = SlowestQueue.init(allocator, {}); - slowest.ensureTotalCapacity(count) catch @panic("OOM"); - return .{ - .max = count, - .timer = timer, - .slowest = slowest, - }; - } - - const TestInfo = struct { - ns: u64, - name: []const u8, - }; - - fn deinit(self: SlowTracker) void { - self.slowest.deinit(); - } - - fn startTiming(self: *SlowTracker) void { - self.timer.reset(); - } - - fn endTiming(self: *SlowTracker, test_name: []const u8) u64 { - var timer = self.timer; - const ns = timer.lap(); - - var slowest = &self.slowest; - - if (slowest.count() < self.max) { - // Capacity is fixed to the # of slow tests we want to track - // If we've tracked fewer tests than this capacity, than always add - slowest.add(TestInfo{ .ns = ns, .name = test_name }) catch @panic("failed to track test timing"); - return ns; - } - - { - // Optimization to avoid shifting the dequeue for the common case - // where the test isn't one of our slowest. - const fastest_of_the_slow = slowest.peekMin() orelse unreachable; - if (fastest_of_the_slow.ns > ns) { - // the test was faster than our fastest slow test, don't add - return ns; - } - } - - // the previous fastest of our slow tests, has been pushed off. - _ = slowest.removeMin(); - slowest.add(TestInfo{ .ns = ns, .name = test_name }) catch @panic("failed to track test timing"); - return ns; - } - - fn display(self: *SlowTracker, printer: Printer) !void { - var slowest = self.slowest; - const count = slowest.count(); - printer.fmt("Slowest {d} test{s}: \n", .{count, if (count != 1) "s" else ""}); - while (slowest.removeMinOrNull()) |info| { - const ms = @as(f64, @floatFromInt(info.ns)) / 1_000_000.0; - printer.fmt(" {d:.2}ms\t{s}\n", .{ms, info.name}); - } - } - - fn compareTiming(context: void, a: TestInfo, b: TestInfo) std.math.Order { - _ = context; - return std.math.order(a.ns, b.ns); - } + const SlowestQueue = std.PriorityDequeue(TestInfo, void, compareTiming); + max: usize, + slowest: SlowestQueue, + timer: std.time.Timer, + + fn init(allocator: Allocator, count: u32) SlowTracker { + const timer = std.time.Timer.start() catch @panic("failed to start timer"); + var slowest = SlowestQueue.init(allocator, {}); + slowest.ensureTotalCapacity(count) catch @panic("OOM"); + return .{ + .max = count, + .timer = timer, + .slowest = slowest, + }; + } + + const TestInfo = struct { + ns: u64, + name: []const u8, + }; + + fn deinit(self: SlowTracker) void { + self.slowest.deinit(); + } + + fn startTiming(self: *SlowTracker) void { + self.timer.reset(); + } + + fn endTiming(self: *SlowTracker, test_name: []const u8) u64 { + var timer = self.timer; + const ns = timer.lap(); + + var slowest = &self.slowest; + + if (slowest.count() < self.max) { + // Capacity is fixed to the # of slow tests we want to track + // If we've tracked fewer tests than this capacity, than always add + slowest.add(TestInfo{ .ns = ns, .name = test_name }) catch @panic("failed to track test timing"); + return ns; + } + + { + // Optimization to avoid shifting the dequeue for the common case + // where the test isn't one of our slowest. + const fastest_of_the_slow = slowest.peekMin() orelse unreachable; + if (fastest_of_the_slow.ns > ns) { + // the test was faster than our fastest slow test, don't add + return ns; + } + } + + // the previous fastest of our slow tests, has been pushed off. + _ = slowest.removeMin(); + slowest.add(TestInfo{ .ns = ns, .name = test_name }) catch @panic("failed to track test timing"); + return ns; + } + + fn display(self: *SlowTracker, printer: Printer) !void { + var slowest = self.slowest; + const count = slowest.count(); + printer.fmt("Slowest {d} test{s}: \n", .{ count, if (count != 1) "s" else "" }); + while (slowest.removeMinOrNull()) |info| { + const ms = @as(f64, @floatFromInt(info.ns)) / 1_000_000.0; + printer.fmt(" {d:.2}ms\t{s}\n", .{ ms, info.name }); + } + } + + fn compareTiming(context: void, a: TestInfo, b: TestInfo) std.math.Order { + _ = context; + return std.math.order(a.ns, b.ns); + } }; const Env = struct { - verbose: bool, - fail_first: bool, - filter: ?[]const u8, - - fn init(allocator: Allocator) Env { - return .{ - .verbose = readEnvBool(allocator, "TEST_VERBOSE", true), - .fail_first = readEnvBool(allocator, "TEST_FAIL_FIRST", false), - .filter = readEnv(allocator, "TEST_FILTER"), - }; - } - - fn deinit(self: Env, allocator: Allocator) void { - if (self.filter) |f| { - allocator.free(f); - } - } - - fn readEnv(allocator: Allocator, key: []const u8) ?[]const u8 { - const v = std.process.getEnvVarOwned(allocator, key) catch |err| { - if (err == error.EnvironmentVariableNotFound) { - return null; - } - std.log.warn("failed to get env var {s} due to err {}", .{key, err}); - return null; - }; - return v; - } - - fn readEnvBool(allocator: Allocator, key: []const u8, deflt: bool) bool { - const value = readEnv(allocator, key) orelse return deflt; - defer allocator.free(value); - return std.ascii.eqlIgnoreCase(value, "true"); - } + verbose: bool, + fail_first: bool, + filter: ?[]const u8, + + fn init(allocator: Allocator) Env { + return .{ + .verbose = readEnvBool(allocator, "TEST_VERBOSE", true), + .fail_first = readEnvBool(allocator, "TEST_FAIL_FIRST", false), + .filter = readEnv(allocator, "TEST_FILTER"), + }; + } + + fn deinit(self: Env, allocator: Allocator) void { + if (self.filter) |f| { + allocator.free(f); + } + } + + fn readEnv(allocator: Allocator, key: []const u8) ?[]const u8 { + const v = std.process.getEnvVarOwned(allocator, key) catch |err| { + if (err == error.EnvironmentVariableNotFound) { + return null; + } + std.log.warn("failed to get env var {s} due to err {}", .{ key, err }); + return null; + }; + return v; + } + + fn readEnvBool(allocator: Allocator, key: []const u8, deflt: bool) bool { + const value = readEnv(allocator, key) orelse return deflt; + defer allocator.free(value); + return std.ascii.eqlIgnoreCase(value, "true"); + } }; fn isUnnamed(t: std.builtin.TestFn) bool { - const marker = ".test_"; - const test_name = t.name; - const index = std.mem.indexOf(u8, test_name, marker) orelse return false; - _ = std.fmt.parseInt(u32, test_name[index + marker.len..], 10) catch return false; - return true; + const marker = ".test_"; + const test_name = t.name; + const index = std.mem.indexOf(u8, test_name, marker) orelse return false; + _ = std.fmt.parseInt(u32, test_name[index + marker.len ..], 10) catch return false; + return true; } fn isSetup(t: std.builtin.TestFn) bool { - return std.mem.endsWith(u8, t.name, "tests:beforeAll"); + return std.mem.endsWith(u8, t.name, "tests:beforeAll"); } fn isTeardown(t: std.builtin.TestFn) bool { - return std.mem.endsWith(u8, t.name, "tests:afterAll"); + return std.mem.endsWith(u8, t.name, "tests:afterAll"); } From 95abf8d1c0ce38f481b1750b671e3989a6096a59 Mon Sep 17 00:00:00 2001 From: Pierre Tachoire Date: Tue, 4 Feb 2025 16:38:04 +0100 Subject: [PATCH 2/2] ci: add zig fmt check --- .github/workflows/zig-fmt.yml | 62 +++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 .github/workflows/zig-fmt.yml diff --git a/.github/workflows/zig-fmt.yml b/.github/workflows/zig-fmt.yml new file mode 100644 index 0000000..a425f98 --- /dev/null +++ b/.github/workflows/zig-fmt.yml @@ -0,0 +1,62 @@ +name: zig-fmt + +env: + ZIG_VERSION: 0.13.0 + +on: + pull_request: + + # By default GH trigger on types opened, synchronize and reopened. + # see https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#pull_request + # Since we skip the job when the PR is in draft state, we want to force CI + # running when the PR is marked ready_for_review w/o other change. + # see https://github.com/orgs/community/discussions/25722#discussioncomment-3248917 + types: [opened, synchronize, reopened, ready_for_review] + + paths: + - ".github/**" + - "build.zig" + - "src/**/*.zig" + - "src/*.zig" + # Allows you to run this workflow manually from the Actions tab + workflow_dispatch: + +jobs: + zig-fmt: + name: zig fmt + + # Don't run the CI with draft PR. + if: github.event.pull_request.draft == false + + runs-on: ubuntu-latest + + steps: + - uses: mlugg/setup-zig@v1 + with: + version: ${{ env.ZIG_VERSION }} + + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Run zig fmt + id: fmt + run: | + zig fmt --check ./*.zig ./**/*.zig 2> zig-fmt.err > zig-fmt.err2 || echo "Failed" + delimiter="$(openssl rand -hex 8)" + echo "zig_fmt_errs<<${delimiter}" >> "${GITHUB_OUTPUT}" + + if [ -s zig-fmt.err ]; then + echo "// The following errors occurred:" >> "${GITHUB_OUTPUT}" + cat zig-fmt.err >> "${GITHUB_OUTPUT}" + fi + + if [ -s zig-fmt.err2 ]; then + echo "// The following files were not formatted:" >> "${GITHUB_OUTPUT}" + cat zig-fmt.err2 >> "${GITHUB_OUTPUT}" + fi + + echo "${delimiter}" >> "${GITHUB_OUTPUT}" + - name: Fail the job + if: steps.fmt.outputs.zig_fmt_errs != '' + run: exit 1