- Add new commands: annotate, narrative, requeue - Refactor WebSocket client into modular components (net/ws/) - Add rsync embedded binary support - Improve error handling and response packet processing - Update build.zig and completions
121 lines
3.8 KiB
Zig
121 lines
3.8 KiB
Zig
const std = @import("std");
|
|
const testing = std.testing;
|
|
|
|
const c = @cImport({
|
|
@cInclude("stdlib.h");
|
|
});
|
|
|
|
const src = @import("src");
|
|
const utils = src.utils;
|
|
|
|
fn isScript(data: []const u8) bool {
|
|
return data.len >= 2 and data[0] == '#' and data[1] == '!';
|
|
}
|
|
|
|
test "embedded rsync binary creation" {
|
|
const allocator = testing.allocator;
|
|
|
|
// Ensure the override doesn't influence this test.
|
|
_ = c.unsetenv("ML_RSYNC_PATH");
|
|
|
|
// Force embedded fallback by clearing PATH for this process so system rsync isn't found.
|
|
const old_path_z = std.posix.getenv("PATH");
|
|
if (c.setenv("PATH", "", 1) != 0) return error.Unexpected;
|
|
defer {
|
|
if (old_path_z) |z| {
|
|
_ = c.setenv("PATH", z, 1);
|
|
} else {
|
|
_ = c.unsetenv("PATH");
|
|
}
|
|
}
|
|
|
|
var embedded_rsync = utils.rsync_embedded.EmbeddedRsync{ .allocator = allocator };
|
|
|
|
const rsync_path = try embedded_rsync.extractRsyncBinary();
|
|
defer allocator.free(rsync_path);
|
|
|
|
try testing.expect(rsync_path.len > 0);
|
|
|
|
// File exists and is executable
|
|
const file = try std.fs.openFileAbsolute(rsync_path, .{});
|
|
defer file.close();
|
|
const stat = try file.stat();
|
|
try testing.expect(stat.mode & 0o111 != 0);
|
|
|
|
// Contents match embedded payload
|
|
const data = try file.readToEndAlloc(allocator, 1024 * 1024 * 4);
|
|
defer allocator.free(data);
|
|
|
|
var digest: [32]u8 = undefined;
|
|
std.crypto.hash.sha2.Sha256.hash(data, &digest, .{});
|
|
const embedded_digest = utils.rsync_embedded_binary.rsyncBinarySha256();
|
|
try testing.expect(std.mem.eql(u8, &digest, &embedded_digest));
|
|
|
|
// Sanity: if we're not using the release binary, it should be the placeholder script.
|
|
if (!utils.rsync_embedded_binary.USING_RELEASE_BINARY) {
|
|
try testing.expect(isScript(data));
|
|
}
|
|
}
|
|
|
|
test "embedded rsync honors ML_RSYNC_PATH override" {
|
|
const allocator = testing.allocator;
|
|
|
|
// Use a known executable. We don't execute it; we only verify the override is returned.
|
|
const override_path = "/bin/sh";
|
|
try testing.expect(std.fs.openFileAbsolute(override_path, .{}) != error.FileNotFound);
|
|
|
|
if (c.setenv("ML_RSYNC_PATH", override_path, 1) != 0) return error.Unexpected;
|
|
defer _ = c.unsetenv("ML_RSYNC_PATH");
|
|
|
|
var embedded_rsync = utils.rsync_embedded.EmbeddedRsync{ .allocator = allocator };
|
|
const rsync_path = try embedded_rsync.extractRsyncBinary();
|
|
defer allocator.free(rsync_path);
|
|
|
|
try testing.expectEqualStrings(override_path, rsync_path);
|
|
}
|
|
|
|
test "embedded rsync prefers system rsync when available" {
|
|
const allocator = testing.allocator;
|
|
|
|
_ = c.unsetenv("ML_RSYNC_PATH");
|
|
|
|
// Find rsync on PATH (simple scan). If not present, skip.
|
|
const path_z = std.posix.getenv("PATH") orelse return;
|
|
const path = std.mem.sliceTo(path_z, 0);
|
|
|
|
var sys_rsync: ?[]u8 = null;
|
|
defer if (sys_rsync) |p| allocator.free(p);
|
|
|
|
var it = std.mem.splitScalar(u8, path, ':');
|
|
while (it.next()) |dir| {
|
|
if (dir.len == 0) continue;
|
|
const candidate = std.fmt.allocPrint(allocator, "{s}/{s}", .{ dir, "rsync" }) catch return;
|
|
errdefer allocator.free(candidate);
|
|
|
|
const file = std.fs.openFileAbsolute(candidate, .{}) catch {
|
|
allocator.free(candidate);
|
|
continue;
|
|
};
|
|
defer file.close();
|
|
|
|
const st = file.stat() catch {
|
|
allocator.free(candidate);
|
|
continue;
|
|
};
|
|
if (st.mode & 0o111 == 0) {
|
|
allocator.free(candidate);
|
|
continue;
|
|
}
|
|
|
|
sys_rsync = candidate;
|
|
break;
|
|
}
|
|
|
|
if (sys_rsync == null) return;
|
|
|
|
var embedded_rsync = utils.rsync_embedded.EmbeddedRsync{ .allocator = allocator };
|
|
const rsync_path = try embedded_rsync.extractRsyncBinary();
|
|
defer allocator.free(rsync_path);
|
|
|
|
try testing.expectEqualStrings(sys_rsync.?, rsync_path);
|
|
}
|