Expose local socket protocol and mimctl basics
This commit is contained in:
+48
-5
@@ -1,5 +1,7 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
const protocol = @import("protocol.zig");
|
||||||
const session = @import("session.zig");
|
const session = @import("session.zig");
|
||||||
|
const socket = @import("socket.zig");
|
||||||
|
|
||||||
pub const version = "0.1.0-dev";
|
pub const version = "0.1.0-dev";
|
||||||
|
|
||||||
@@ -9,9 +11,10 @@ const help_text =
|
|||||||
\\Usage:
|
\\Usage:
|
||||||
\\ mim [--help]
|
\\ mim [--help]
|
||||||
\\ mim [--version]
|
\\ mim [--version]
|
||||||
|
\\ mim --mimctl <socket> <request...>
|
||||||
\\
|
\\
|
||||||
\\This skeleton intentionally starts with only the canonical smoke path.
|
\\This early build exposes the canonical smoke path and a minimal local
|
||||||
\\Future editor behavior is added through verified product slices.
|
\\session-control client for trusted tools.
|
||||||
\\
|
\\
|
||||||
;
|
;
|
||||||
|
|
||||||
@@ -36,18 +39,44 @@ fn versionText() []const u8 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn main(init: std.process.Init) !u8 {
|
pub fn main(init: std.process.Init) !u8 {
|
||||||
|
var debug_allocator = std.heap.DebugAllocator(.{}){};
|
||||||
|
defer _ = debug_allocator.deinit();
|
||||||
|
const allocator = debug_allocator.allocator();
|
||||||
|
|
||||||
var args = std.process.Args.Iterator.init(init.minimal.args);
|
var args = std.process.Args.Iterator.init(init.minimal.args);
|
||||||
_ = args.skip();
|
_ = args.skip();
|
||||||
|
|
||||||
|
const stdout = std.Io.File.stdout();
|
||||||
|
const stderr = std.Io.File.stderr();
|
||||||
|
|
||||||
const first_arg = args.next();
|
const first_arg = args.next();
|
||||||
|
if (first_arg) |arg| {
|
||||||
|
if (std.mem.eql(u8, arg, "--mimctl")) {
|
||||||
|
const socket_path = args.next() orelse {
|
||||||
|
try stderr.writeStreamingAll(init.io, "mim: --mimctl requires a socket path\n");
|
||||||
|
return 64;
|
||||||
|
};
|
||||||
|
const line = try collectRemainingArgs(allocator, &args);
|
||||||
|
defer allocator.free(line);
|
||||||
|
if (line.len == 0) {
|
||||||
|
try stderr.writeStreamingAll(init.io, "mim: --mimctl requires a request\n");
|
||||||
|
return 64;
|
||||||
|
}
|
||||||
|
const response = socket.request(allocator, socket_path, line) catch {
|
||||||
|
try stderr.writeStreamingAll(init.io, "mim: mimctl request failed\n");
|
||||||
|
return 69;
|
||||||
|
};
|
||||||
|
defer allocator.free(response);
|
||||||
|
try stdout.writeStreamingAll(init.io, response);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const command = if (first_arg) |arg|
|
const command = if (first_arg) |arg|
|
||||||
parseArgs(&.{ "mim", arg })
|
parseArgs(&.{ "mim", arg })
|
||||||
else
|
else
|
||||||
parseArgs(&.{"mim"});
|
parseArgs(&.{"mim"});
|
||||||
|
|
||||||
const stdout = std.Io.File.stdout();
|
|
||||||
const stderr = std.Io.File.stderr();
|
|
||||||
|
|
||||||
switch (command) {
|
switch (command) {
|
||||||
.help => try stdout.writeStreamingAll(init.io, help_text),
|
.help => try stdout.writeStreamingAll(init.io, help_text),
|
||||||
.version => try stdout.writeStreamingAll(init.io, versionText()),
|
.version => try stdout.writeStreamingAll(init.io, versionText()),
|
||||||
@@ -63,8 +92,22 @@ pub fn main(init: std.process.Init) !u8 {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn collectRemainingArgs(allocator: std.mem.Allocator, args: *std.process.Args.Iterator) ![]u8 {
|
||||||
|
var line = std.ArrayList(u8).empty;
|
||||||
|
errdefer line.deinit(allocator);
|
||||||
|
var first = true;
|
||||||
|
while (args.next()) |arg| {
|
||||||
|
if (!first) try line.append(allocator, ' ');
|
||||||
|
try line.appendSlice(allocator, arg);
|
||||||
|
first = false;
|
||||||
|
}
|
||||||
|
return line.toOwnedSlice(allocator);
|
||||||
|
}
|
||||||
|
|
||||||
test {
|
test {
|
||||||
|
_ = protocol;
|
||||||
_ = session;
|
_ = session;
|
||||||
|
_ = socket;
|
||||||
}
|
}
|
||||||
|
|
||||||
test "regular: help text names the binary and smoke boundary" {
|
test "regular: help text names the binary and smoke boundary" {
|
||||||
|
|||||||
@@ -0,0 +1,119 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const session_mod = @import("session.zig");
|
||||||
|
|
||||||
|
// Tiny local session protocol for trusted same-user tools.
|
||||||
|
// req: session/002, session/003, governance/003
|
||||||
|
|
||||||
|
test {
|
||||||
|
_ = handleLine;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const max_request_bytes = 4096;
|
||||||
|
|
||||||
|
pub fn handleLine(allocator: std.mem.Allocator, session: *session_mod.Session, line: []const u8) ![]u8 {
|
||||||
|
const trimmed = std.mem.trimEnd(u8, line, "\r\n");
|
||||||
|
if (std.mem.eql(u8, trimmed, "state")) return stateResponse(allocator, session);
|
||||||
|
if (std.mem.startsWith(u8, trimmed, "open ")) {
|
||||||
|
const bytes = trimmed[5..];
|
||||||
|
if (!std.unicode.utf8ValidateSlice(bytes)) return allocator.dupe(u8, "err invalid utf8\n");
|
||||||
|
try session.openFixture(bytes);
|
||||||
|
return stateResponse(allocator, session);
|
||||||
|
}
|
||||||
|
if (std.mem.startsWith(u8, trimmed, "command ")) {
|
||||||
|
return commandResponse(allocator, session, trimmed[8..]);
|
||||||
|
}
|
||||||
|
return allocator.dupe(u8, "err unknown request\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn commandResponse(allocator: std.mem.Allocator, session: *session_mod.Session, command: []const u8) ![]u8 {
|
||||||
|
if (std.mem.eql(u8, command, "move_left")) return dispatchAndRespond(allocator, session, .move_left);
|
||||||
|
if (std.mem.eql(u8, command, "move_right")) return dispatchAndRespond(allocator, session, .move_right);
|
||||||
|
if (std.mem.eql(u8, command, "delete_backward")) return dispatchAndRespond(allocator, session, .delete_backward);
|
||||||
|
if (std.mem.startsWith(u8, command, "insert ")) return dispatchAndRespond(allocator, session, .{ .insert = command[7..] });
|
||||||
|
return allocator.dupe(u8, "err unknown command\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dispatchAndRespond(allocator: std.mem.Allocator, session: *session_mod.Session, command: session_mod.Command) ![]u8 {
|
||||||
|
session.dispatch(command) catch |err| switch (err) {
|
||||||
|
error.InvalidUtf8Insertion => return allocator.dupe(u8, "err invalid utf8\n"),
|
||||||
|
error.NoBufferOpen => return allocator.dupe(u8, "err no buffer open\n"),
|
||||||
|
else => return err,
|
||||||
|
};
|
||||||
|
return stateResponse(allocator, session);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn stateResponse(allocator: std.mem.Allocator, session: *session_mod.Session) ![]u8 {
|
||||||
|
const snap = session.snapshot() catch |err| switch (err) {
|
||||||
|
error.NoBufferOpen => return allocator.dupe(u8, "err no buffer open\n"),
|
||||||
|
};
|
||||||
|
return std.fmt.allocPrint(
|
||||||
|
allocator,
|
||||||
|
"ok state cursor_byte={d} cursor_cell={d} bytes_len={d}\n",
|
||||||
|
.{ snap.cursor_byte, snap.cursor_cell, snap.bytes.len },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "regular: protocol opens bytes, reports state, and dispatches commands" {
|
||||||
|
var session = session_mod.Session.init(std.testing.allocator);
|
||||||
|
defer session.deinit();
|
||||||
|
|
||||||
|
const open = try handleLine(std.testing.allocator, &session, "open café\n");
|
||||||
|
defer std.testing.allocator.free(open);
|
||||||
|
try std.testing.expectEqualStrings("ok state cursor_byte=0 cursor_cell=0 bytes_len=5\n", open);
|
||||||
|
|
||||||
|
const moved = try handleLine(std.testing.allocator, &session, "command move_right\n");
|
||||||
|
defer std.testing.allocator.free(moved);
|
||||||
|
try std.testing.expectEqualStrings("ok state cursor_byte=1 cursor_cell=1 bytes_len=5\n", moved);
|
||||||
|
|
||||||
|
const inserted = try handleLine(std.testing.allocator, &session, "command insert 🔥\n");
|
||||||
|
defer std.testing.allocator.free(inserted);
|
||||||
|
try std.testing.expectEqualStrings("ok state cursor_byte=5 cursor_cell=3 bytes_len=9\n", inserted);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "regular: protocol delete command removes a whole UTF-8 codepoint" {
|
||||||
|
var session = session_mod.Session.init(std.testing.allocator);
|
||||||
|
defer session.deinit();
|
||||||
|
|
||||||
|
var response = try handleLine(std.testing.allocator, &session, "open aé\n");
|
||||||
|
std.testing.allocator.free(response);
|
||||||
|
response = try handleLine(std.testing.allocator, &session, "command move_right\n");
|
||||||
|
std.testing.allocator.free(response);
|
||||||
|
response = try handleLine(std.testing.allocator, &session, "command move_right\n");
|
||||||
|
std.testing.allocator.free(response);
|
||||||
|
response = try handleLine(std.testing.allocator, &session, "command delete_backward\n");
|
||||||
|
defer std.testing.allocator.free(response);
|
||||||
|
|
||||||
|
try std.testing.expectEqualStrings("ok state cursor_byte=1 cursor_cell=1 bytes_len=1\n", response);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "adversarial: protocol rejects unknown requests and commands" {
|
||||||
|
var session = session_mod.Session.init(std.testing.allocator);
|
||||||
|
defer session.deinit();
|
||||||
|
|
||||||
|
const request = try handleLine(std.testing.allocator, &session, "subscribe everything\n");
|
||||||
|
defer std.testing.allocator.free(request);
|
||||||
|
try std.testing.expectEqualStrings("err unknown request\n", request);
|
||||||
|
|
||||||
|
try session.openFixture("abc");
|
||||||
|
const command = try handleLine(std.testing.allocator, &session, "command plugin.load\n");
|
||||||
|
defer std.testing.allocator.free(command);
|
||||||
|
try std.testing.expectEqualStrings("err unknown command\n", command);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "adversarial: protocol reports no buffer and invalid utf8 without corrupting state" {
|
||||||
|
var session = session_mod.Session.init(std.testing.allocator);
|
||||||
|
defer session.deinit();
|
||||||
|
|
||||||
|
const no_buffer = try handleLine(std.testing.allocator, &session, "state\n");
|
||||||
|
defer std.testing.allocator.free(no_buffer);
|
||||||
|
try std.testing.expectEqualStrings("err no buffer open\n", no_buffer);
|
||||||
|
|
||||||
|
try session.openFixture("safe");
|
||||||
|
const before = try session.snapshot();
|
||||||
|
const bad = [_]u8{ 'c', 'o', 'm', 'm', 'a', 'n', 'd', ' ', 'i', 'n', 's', 'e', 'r', 't', ' ', 0xc3, 0x28 };
|
||||||
|
const invalid = try handleLine(std.testing.allocator, &session, &bad);
|
||||||
|
defer std.testing.allocator.free(invalid);
|
||||||
|
try std.testing.expectEqualStrings("err invalid utf8\n", invalid);
|
||||||
|
const after = try session.snapshot();
|
||||||
|
try std.testing.expectEqualStrings(before.bytes, after.bytes);
|
||||||
|
}
|
||||||
+225
@@ -0,0 +1,225 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const protocol = @import("protocol.zig");
|
||||||
|
const session_mod = @import("session.zig");
|
||||||
|
|
||||||
|
// Local Unix socket transport for a running editor session.
|
||||||
|
// req: session/002, session/003, governance/003
|
||||||
|
|
||||||
|
test {
|
||||||
|
_ = Server;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const socket_env_name = "MIM_SOCKET";
|
||||||
|
|
||||||
|
const SocketError = error{
|
||||||
|
PathTooLong,
|
||||||
|
SocketCreateFailed,
|
||||||
|
BindFailed,
|
||||||
|
ListenFailed,
|
||||||
|
AcceptFailed,
|
||||||
|
ConnectFailed,
|
||||||
|
WriteFailed,
|
||||||
|
RequestTooLarge,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const Server = struct {
|
||||||
|
allocator: std.mem.Allocator,
|
||||||
|
path: []const u8,
|
||||||
|
fd: std.posix.socket_t,
|
||||||
|
session: *session_mod.Session,
|
||||||
|
|
||||||
|
pub fn listen(allocator: std.mem.Allocator, path: []const u8, session: *session_mod.Session) !Server {
|
||||||
|
unlinkPath(path);
|
||||||
|
const fd = try createSocket();
|
||||||
|
errdefer closeFd(fd);
|
||||||
|
|
||||||
|
const addr = try unixAddress(path);
|
||||||
|
if (std.posix.errno(std.posix.system.bind(fd, @ptrCast(&addr.un), addr.len)) != .SUCCESS) return SocketError.BindFailed;
|
||||||
|
if (std.posix.errno(std.posix.system.listen(fd, 8)) != .SUCCESS) return SocketError.ListenFailed;
|
||||||
|
|
||||||
|
return .{
|
||||||
|
.allocator = allocator,
|
||||||
|
.path = try allocator.dupe(u8, path),
|
||||||
|
.fd = fd,
|
||||||
|
.session = session,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: *Server) void {
|
||||||
|
closeFd(self.fd);
|
||||||
|
unlinkPath(self.path);
|
||||||
|
self.allocator.free(self.path);
|
||||||
|
self.* = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn acceptOnce(self: *Server) !void {
|
||||||
|
var storage: std.posix.sockaddr.storage = undefined;
|
||||||
|
var len: std.posix.socklen_t = @sizeOf(std.posix.sockaddr.storage);
|
||||||
|
const accepted_rc = std.posix.system.accept(self.fd, @ptrCast(&storage), &len);
|
||||||
|
if (std.posix.errno(accepted_rc) != .SUCCESS) return SocketError.AcceptFailed;
|
||||||
|
const accepted: std.posix.fd_t = @intCast(accepted_rc);
|
||||||
|
defer closeFd(accepted);
|
||||||
|
|
||||||
|
var request_buf: [protocol.max_request_bytes + 1]u8 = undefined;
|
||||||
|
const line = try readRequest(accepted, &request_buf);
|
||||||
|
const response = protocol.handleLine(self.allocator, self.session, line) catch |err| switch (err) {
|
||||||
|
error.OutOfMemory => return err,
|
||||||
|
else => try self.allocator.dupe(u8, "err internal\n"),
|
||||||
|
};
|
||||||
|
defer self.allocator.free(response);
|
||||||
|
try writeAll(accepted, response);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn request(allocator: std.mem.Allocator, path: []const u8, line: []const u8) ![]u8 {
|
||||||
|
const fd = try createSocket();
|
||||||
|
defer closeFd(fd);
|
||||||
|
|
||||||
|
const addr = try unixAddress(path);
|
||||||
|
if (std.posix.errno(std.posix.system.connect(fd, @ptrCast(&addr.un), addr.len)) != .SUCCESS) return SocketError.ConnectFailed;
|
||||||
|
|
||||||
|
try writeAll(fd, line);
|
||||||
|
if (!std.mem.endsWith(u8, line, "\n")) try writeAll(fd, "\n");
|
||||||
|
|
||||||
|
var response = std.ArrayList(u8).empty;
|
||||||
|
errdefer response.deinit(allocator);
|
||||||
|
var buf: [512]u8 = undefined;
|
||||||
|
while (true) {
|
||||||
|
const n = try readSome(fd, &buf);
|
||||||
|
if (n == 0) break;
|
||||||
|
try response.appendSlice(allocator, buf[0..n]);
|
||||||
|
if (std.mem.indexOfScalar(u8, buf[0..n], '\n') != null) break;
|
||||||
|
}
|
||||||
|
return response.toOwnedSlice(allocator);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn createSocket() !std.posix.socket_t {
|
||||||
|
const rc = std.posix.system.socket(std.posix.AF.UNIX, std.posix.SOCK.STREAM | std.posix.SOCK.CLOEXEC, 0);
|
||||||
|
if (std.posix.errno(rc) != .SUCCESS) return SocketError.SocketCreateFailed;
|
||||||
|
return @intCast(rc);
|
||||||
|
}
|
||||||
|
|
||||||
|
const UnixAddress = struct {
|
||||||
|
un: std.posix.sockaddr.un,
|
||||||
|
len: std.posix.socklen_t,
|
||||||
|
};
|
||||||
|
|
||||||
|
fn unixAddress(path: []const u8) !UnixAddress {
|
||||||
|
var addr: std.posix.sockaddr.un = .{
|
||||||
|
.family = std.posix.AF.UNIX,
|
||||||
|
.path = [_]u8{0} ** 108,
|
||||||
|
};
|
||||||
|
if (path.len >= addr.path.len) return SocketError.PathTooLong;
|
||||||
|
@memcpy(addr.path[0..path.len], path);
|
||||||
|
return .{
|
||||||
|
.un = addr,
|
||||||
|
.len = @intCast(@offsetOf(std.posix.sockaddr.un, "path") + path.len + 1),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn readRequest(fd: std.posix.fd_t, buf: []u8) ![]const u8 {
|
||||||
|
var len: usize = 0;
|
||||||
|
while (len < buf.len) {
|
||||||
|
const n = try readSome(fd, buf[len..]);
|
||||||
|
if (n == 0) break;
|
||||||
|
len += n;
|
||||||
|
if (std.mem.indexOfScalar(u8, buf[0..len], '\n')) |newline| return buf[0 .. newline + 1];
|
||||||
|
}
|
||||||
|
if (len >= buf.len) return SocketError.RequestTooLarge;
|
||||||
|
return buf[0..len];
|
||||||
|
}
|
||||||
|
|
||||||
|
fn readSome(fd: std.posix.fd_t, buf: []u8) !usize {
|
||||||
|
while (true) {
|
||||||
|
const rc = std.posix.system.read(fd, buf.ptr, buf.len);
|
||||||
|
switch (std.posix.errno(rc)) {
|
||||||
|
.SUCCESS => return @intCast(rc),
|
||||||
|
.INTR => continue,
|
||||||
|
else => return SocketError.ConnectFailed,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn writeAll(fd: std.posix.fd_t, bytes: []const u8) !void {
|
||||||
|
var offset: usize = 0;
|
||||||
|
while (offset < bytes.len) {
|
||||||
|
const rc = std.posix.system.write(fd, bytes[offset..].ptr, bytes.len - offset);
|
||||||
|
switch (std.posix.errno(rc)) {
|
||||||
|
.SUCCESS => offset += @intCast(rc),
|
||||||
|
.INTR => continue,
|
||||||
|
else => return SocketError.WriteFailed,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn closeFd(fd: std.posix.fd_t) void {
|
||||||
|
_ = std.posix.system.close(fd);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn unlinkPath(path: []const u8) void {
|
||||||
|
const posix_path = std.posix.toPosixPath(path) catch return;
|
||||||
|
_ = std.posix.system.unlink(&posix_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "regular: local socket state request observes a running session" {
|
||||||
|
var session = session_mod.Session.init(std.testing.allocator);
|
||||||
|
defer session.deinit();
|
||||||
|
try session.openFixture("abc");
|
||||||
|
|
||||||
|
const path = "/tmp/mim-test-state.sock";
|
||||||
|
var server = try Server.listen(std.testing.allocator, path, &session);
|
||||||
|
defer server.deinit();
|
||||||
|
|
||||||
|
const thread = try std.Thread.spawn(.{}, Server.acceptOnce, .{&server});
|
||||||
|
const response = try request(std.testing.allocator, path, "state");
|
||||||
|
defer std.testing.allocator.free(response);
|
||||||
|
thread.join();
|
||||||
|
|
||||||
|
try std.testing.expectEqualStrings("ok state cursor_byte=0 cursor_cell=0 bytes_len=3\n", response);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "regular: local socket command mutates session state" {
|
||||||
|
var session = session_mod.Session.init(std.testing.allocator);
|
||||||
|
defer session.deinit();
|
||||||
|
try session.openFixture("abc");
|
||||||
|
|
||||||
|
const path = "/tmp/mim-test-command.sock";
|
||||||
|
var server = try Server.listen(std.testing.allocator, path, &session);
|
||||||
|
defer server.deinit();
|
||||||
|
|
||||||
|
var thread = try std.Thread.spawn(.{}, Server.acceptOnce, .{&server});
|
||||||
|
var response = try request(std.testing.allocator, path, "command move_right");
|
||||||
|
std.testing.allocator.free(response);
|
||||||
|
thread.join();
|
||||||
|
|
||||||
|
thread = try std.Thread.spawn(.{}, Server.acceptOnce, .{&server});
|
||||||
|
response = try request(std.testing.allocator, path, "command insert é");
|
||||||
|
defer std.testing.allocator.free(response);
|
||||||
|
thread.join();
|
||||||
|
|
||||||
|
try std.testing.expectEqualStrings("ok state cursor_byte=3 cursor_cell=2 bytes_len=5\n", response);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "adversarial: socket transport returns explicit protocol errors" {
|
||||||
|
var session = session_mod.Session.init(std.testing.allocator);
|
||||||
|
defer session.deinit();
|
||||||
|
|
||||||
|
const path = "/tmp/mim-test-error.sock";
|
||||||
|
var server = try Server.listen(std.testing.allocator, path, &session);
|
||||||
|
defer server.deinit();
|
||||||
|
|
||||||
|
const thread = try std.Thread.spawn(.{}, Server.acceptOnce, .{&server});
|
||||||
|
const response = try request(std.testing.allocator, path, "command move_right");
|
||||||
|
defer std.testing.allocator.free(response);
|
||||||
|
thread.join();
|
||||||
|
|
||||||
|
try std.testing.expectEqualStrings("err no buffer open\n", response);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "adversarial: socket path rejects overlong Unix socket paths" {
|
||||||
|
var session = session_mod.Session.init(std.testing.allocator);
|
||||||
|
defer session.deinit();
|
||||||
|
|
||||||
|
const long_path = "/tmp/this-mim-socket-path-is-intentionally-far-too-long-for-a-portable-unix-domain-socket-address-and-must-fail.sock";
|
||||||
|
try std.testing.expectError(SocketError.PathTooLong, Server.listen(std.testing.allocator, long_path, &session));
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user