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); }