Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 8 additions & 11 deletions src/installer/orchestrator.zig
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ const nullclaw_web_channel = @import("../core/nullclaw_web_channel.zig");
const manager_mod = @import("../supervisor/manager.zig");
const ui_modules_mod = @import("ui_modules.zig");
const managed_skills = @import("../managed_skills.zig");
const test_helpers = @import("../test_helpers.zig");
const MAX_CONFIG_BYTES = 4 * 1024 * 1024;

// ─── Types ───────────────────────────────────────────────────────────────────
Expand Down Expand Up @@ -898,32 +899,28 @@ test "writeFile creates file with correct content" {

test "directory creation succeeds in temp directory" {
const allocator = std.testing.allocator;
const tmp_root = "/tmp/test-orchestrator-dirs";
std_compat.fs.deleteTreeAbsolute(tmp_root) catch {};
defer std_compat.fs.deleteTreeAbsolute(tmp_root) catch {};

var p = try paths_mod.Paths.init(allocator, tmp_root);
defer p.deinit(allocator);
var fixture = try test_helpers.TempPaths.init(allocator);
defer fixture.deinit();

// Create top-level dirs
try p.ensureDirs();
try fixture.paths.ensureDirs();

// Create component dir
const comp_dir = try std.fs.path.join(allocator, &.{ p.root, "instances", "testcomp" });
const comp_dir = try std.fs.path.join(allocator, &.{ fixture.paths.root, "instances", "testcomp" });
defer allocator.free(comp_dir);
try std_compat.fs.makeDirAbsolute(comp_dir);

// Create instance dir
const inst_dir = try p.instanceDir(allocator, "testcomp", "myinst");
const inst_dir = try fixture.paths.instanceDir(allocator, "testcomp", "myinst");
defer allocator.free(inst_dir);
try std_compat.fs.makeDirAbsolute(inst_dir);

// Create data and logs subdirs
const data_dir = try p.instanceData(allocator, "testcomp", "myinst");
const data_dir = try fixture.paths.instanceData(allocator, "testcomp", "myinst");
defer allocator.free(data_dir);
try std_compat.fs.makeDirAbsolute(data_dir);

const logs_dir = try p.instanceLogs(allocator, "testcomp", "myinst");
const logs_dir = try fixture.paths.instanceLogs(allocator, "testcomp", "myinst");
defer allocator.free(logs_dir);
try std_compat.fs.makeDirAbsolute(logs_dir);

Expand Down
28 changes: 10 additions & 18 deletions src/supervisor/manager.zig
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ const health = @import("health.zig");
const runtime_state = @import("runtime_state.zig");
const paths_mod = @import("../core/paths.zig");
const component_cli = @import("../core/component_cli.zig");
const test_helpers = @import("../test_helpers.zig");

pub const Status = enum {
stopped,
Expand Down Expand Up @@ -866,20 +867,16 @@ test "getStatus returns null for unknown instance" {

test "logSupervisor appends diagnostics to nullhub.log" {
const allocator = std.testing.allocator;
const tmp_root = "/tmp/test-nullhub-mgr-log-supervisor";
std_compat.fs.deleteTreeAbsolute(tmp_root) catch {};
defer std_compat.fs.deleteTreeAbsolute(tmp_root) catch {};
var fixture = try test_helpers.TempPaths.init(allocator);
defer fixture.deinit();

var p = try paths_mod.Paths.init(allocator, tmp_root);
defer p.deinit(allocator);

var mgr = Manager.init(allocator, p);
var mgr = Manager.init(allocator, fixture.paths);
defer mgr.deinit();

mgr.logSupervisor("nullclaw", "diag", "first diagnostic {d}", .{@as(u8, 1)});
mgr.logSupervisor("nullclaw", "diag", "second diagnostic", .{});

const logs_dir = try p.instanceLogs(allocator, "nullclaw", "diag");
const logs_dir = try fixture.paths.instanceLogs(allocator, "nullclaw", "diag");
defer allocator.free(logs_dir);
const log_path = try std.fs.path.join(allocator, &.{ logs_dir, "nullhub.log" });
defer allocator.free(log_path);
Expand Down Expand Up @@ -935,14 +932,12 @@ test "restart preserves launch args with spaces" {
if (comptime builtin.os.tag == .windows) return error.SkipZigTest;

const allocator = std.testing.allocator;
const tmp_root = "/tmp/test-nullhub-mgr-restart-argv";
std_compat.fs.deleteTreeAbsolute(tmp_root) catch {};
defer std_compat.fs.deleteTreeAbsolute(tmp_root) catch {};
try std_compat.fs.makeDirAbsolute(tmp_root);
var fixture = try test_helpers.TempPaths.init(allocator);
defer fixture.deinit();

const script_path = try std.fs.path.join(allocator, &.{ tmp_root, "capture-arg.sh" });
const script_path = try fixture.path(allocator, "capture-arg.sh");
defer allocator.free(script_path);
const output_path = try std.fs.path.join(allocator, &.{ tmp_root, "captured.txt" });
const output_path = try fixture.path(allocator, "captured.txt");
defer allocator.free(output_path);

const script =
Expand All @@ -954,10 +949,7 @@ test "restart preserves launch args with spaces" {
defer script_file.close();
try script_file.writeAll(script);

var p = try paths_mod.Paths.init(allocator, tmp_root);
defer p.deinit(allocator);

var mgr = Manager.init(allocator, p);
var mgr = Manager.init(allocator, fixture.paths);
defer mgr.deinit();

const launch_args = [_][]const u8{ script_path, "hello world", output_path };
Expand Down
17 changes: 7 additions & 10 deletions src/supervisor/runtime_state.zig
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ const std = @import("std");
const std_compat = @import("compat");
const fs_compat = @import("../fs_compat.zig");
const paths_mod = @import("../core/paths.zig");
const test_helpers = @import("../test_helpers.zig");

pub const PersistedRuntimeView = struct {
pid: u64,
Expand Down Expand Up @@ -127,14 +128,10 @@ pub fn delete(

test "runtime state round-trips through instance.json" {
const allocator = std.testing.allocator;
const tmp_root = "/tmp/nullhub-test-runtime-state";
std_compat.fs.deleteTreeAbsolute(tmp_root) catch {};
defer std_compat.fs.deleteTreeAbsolute(tmp_root) catch {};
var fixture = try test_helpers.TempPaths.init(allocator);
defer fixture.deinit();

var paths = try paths_mod.Paths.init(allocator, tmp_root);
defer paths.deinit(allocator);

try write(allocator, paths, "nullclaw", "demo", .{
try write(allocator, fixture.paths, "nullclaw", "demo", .{
.pid = 42,
.port = 8080,
.health_endpoint = "/health",
Expand All @@ -147,7 +144,7 @@ test "runtime state round-trips through instance.json" {
.starting_since = 1000,
});

var loaded = (try load(allocator, paths, "nullclaw", "demo")).?;
var loaded = (try load(allocator, fixture.paths, "nullclaw", "demo")).?;
defer loaded.deinit(allocator);

try std.testing.expectEqual(@as(u64, 42), loaded.pid);
Expand All @@ -158,6 +155,6 @@ test "runtime state round-trips through instance.json" {
try std.testing.expectEqual(@as(usize, 2), loaded.launch_args.len);
try std.testing.expectEqualStrings("--verbose", loaded.launch_args[1]);

delete(allocator, paths, "nullclaw", "demo");
try std.testing.expect((try load(allocator, paths, "nullclaw", "demo")) == null);
delete(allocator, fixture.paths, "nullclaw", "demo");
try std.testing.expect((try load(allocator, fixture.paths, "nullclaw", "demo")) == null);
}
56 changes: 56 additions & 0 deletions src/test_helpers.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
const std = @import("std");
const std_compat = @import("compat");
const paths_mod = @import("core/paths.zig");

pub const TempPaths = struct {
allocator: std.mem.Allocator,
tmp: std.testing.TmpDir,
root: []u8,
paths: paths_mod.Paths,

pub fn init(allocator: std.mem.Allocator) !TempPaths {
const tmp = std.testing.tmpDir(.{});
const root = try std_compat.fs.Dir.wrap(tmp.dir).realpathAlloc(allocator, ".");
errdefer allocator.free(root);

const paths = try paths_mod.Paths.init(allocator, root);
errdefer {
var owned_paths = paths;
owned_paths.deinit(allocator);
}

return .{
.allocator = allocator,
.tmp = tmp,
.root = root,
.paths = paths,
};
}

pub fn deinit(self: *TempPaths) void {
self.paths.deinit(self.allocator);
self.allocator.free(self.root);
self.tmp.cleanup();
self.* = undefined;
}

pub fn path(self: TempPaths, allocator: std.mem.Allocator, sub_path: []const u8) ![]const u8 {
return std.fs.path.join(allocator, &.{ self.root, sub_path });
}
};

test "TempPaths creates isolated nullhub root" {
const allocator = std.testing.allocator;

var fixture = try TempPaths.init(allocator);
defer fixture.deinit();

try std.testing.expect(std.fs.path.isAbsolute(fixture.root));
try std.testing.expectEqualStrings(fixture.root, fixture.paths.root);

try fixture.paths.ensureDirs();

const state_path = try fixture.path(allocator, "state.json");
defer allocator.free(state_path);
try std.testing.expect(std.mem.startsWith(u8, state_path, fixture.root));
}