From 354ae8212cf761d51b1a4cbe05c8ffbdddd1ff2e Mon Sep 17 00:00:00 2001 From: Bahanur Enis Date: Sat, 21 Jun 2025 00:31:09 +0200 Subject: [PATCH 01/19] test: setup and teardown ready for integration tests Signed-off-by: Bahanur Enis --- test/integration.zig | 122 +++++++++++++++++++++++++---------- test/sparse_feature_test.zig | 88 +++++++++++++++++++++++++ 2 files changed, 176 insertions(+), 34 deletions(-) create mode 100644 test/sparse_feature_test.zig diff --git a/test/integration.zig b/test/integration.zig index a4c96b7..e27c402 100644 --- a/test/integration.zig +++ b/test/integration.zig @@ -1,10 +1,44 @@ const builtin = @import("builtin"); const std = @import("std"); +const RunResult = std.process.Child.RunResult; +const Allocator = std.mem.Allocator; const assert = @import("std").debug.assert; const log = std.log.scoped(.integration); const sparse = @import("sparse"); const build_options = @import("build_options"); +pub const IntegrationTest = union(enum) { + feature: SparseFeatureTest, + pub fn setup( + self: IntegrationTest, + alloc: Allocator, + comptime T: anytype, + ) !T { + switch (self) { + inline else => |integration_test| return try integration_test.setup( + alloc, + ), + } + } + + pub fn teardown( + self: IntegrationTest, + alloc: Allocator, + data: anytype, + ) !void { + switch (self) { + inline else => |integration_test| return integration_test.teardown( + alloc, + data, + ), + } + } + // pub fn run(self: IntegrationTest, alloc: Allocator) !u8 { + // switch (self) { + // inline else => |integration_test| return integration_test.run(alloc), + // } + // } +}; pub fn main() !void { var gpa = std.heap.GeneralPurposeAllocator(.{}){}; defer std.debug.assert(gpa.deinit() == .ok); @@ -12,6 +46,7 @@ pub fn main() !void { const args = try std.process.argsAlloc(allocator); defer std.process.argsFree(allocator, args); + std.testing.log_level = .debug; log.debug( "main:: args={s} build_options={s} output_dir={s}", .{ @@ -22,43 +57,60 @@ pub fn main() !void { ); const repo_dir = try std.fs.path.join(allocator, &.{ build_options.output_dir, "sparse_test_repo" }); defer allocator.free(repo_dir); - { - const rr = try system.system(.{ - .allocator = allocator, - .args = &.{ - "mkdir", - "-p", - repo_dir, - }, - }); - defer allocator.free(rr.stdout); - defer allocator.free(rr.stderr); - } - { - const rr = try system.git(.{ - .allocator = allocator, - .args = &.{ "init", "." }, - .cwd = repo_dir, - }); - defer allocator.free(rr.stdout); - defer allocator.free(rr.stderr); - } - { - const rr = try system.system(.{ - .allocator = allocator, - .args = &.{ - "rm", - "-r", - repo_dir, - }, - }); - defer allocator.free(rr.stdout); - defer allocator.free(rr.stderr); - } + + //const sparse_test: IntegrationTest = undefined; + //sparse_test.setup(allocator, repo_dir); + //sparse_test.run(allocator); + //sparse_test.teardown(allocator, repo_dir); + // { + // const rr = try system.system(.{ + // .allocator = allocator, + // .args = &.{ + // "mkdir", + // "-p", + // repo_dir, + // }, + // }); + // defer allocator.free(rr.stdout); + // defer allocator.free(rr.stderr); + // } + // { + // const rr = try system.git(.{ + // .allocator = allocator, + // .args = &.{ "init", "." }, + // .cwd = repo_dir, + // }); + // defer allocator.free(rr.stdout); + // defer allocator.free(rr.stderr); + // } + // { + // const rr = try system.system(.{ + // .allocator = allocator, + // .args = &.{ + // "rm", + // "-r", + // repo_dir, + // }, + // }); + // defer allocator.free(rr.stdout); + // defer allocator.free(rr.stderr); + // } } test "Hello Integration" { - try std.testing.expect(false); + const test_alloc = std.testing.allocator; + const args = try std.process.argsAlloc(test_alloc); + defer std.process.argsFree(test_alloc, args); + + const integration: IntegrationTest = undefined; + const sparse_feature_test = @field( + integration, + "feature", + ); + const data = try sparse_feature_test.setup(test_alloc, SparseFeatureTestData); + try std.testing.expect(data.repo_dir != null); + try sparse_feature_test.teardown(test_alloc, data); + try std.testing.expect(true); } test "Hello Integration2" { @@ -66,3 +118,5 @@ test "Hello Integration2" { } const system = @import("system.zig"); +const SparseFeatureTest = @import("sparse_feature_test.zig").SparseFeatureTest; +const SparseFeatureTestData = @import("sparse_feature_test.zig").TestData; diff --git a/test/sparse_feature_test.zig b/test/sparse_feature_test.zig new file mode 100644 index 0000000..bbdccc4 --- /dev/null +++ b/test/sparse_feature_test.zig @@ -0,0 +1,88 @@ +const std = @import("std"); +const log = std.log.scoped(.sparse_feature_test); +const debug = std.debug.print; +const Allocator = std.mem.Allocator; +const RunResult = std.process.Child.RunResult; + +const mystruct = @This(); +pub const TestData = struct { + repo_dir: ?[]const u8 = null, + pub fn free(self: TestData, alloc: Allocator) void { + if (self.repo_dir) |repo_dir| { + alloc.free(repo_dir); + } + } +}; + +pub const SparseFeatureTest = struct { + pub fn setup( + self: SparseFeatureTest, + alloc: Allocator, + comptime T: anytype, + ) !T { + _ = self; + var data: TestData = .{}; + //_ = alloc; + //_ = repo_dir; + // + + const rr_temp_dir = try system.system(.{ + .allocator = alloc, + .args = &.{ + "mktemp", + "-d", + }, + }); + defer alloc.free(rr_temp_dir.stdout); + defer alloc.free(rr_temp_dir.stderr); + + try std.testing.expect(rr_temp_dir.term.Exited == 0); + try std.testing.expect(std.mem.eql(u8, rr_temp_dir.stderr, "")); + try std.testing.expect(!std.mem.eql(u8, rr_temp_dir.stdout, "")); + + data.repo_dir = try alloc.dupe(u8, std.mem.trim(u8, rr_temp_dir.stdout, "\n\t \r")); + const rr_git_init = try system.git(.{ + .allocator = alloc, + .args = &.{ "init", "." }, + .cwd = data.repo_dir.?, + }); + defer alloc.free(rr_git_init.stdout); + defer alloc.free(rr_git_init.stderr); + + try std.testing.expect(rr_git_init.term.Exited == 0); + try std.testing.expect(std.mem.eql(u8, rr_git_init.stderr, "")); + try std.testing.expect(!std.mem.eql(u8, rr_git_init.stdout, "")); + return @as(T, data); + } + + pub fn teardown( + self: SparseFeatureTest, + alloc: Allocator, + data: anytype, + ) !void { + _ = self; + + const test_data: TestData = @as(TestData, data); + defer test_data.free(alloc); + std.testing.log_level = .debug; + log.info("repo_dir {s}\n", .{test_data.repo_dir.?}); + const rr_temp_dir = try system.system(.{ + .allocator = alloc, + .args = &.{ + "rm", + "-r", + test_data.repo_dir.?, + }, + }); + defer alloc.free(rr_temp_dir.stdout); + defer alloc.free(rr_temp_dir.stderr); + } + // pub fn run(self: SparseFeatureTest, alloc: Allocator) !u8 { + // _ = self; + // _ = alloc; + // } +}; + +const sparse = @import("sparse"); +const system = @import("system.zig"); +const integration_test = @import("integration.zig"); From 5dc8f50a7bfc27f6e8b69f0c6a687aaaa72e7f37 Mon Sep 17 00:00:00 2001 From: Bahanur Enis Date: Sat, 21 Jun 2025 15:53:59 +0200 Subject: [PATCH 02/19] test: update run to take sparse func as parameter Signed-off-by: Bahanur Enis --- test/integration.zig | 134 +++++++++++++++++++---------------- test/sparse_feature_test.zig | 52 ++++++++++++-- 2 files changed, 118 insertions(+), 68 deletions(-) diff --git a/test/integration.zig b/test/integration.zig index e27c402..8a75507 100644 --- a/test/integration.zig +++ b/test/integration.zig @@ -2,10 +2,11 @@ const builtin = @import("builtin"); const std = @import("std"); const RunResult = std.process.Child.RunResult; const Allocator = std.mem.Allocator; -const assert = @import("std").debug.assert; const log = std.log.scoped(.integration); -const sparse = @import("sparse"); const build_options = @import("build_options"); +pub const IntegrationTestError = error{ + UNEXPECTED_ERROR, +}; pub const IntegrationTest = union(enum) { feature: SparseFeatureTest, @@ -33,11 +34,22 @@ pub const IntegrationTest = union(enum) { ), } } - // pub fn run(self: IntegrationTest, alloc: Allocator) !u8 { - // switch (self) { - // inline else => |integration_test| return integration_test.run(alloc), - // } - // } + pub fn run( + self: IntegrationTest, + alloc: Allocator, + comptime T: anytype, + data: T, + comptime func: fn (Allocator, T) bool, + ) IntegrationTestError!bool { + switch (self) { + inline else => |integration_test| return try integration_test.run( + alloc, + T, + data, + func, + ), + } + } }; pub fn main() !void { var gpa = std.heap.GeneralPurposeAllocator(.{}){}; @@ -47,69 +59,68 @@ pub fn main() !void { defer std.process.argsFree(allocator, args); std.testing.log_level = .debug; - log.debug( - "main:: args={s} build_options={s} output_dir={s}", - .{ - args, - build_options.sparse_exe_path, - build_options.output_dir, - }, - ); + // log.debug( + // "main:: args={s} build_options={s} output_dir={s}", + // .{ + // args, + // build_options.sparse_exe_path, + // build_options.output_dir, + // }, + // ); const repo_dir = try std.fs.path.join(allocator, &.{ build_options.output_dir, "sparse_test_repo" }); defer allocator.free(repo_dir); - - //const sparse_test: IntegrationTest = undefined; - //sparse_test.setup(allocator, repo_dir); - //sparse_test.run(allocator); - //sparse_test.teardown(allocator, repo_dir); - // { - // const rr = try system.system(.{ - // .allocator = allocator, - // .args = &.{ - // "mkdir", - // "-p", - // repo_dir, - // }, - // }); - // defer allocator.free(rr.stdout); - // defer allocator.free(rr.stderr); - // } - // { - // const rr = try system.git(.{ - // .allocator = allocator, - // .args = &.{ "init", "." }, - // .cwd = repo_dir, - // }); - // defer allocator.free(rr.stdout); - // defer allocator.free(rr.stderr); - // } - // { - // const rr = try system.system(.{ - // .allocator = allocator, - // .args = &.{ - // "rm", - // "-r", - // repo_dir, - // }, - // }); - // defer allocator.free(rr.stdout); - // defer allocator.free(rr.stderr); - // } } -test "Hello Integration" { - const test_alloc = std.testing.allocator; - const args = try std.process.argsAlloc(test_alloc); - defer std.process.argsFree(test_alloc, args); +// Good Wheater +test "Create Sparse Feature to default target with only feature name" { + if (true) { + return error.SkipZigTest; + } + const test_allocator = std.testing.allocator; + const args = try std.process.argsAlloc(test_allocator); + defer std.process.argsFree(test_allocator, args); const integration: IntegrationTest = undefined; - const sparse_feature_test = @field( + const feature_integration = @field( integration, "feature", ); - const data = try sparse_feature_test.setup(test_alloc, SparseFeatureTestData); - try std.testing.expect(data.repo_dir != null); - try sparse_feature_test.teardown(test_alloc, data); + const data: SparseFeatureTestData = try feature_integration.setup( + test_allocator, + SparseFeatureTestData, + ); + defer data.free(test_allocator); + _ = try feature_integration.run( + test_allocator, + SparseFeatureTestData, + data, + sparse_feature_test.createFeature, + ); + //try feature_integration.teardown(test_allocator, data); + try std.testing.expect(false); +} +test "Create Sparse Feature with only feature name" { + const test_allocator = std.testing.allocator; + const args = try std.process.argsAlloc(test_allocator); + defer std.process.argsFree(test_allocator, args); + + const integration: IntegrationTest = undefined; + const feature_integration = @field( + integration, + "feature", + ); + const data: SparseFeatureTestData = try feature_integration.setup( + test_allocator, + SparseFeatureTestData, + ); + defer data.free(test_allocator); + _ = try feature_integration.run( + test_allocator, + SparseFeatureTestData, + data, + sparse_feature_test.createFeature, + ); + //try feature_integration.teardown(test_allocator, data); try std.testing.expect(true); } @@ -119,4 +130,5 @@ test "Hello Integration2" { const system = @import("system.zig"); const SparseFeatureTest = @import("sparse_feature_test.zig").SparseFeatureTest; +const sparse_feature_test = @import("sparse_feature_test.zig"); const SparseFeatureTestData = @import("sparse_feature_test.zig").TestData; diff --git a/test/sparse_feature_test.zig b/test/sparse_feature_test.zig index bbdccc4..0046f42 100644 --- a/test/sparse_feature_test.zig +++ b/test/sparse_feature_test.zig @@ -1,6 +1,6 @@ const std = @import("std"); +const build_options = @import("build_options"); const log = std.log.scoped(.sparse_feature_test); -const debug = std.debug.print; const Allocator = std.mem.Allocator; const RunResult = std.process.Child.RunResult; @@ -26,6 +26,7 @@ pub const SparseFeatureTest = struct { //_ = repo_dir; // + std.testing.log_level = .debug; const rr_temp_dir = try system.system(.{ .allocator = alloc, .args = &.{ @@ -46,6 +47,10 @@ pub const SparseFeatureTest = struct { .args = &.{ "init", "." }, .cwd = data.repo_dir.?, }); + log.debug( + "sparse::feature::test:: repo_dir {s}", + .{data.repo_dir.?}, + ); defer alloc.free(rr_git_init.stdout); defer alloc.free(rr_git_init.stderr); @@ -63,7 +68,6 @@ pub const SparseFeatureTest = struct { _ = self; const test_data: TestData = @as(TestData, data); - defer test_data.free(alloc); std.testing.log_level = .debug; log.info("repo_dir {s}\n", .{test_data.repo_dir.?}); const rr_temp_dir = try system.system(.{ @@ -74,15 +78,49 @@ pub const SparseFeatureTest = struct { test_data.repo_dir.?, }, }); + log.info("stdout {s}\n", .{rr_temp_dir.stdout}); defer alloc.free(rr_temp_dir.stdout); defer alloc.free(rr_temp_dir.stderr); + + try std.testing.expect(rr_temp_dir.term.Exited == 0); + try std.testing.expect(std.mem.eql(u8, rr_temp_dir.stderr, "")); + try std.testing.expect(std.mem.eql(u8, rr_temp_dir.stdout, "")); + } + pub fn run( + self: SparseFeatureTest, + alloc: Allocator, + comptime T: anytype, + data: T, + comptime func: fn (Allocator, T) IntegrationTestError!bool, + ) IntegrationTestError!bool { + _ = self; + if (try func(alloc, data)) { + return true; + } + return false; } - // pub fn run(self: SparseFeatureTest, alloc: Allocator) !u8 { - // _ = self; - // _ = alloc; - // } }; - +pub fn createFeature(alloc: Allocator, data: TestData) IntegrationTestError!bool { + std.testing.log_level = .debug; + const rr_temp_dir = system.system(.{ + .allocator = alloc, + .args = &.{ + build_options.sparse_exe_path, + "feature", + "myNewFeature", + }, + .cwd = data.repo_dir.?, + }) catch return IntegrationTestError.UNEXPECTED_ERROR; + defer alloc.free(rr_temp_dir.stdout); + defer alloc.free(rr_temp_dir.stderr); + //_ = rr_temp_dir; + log.debug( + "sparse::feature::test:: createFeature stdout: {s}\n stderr:{s}\n", + .{ rr_temp_dir.stdout, rr_temp_dir.stderr }, + ); + return true; +} const sparse = @import("sparse"); const system = @import("system.zig"); const integration_test = @import("integration.zig"); +const IntegrationTestError = integration_test.IntegrationTestError; From a6ab4fe8e69c01524b7e09ef9201725f5726b7cc Mon Sep 17 00:00:00 2001 From: Bahanur Enis Date: Sat, 21 Jun 2025 18:01:27 +0200 Subject: [PATCH 03/19] test: happy create feature test Signed-off-by: Bahanur Enis --- test/integration.zig | 6 ++++++ test/sparse_feature_test.zig | 40 ++++++++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+) diff --git a/test/integration.zig b/test/integration.zig index 8a75507..0cbed1c 100644 --- a/test/integration.zig +++ b/test/integration.zig @@ -114,6 +114,12 @@ test "Create Sparse Feature with only feature name" { SparseFeatureTestData, ); defer data.free(test_allocator); + _ = try feature_integration.run( + test_allocator, + SparseFeatureTestData, + data, + sparse_feature_test.createCommitOnTarget, + ); _ = try feature_integration.run( test_allocator, SparseFeatureTestData, diff --git a/test/sparse_feature_test.zig b/test/sparse_feature_test.zig index 0046f42..a650215 100644 --- a/test/sparse_feature_test.zig +++ b/test/sparse_feature_test.zig @@ -100,6 +100,46 @@ pub const SparseFeatureTest = struct { return false; } }; +pub fn createCommitOnTarget(alloc: Allocator, data: TestData) IntegrationTestError!bool { + std.testing.log_level = .debug; + const rr_new_file = system.system(.{ + .allocator = alloc, + .args = &.{ + "touch", + "test.txt", + }, + .cwd = data.repo_dir.?, + }) catch return IntegrationTestError.UNEXPECTED_ERROR; + defer alloc.free(rr_new_file.stdout); + defer alloc.free(rr_new_file.stderr); + + //try std.testing.expect(rr_new_file.term.Exited == 0); + + const rr_git_add = system.git( + .{ + .allocator = alloc, + .args = &.{ "add", "." }, + .cwd = data.repo_dir.?, + }, + ) catch return IntegrationTestError.UNEXPECTED_ERROR; + defer alloc.free(rr_git_add.stdout); + defer alloc.free(rr_git_add.stderr); + + //try std.testing.expect(rr_git_add.term.Exited == 0); + const rr_git_commit = system.git(.{ + .allocator = alloc, + .args = &.{ "commit", "-m", "first commit" }, + .cwd = data.repo_dir.?, + }) catch return IntegrationTestError.UNEXPECTED_ERROR; + defer alloc.free(rr_git_commit.stdout); + defer alloc.free(rr_git_commit.stderr); + + std.testing.expect(rr_git_commit.term.Exited == 0) catch { + return IntegrationTestError.UNEXPECTED_ERROR; + }; + + return true; +} pub fn createFeature(alloc: Allocator, data: TestData) IntegrationTestError!bool { std.testing.log_level = .debug; const rr_temp_dir = system.system(.{ From b3676bd768a7485e9e7d57612129c540b8e93335 Mon Sep 17 00:00:00 2001 From: Bahanur Enis Date: Sat, 21 Jun 2025 18:58:38 +0200 Subject: [PATCH 04/19] test: add test result union to integration tests Signed-off-by: Bahanur Enis --- test/integration.zig | 24 +++++++++++++++++----- test/sparse_feature_test.zig | 40 +++++++++++++++++++----------------- 2 files changed, 40 insertions(+), 24 deletions(-) diff --git a/test/integration.zig b/test/integration.zig index 0cbed1c..fe51d08 100644 --- a/test/integration.zig +++ b/test/integration.zig @@ -5,8 +5,17 @@ const Allocator = std.mem.Allocator; const log = std.log.scoped(.integration); const build_options = @import("build_options"); pub const IntegrationTestError = error{ + TERM_EXIT_FAILED, UNEXPECTED_ERROR, }; +pub const IntegrationTestResult = union(enum) { + feature: SparseFeatureTestResult, + pub fn status(self: IntegrationTestResult) bool { + switch (self) { + inline else => |test_result| return test_result.status(), + } + } +}; pub const IntegrationTest = union(enum) { feature: SparseFeatureTest, @@ -39,8 +48,8 @@ pub const IntegrationTest = union(enum) { alloc: Allocator, comptime T: anytype, data: T, - comptime func: fn (Allocator, T) bool, - ) IntegrationTestError!bool { + comptime func: fn (Allocator, T) IntegrationTestResult, + ) IntegrationTestResult { switch (self) { inline else => |integration_test| return try integration_test.run( alloc, @@ -90,7 +99,7 @@ test "Create Sparse Feature to default target with only feature name" { SparseFeatureTestData, ); defer data.free(test_allocator); - _ = try feature_integration.run( + _ = feature_integration.run( test_allocator, SparseFeatureTestData, data, @@ -114,13 +123,17 @@ test "Create Sparse Feature with only feature name" { SparseFeatureTestData, ); defer data.free(test_allocator); - _ = try feature_integration.run( + const create_commit_result: IntegrationTestResult = feature_integration.run( test_allocator, SparseFeatureTestData, data, sparse_feature_test.createCommitOnTarget, ); - _ = try feature_integration.run( + // try std.testing.expect(create_commit_result.status()); + if (!create_commit_result.status()) { + log.err("exit_code: {d}", .{create_commit_result.feature.exit_code}); + } + _ = feature_integration.run( test_allocator, SparseFeatureTestData, data, @@ -138,3 +151,4 @@ const system = @import("system.zig"); const SparseFeatureTest = @import("sparse_feature_test.zig").SparseFeatureTest; const sparse_feature_test = @import("sparse_feature_test.zig"); const SparseFeatureTestData = @import("sparse_feature_test.zig").TestData; +const SparseFeatureTestResult = @import("sparse_feature_test.zig").TestResult; diff --git a/test/sparse_feature_test.zig b/test/sparse_feature_test.zig index a650215..54c8512 100644 --- a/test/sparse_feature_test.zig +++ b/test/sparse_feature_test.zig @@ -4,7 +4,6 @@ const log = std.log.scoped(.sparse_feature_test); const Allocator = std.mem.Allocator; const RunResult = std.process.Child.RunResult; -const mystruct = @This(); pub const TestData = struct { repo_dir: ?[]const u8 = null, pub fn free(self: TestData, alloc: Allocator) void { @@ -13,6 +12,14 @@ pub const TestData = struct { } } }; +pub const TestResult = struct { + err: IntegrationTestError = IntegrationTestError.UNEXPECTED_ERROR, + exit_code: u8 = 1, + + pub fn status(self: TestResult) bool { + return self.exit_code == 0; + } +}; pub const SparseFeatureTest = struct { pub fn setup( @@ -91,16 +98,13 @@ pub const SparseFeatureTest = struct { alloc: Allocator, comptime T: anytype, data: T, - comptime func: fn (Allocator, T) IntegrationTestError!bool, - ) IntegrationTestError!bool { + comptime func: fn (Allocator, T) IntegrationTestResult, + ) IntegrationTestResult { _ = self; - if (try func(alloc, data)) { - return true; - } - return false; + return func(alloc, data); } }; -pub fn createCommitOnTarget(alloc: Allocator, data: TestData) IntegrationTestError!bool { +pub fn createCommitOnTarget(alloc: Allocator, data: TestData) IntegrationTestResult { std.testing.log_level = .debug; const rr_new_file = system.system(.{ .allocator = alloc, @@ -109,7 +113,8 @@ pub fn createCommitOnTarget(alloc: Allocator, data: TestData) IntegrationTestErr "test.txt", }, .cwd = data.repo_dir.?, - }) catch return IntegrationTestError.UNEXPECTED_ERROR; + }) catch return .{ .feature = .{ .err = IntegrationTestError.TERM_EXIT_FAILED } }; + defer alloc.free(rr_new_file.stdout); defer alloc.free(rr_new_file.stderr); @@ -121,7 +126,7 @@ pub fn createCommitOnTarget(alloc: Allocator, data: TestData) IntegrationTestErr .args = &.{ "add", "." }, .cwd = data.repo_dir.?, }, - ) catch return IntegrationTestError.UNEXPECTED_ERROR; + ) catch return .{ .feature = .{ .err = IntegrationTestError.TERM_EXIT_FAILED } }; defer alloc.free(rr_git_add.stdout); defer alloc.free(rr_git_add.stderr); @@ -130,17 +135,13 @@ pub fn createCommitOnTarget(alloc: Allocator, data: TestData) IntegrationTestErr .allocator = alloc, .args = &.{ "commit", "-m", "first commit" }, .cwd = data.repo_dir.?, - }) catch return IntegrationTestError.UNEXPECTED_ERROR; + }) catch return .{ .feature = .{ .err = IntegrationTestError.TERM_EXIT_FAILED } }; defer alloc.free(rr_git_commit.stdout); defer alloc.free(rr_git_commit.stderr); - std.testing.expect(rr_git_commit.term.Exited == 0) catch { - return IntegrationTestError.UNEXPECTED_ERROR; - }; - - return true; + return .{ .feature = .{ .exit_code = 0 } }; } -pub fn createFeature(alloc: Allocator, data: TestData) IntegrationTestError!bool { +pub fn createFeature(alloc: Allocator, data: TestData) IntegrationTestResult { std.testing.log_level = .debug; const rr_temp_dir = system.system(.{ .allocator = alloc, @@ -150,7 +151,7 @@ pub fn createFeature(alloc: Allocator, data: TestData) IntegrationTestError!bool "myNewFeature", }, .cwd = data.repo_dir.?, - }) catch return IntegrationTestError.UNEXPECTED_ERROR; + }) catch return .{ .feature = .{ .err = IntegrationTestError.TERM_EXIT_FAILED } }; defer alloc.free(rr_temp_dir.stdout); defer alloc.free(rr_temp_dir.stderr); //_ = rr_temp_dir; @@ -158,9 +159,10 @@ pub fn createFeature(alloc: Allocator, data: TestData) IntegrationTestError!bool "sparse::feature::test:: createFeature stdout: {s}\n stderr:{s}\n", .{ rr_temp_dir.stdout, rr_temp_dir.stderr }, ); - return true; + return .{ .feature = .{ .exit_code = 0 } }; } const sparse = @import("sparse"); const system = @import("system.zig"); const integration_test = @import("integration.zig"); +const IntegrationTestResult = integration_test.IntegrationTestResult; const IntegrationTestError = integration_test.IntegrationTestError; From 15b91e0297d29619e0c137dae4a552e06f47696e Mon Sep 17 00:00:00 2001 From: Bahanur Enis Date: Sun, 22 Jun 2025 14:12:53 +0200 Subject: [PATCH 05/19] test: add err_context to integration feature Signed-off-by: Bahanur Enis --- test/integration.zig | 18 ++++----- test/sparse_feature_test.zig | 76 ++++++++++++++++++++++-------------- 2 files changed, 55 insertions(+), 39 deletions(-) diff --git a/test/integration.zig b/test/integration.zig index fe51d08..bcb4d27 100644 --- a/test/integration.zig +++ b/test/integration.zig @@ -123,16 +123,16 @@ test "Create Sparse Feature with only feature name" { SparseFeatureTestData, ); defer data.free(test_allocator); - const create_commit_result: IntegrationTestResult = feature_integration.run( - test_allocator, - SparseFeatureTestData, - data, - sparse_feature_test.createCommitOnTarget, - ); + // const create_commit_result: IntegrationTestResult = feature_integration.run( + // test_allocator, + // SparseFeatureTestData, + // data, + // sparse_feature_test.createCommitOnTarget, + // ); // try std.testing.expect(create_commit_result.status()); - if (!create_commit_result.status()) { - log.err("exit_code: {d}", .{create_commit_result.feature.exit_code}); - } + // if (!create_commit_result.status()) { + // log.err("exit_code: {d}", .{create_commit_result.feature.exit_code}); + // } _ = feature_integration.run( test_allocator, SparseFeatureTestData, diff --git a/test/sparse_feature_test.zig b/test/sparse_feature_test.zig index 54c8512..c656b07 100644 --- a/test/sparse_feature_test.zig +++ b/test/sparse_feature_test.zig @@ -3,9 +3,9 @@ const build_options = @import("build_options"); const log = std.log.scoped(.sparse_feature_test); const Allocator = std.mem.Allocator; const RunResult = std.process.Child.RunResult; - pub const TestData = struct { repo_dir: ?[]const u8 = null, + feature_name: ?[]const u8 = null, pub fn free(self: TestData, alloc: Allocator) void { if (self.repo_dir) |repo_dir| { alloc.free(repo_dir); @@ -13,7 +13,10 @@ pub const TestData = struct { } }; pub const TestResult = struct { - err: IntegrationTestError = IntegrationTestError.UNEXPECTED_ERROR, + error_context: ?struct { + err: IntegrationTestError, + err_msg: ?[]const u8 = null, + } = null, exit_code: u8 = 1, pub fn status(self: TestResult) bool { @@ -104,62 +107,75 @@ pub const SparseFeatureTest = struct { return func(alloc, data); } }; -pub fn createCommitOnTarget(alloc: Allocator, data: TestData) IntegrationTestResult { +pub fn createFeature(alloc: Allocator, data: TestData) IntegrationTestResult { + std.testing.log_level = .debug; + createCommitOnTarget(alloc, data) catch return .{ + .feature = .{ + .exit_code = 1, + .error_context = .{ + .err = IntegrationTestError.TERM_EXIT_FAILED, + }, + }, + }; + const rr_temp_dir = system.system(.{ + .allocator = alloc, + .args = &.{ + build_options.sparse_exe_path, + "feature", + "myNewFeature", + }, + .cwd = data.repo_dir.?, + }) catch return .{ + .feature = .{ + .exit_code = 1, + .error_context = .{ .err = IntegrationTestError.TERM_EXIT_FAILED }, + }, + }; + defer alloc.free(rr_temp_dir.stdout); + defer alloc.free(rr_temp_dir.stderr); + //_ = rr_temp_dir; + log.debug( + "sparse::feature::test:: createFeature stdout: {s}\n stderr:{s}\n", + .{ rr_temp_dir.stdout, rr_temp_dir.stderr }, + ); + return .{ .feature = .{ .exit_code = 0 } }; +} +fn createCommitOnTarget(alloc: Allocator, data: TestData) !void { std.testing.log_level = .debug; - const rr_new_file = system.system(.{ + const rr_new_file = try system.system(.{ .allocator = alloc, .args = &.{ "touch", "test.txt", }, .cwd = data.repo_dir.?, - }) catch return .{ .feature = .{ .err = IntegrationTestError.TERM_EXIT_FAILED } }; + }); defer alloc.free(rr_new_file.stdout); defer alloc.free(rr_new_file.stderr); //try std.testing.expect(rr_new_file.term.Exited == 0); - const rr_git_add = system.git( + const rr_git_add = try system.git( .{ .allocator = alloc, .args = &.{ "add", "." }, .cwd = data.repo_dir.?, }, - ) catch return .{ .feature = .{ .err = IntegrationTestError.TERM_EXIT_FAILED } }; + ); defer alloc.free(rr_git_add.stdout); defer alloc.free(rr_git_add.stderr); //try std.testing.expect(rr_git_add.term.Exited == 0); - const rr_git_commit = system.git(.{ + const rr_git_commit = try system.git(.{ .allocator = alloc, .args = &.{ "commit", "-m", "first commit" }, .cwd = data.repo_dir.?, - }) catch return .{ .feature = .{ .err = IntegrationTestError.TERM_EXIT_FAILED } }; + }); defer alloc.free(rr_git_commit.stdout); defer alloc.free(rr_git_commit.stderr); - return .{ .feature = .{ .exit_code = 0 } }; -} -pub fn createFeature(alloc: Allocator, data: TestData) IntegrationTestResult { - std.testing.log_level = .debug; - const rr_temp_dir = system.system(.{ - .allocator = alloc, - .args = &.{ - build_options.sparse_exe_path, - "feature", - "myNewFeature", - }, - .cwd = data.repo_dir.?, - }) catch return .{ .feature = .{ .err = IntegrationTestError.TERM_EXIT_FAILED } }; - defer alloc.free(rr_temp_dir.stdout); - defer alloc.free(rr_temp_dir.stderr); - //_ = rr_temp_dir; - log.debug( - "sparse::feature::test:: createFeature stdout: {s}\n stderr:{s}\n", - .{ rr_temp_dir.stdout, rr_temp_dir.stderr }, - ); - return .{ .feature = .{ .exit_code = 0 } }; + // return .{ .feature = .{ .exit_code = 0 } }; } const sparse = @import("sparse"); const system = @import("system.zig"); From 0c82265fa206de4d90de81e456d2975e321b977a Mon Sep 17 00:00:00 2001 From: Bahanur Enis Date: Sun, 22 Jun 2025 14:24:22 +0200 Subject: [PATCH 06/19] test: update integration test for create feature Signed-off-by: Bahanur Enis --- test/integration.zig | 20 +++++++++----------- test/sparse_feature_test.zig | 2 +- 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/test/integration.zig b/test/integration.zig index bcb4d27..9bb9828 100644 --- a/test/integration.zig +++ b/test/integration.zig @@ -123,22 +123,20 @@ test "Create Sparse Feature with only feature name" { SparseFeatureTestData, ); defer data.free(test_allocator); - // const create_commit_result: IntegrationTestResult = feature_integration.run( - // test_allocator, - // SparseFeatureTestData, - // data, - // sparse_feature_test.createCommitOnTarget, - // ); - // try std.testing.expect(create_commit_result.status()); - // if (!create_commit_result.status()) { - // log.err("exit_code: {d}", .{create_commit_result.feature.exit_code}); - // } - _ = feature_integration.run( + const rr_sparse_feature = feature_integration.run( test_allocator, SparseFeatureTestData, data, sparse_feature_test.createFeature, ); + if (!rr_sparse_feature.feature.status()) { + log.err("Test Failed with exit_code {d} {any} - {s}", .{ + rr_sparse_feature.feature.exit_code, + rr_sparse_feature.feature.error_context.?.err, + rr_sparse_feature.feature.error_context.?.err_msg.?, + }); + } + try std.testing.expect(rr_sparse_feature.feature.exit_code == 0); //try feature_integration.teardown(test_allocator, data); try std.testing.expect(true); } diff --git a/test/sparse_feature_test.zig b/test/sparse_feature_test.zig index c656b07..6477ff2 100644 --- a/test/sparse_feature_test.zig +++ b/test/sparse_feature_test.zig @@ -15,7 +15,7 @@ pub const TestData = struct { pub const TestResult = struct { error_context: ?struct { err: IntegrationTestError, - err_msg: ?[]const u8 = null, + err_msg: ?[]const u8 = "", } = null, exit_code: u8 = 1, From 5a8617dc1e8045760e3357f2807f0508e43bbfdd Mon Sep 17 00:00:00 2001 From: Bahanur Enis Date: Sun, 22 Jun 2025 14:32:07 +0200 Subject: [PATCH 07/19] test: set feature name from testcase Signed-off-by: Bahanur Enis --- test/integration.zig | 8 ++++---- test/sparse_feature_test.zig | 10 ++-------- 2 files changed, 6 insertions(+), 12 deletions(-) diff --git a/test/integration.zig b/test/integration.zig index 9bb9828..569f760 100644 --- a/test/integration.zig +++ b/test/integration.zig @@ -118,16 +118,18 @@ test "Create Sparse Feature with only feature name" { integration, "feature", ); - const data: SparseFeatureTestData = try feature_integration.setup( + var data: SparseFeatureTestData = try feature_integration.setup( test_allocator, SparseFeatureTestData, ); defer data.free(test_allocator); + // set a feature name + data.feature_name = "hellofeature"; const rr_sparse_feature = feature_integration.run( test_allocator, SparseFeatureTestData, data, - sparse_feature_test.createFeature, + sparse_feature_test.createFeatureStep, ); if (!rr_sparse_feature.feature.status()) { log.err("Test Failed with exit_code {d} {any} - {s}", .{ @@ -137,8 +139,6 @@ test "Create Sparse Feature with only feature name" { }); } try std.testing.expect(rr_sparse_feature.feature.exit_code == 0); - //try feature_integration.teardown(test_allocator, data); - try std.testing.expect(true); } test "Hello Integration2" { diff --git a/test/sparse_feature_test.zig b/test/sparse_feature_test.zig index 6477ff2..c196a58 100644 --- a/test/sparse_feature_test.zig +++ b/test/sparse_feature_test.zig @@ -107,7 +107,7 @@ pub const SparseFeatureTest = struct { return func(alloc, data); } }; -pub fn createFeature(alloc: Allocator, data: TestData) IntegrationTestResult { +pub fn createFeatureStep(alloc: Allocator, data: TestData) IntegrationTestResult { std.testing.log_level = .debug; createCommitOnTarget(alloc, data) catch return .{ .feature = .{ @@ -122,7 +122,7 @@ pub fn createFeature(alloc: Allocator, data: TestData) IntegrationTestResult { .args = &.{ build_options.sparse_exe_path, "feature", - "myNewFeature", + data.feature_name.?, }, .cwd = data.repo_dir.?, }) catch return .{ @@ -133,7 +133,6 @@ pub fn createFeature(alloc: Allocator, data: TestData) IntegrationTestResult { }; defer alloc.free(rr_temp_dir.stdout); defer alloc.free(rr_temp_dir.stderr); - //_ = rr_temp_dir; log.debug( "sparse::feature::test:: createFeature stdout: {s}\n stderr:{s}\n", .{ rr_temp_dir.stdout, rr_temp_dir.stderr }, @@ -154,8 +153,6 @@ fn createCommitOnTarget(alloc: Allocator, data: TestData) !void { defer alloc.free(rr_new_file.stdout); defer alloc.free(rr_new_file.stderr); - //try std.testing.expect(rr_new_file.term.Exited == 0); - const rr_git_add = try system.git( .{ .allocator = alloc, @@ -166,7 +163,6 @@ fn createCommitOnTarget(alloc: Allocator, data: TestData) !void { defer alloc.free(rr_git_add.stdout); defer alloc.free(rr_git_add.stderr); - //try std.testing.expect(rr_git_add.term.Exited == 0); const rr_git_commit = try system.git(.{ .allocator = alloc, .args = &.{ "commit", "-m", "first commit" }, @@ -174,8 +170,6 @@ fn createCommitOnTarget(alloc: Allocator, data: TestData) !void { }); defer alloc.free(rr_git_commit.stdout); defer alloc.free(rr_git_commit.stderr); - - // return .{ .feature = .{ .exit_code = 0 } }; } const sparse = @import("sparse"); const system = @import("system.zig"); From 5a62c83812bdbfbac26fb50b196bb87fc59184ae Mon Sep 17 00:00:00 2001 From: Bahanur Enis Date: Sun, 22 Jun 2025 14:54:31 +0200 Subject: [PATCH 08/19] test: add git show ref to feature Signed-off-by: Bahanur Enis --- test/integration.zig | 12 ++++++------ test/sparse_feature_test.zig | 33 +++++++++++++++++++++++++++------ 2 files changed, 33 insertions(+), 12 deletions(-) diff --git a/test/integration.zig b/test/integration.zig index 569f760..27df3fe 100644 --- a/test/integration.zig +++ b/test/integration.zig @@ -125,20 +125,20 @@ test "Create Sparse Feature with only feature name" { defer data.free(test_allocator); // set a feature name data.feature_name = "hellofeature"; - const rr_sparse_feature = feature_integration.run( + const rr_feature_step = feature_integration.run( test_allocator, SparseFeatureTestData, data, sparse_feature_test.createFeatureStep, ); - if (!rr_sparse_feature.feature.status()) { + if (!rr_feature_step.feature.status()) { log.err("Test Failed with exit_code {d} {any} - {s}", .{ - rr_sparse_feature.feature.exit_code, - rr_sparse_feature.feature.error_context.?.err, - rr_sparse_feature.feature.error_context.?.err_msg.?, + rr_feature_step.feature.exit_code, + rr_feature_step.feature.error_context.?.err, + rr_feature_step.feature.error_context.?.err_msg.?, }); } - try std.testing.expect(rr_sparse_feature.feature.exit_code == 0); + try std.testing.expect(rr_feature_step.feature.exit_code == 0); } test "Hello Integration2" { diff --git a/test/sparse_feature_test.zig b/test/sparse_feature_test.zig index c196a58..0453242 100644 --- a/test/sparse_feature_test.zig +++ b/test/sparse_feature_test.zig @@ -6,6 +6,7 @@ const RunResult = std.process.Child.RunResult; pub const TestData = struct { repo_dir: ?[]const u8 = null, feature_name: ?[]const u8 = null, + feature_to: ?[]const u8 = null, pub fn free(self: TestData, alloc: Allocator) void { if (self.repo_dir) |repo_dir| { alloc.free(repo_dir); @@ -17,6 +18,7 @@ pub const TestResult = struct { err: IntegrationTestError, err_msg: ?[]const u8 = "", } = null, + output: ?[]const []const u8 = null, exit_code: u8 = 1, pub fn status(self: TestResult) bool { @@ -32,10 +34,6 @@ pub const SparseFeatureTest = struct { ) !T { _ = self; var data: TestData = .{}; - //_ = alloc; - //_ = repo_dir; - // - std.testing.log_level = .debug; const rr_temp_dir = try system.system(.{ .allocator = alloc, @@ -133,12 +131,35 @@ pub fn createFeatureStep(alloc: Allocator, data: TestData) IntegrationTestResult }; defer alloc.free(rr_temp_dir.stdout); defer alloc.free(rr_temp_dir.stderr); + // log.debug( + // "sparse::feature::test:: createFeature stderr:{s}\n", + // .{rr_temp_dir.stderr}, + // ); + const rr_git_show_ref = system.git( + .{ + .allocator = alloc, + .args = &.{"show-ref"}, + .cwd = data.repo_dir.?, + }, + ) catch return .{ + .feature = .{ + .exit_code = 1, + .error_context = .{ + .err = IntegrationTestError.TERM_EXIT_FAILED, + .err_msg = "git show-ref command failed.", + }, + }, + }; + log.debug( - "sparse::feature::test:: createFeature stdout: {s}\n stderr:{s}\n", - .{ rr_temp_dir.stdout, rr_temp_dir.stderr }, + "SparseFeatureTest::createFeature stderr:{s}\n", + .{rr_git_show_ref.stdout}, ); + defer alloc.free(rr_git_show_ref.stdout); + defer alloc.free(rr_git_show_ref.stderr); return .{ .feature = .{ .exit_code = 0 } }; } +//test facility functions (TODO: move another module later maybe?) fn createCommitOnTarget(alloc: Allocator, data: TestData) !void { std.testing.log_level = .debug; const rr_new_file = try system.system(.{ From df5cf6a29263eb2010caf8314676b35f4a3346f5 Mon Sep 17 00:00:00 2001 From: Bahanur Enis Date: Sun, 22 Jun 2025 15:21:16 +0200 Subject: [PATCH 09/19] test: add parseGitShowRef Signed-off-by: Bahanur Enis --- test/sparse_feature_test.zig | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/test/sparse_feature_test.zig b/test/sparse_feature_test.zig index 0453242..4da5566 100644 --- a/test/sparse_feature_test.zig +++ b/test/sparse_feature_test.zig @@ -18,7 +18,14 @@ pub const TestResult = struct { err: IntegrationTestError, err_msg: ?[]const u8 = "", } = null, - output: ?[]const []const u8 = null, + // output: ?struct { + // feature_name: ?[]const u8 = null, + // feature_prefix: []const u8 = "refs/heads/sparse/", + // target: ?[]const u8 = null, + // user_config: ?[]const u8 = null, + // slice_prefix: []const u8 = "/slice/", + // slice_name: ?[]const u8 = null, + // } = null, exit_code: u8 = 1, pub fn status(self: TestResult) bool { @@ -150,11 +157,11 @@ pub fn createFeatureStep(alloc: Allocator, data: TestData) IntegrationTestResult }, }, }; - - log.debug( - "SparseFeatureTest::createFeature stderr:{s}\n", - .{rr_git_show_ref.stdout}, - ); + try parseGitShowRefResult(alloc, rr_git_show_ref.stdout); + // log.debug( + // "SparseFeatureTest::createFeature stderr:{s}\n", + // .{rr_git_show_ref.stdout}, + // ); defer alloc.free(rr_git_show_ref.stdout); defer alloc.free(rr_git_show_ref.stderr); return .{ .feature = .{ .exit_code = 0 } }; @@ -192,6 +199,14 @@ fn createCommitOnTarget(alloc: Allocator, data: TestData) !void { defer alloc.free(rr_git_commit.stdout); defer alloc.free(rr_git_commit.stderr); } +fn parseGitShowRefResult(alloc: Allocator, stdout: []u8) !void { + _ = alloc; + + log.debug( + "SparseFeatureTest::createFeature stderr:{s}\n", + .{stdout}, + ); +} const sparse = @import("sparse"); const system = @import("system.zig"); const integration_test = @import("integration.zig"); From f99d994d1d5cb522a75c305e1b5f0b4f38244ea9 Mon Sep 17 00:00:00 2001 From: Bahanur Enis Date: Sun, 22 Jun 2025 17:39:26 +0200 Subject: [PATCH 10/19] test: parseGitShowRef kind of ready to use Signed-off-by: Bahanur Enis --- test/integration.zig | 2 + test/sparse_feature_test.zig | 103 +++++++++++++++++++++++++++++++---- 2 files changed, 95 insertions(+), 10 deletions(-) diff --git a/test/integration.zig b/test/integration.zig index 27df3fe..c2ff752 100644 --- a/test/integration.zig +++ b/test/integration.zig @@ -7,6 +7,8 @@ const build_options = @import("build_options"); pub const IntegrationTestError = error{ TERM_EXIT_FAILED, UNEXPECTED_ERROR, + SPARSE_FEATURE_EMPTY_REF, + SPARSE_FEATURE_NOT_FOUND, }; pub const IntegrationTestResult = union(enum) { feature: SparseFeatureTestResult, diff --git a/test/sparse_feature_test.zig b/test/sparse_feature_test.zig index 4da5566..c3ce1ef 100644 --- a/test/sparse_feature_test.zig +++ b/test/sparse_feature_test.zig @@ -157,11 +157,37 @@ pub fn createFeatureStep(alloc: Allocator, data: TestData) IntegrationTestResult }, }, }; - try parseGitShowRefResult(alloc, rr_git_show_ref.stdout); - // log.debug( - // "SparseFeatureTest::createFeature stderr:{s}\n", - // .{rr_git_show_ref.stdout}, - // ); + const ref_target, const sparce_slice = parseGitShowRefResult( + alloc, + rr_git_show_ref.stdout, + ) catch |res| switch (res) { + IntegrationTestError.SPARSE_FEATURE_NOT_FOUND => return .{ + .feature = .{ + .exit_code = 1, + .error_context = .{ + .err = IntegrationTestError.SPARSE_FEATURE_NOT_FOUND, + }, + }, + }, + IntegrationTestError.SPARSE_FEATURE_EMPTY_REF => return .{ + .feature = .{ + .exit_code = 1, + .error_context = .{ + .err = IntegrationTestError.SPARSE_FEATURE_EMPTY_REF, + }, + }, + }, + else => return .{ + .feature = .{ + .exit_code = 1, + .error_context = .{ + .err = IntegrationTestError.UNEXPECTED_ERROR, + }, + }, + }, + }; + _ = ref_target; + _ = sparce_slice; defer alloc.free(rr_git_show_ref.stdout); defer alloc.free(rr_git_show_ref.stderr); return .{ .feature = .{ .exit_code = 0 } }; @@ -199,13 +225,70 @@ fn createCommitOnTarget(alloc: Allocator, data: TestData) !void { defer alloc.free(rr_git_commit.stdout); defer alloc.free(rr_git_commit.stderr); } -fn parseGitShowRefResult(alloc: Allocator, stdout: []u8) !void { +fn parseGitShowRefResult(alloc: Allocator, stdout: []u8) IntegrationTestError!struct { []const u8, []const u8 } { + // Attention!: this function will not be responsible to free stdout, + // it should be done in caller side _ = alloc; - log.debug( - "SparseFeatureTest::createFeature stderr:{s}\n", - .{stdout}, - ); + // log.debug( + // "SparseFeatureTest::createFeature stderr:{s}\n", + // .{stdout}, + // ); + + const ref_result = std.mem.trim(u8, stdout, "\n\t \r"); + + // log.debug( + // "::parseGitShowRefResult :{s}\n", + // .{ref_result}, + // ); + var ref_target: ?[]const u8 = null; + var sparse_slice: ?[]const u8 = null; + if (ref_result.len != 0) { + var split_ref_result = std.mem.tokenizeAny( + u8, + stdout, + " \n", + ); + while (split_ref_result.next()) |iter| { + if (ref_target != null and sparse_slice != null) { + break; + } + if (std.mem.startsWith(u8, iter, "refs/heads/")) { + if (std.mem.containsAtLeast(u8, iter, 1, "sparse")) { + sparse_slice = iter; + } else { + ref_target = iter; + } + } + } + // else { + // ref_target = null; + // sparse_slice = null; + // return IntegrationTestError.SPARSE_FEATURE_EMPTY_REF_ERROR; + // } + + return .{ ref_target.?, sparse_slice.? }; + } + if (ref_target == null) { + return IntegrationTestError.SPARSE_FEATURE_EMPTY_REF; + } + if (sparse_slice == null) { + return IntegrationTestError.SPARSE_FEATURE_NOT_FOUND; + } + // split_ref_result.reset(); + // while (split_ref_result.index < split_ref_result.buffer.len) : (split_ref_result.index += 1) { + + // _ = split_ref_result.next().?; + // const ref_target = split_ref_result.next().?; + // _ = split_ref_result.next().?; + // const sparse_slice = split_ref_result.next().?; + // log.debug( + // "SparseFeatureTest::createFeature stderr: target:{s}\n slice: {s}\n", + // .{ ref_target, sparse_slice }, + // ); + // } + // } + return IntegrationTestError.SPARSE_FEATURE_NOT_FOUND; } const sparse = @import("sparse"); const system = @import("system.zig"); From 05faea4782c4ab586ae81c0fb6d8040c535dea19 Mon Sep 17 00:00:00 2001 From: Bahanur Enis Date: Sun, 22 Jun 2025 19:16:48 +0200 Subject: [PATCH 11/19] test: update createFeatureStep this will be changed Signed-off-by: Bahanur Enis --- test/integration.zig | 6 +- test/sparse_feature_test.zig | 169 +++++++++++++---------------------- 2 files changed, 67 insertions(+), 108 deletions(-) diff --git a/test/integration.zig b/test/integration.zig index c2ff752..b1d158a 100644 --- a/test/integration.zig +++ b/test/integration.zig @@ -9,6 +9,7 @@ pub const IntegrationTestError = error{ UNEXPECTED_ERROR, SPARSE_FEATURE_EMPTY_REF, SPARSE_FEATURE_NOT_FOUND, + SPARSE_FEATURE_TARGET_MISMATCH, }; pub const IntegrationTestResult = union(enum) { feature: SparseFeatureTestResult, @@ -134,12 +135,13 @@ test "Create Sparse Feature with only feature name" { sparse_feature_test.createFeatureStep, ); if (!rr_feature_step.feature.status()) { - log.err("Test Failed with exit_code {d} {any} - {s}", .{ + log.err("Test Failed with exit_code {d} {any}", .{ rr_feature_step.feature.exit_code, rr_feature_step.feature.error_context.?.err, - rr_feature_step.feature.error_context.?.err_msg.?, + //rr_feature_step.feature.error_context.?.err_msg.?, }); } + try feature_integration.teardown(test_allocator, data); try std.testing.expect(rr_feature_step.feature.exit_code == 0); } diff --git a/test/sparse_feature_test.zig b/test/sparse_feature_test.zig index c3ce1ef..84cb938 100644 --- a/test/sparse_feature_test.zig +++ b/test/sparse_feature_test.zig @@ -15,7 +15,7 @@ pub const TestData = struct { }; pub const TestResult = struct { error_context: ?struct { - err: IntegrationTestError, + err: ?IntegrationTestError = null, err_msg: ?[]const u8 = "", } = null, // output: ?struct { @@ -78,19 +78,18 @@ pub const SparseFeatureTest = struct { pub fn teardown( self: SparseFeatureTest, alloc: Allocator, - data: anytype, + data: TestData, ) !void { _ = self; - const test_data: TestData = @as(TestData, data); std.testing.log_level = .debug; - log.info("repo_dir {s}\n", .{test_data.repo_dir.?}); + log.info("repo_dir {s}\n", .{data.repo_dir.?}); const rr_temp_dir = try system.system(.{ .allocator = alloc, .args = &.{ "rm", "-r", - test_data.repo_dir.?, + data.repo_dir.?, }, }); log.info("stdout {s}\n", .{rr_temp_dir.stdout}); @@ -114,15 +113,21 @@ pub const SparseFeatureTest = struct { }; pub fn createFeatureStep(alloc: Allocator, data: TestData) IntegrationTestResult { std.testing.log_level = .debug; - createCommitOnTarget(alloc, data) catch return .{ + var test_result: IntegrationTestResult = .{ .feature = .{ .exit_code = 1, .error_context = .{ - .err = IntegrationTestError.TERM_EXIT_FAILED, + .err = null, + .err_msg = null, }, }, }; - const rr_temp_dir = system.system(.{ + createCommitOnTarget(alloc, data) catch { + test_result.feature.error_context.?.err = IntegrationTestError.TERM_EXIT_FAILED; + return test_result; + }; + // run sparse feature [feature_name] --to = null + const rr_sparse_feature = system.system(.{ .allocator = alloc, .args = &.{ build_options.sparse_exe_path, @@ -130,67 +135,51 @@ pub fn createFeatureStep(alloc: Allocator, data: TestData) IntegrationTestResult data.feature_name.?, }, .cwd = data.repo_dir.?, - }) catch return .{ - .feature = .{ - .exit_code = 1, - .error_context = .{ .err = IntegrationTestError.TERM_EXIT_FAILED }, - }, + }) catch { + test_result.feature.error_context.?.err = IntegrationTestError.TERM_EXIT_FAILED; + return test_result; }; - defer alloc.free(rr_temp_dir.stdout); - defer alloc.free(rr_temp_dir.stderr); - // log.debug( - // "sparse::feature::test:: createFeature stderr:{s}\n", - // .{rr_temp_dir.stderr}, - // ); + defer alloc.free(rr_sparse_feature.stdout); + defer alloc.free(rr_sparse_feature.stderr); const rr_git_show_ref = system.git( .{ .allocator = alloc, .args = &.{"show-ref"}, .cwd = data.repo_dir.?, }, - ) catch return .{ - .feature = .{ - .exit_code = 1, - .error_context = .{ - .err = IntegrationTestError.TERM_EXIT_FAILED, - .err_msg = "git show-ref command failed.", - }, - }, + ) catch { + test_result.feature.error_context.?.err = IntegrationTestError.TERM_EXIT_FAILED; + test_result.feature.error_context.?.err_msg = "git show-ref command failed"; + return test_result; }; - const ref_target, const sparce_slice = parseGitShowRefResult( + log.debug( + "sparse::feature::test:: git show ref stderr:{s}\n", + .{rr_git_show_ref.stdout}, + ); + defer alloc.free(rr_git_show_ref.stdout); + defer alloc.free(rr_git_show_ref.stderr); + + // Parsing git-show-ref + const sparce_slice = parseGitShowRefResult( alloc, rr_git_show_ref.stdout, - ) catch |res| switch (res) { - IntegrationTestError.SPARSE_FEATURE_NOT_FOUND => return .{ - .feature = .{ - .exit_code = 1, - .error_context = .{ - .err = IntegrationTestError.SPARSE_FEATURE_NOT_FOUND, - }, - }, - }, - IntegrationTestError.SPARSE_FEATURE_EMPTY_REF => return .{ - .feature = .{ - .exit_code = 1, - .error_context = .{ - .err = IntegrationTestError.SPARSE_FEATURE_EMPTY_REF, - }, - }, - }, - else => return .{ - .feature = .{ - .exit_code = 1, - .error_context = .{ - .err = IntegrationTestError.UNEXPECTED_ERROR, - }, - }, - }, + data.feature_name.?, + "bahanurenis@gmail.com", + ) catch |res| { + switch (res) { + IntegrationTestError.SPARSE_FEATURE_NOT_FOUND => test_result.feature.error_context.?.err = IntegrationTestError.SPARSE_FEATURE_NOT_FOUND, + IntegrationTestError.SPARSE_FEATURE_EMPTY_REF => test_result.feature.error_context.?.err = IntegrationTestError.SPARSE_FEATURE_EMPTY_REF, + else => test_result.feature.error_context.?.err = IntegrationTestError.UNEXPECTED_ERROR, + } + + return test_result; }; - _ = ref_target; - _ = sparce_slice; - defer alloc.free(rr_git_show_ref.stdout); - defer alloc.free(rr_git_show_ref.stderr); - return .{ .feature = .{ .exit_code = 0 } }; + + log.debug(":: My Sparse Slice {s}\n", .{sparce_slice}); + if (test_result.feature.error_context.?.err == null) { + test_result.feature.exit_code = 0; + } + return test_result; } //test facility functions (TODO: move another module later maybe?) fn createCommitOnTarget(alloc: Allocator, data: TestData) !void { @@ -225,24 +214,24 @@ fn createCommitOnTarget(alloc: Allocator, data: TestData) !void { defer alloc.free(rr_git_commit.stdout); defer alloc.free(rr_git_commit.stderr); } -fn parseGitShowRefResult(alloc: Allocator, stdout: []u8) IntegrationTestError!struct { []const u8, []const u8 } { +fn parseGitShowRefResult( + alloc: Allocator, + stdout: []u8, + feature_name: []const u8, + user_config: []const u8, +) IntegrationTestError![]const u8 { // Attention!: this function will not be responsible to free stdout, // it should be done in caller side - _ = alloc; - - // log.debug( - // "SparseFeatureTest::createFeature stderr:{s}\n", - // .{stdout}, - // ); + std.testing.log_level = .debug; const ref_result = std.mem.trim(u8, stdout, "\n\t \r"); - // log.debug( - // "::parseGitShowRefResult :{s}\n", - // .{ref_result}, - // ); - var ref_target: ?[]const u8 = null; - var sparse_slice: ?[]const u8 = null; + const expected_sparse_slice = std.fmt.allocPrint( + alloc, + "refs/heads/sparse/{s}/{s}/slice/", + .{ user_config, feature_name }, + ) catch return IntegrationTestError.SPARSE_FEATURE_NOT_FOUND; + defer alloc.free(expected_sparse_slice); if (ref_result.len != 0) { var split_ref_result = std.mem.tokenizeAny( u8, @@ -250,44 +239,12 @@ fn parseGitShowRefResult(alloc: Allocator, stdout: []u8) IntegrationTestError!st " \n", ); while (split_ref_result.next()) |iter| { - if (ref_target != null and sparse_slice != null) { - break; - } - if (std.mem.startsWith(u8, iter, "refs/heads/")) { - if (std.mem.containsAtLeast(u8, iter, 1, "sparse")) { - sparse_slice = iter; - } else { - ref_target = iter; - } + log.debug("ref iter {s}\n ", .{iter}); + if (std.mem.startsWith(u8, iter, expected_sparse_slice)) { + return iter; } } - // else { - // ref_target = null; - // sparse_slice = null; - // return IntegrationTestError.SPARSE_FEATURE_EMPTY_REF_ERROR; - // } - - return .{ ref_target.?, sparse_slice.? }; - } - if (ref_target == null) { - return IntegrationTestError.SPARSE_FEATURE_EMPTY_REF; } - if (sparse_slice == null) { - return IntegrationTestError.SPARSE_FEATURE_NOT_FOUND; - } - // split_ref_result.reset(); - // while (split_ref_result.index < split_ref_result.buffer.len) : (split_ref_result.index += 1) { - - // _ = split_ref_result.next().?; - // const ref_target = split_ref_result.next().?; - // _ = split_ref_result.next().?; - // const sparse_slice = split_ref_result.next().?; - // log.debug( - // "SparseFeatureTest::createFeature stderr: target:{s}\n slice: {s}\n", - // .{ ref_target, sparse_slice }, - // ); - // } - // } return IntegrationTestError.SPARSE_FEATURE_NOT_FOUND; } const sparse = @import("sparse"); From 9e4f2b856ae699129cef25faff56b065d0bda38d Mon Sep 17 00:00:00 2001 From: Talha Can Havadar Date: Sat, 20 Sep 2025 01:04:46 +0200 Subject: [PATCH 12/19] test: use exampleUser for testing --- test/integration.zig | 44 ++++-------------------------------- test/sparse_feature_test.zig | 38 ++++++++++++++++++++++--------- 2 files changed, 31 insertions(+), 51 deletions(-) diff --git a/test/integration.zig b/test/integration.zig index b1d158a..946cab6 100644 --- a/test/integration.zig +++ b/test/integration.zig @@ -4,6 +4,7 @@ const RunResult = std.process.Child.RunResult; const Allocator = std.mem.Allocator; const log = std.log.scoped(.integration); const build_options = @import("build_options"); + pub const IntegrationTestError = error{ TERM_EXIT_FAILED, UNEXPECTED_ERROR, @@ -11,6 +12,7 @@ pub const IntegrationTestError = error{ SPARSE_FEATURE_NOT_FOUND, SPARSE_FEATURE_TARGET_MISMATCH, }; + pub const IntegrationTestResult = union(enum) { feature: SparseFeatureTestResult, pub fn status(self: IntegrationTestResult) bool { @@ -46,6 +48,7 @@ pub const IntegrationTest = union(enum) { ), } } + pub fn run( self: IntegrationTest, alloc: Allocator, @@ -63,6 +66,7 @@ pub const IntegrationTest = union(enum) { } } }; + pub fn main() !void { var gpa = std.heap.GeneralPurposeAllocator(.{}){}; defer std.debug.assert(gpa.deinit() == .ok); @@ -71,46 +75,10 @@ pub fn main() !void { defer std.process.argsFree(allocator, args); std.testing.log_level = .debug; - // log.debug( - // "main:: args={s} build_options={s} output_dir={s}", - // .{ - // args, - // build_options.sparse_exe_path, - // build_options.output_dir, - // }, - // ); const repo_dir = try std.fs.path.join(allocator, &.{ build_options.output_dir, "sparse_test_repo" }); defer allocator.free(repo_dir); } -// Good Wheater -test "Create Sparse Feature to default target with only feature name" { - if (true) { - return error.SkipZigTest; - } - const test_allocator = std.testing.allocator; - const args = try std.process.argsAlloc(test_allocator); - defer std.process.argsFree(test_allocator, args); - - const integration: IntegrationTest = undefined; - const feature_integration = @field( - integration, - "feature", - ); - const data: SparseFeatureTestData = try feature_integration.setup( - test_allocator, - SparseFeatureTestData, - ); - defer data.free(test_allocator); - _ = feature_integration.run( - test_allocator, - SparseFeatureTestData, - data, - sparse_feature_test.createFeature, - ); - //try feature_integration.teardown(test_allocator, data); - try std.testing.expect(false); -} test "Create Sparse Feature with only feature name" { const test_allocator = std.testing.allocator; const args = try std.process.argsAlloc(test_allocator); @@ -145,10 +113,6 @@ test "Create Sparse Feature with only feature name" { try std.testing.expect(rr_feature_step.feature.exit_code == 0); } -test "Hello Integration2" { - try std.testing.expect(true); -} - const system = @import("system.zig"); const SparseFeatureTest = @import("sparse_feature_test.zig").SparseFeatureTest; const sparse_feature_test = @import("sparse_feature_test.zig"); diff --git a/test/sparse_feature_test.zig b/test/sparse_feature_test.zig index 84cb938..c34850b 100644 --- a/test/sparse_feature_test.zig +++ b/test/sparse_feature_test.zig @@ -3,6 +3,8 @@ const build_options = @import("build_options"); const log = std.log.scoped(.sparse_feature_test); const Allocator = std.mem.Allocator; const RunResult = std.process.Child.RunResult; +const TEST_SPARSE_USER_ID: []const u8 = "exampleUser"; + pub const TestData = struct { repo_dir: ?[]const u8 = null, feature_name: ?[]const u8 = null, @@ -57,21 +59,31 @@ pub const SparseFeatureTest = struct { try std.testing.expect(!std.mem.eql(u8, rr_temp_dir.stdout, "")); data.repo_dir = try alloc.dupe(u8, std.mem.trim(u8, rr_temp_dir.stdout, "\n\t \r")); - const rr_git_init = try system.git(.{ - .allocator = alloc, - .args = &.{ "init", "." }, - .cwd = data.repo_dir.?, - }); + { + const rr = try system.git(.{ + .allocator = alloc, + .args = &.{ "init", "." }, + .cwd = data.repo_dir.?, + }); + defer alloc.free(rr.stdout); + defer alloc.free(rr.stderr); + try std.testing.expect(rr.term.Exited == 0); + } + { + const rr = try system.git(.{ + .allocator = alloc, + .args = &.{ "config", "sparse.user.id", TEST_SPARSE_USER_ID }, + .cwd = data.repo_dir.?, + }); + defer alloc.free(rr.stdout); + defer alloc.free(rr.stderr); + } + log.debug( "sparse::feature::test:: repo_dir {s}", .{data.repo_dir.?}, ); - defer alloc.free(rr_git_init.stdout); - defer alloc.free(rr_git_init.stderr); - try std.testing.expect(rr_git_init.term.Exited == 0); - try std.testing.expect(std.mem.eql(u8, rr_git_init.stderr, "")); - try std.testing.expect(!std.mem.eql(u8, rr_git_init.stdout, "")); return @as(T, data); } @@ -111,6 +123,7 @@ pub const SparseFeatureTest = struct { return func(alloc, data); } }; + pub fn createFeatureStep(alloc: Allocator, data: TestData) IntegrationTestResult { std.testing.log_level = .debug; var test_result: IntegrationTestResult = .{ @@ -164,7 +177,7 @@ pub fn createFeatureStep(alloc: Allocator, data: TestData) IntegrationTestResult alloc, rr_git_show_ref.stdout, data.feature_name.?, - "bahanurenis@gmail.com", + TEST_SPARSE_USER_ID, ) catch |res| { switch (res) { IntegrationTestError.SPARSE_FEATURE_NOT_FOUND => test_result.feature.error_context.?.err = IntegrationTestError.SPARSE_FEATURE_NOT_FOUND, @@ -181,6 +194,7 @@ pub fn createFeatureStep(alloc: Allocator, data: TestData) IntegrationTestResult } return test_result; } + //test facility functions (TODO: move another module later maybe?) fn createCommitOnTarget(alloc: Allocator, data: TestData) !void { std.testing.log_level = .debug; @@ -214,6 +228,7 @@ fn createCommitOnTarget(alloc: Allocator, data: TestData) !void { defer alloc.free(rr_git_commit.stdout); defer alloc.free(rr_git_commit.stderr); } + fn parseGitShowRefResult( alloc: Allocator, stdout: []u8, @@ -247,6 +262,7 @@ fn parseGitShowRefResult( } return IntegrationTestError.SPARSE_FEATURE_NOT_FOUND; } + const sparse = @import("sparse"); const system = @import("system.zig"); const integration_test = @import("integration.zig"); From 0865b7dfc268012f74306abdab735b99f0401fcf Mon Sep 17 00:00:00 2001 From: Talha Can Havadar Date: Sat, 20 Sep 2025 01:06:00 +0200 Subject: [PATCH 13/19] fixup! test: use exampleUser for testing --- test/sparse_feature_test.zig | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/test/sparse_feature_test.zig b/test/sparse_feature_test.zig index c34850b..dadd60b 100644 --- a/test/sparse_feature_test.zig +++ b/test/sparse_feature_test.zig @@ -229,15 +229,14 @@ fn createCommitOnTarget(alloc: Allocator, data: TestData) !void { defer alloc.free(rr_git_commit.stderr); } +/// Attention!: this function will not be responsible to free stdout, +/// it should be done in caller side fn parseGitShowRefResult( alloc: Allocator, stdout: []u8, feature_name: []const u8, user_config: []const u8, ) IntegrationTestError![]const u8 { - // Attention!: this function will not be responsible to free stdout, - // it should be done in caller side - std.testing.log_level = .debug; const ref_result = std.mem.trim(u8, stdout, "\n\t \r"); From e6be6c55161c73520505e15c002ffbfa73ed11a1 Mon Sep 17 00:00:00 2001 From: Bahanur Enis Date: Fri, 14 Nov 2025 15:25:26 +0100 Subject: [PATCH 14/19] dropme: this needs to be fixed properly --- src/lib/Feature.zig | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lib/Feature.zig b/src/lib/Feature.zig index c40a02e..65360cf 100644 --- a/src/lib/Feature.zig +++ b/src/lib/Feature.zig @@ -368,14 +368,14 @@ test "asFeatureRefName" { const expectEqualStrings = std.testing.expectEqualStrings; const allocator = std.testing.allocator; { - const res = try asFeatureRefName(allocator, "refs/heads/sparse/talhaHavadar/test/slice/1"); + const res = try asFeatureRefName(allocator, "refs/heads/sparse/bahanurenis/test/slice/1"); defer allocator.free(res); - try expectEqualStrings("refs/heads/sparse/talhaHavadar/test", res); + try expectEqualStrings("refs/heads/sparse/bahanurenis/test", res); } { const res = try asFeatureRefName(allocator, "test"); defer allocator.free(res); - try expectEqualStrings("refs/heads/sparse/talhaHavadar/test", res); + try expectEqualStrings("refs/heads/sparse/bahanurenis/test", res); } } From cb30f7d9a4f337fbfdaaadff9fa07c52f0f81186 Mon Sep 17 00:00:00 2001 From: Bahanur Enis Date: Fri, 14 Nov 2025 15:21:57 +0100 Subject: [PATCH 15/19] dropme: this is a test commit --- test/integration.zig | 1 + 1 file changed, 1 insertion(+) diff --git a/test/integration.zig b/test/integration.zig index 946cab6..6b45fe6 100644 --- a/test/integration.zig +++ b/test/integration.zig @@ -113,6 +113,7 @@ test "Create Sparse Feature with only feature name" { try std.testing.expect(rr_feature_step.feature.exit_code == 0); } +// TODO: remove this comment please const system = @import("system.zig"); const SparseFeatureTest = @import("sparse_feature_test.zig").SparseFeatureTest; const sparse_feature_test = @import("sparse_feature_test.zig"); From 4a43d3651e3b30abeebbbfc851712140472a17f3 Mon Sep 17 00:00:00 2001 From: Bahanur Enis Date: Sun, 18 Jan 2026 17:52:28 +0100 Subject: [PATCH 16/19] migration: update the code for zig 0.15.2 Signed-off-by: Bahanur Enis --- build.zig | 52 ++++++++------ build.zig.zon | 7 +- flake.lock | 8 +-- flake.nix | 3 +- src/cli.zig | 46 +++++++++---- src/cli/command.zig | 1 + src/cli/feature_command.zig | 12 +++- src/cli/helpgen.zig | 49 ++++++++------ src/cli/slice_command.zig | 14 +++- src/cli/status_command.zig | 14 +++- src/cli/update_command.zig | 15 ++++- src/lib/Feature.zig | 10 +-- src/lib/slice.zig | 19 ++++-- src/lib/sparse.zig | 70 +++++++++++++------ src/lib/state.zig | 4 +- src/lib/system/Git.zig | 24 +++---- src/main.zig | 2 +- test/sparse_feature_test.zig | 2 +- test/system.zig | 39 +++++++---- vendored/apple-sdk/build.zig | 17 +++-- vendored/libgit2/ClarTestStep.zig | 42 ++++-------- vendored/libgit2/build.zig | 108 ++++++++++++++++++++---------- vendored/libgit2/build.zig.zon | 2 +- vendored/libgit2/chmod.zig | 22 ++++++ vendored/libgit2/clar_fix.zig | 67 ++++++++++++++++++ 25 files changed, 450 insertions(+), 199 deletions(-) create mode 100644 vendored/libgit2/chmod.zig create mode 100644 vendored/libgit2/clar_fix.zig diff --git a/build.zig b/build.zig index a5f528b..478a7b5 100644 --- a/build.zig +++ b/build.zig @@ -87,32 +87,46 @@ pub fn build(b: *std.Build) !void { var test_step = b.step("test", "Run unit tests"); test_step.dependOn(&run_lib_unit_tests.step); test_step.dependOn(&run_exe_unit_tests.step); - { - const helpgen_exe = b.addExecutable(.{ - .name = "helpgen", + + // Setup helpgen for generating help_strings module + const helpgen_exe = b.addExecutable(.{ + .name = "helpgen", + .root_module = b.createModule(.{ + .root_source_file = b.path("src/cli/helpgen.zig"), + .target = b.graph.host, + }), + }); + const help_run = b.addRunArtifact(helpgen_exe); + const help_strings_stdout = help_run.captureStdOut(); + // Workaround for Zig 0.15 regression: module files need .zig extension + // See: https://github.com/ziglang/zig/issues/24957 + const wf = b.addWriteFiles(); + const help_strings_output = wf.addCopyFile(help_strings_stdout, "help_strings.zig"); + exe_mod.addAnonymousImport( + "help_strings", + .{ + .root_source_file = help_strings_output, + }, + ); + var helpgen_step = b.step("helpgen", "Generate Help Text"); + helpgen_step.dependOn(&help_run.step); + + const integration_tests_step = step: { + const integration_tests = b.addExecutable(.{ + .name = "sparse-integration-tests", .root_module = b.createModule(.{ - .root_source_file = b.path("src/cli/helpgen.zig"), - .target = b.graph.host, + .root_source_file = b.path("test/integration.zig"), + .optimize = optimize, + .target = target, }), }); - const help_run = b.addRunArtifact(helpgen_exe); - const output = help_run.captureStdOut(); - exe.root_module.addAnonymousImport( + integration_tests.root_module.addImport("sparse", exe_mod); + integration_tests.root_module.addAnonymousImport( "help_strings", .{ - .root_source_file = output, + .root_source_file = help_strings_output, }, ); - } - - const integration_tests_step = step: { - const integration_tests = b.addExecutable(.{ - .name = "sparse-integration-tests", - .root_source_file = b.path("test/integration.zig"), - .optimize = optimize, - .target = target, - }); - integration_tests.root_module.addImport("sparse", exe_mod); const integration_unit_tests = b.addTest(.{ .root_module = integration_tests.root_module, diff --git a/build.zig.zon b/build.zig.zon index 7e924ee..3d72924 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -2,9 +2,12 @@ .name = .sparse, .version = "0.0.0", .fingerprint = 0x2883adb9d90cf982, // Changing this has security and trust implications. - .minimum_zig_version = "0.14.0", + .minimum_zig_version = "0.15.2", .dependencies = .{ - .libgit2 = .{ .path = "vendored/libgit2" }, + .libgit2 = .{ + .url = "git+https://github.com/allyourcodebase/libgit2#58dfd002d47a8c9fbd99d4a939cb343172590e1b", + .hash = "libgit2-1.9.0-uizqTQnZAADyPOmBklxzj_lrnfJoRLRDBbbTvi28SrZX", + }, .apple_sdk = .{ .path = "vendored/apple-sdk" }, }, diff --git a/flake.lock b/flake.lock index 485c4cf..2b2d7af 100644 --- a/flake.lock +++ b/flake.lock @@ -41,17 +41,17 @@ }, "zig-nixpkgs": { "locked": { - "lastModified": 1744502386, - "narHash": "sha256-QAd1L37eU7ktL2WeLLLTmI6P9moz9+a/ONO8qNBYJgM=", + "lastModified": 1767151656, + "narHash": "sha256-ujL2AoYBnJBN262HD95yer7QYUmYp5kFZGYbyCCKxq8=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "f6db44a8daa59c40ae41ba6e5823ec77fe0d2124", + "rev": "f665af0cdb70ed27e1bd8f9fdfecaf451260fc55", "type": "github" }, "original": { "owner": "NixOS", "repo": "nixpkgs", - "rev": "f6db44a8daa59c40ae41ba6e5823ec77fe0d2124", + "rev": "f665af0cdb70ed27e1bd8f9fdfecaf451260fc55", "type": "github" } } diff --git a/flake.nix b/flake.nix index 54c8ed5..20fa3ba 100644 --- a/flake.nix +++ b/flake.nix @@ -5,7 +5,7 @@ flake-utils.url = "github:numtide/flake-utils"; # 0.14.0 - zig-nixpkgs.url = "github:NixOS/nixpkgs/f6db44a8daa59c40ae41ba6e5823ec77fe0d2124"; + zig-nixpkgs.url = "github:NixOS/nixpkgs/f665af0cdb70ed27e1bd8f9fdfecaf451260fc55"; # 1.9.0 # libgit2-nixpkgs.url = "github:NixOS/nixpkgs/f6db44a8daa59c40ae41ba6e5823ec77fe0d2124"; }; @@ -115,6 +115,7 @@ devShells.default = zig-nixpkgs.mkShell { packages = [ zig-nixpkgs.zig + zig-nixpkgs.zls zig-nixpkgs.openssl ]; diff --git a/src/cli.zig b/src/cli.zig index 63174b5..505324b 100644 --- a/src/cli.zig +++ b/src/cli.zig @@ -11,9 +11,15 @@ fn getCommandDescription(command_name: []const u8) []const u8 { return "Unknown command"; } -fn showHelp() void { - const writer = std.io.getStdOut().writer(); - +fn showHelp() !void { + //const writer = std.fs.File.stdout().writer(stdout) + var buffer: [4096]u8 = undefined; + var stdout_writer = std.fs.File.stdout().writer(&buffer); + const writer = &stdout_writer.interface; + //TODO: add error log to flush + defer { + writer.flush() catch {}; + } writer.print("Sparse - A CLI tool for stacked pull request workflows\n\n", .{}) catch return; writer.print("USAGE:\n sparse [options]\n\n", .{}) catch return; writer.print("COMMANDS:\n", .{}) catch return; @@ -35,6 +41,13 @@ fn showHelp() void { } fn parse(args: [][:0]u8) !Command { + var buffer: [4096]u8 = undefined; + var stdout_writer = std.fs.File.stdout().writer(&buffer); + const writer = &stdout_writer.interface; + // TODO: add err log to defer + defer { + writer.flush() catch {}; + } const my_commands = @typeInfo(Command).@"union".fields; if (args.len < 2) { @@ -43,7 +56,7 @@ fn parse(args: [][:0]u8) !Command { // Check for global --help flag if (std.mem.eql(u8, args[1], "--help")) { - showHelp(); + try showHelp(); std.process.exit(0); } @@ -56,25 +69,32 @@ fn parse(args: [][:0]u8) !Command { } pub fn run(alloc: Allocator) !void { + var buffer: [4096]u8 = undefined; + var stdout_writer = std.fs.File.stdout().writer(&buffer); + var writer = &stdout_writer.interface; + //TODO: add error log to flush + defer { + writer.flush() catch {}; + } const args = try std.process.argsAlloc(alloc); defer std.process.argsFree(alloc, args); const command = parse(args) catch |err| switch (err) { CommandError.UnknownCommand => { - const stdout = std.io.getStdOut().writer(); + //const stdout = std.io.getStdOut().writer(); if (args.len >= 2) { - stdout.print("'{s}' is not a sparse command.\n\n", .{args[1]}) catch {}; + writer.print("'{s}' is not a sparse command.\n\n", .{args[1]}) catch {}; } else { - stdout.print("No command specified.\n\n", .{}) catch {}; + writer.print("No command specified.\n\n", .{}) catch {}; } - stdout.print("Available commands: ", .{}) catch {}; + writer.print("Available commands: ", .{}) catch {}; const my_commands = @typeInfo(Command).@"union".fields; inline for (my_commands, 0..) |c, i| { - if (i > 0) stdout.print(", ", .{}) catch {}; - stdout.print("{s}", .{c.name}) catch {}; + if (i > 0) writer.print(", ", .{}) catch {}; + writer.print("{s}", .{c.name}) catch {}; } - stdout.print("\n\nFor more help: sparse --help\n", .{}) catch {}; + writer.print("\n\nFor more help: sparse --help\n", .{}) catch {}; std.process.exit(1); }, else => return err, @@ -82,10 +102,10 @@ pub fn run(alloc: Allocator) !void { const return_code = try command.run(alloc); std.process.exit(return_code); } - +// TODO: add tests about writer test "parse a non existent command" { const expectEqual = std.testing.expectEqual; const args: [2][:0]const u8 = .{ "sparse", "boo" }; - const command = parse(@constCast(@ptrCast(&args))) catch |e| e; + const command = parse(@ptrCast(@constCast(&args))) catch |e| e; try expectEqual(CommandError.UnknownCommand, command); } diff --git a/src/cli/command.zig b/src/cli/command.zig index bb2404a..5058acb 100644 --- a/src/cli/command.zig +++ b/src/cli/command.zig @@ -1,5 +1,6 @@ const std = @import("std"); const log = std.log.scoped(.command); +//const FileWriter = std.Io.Writer; const Allocator = std.mem.Allocator; diff --git a/src/cli/feature_command.zig b/src/cli/feature_command.zig index a4f90b3..79874bb 100644 --- a/src/cli/feature_command.zig +++ b/src/cli/feature_command.zig @@ -26,7 +26,13 @@ const Params = struct { @"-h": *const fn () void = Options.help, pub fn help() void { - std.io.getStdOut().writer().print(help_strings.sparse_feature, .{}) catch return; + var buffer: [4096]u8 = .{0} ** 4096; + var stdout_writer = std.fs.File.stdout().writer(&buffer); + const stdout = &stdout_writer.interface; + defer { + stdout.flush() catch {}; + } + stdout.print(help_strings.sparse_feature, .{}) catch {}; } } = .{}, }; @@ -40,7 +46,9 @@ pub const FeatureCommand = struct { var params = Params{ .feature_name = undefined }; const args = try std.process.argsAlloc(alloc); defer std.process.argsFree(alloc, args); - log.debug("got cli arguments: {s}", .{args}); + for (args) |arg| { + log.debug("got cli arguments: {s}", .{arg}); + } const cli_positionals = command.parseOptions( @TypeOf(params._options), diff --git a/src/cli/helpgen.zig b/src/cli/helpgen.zig index 0d6b476..6863240 100644 --- a/src/cli/helpgen.zig +++ b/src/cli/helpgen.zig @@ -1,4 +1,5 @@ const std = @import("std"); +const Writer = std.Io.Writer; const Tag = std.zig.Token.Tag; const Ast = std.zig.Ast; const Allocator = std.mem.Allocator; @@ -6,21 +7,29 @@ const Allocator = std.mem.Allocator; pub fn main() !void { var gpa = std.heap.GeneralPurposeAllocator(.{}){}; const alloc = gpa.allocator(); - const stdout = std.io.getStdOut().writer(); + var buffer: [4096]u8 = .{0} ** 4096; + var stdout_writer = std.fs.File.stdout().writer(&buffer); + const stdout = &stdout_writer.interface; + defer { + stdout.flush() catch {}; + } try genCommands(alloc, stdout); } -fn genCommands(alloc: std.mem.Allocator, writer: anytype) !void { +fn genCommands(alloc: std.mem.Allocator, writer: *Writer) !void { try extractFileIntoHelp(alloc, writer, "feature_command.zig", "sparse_feature"); try extractFileIntoHelp(alloc, writer, "slice_command.zig", "sparse_slice"); try extractFileIntoHelp(alloc, writer, "update_command.zig", "sparse_update"); try extractFileIntoHelp(alloc, writer, "status_command.zig", "sparse_status"); } -fn extractFileIntoHelp(alloc: Allocator, writer: anytype, comptime zig_file: []const u8, comptime const_name: []const u8) !void { +fn extractFileIntoHelp(alloc: Allocator, writer: *Writer, comptime zig_file: []const u8, comptime const_name: []const u8) !void { var ast = try Ast.parse(alloc, @embedFile(zig_file), .zig); defer ast.deinit(alloc); + defer { + writer.flush() catch {}; + } const tokens = ast.tokens.items(.tag); const maybe_params_struct = findToken(ast, tokens, isParams); if (maybe_params_struct) |params_struct| { @@ -65,15 +74,15 @@ fn isParams(tt: []Tag, current_index: usize, a: Ast) bool { /// First token must be .l_brace fn extractNextStruct(alloc: Allocator, ast: Ast, start_idx: usize) ![]const u8 { - var stack = std.ArrayList(Tag).init(alloc); - defer stack.deinit(); - var lines = std.ArrayList([]const u8).init(alloc); - defer lines.deinit(); + var stack = std.ArrayList(Tag).empty; + defer stack.deinit(alloc); + var lines = std.ArrayList([]const u8).empty; + defer lines.deinit(alloc); const tokens = ast.tokens.items(.tag); for (tokens[start_idx..], start_idx..) |token, i| { //std.debug.print("Found {} name: {s}\n", .{ token, ast.tokenSlice(@intCast(i)) }); - if (token == .l_brace) _ = try stack.append(token); + if (token == .l_brace) _ = try stack.append(alloc, token); if (token == .r_brace) _ = stack.pop(); if (stack.items.len == 0) break; @@ -81,15 +90,15 @@ fn extractNextStruct(alloc: Allocator, ast: Ast, start_idx: usize) ![]const u8 { if (token != .identifier) continue; if (tokens[i - 2] != .doc_comment and tokens[i - 1] != .doc_comment) continue; const extracted = try extractDocComments(alloc, ast, @intCast(i), tokens); - try lines.append(extracted); + try lines.append(alloc, extracted); } - var buffer = std.ArrayList(u8).init(alloc); - defer buffer.deinit(); + var buffer = std.ArrayList(u8).empty; + defer buffer.deinit(alloc); for (lines.items) |line| { - try buffer.writer().print("{s}", .{line}); + try buffer.print(alloc, "{s}", .{line}); } - return buffer.toOwnedSlice(); + return buffer.toOwnedSlice(alloc); } fn extractDocComments( @@ -107,24 +116,22 @@ fn extractDocComments( } else unreachable; // Go through and build up the lines. - var lines = std.ArrayList([]const u8).init(alloc); - defer lines.deinit(); + var lines = std.ArrayList([]const u8).empty; + defer lines.deinit(alloc); for (start_idx..index + 1) |i| { const token = tokens[i]; if (token != .doc_comment) break; - try lines.append(ast.tokenSlice(@intCast(i))[3..]); + try lines.append(alloc, ast.tokenSlice(@intCast(i))[3..]); } // Convert the lines to a multiline string. - var buffer = std.ArrayList(u8).init(alloc); - const writer = buffer.writer(); + var buffer = std.ArrayList(u8).empty; const prefix = findCommonPrefix(lines); for (lines.items) |line| { - try writer.writeAll(line[@min(prefix, line.len)..]); - try writer.writeAll("\n"); + try buffer.print(alloc, "{s}\n", .{line[@min(prefix, line.len)..]}); } - return buffer.toOwnedSlice(); + return buffer.toOwnedSlice(alloc); } fn findCommonPrefix(lines: std.ArrayList([]const u8)) usize { diff --git a/src/cli/slice_command.zig b/src/cli/slice_command.zig index 8763d20..23b4c85 100644 --- a/src/cli/slice_command.zig +++ b/src/cli/slice_command.zig @@ -20,7 +20,14 @@ const Params = struct { @"-h": *const fn () void = Options.help, pub fn help() void { - std.io.getStdOut().writer().print(help_strings.sparse_slice, .{}) catch return; + var buffer: [4096]u8 = undefined; + var stdout_writer = std.fs.File.stdout().writer(&buffer); + const stdout = &stdout_writer.interface; + // TODO: add err log + defer { + stdout.flush() catch {}; + } + stdout.print(help_strings.sparse_slice, .{}) catch {}; } } = .{}, }; @@ -38,7 +45,10 @@ pub const SliceCommand = struct { var params = Params{ .slice_name = undefined }; const args = try std.process.argsAlloc(alloc); defer std.process.argsFree(alloc, args); - log.debug("run:: args: {s}", .{args}); + for (args) |arg| { + log.debug("got cli arguments: {s}", .{arg}); + } + //log.debug("run:: args: {any}", .{args}); const cli_positionals = command.parseOptions( @TypeOf(params._options), diff --git a/src/cli/status_command.zig b/src/cli/status_command.zig index 357af6f..ae570c3 100644 --- a/src/cli/status_command.zig +++ b/src/cli/status_command.zig @@ -16,7 +16,14 @@ const Params = struct { @"-h": *const fn () void = Options.help, pub fn help() void { - std.io.getStdOut().writer().print(help_strings.sparse_status, .{}) catch return; + //std.io.getStdOut().writer().print(help_strings.sparse_status, .{}) catch return; + var buffer: [4096]u8 = undefined; + var stdout_writer = std.fs.File.stdout().writer(&buffer); + const stdout = &stdout_writer.interface; + defer { + stdout.flush() catch {}; + } + stdout.print(help_strings.sparse_status, .{}) catch {}; } } = .{}, }; @@ -30,7 +37,10 @@ pub const StatusCommand = struct { var params = Params{}; const args = try std.process.argsAlloc(alloc); defer std.process.argsFree(alloc, args); - log.debug("run:: args: {s}", .{args}); + //log.debug("run:: args: {any}", .{args}); + for (args) |arg| { + log.debug("got cli arguments: {s}", .{arg}); + } const cli_positionals = command.parseOptions( @TypeOf(params._options), diff --git a/src/cli/update_command.zig b/src/cli/update_command.zig index ede5aa4..929d902 100644 --- a/src/cli/update_command.zig +++ b/src/cli/update_command.zig @@ -23,7 +23,15 @@ const Params = struct { @"-h": *const fn () void = Options.help, pub fn help() void { - std.io.getStdOut().writer().print(help_strings.sparse_update, .{}) catch return; + //std.io.getStdOut().writer().print(help_strings.sparse_update, .{}) catch return; + var buffer: [4096]u8 = undefined; + var stdout_writer = std.fs.File.stdout().writer(&buffer); + const stdout = &stdout_writer.interface; + stdout.print(help_strings.sparse_slice, .{}) catch {}; + defer { + stdout.flush() catch {}; + } + //try stdout.flush(); } } = .{}, }; @@ -37,7 +45,10 @@ pub const UpdateCommand = struct { var params = Params{}; const args = try std.process.argsAlloc(alloc); defer std.process.argsFree(alloc, args); - log.debug("run:: args: {s}", .{args}); + //log.debug("run:: args: {any}", .{args}); + for (args) |arg| { + log.debug("got cli arguments: {s}", .{arg}); + } const cli_positionals = command.parseOptions( @TypeOf(params._options), diff --git a/src/lib/Feature.zig b/src/lib/Feature.zig index 65360cf..ce7b3f0 100644 --- a/src/lib/Feature.zig +++ b/src/lib/Feature.zig @@ -16,16 +16,17 @@ pub fn new(o: struct { slices: ?[]Slice = null, }) !Feature { const dup = try o.alloc.dupe(u8, o.name); + log.debug("helloo/n", .{}); var f = Feature{ .name = dup, .ref_name = if (o.ref_name) |r| try o.alloc.dupe(u8, r) else try asFeatureRefName(o.alloc, dup), }; if (o.slices) |s| { if (f.slices) |*fs| { - try fs.appendSlice(s); + try fs.appendSlice(o.alloc, s); } else { f.slices = try std.ArrayList(Slice).initCapacity(o.alloc, s.len); - try f.slices.?.appendSlice(s); + try f.slices.?.appendSlice(o.alloc, s); } const orphan_count, const forked_count = try Slice.constructLinks( @@ -98,9 +99,10 @@ pub fn target(self: Feature, alloc: Allocator) !?GitReference { pub fn free(self: *Feature, allocator: Allocator) void { allocator.free(self.ref_name); - if (self.slices) |s| { + //TODO: look this line didn't understand + if (self.slices) |*s| { for (s.items) |*i| i.free(allocator); - s.deinit(); + s.deinit(allocator); } allocator.free(self.name); } diff --git a/src/lib/slice.zig b/src/lib/slice.zig index ca77044..d61a4b6 100644 --- a/src/lib/slice.zig +++ b/src/lib/slice.zig @@ -1,6 +1,6 @@ const std = @import("std"); const log = std.log.scoped(.slice); -const ArrayListUnmanaged = std.ArrayListUnmanaged; +const ArrayList = std.ArrayList; const Allocator = std.mem.Allocator; const StringHashMap = std.StringHashMap; @@ -8,7 +8,7 @@ pub const Slice = struct { repo: GitRepository, ref: GitReference, target: ?*Slice = null, - children: ArrayListUnmanaged(*Slice) = ArrayListUnmanaged(*Slice).empty, + children: ArrayList(*Slice) = .empty, /// cache to hold already calculated isMerged calls _is_merge_into_map: StringHashMap(bool), @@ -110,7 +110,7 @@ pub const Slice = struct { alloc: Allocator, slice_pool: []Slice, }) ![]*Slice { - var leaves = ArrayListUnmanaged(*Slice).empty; + var leaves = ArrayList(*Slice).empty; defer leaves.deinit(o.alloc); for (o.slice_pool) |*s| { @@ -121,9 +121,14 @@ pub const Slice = struct { return try leaves.toOwnedSlice(o.alloc); } - pub fn printSliceGraph(writer: anytype, slice_pool: []Slice) !void { + pub fn printSliceGraph(writer: *std.Io.Writer, slice_pool: []Slice) !void { var gpa = std.heap.GeneralPurposeAllocator(.{}){}; defer std.debug.assert(gpa.deinit() == .ok); + defer { + writer.flush() catch |e| { + log.err("Writer failed with {t}", .{e}); + }; + } const allocator = gpa.allocator(); // find leaf nodes @@ -136,13 +141,13 @@ pub const Slice = struct { defer allocator.free(leaves); for (leaves, 0..) |l, leaf_index| { - var slice_chain = std.ArrayList(*Slice).init(allocator); - defer slice_chain.deinit(); + var slice_chain: std.ArrayList(*Slice) = .empty; //std.ArrayList(*Slice).init(allocator); + defer slice_chain.deinit(allocator); // Build the chain from leaf to root var current_slice: ?*Slice = l; while (current_slice != null) { - try slice_chain.append(current_slice.?); + try slice_chain.append(allocator, current_slice.?); current_slice = current_slice.?.target; } diff --git a/src/lib/sparse.zig b/src/lib/sparse.zig index 4afa8ce..9a1ab4b 100644 --- a/src/lib/sparse.zig +++ b/src/lib/sparse.zig @@ -222,16 +222,21 @@ pub fn update(o: struct { // and updated to reflect new commit IDs after the update try LibGit.init(); defer LibGit.shutdown() catch @panic("Oops: couldn't shutdown libgit2, something weird is cooking..."); + var buffer: [4096]u8 = undefined; + var stdout_writer = std.fs.File.stdout().writer(&buffer); + const stdout = &stdout_writer.interface; + defer { + stdout.flush() catch {}; + } const repo = try LibGit.GitRepository.open(); defer repo.free(); - var state = try State.Update.load(o.alloc, repo); defer state.free(o.alloc); // Check if a rebase is in progress const is_rebase_in_progress = try Git.isRebaseInProgress(o.alloc, repo); if (is_rebase_in_progress) { log.err("update:: rebase in progress", .{}); - const stdout = std.io.getStdOut().writer(); + //const stdout = std.io.getStdOut().writer(); try stdout.print("⚠️ A rebase is currently in progress.\n", .{}); try stdout.print("Please resolve any conflicts and run:\n", .{}); try stdout.print(" git rebase --continue\n", .{}); @@ -250,14 +255,14 @@ pub fn update(o: struct { } else { if (!o.@"continue") { log.err("update:: not able to detect current branch", .{}); - const stdout = std.io.getStdOut().writer(); + //const stdout = std.io.getStdOut().writer(); try stdout.print("❌ Unable to detect current sparse feature.\n", .{}); try stdout.print("Make sure you're on a sparse feature branch before running update.\n", .{}); return Error.UNABLE_TO_DETECT_CURRENT_FEATURE; } if (!state.inProgress()) { try state.delete(); - const stdout = std.io.getStdOut().writer(); + // const stdout = std.io.getStdOut().writer(); try stdout.print("❌ No update in progress to continue.\n", .{}); try stdout.print("Run 'sparse update' without --continue to start a new update.\n", .{}); return Error.NO_UPDATE_IN_PROGRESS; @@ -312,7 +317,12 @@ pub fn status(o: struct { try LibGit.init(); defer LibGit.shutdown() catch @panic("Oops: couldn't shutdown libgit2, something weird is cooking..."); - const stdout = std.io.getStdOut().writer(); + var buffer: [4096]u8 = undefined; + var stdout_writer = std.fs.File.stdout().writer(&buffer); + const stdout = &stdout_writer.interface; + defer { + stdout.flush() catch {}; + } log.debug("status:: checking for active feature", .{}); // Check if we are currently on an active feature @@ -321,6 +331,7 @@ pub fn status(o: struct { if (active_feature == null) { try stdout.print("\n🚫 \x1b[33mNo active sparse feature detected\x1b[0m\n", .{}); try stdout.print(" Use `sparse feature ` to create or switch to a feature\n\n", .{}); + //try stdout.flush(); return; } @@ -433,6 +444,7 @@ pub fn status(o: struct { try stdout.print("│ 📊 Total slices: \x1b[1m{d}\x1b[0m\n", .{slices.len}); try stdout.print("│\n", .{}); try stdout.print("└─ \x1b[2mℹ Note: Cannot check merge status without a target reference\x1b[0m\n\n", .{}); + try stdout.flush(); return; } @@ -546,9 +558,15 @@ fn updateGoodWeather(o: struct { }) !void { const target = try o.feature.target(o.alloc); o.state.free(o.alloc); + var buffer: [4096]u8 = undefined; + var stdout_writer = std.fs.File.stdout().writer(&buffer); + const stdout = &stdout_writer.interface; + defer { + stdout.flush() catch {}; + } // Print update information - const stdout = std.io.getStdOut().writer(); + //const stdout = std.io.getStdOut().writer(); try stdout.print("Updating feature '{s}' from target '{s}'\n", .{ o.feature.name, target.?.name() }); try fetchTarget(.{ .alloc = o.alloc, .target = target.? }); @@ -611,6 +629,7 @@ fn updateGoodWeather(o: struct { .args = &.{ "--oneline", "--decorate", "--graph", log_range }, }) catch |err| { try stdout.print("Unable to show commit log: {}\n", .{err}); + try stdout.flush(); return err; }; defer o.alloc.free(log_result.stdout); @@ -637,8 +656,8 @@ fn updateGoodWeather(o: struct { // push all unmerged slices in remotes ss = leaves[0]; - var pushed_slices = std.ArrayList(*Slice).init(o.alloc); - defer pushed_slices.deinit(); + var pushed_slices: std.ArrayList(*Slice) = .empty; //std.ArrayList(*Slice).init(o.alloc); + defer pushed_slices.deinit(o.alloc); while (ss != null) : (ss = ss.?.target) { const is_merged = try ss.?.isMerged(.{ @@ -648,7 +667,7 @@ fn updateGoodWeather(o: struct { if (!is_merged) { try ss.?.activate(o.alloc); try ss.?.push(o.alloc); - try pushed_slices.append(ss.?); + try pushed_slices.append(o.alloc, ss.?); } else break; } @@ -687,7 +706,14 @@ fn updateGoodWeather(o: struct { } fn handleUpdateInProgress(alloc: std.mem.Allocator, state: *State.Update) !void { - const stdout = std.io.getStdOut().writer(); + // const stdout = std.io.getStdOut().writer(); + + var buffer: [4096]u8 = undefined; + var stdout_writer = std.fs.File.stdout().writer(&buffer); + const stdout = &stdout_writer.interface; + defer { + stdout.flush() catch {}; + } try stdout.print("🔄 Continuing update for feature '{s}'...\n", .{state._data.feature.?}); var feature_updated = try Feature.findFeatureByName(.{ @@ -709,6 +735,7 @@ fn handleUpdateInProgress(alloc: std.mem.Allocator, state: *State.Update) !void try updateGoodWeather(.{ .alloc = alloc, .feature = f, .state = state }); } else { try stdout.print("❌ Unable to find feature to continue update\n", .{}); + try stdout.flush(); return Error.UNABLE_TO_DETECT_CURRENT_FEATURE; } return Error.UNABLE_TO_DETECT_CURRENT_FEATURE; @@ -728,8 +755,8 @@ fn handleUpdateInProgress(alloc: std.mem.Allocator, state: *State.Update) !void const upstream = try target.upstream(ss.?.repo); defer upstream.free(); - var pushed_slices = std.ArrayList(*Slice).init(alloc); - defer pushed_slices.deinit(); + var pushed_slices: std.ArrayList(*Slice) = .empty; //std.ArrayList(*Slice).init(alloc); + defer pushed_slices.deinit(alloc); while (ss != null) : (ss = ss.?.target) { const is_merged = try ss.?.isMerged(.{ @@ -739,7 +766,7 @@ fn handleUpdateInProgress(alloc: std.mem.Allocator, state: *State.Update) !void if (!is_merged) { try ss.?.activate(alloc); try ss.?.push(alloc); - try pushed_slices.append(ss.?); + try pushed_slices.append(alloc, ss.?); } else break; } @@ -759,11 +786,13 @@ fn handleUpdateInProgress(alloc: std.mem.Allocator, state: *State.Update) !void } else { try stdout.print("❌ Unable to find feature to continue update\n", .{}); } + try stdout.flush(); }, .Complete => { log.debug("update:: failed when complete command is called before, no need to continue updating", .{}); try stdout.print("✓ Update already completed - nothing to continue\n", .{}); try state.delete(); + try stdout.flush(); }, } } @@ -828,7 +857,7 @@ fn jump(o: struct { } // Helper function to display git notes information for slices -fn displayGitNotesInfo(alloc: std.mem.Allocator, writer: anytype, slices: []Slice) !void { +fn displayGitNotesInfo(alloc: std.mem.Allocator, stdout: *std.Io.Writer, slices: []Slice) !void { // Track if any notes were found var notes_found = false; @@ -851,7 +880,7 @@ fn displayGitNotesInfo(alloc: std.mem.Allocator, writer: anytype, slices: []Slic notes_with_parents += 1; const slice_name = slice_item.name(); - try writer.print("│ 📝 \x1b[1m{s}:\x1b[0m parent → \x1b[32m{s}\x1b[0m\n", .{ slice_name, parent_info }); + try stdout.print("│ 📝 \x1b[1m{s}:\x1b[0m parent → \x1b[32m{s}\x1b[0m\n", .{ slice_name, parent_info }); } else { // No note exists for this slice notes_without_parents += 1; @@ -864,19 +893,20 @@ fn displayGitNotesInfo(alloc: std.mem.Allocator, writer: anytype, slices: []Slic // Show summary of notes status if (notes_found) { - try writer.print("│ ✅ Slices with parent notes: \x1b[1;32m{d}\x1b[0m\n", .{notes_with_parents}); + try stdout.print("│ ✅ Slices with parent notes: \x1b[1;32m{d}\x1b[0m\n", .{notes_with_parents}); if (notes_without_parents > 0) { - try writer.print("│ ⚠️ Slices without parent notes: \x1b[1;33m{d}\x1b[0m\n", .{notes_without_parents}); + try stdout.print("│ ⚠️ Slices without parent notes: \x1b[1;33m{d}\x1b[0m\n", .{notes_without_parents}); } } else { - try writer.print("│ 📄 No git notes found for slice relationships\n", .{}); - try writer.print("│ 💡 \x1b[2mTip: Use git notes to preserve relationships after rebasing\x1b[0m\n", .{}); + try stdout.print("│ 📄 No git notes found for slice relationships\n", .{}); + try stdout.print("│ 💡 \x1b[2mTip: Use git notes to preserve relationships after rebasing\x1b[0m\n", .{}); } // Show instructions for team collaboration if notes exist if (notes_found) { - try writer.print("│ \x1b[2m💡 Team tip: Push notes with 'git push origin refs/notes/commits'\x1b[0m\n", .{}); + try stdout.print("│ \x1b[2m💡 Team tip: Push notes with 'git push origin refs/notes/commits'\x1b[0m\n", .{}); } + try stdout.flush(); } test { diff --git a/src/lib/state.zig b/src/lib/state.zig index 5c79af6..d130de2 100644 --- a/src/lib/state.zig +++ b/src/lib/state.zig @@ -37,7 +37,9 @@ pub const Update = struct { const file = try state_dir.createFile(file_name, .{}); defer file.close(); - try std.zon.stringify.serialize(self._data, .{}, file.writer()); + var buffer: [4096]u8 = .{0} ** 4096; + var fw = file.writer(&buffer); + try std.zon.stringify.serialize(self._data, .{}, &fw.interface); } pub fn delete(self: Update) !void { diff --git a/src/lib/system/Git.zig b/src/lib/system/Git.zig index bef8281..9d5c2ea 100644 --- a/src/lib/system/Git.zig +++ b/src/lib/system/Git.zig @@ -19,7 +19,7 @@ pub const Ref = struct { oname: []const u8, rname: []const u8, }) !Ref { - logger.debug("Ref::new:: objectname:{s} refname:{s}", .{ o.oname, o.rname }); + logger.debug("Ref::new:: objectname:{any} refname:{any}", .{ o.oname, o.rname }); // duping strings since we got them from RunResult which we free before returning const oname = try o.alloc.dupe(u8, o.oname); @@ -68,7 +68,7 @@ pub fn isRebaseInProgress(alloc: std.mem.Allocator, repo: GitRepository) !bool { &.{ git_dir, "rebase-merge" }, ); defer alloc.free(rebase_merge_path); - logger.debug("isRebaseInProgress:: rebase_merge_path: {s}", .{rebase_merge_path}); + logger.debug("isRebaseInProgress:: rebase_merge_path: {any}", .{rebase_merge_path}); // We wont open the file afterwards so it is ok to check the existence of the directory std.fs.accessAbsolute(rebase_merge_path, .{ .mode = .read_only }) catch |err| { @@ -81,7 +81,7 @@ pub fn isRebaseInProgress(alloc: std.mem.Allocator, repo: GitRepository) !bool { &.{ git_dir, "rebase-apply" }, ); defer alloc.free(rebase_apply_path); - logger.debug("isRebaseInProgress:: rebase_apply_path: {s}", .{rebase_apply_path}); + logger.debug("isRebaseInProgress:: rebase_apply_path: {any}", .{rebase_apply_path}); std.fs.accessAbsolute( rebase_apply_path, @@ -231,7 +231,7 @@ fn @"for-each-ref"(o: struct { allocator: std.mem.Allocator, args: []const []const u8 = &.{}, }) !RunResult { - logger.debug("for-each-ref:: args:{s}", .{o.args}); + logger.debug("for-each-ref:: args:{any}", .{o.args}); const command: []const []const u8 = &.{ "git", "for-each-ref", @@ -251,7 +251,7 @@ fn @"show-ref"(options: struct { allocator: std.mem.Allocator, args: []const []const u8 = &.{}, }) !RunResult { - logger.debug("show-ref:: args:{s}", .{options.args}); + logger.debug("show-ref:: args:{any}", .{options.args}); const command: []const []const u8 = &.{ "git", "show-ref", @@ -271,7 +271,7 @@ pub fn @"rev-parse"(o: struct { allocator: std.mem.Allocator, args: []const []const u8, }) !RunResult { - logger.debug("rev-parse:: args:{s}", .{o.args}); + logger.debug("rev-parse:: args:{any}", .{o.args}); const command: []const []const u8 = &.{ "git", "rev-parse", @@ -289,7 +289,7 @@ pub fn @"switch"(o: struct { allocator: std.mem.Allocator, args: []const []const u8, }) !RunResult { - logger.debug("switch:: args:{s}", .{o.args}); + logger.debug("switch:: args:{any}", .{o.args}); const command: []const []const u8 = &.{ "git", "switch", @@ -307,7 +307,7 @@ pub fn log(o: struct { allocator: std.mem.Allocator, args: []const []const u8, }) !RunResult { - logger.debug("log:: args:{s}", .{o.args}); + logger.debug("log:: args:{any}", .{o.args}); const command: []const []const u8 = &.{ "git", "log", @@ -325,7 +325,7 @@ pub fn rebase(o: struct { allocator: std.mem.Allocator, args: []const []const u8, }) !RunResult { - logger.debug("rebase:: args:{s}", .{o.args}); + logger.debug("rebase:: args:{any}", .{o.args}); const command: []const []const u8 = &.{ "git", "rebase", @@ -343,7 +343,7 @@ pub fn push(o: struct { allocator: std.mem.Allocator, args: []const []const u8, }) !RunResult { - logger.debug("push:: args:{s}", .{o.args}); + logger.debug("push:: args:{any}", .{o.args}); const command: []const []const u8 = &.{ "git", "push", @@ -361,7 +361,7 @@ pub fn @"merge-base"(o: struct { allocator: std.mem.Allocator, args: []const []const u8, }) !RunResult { - logger.debug("merge-base:: args:{s}", .{o.args}); + logger.debug("merge-base:: args:{any}", .{o.args}); const command: []const []const u8 = &.{ "git", "merge-base", @@ -379,7 +379,7 @@ pub fn fetch(o: struct { allocator: std.mem.Allocator, args: []const []const u8, }) !RunResult { - logger.debug("fetch:: args:{s}", .{o.args}); + logger.debug("fetch:: args:{any}", .{o.args}); const command: []const []const u8 = &.{ "git", "fetch", diff --git a/src/main.zig b/src/main.zig index a5729c7..59a0818 100644 --- a/src/main.zig +++ b/src/main.zig @@ -1,7 +1,7 @@ const std = @import("std"); // Global log level based on environment variable -var runtime_log_level: ?std.log.Level = null; // Default to no logs +var runtime_log_level: ?std.log.Level = .debug; // Default to no logs pub const std_options: std.Options = .{ .logFn = logFn, diff --git a/test/sparse_feature_test.zig b/test/sparse_feature_test.zig index dadd60b..8eb1dcb 100644 --- a/test/sparse_feature_test.zig +++ b/test/sparse_feature_test.zig @@ -188,7 +188,7 @@ pub fn createFeatureStep(alloc: Allocator, data: TestData) IntegrationTestResult return test_result; }; - log.debug(":: My Sparse Slice {s}\n", .{sparce_slice}); + log.debug(":: My Sparse Slice {any}\n", .{sparce_slice}); if (test_result.feature.error_context.?.err == null) { test_result.feature.exit_code = 0; } diff --git a/test/system.zig b/test/system.zig index 5b485bb..c2d531c 100644 --- a/test/system.zig +++ b/test/system.zig @@ -18,13 +18,19 @@ pub fn git(o: struct { }; const argv = try combine([]const u8, o.allocator, command, o.args); defer o.allocator.free(argv); - log.debug( - "git:: args:{s} cwd:{s}", - .{ - argv, + for (argv) |arg| { + log.debug("git:: args:{s} cwd:{s}", .{ + arg, if (o.cwd) |c| c else "null", - }, - ); + }); + } + // log.debug( + // "git:: args:{any} cwd:{s}", + // .{ + // argv, + // if (o.cwd) |c| c else "null", + // }, + // ); return try std.process.Child.run(.{ .allocator = o.allocator, @@ -38,13 +44,19 @@ pub fn system(o: struct { args: []const []const u8, cwd: ?[]const u8 = null, }) !RunResult { - log.debug( - "system:: args:{s} cwd:{s}", - .{ - o.args, + for (o.args) |arg| { + log.debug("system:: args:{s} cwd:{s}", .{ + arg, if (o.cwd) |c| c else "null", - }, - ); + }); + } + // log.debug( + // "system:: args:{any} cwd:{s}", + // .{ + // o.args, + // if (o.cwd) |c| c else "null", + // }, + // ); return try std.process.Child.run(.{ .allocator = o.allocator, @@ -59,7 +71,8 @@ pub fn combine( arr1: []const T, arr2: []const T, ) ![]T { - var arr_list: std.ArrayListUnmanaged(T) = try std.ArrayListUnmanaged(T).initCapacity(allocator, arr1.len + arr2.len); + var arr_list: std.ArrayList(T) = try std.ArrayList(T).initCapacity(allocator, arr1.len + arr2.len); + for (arr1) |item| { try arr_list.append(allocator, item); } diff --git a/vendored/apple-sdk/build.zig b/vendored/apple-sdk/build.zig index 18a6c09..96ad8fd 100644 --- a/vendored/apple-sdk/build.zig +++ b/vendored/apple-sdk/build.zig @@ -1,4 +1,5 @@ const std = @import("std"); +const AllocatingWriter = @import("std").Io.Writer.Allocating; pub fn build(b: *std.Build) !void { const target = b.standardTargetOptions(.{}); @@ -46,20 +47,22 @@ pub fn addPaths( // find the SDK path. const libc = try std.zig.LibCInstallation.findNative(.{ .allocator = b.allocator, - .target = step.rootModuleTarget(), + .target = &step.rootModuleTarget(), .verbose = false, }); // Render the file compatible with the `--libc` Zig flag. - var list: std.ArrayList(u8) = .init(b.allocator); - defer list.deinit(); - try libc.render(list.writer()); - + var buffer: std.ArrayList(u8) = .empty; + //defer buffer.deinit(b.allocator); + var writer_allocating: AllocatingWriter = AllocatingWriter.fromArrayList(b.allocator, &buffer); + defer writer_allocating.deinit(); + try libc.render(&writer_allocating.writer); // Create a temporary file to store the libc path because // `--libc` expects a file path. const wf = b.addWriteFiles(); - const path = wf.add("libc.txt", list.items); - + const path = wf.add("libc.txt", writer_allocating.written()); + //try writer.flush(); + //list.deinit(); // Determine our framework path. Zig has a bug where it doesn't // parse this from the libc txt file for `-framework` flags: // https://github.com/ziglang/zig/issues/24024 diff --git a/vendored/libgit2/ClarTestStep.zig b/vendored/libgit2/ClarTestStep.zig index 3679cf5..91cc013 100644 --- a/vendored/libgit2/ClarTestStep.zig +++ b/vendored/libgit2/ClarTestStep.zig @@ -41,15 +41,15 @@ fn make(step: *Step, options: Step.MakeOptions) !void { var man = b.graph.cache.obtain(); defer man.deinit(); - var argv_list: std.ArrayList([]const u8) = .init(arena); + var argv_list: std.ArrayList([]const u8) = .empty; { const file_path = clar.runner.installed_path orelse clar.runner.generated_bin.?.path.?; - try argv_list.append(file_path); + try argv_list.append(arena, file_path); _ = try man.addFile(file_path, null); } - try argv_list.append("-t"); // force TAP output + try argv_list.append(arena, "-t"); // force TAP output for (clar.args.items) |arg| { - try argv_list.append(arg); + try argv_list.append(arena, arg); man.hash.addBytes(arg); } @@ -67,33 +67,16 @@ fn make(step: *Step, options: Step.MakeOptions) !void { try child.spawn(); - var poller = std.io.poll( - b.allocator, - enum { stdout }, - .{ .stdout = child.stdout.? }, - ); - defer poller.deinit(); - - const fifo = poller.fifo(.stdout); - const r = fifo.reader(); - - var buf: std.BoundedArray(u8, 1024) = .{}; - const w = buf.writer(); + var reader_buf: [1024]u8 = undefined; + var file_reader = child.stdout.?.readerStreaming(&reader_buf); + const r = &file_reader.interface; var parser: TapParser = .default; var node: ?std.Progress.Node = null; defer if (node) |n| n.end(); - while (true) { - r.streamUntilDelimiter(w, '\n', null) catch |err| switch (err) { - error.EndOfStream => if (try poller.poll()) continue else break, - else => return err, - }; - - const line = buf.constSlice(); - defer buf.resize(0) catch unreachable; - - switch (try parser.parseLine(arena, line)) { + while (r.takeDelimiter('\n')) |line| { + switch (try parser.parseLine(arena, line orelse break)) { .start_suite => |suite| { if (node) |n| n.end(); node = options.progress_node.start(suite, 0); @@ -111,6 +94,9 @@ fn make(step: *Step, options: Step.MakeOptions) !void { }, .feed_line => {}, } + } else |err| switch (err) { + error.ReadFailed => return file_reader.err.?, + error.StreamTooLong => return error.TapLineTooLong, } const term = try child.wait(); @@ -131,8 +117,8 @@ const TapParser = struct { feed_line, const Failure = struct { - description: std.ArrayListUnmanaged(u8), - reasons: std.ArrayListUnmanaged([]const u8), + description: std.ArrayList(u8), + reasons: std.ArrayList([]const u8), }; }; diff --git a/vendored/libgit2/build.zig b/vendored/libgit2/build.zig index 4634252..4c4f3f8 100644 --- a/vendored/libgit2/build.zig +++ b/vendored/libgit2/build.zig @@ -445,34 +445,19 @@ pub fn build(b: *std.Build) !void { const test_step = b.step("test", "Run core unit tests (requires python)"); { - if (builtin.os.tag != .windows) { - // Fix the test fixture file permissions. This is necessary because Zig does - // not respect the execute permission on arbitrary files it extracts from dependencies. - // Since we need those files to have the execute permission set for tests to - // run successfully, we need to patch them before we bake them into the - // test executable. While modifying the global cache is hacky, it wont break - // hashes for the same reason above. -blurrycat 3/31/25 - for ([_]std.Build.LazyPath{ - libgit_root.path(b, "tests/resources/filemodes/exec_on"), - libgit_root.path(b, "tests/resources/filemodes/exec_off2on_staged"), - libgit_root.path(b, "tests/resources/filemodes/exec_off2on_workdir"), - libgit_root.path(b, "tests/resources/filemodes/exec_on_untracked"), - }) |lazy| { - const path = lazy.getPath2(b, null); - const file = try std.fs.cwd().openFile(path, .{ - .mode = .read_write, - }); - defer file.close(); - try file.setPermissions(.{ .inner = .{ .mode = 0o755 } }); - } - } - const gen_cmd = b.addSystemCommand(&.{"python3"}); gen_cmd.addFileArg(libgit_src.path("tests/clar/generate.py")); const clar_suite = gen_cmd.addPrefixedOutputDirectoryArg("-o", "clar_suite"); gen_cmd.addArgs(&.{ "-f", "-xonline", "-xstress", "-xperf" }); gen_cmd.addDirectoryArg(libgit_src.path("tests/libgit2")); + // Copy the clar source so it can be modified below. + const clar_src = b.addWriteFiles().addCopyDirectory( + libgit_src.path("tests/clar"), + "clar_src", + .{}, + ); + const runner = b.addExecutable(.{ .name = "libgit2_tests", .root_module = b.createModule(.{ @@ -482,7 +467,7 @@ pub fn build(b: *std.Build) !void { }), }); runner.addIncludePath(clar_suite); - runner.addIncludePath(libgit_src.path("tests/clar")); + runner.addIncludePath(clar_src); runner.addIncludePath(libgit_src.path("tests/libgit2")); runner.addConfigHeader(features); @@ -496,22 +481,73 @@ pub fn build(b: *std.Build) !void { runner.linkLibrary(lib); + const runner_flags = &.{ + "-DCLAR_FIXTURE_PATH", // See clar_fix step below + "-DCLAR_TMPDIR=\"libgit2_tests\"", + "-DCLAR_WIN32_LONGPATHS", + "-DGIT_DEPRECATE_HARD", + }; runner.addCSourceFiles(.{ - .root = libgit_src.path("tests/"), - .files = &(clar_sources ++ libgit2_test_sources), - .flags = &.{ - b.fmt( - "-DCLAR_FIXTURE_PATH=\"{s}\"", - // clar expects the fixture path to only have posix seperators or else some tests will break on windows - .{try getNormalizedPath(libgit_src.path("tests/resources"), b, &runner.step)}, - ), - "-DCLAR_TMPDIR=\"libgit2_tests\"", - "-DCLAR_WIN32_LONGPATHS", - "-D_FILE_OFFSET_BITS=64", - "-DGIT_DEPRECATE_HARD", - }, + .root = libgit_src.path("tests/libgit2/"), + .files = &libgit2_test_sources, + .flags = runner_flags, + }); + runner.addCSourceFiles(.{ + .root = clar_src, + .files = &clar_sources, + .flags = runner_flags, }); + const resources_dir = switch (@import("builtin").os.tag) { + .windows => libgit_src.path("tests/resources/"), + else => dir: { + // Fix the test fixture file permissions. This is necessary because Zig does + // not respect the execute permission on arbitrary files it extracts from dependencies. + // Since we need those files to have the execute permission set for tests to + // run successfully, we need to patch them before we bake them into the + // test executable. + const resources_dir = b.addWriteFiles().addCopyDirectory( + libgit_root.path(b, "tests/resources/"), + "test_resources", + .{}, + ); + const chmod = b.addExecutable(.{ + .name = "chmod", + .root_module = b.createModule(.{ + .root_source_file = b.path("chmod.zig"), + .target = b.graph.host, + }), + }); + const run_chmod = b.addRunArtifact(chmod); + run_chmod.addFileArg(resources_dir.path(b, "filemodes/exec_on")); + run_chmod.addFileArg(resources_dir.path(b, "filemodes/exec_off2on_staged")); + run_chmod.addFileArg(resources_dir.path(b, "filemodes/exec_off2on_workdir")); + run_chmod.addFileArg(resources_dir.path(b, "filemodes/exec_on_untracked")); + runner.step.dependOn(&run_chmod.step); + + break :dir resources_dir; + }, + }; + { + // Clar hardcodes the path to resources_dir via the `-DCLAR_FIXTURE_PATH="..."` flag. + // This path isn't known at configure-time, so we have to create a dedicated build step. + // This step replaces *reads* of the `CLAR_FIXTURE_PATH` macro in a local-cache copy of the source code + // (see clar_src). Thankfully the macro is only read by `tests/clar/clar/fixture.h` once. + const clar_fix = b.addExecutable(.{ + .name = "clar_fix", + .root_module = b.createModule(.{ + .root_source_file = b.path("clar_fix.zig"), + .target = b.graph.host, + }), + }); + + const run_fix = b.addRunArtifact(clar_fix); + // run_fix.has_side_effects = true; // @Todo is this necessary? What are the rules for cache invalidation with Run steps? + run_fix.addFileArg(clar_src.path(b, "clar/fixtures.h")); + run_fix.addDirectoryArg(resources_dir); + runner.step.dependOn(&run_fix.step); + } + const TestHelper = struct { b: *std.Build, top_level_step: *std.Build.Step, diff --git a/vendored/libgit2/build.zig.zon b/vendored/libgit2/build.zig.zon index 4821e82..9090aa6 100644 --- a/vendored/libgit2/build.zig.zon +++ b/vendored/libgit2/build.zig.zon @@ -1,7 +1,7 @@ .{ .name = .libgit2, .version = "1.9.0", - .minimum_zig_version = "0.14.0", + .minimum_zig_version = "0.15.2", .fingerprint = 0x7f0051374dea2cba, .dependencies = .{ .libgit2 = .{ diff --git a/vendored/libgit2/chmod.zig b/vendored/libgit2/chmod.zig new file mode 100644 index 0000000..7a07480 --- /dev/null +++ b/vendored/libgit2/chmod.zig @@ -0,0 +1,22 @@ +//! Usage: chmod [file-path...] +//! Accepts a list of file paths as input and changes their permission bits to `0o755`. +//! POSIX only. + +pub fn main() !void { + var args = std.process.args(); + _ = args.skip(); + while (args.next()) |path| { + const file = std.fs.cwd().openFile(path, .{ .mode = .read_write }) catch |err| + fatal("unable to open file '{s}': {t}", .{ path, err }); + defer file.close(); + file.setPermissions(.{ .inner = .{ .mode = 0o755 } }) catch |err| + fatal("unable to set permissions on file '{s}': {t}", .{ path, err }); + } +} + +fn fatal(comptime fmt: []const u8, args: anytype) noreturn { + std.log.err(fmt, args); + std.process.exit(1); +} + +const std = @import("std"); diff --git a/vendored/libgit2/clar_fix.zig b/vendored/libgit2/clar_fix.zig new file mode 100644 index 0000000..8126e67 --- /dev/null +++ b/vendored/libgit2/clar_fix.zig @@ -0,0 +1,67 @@ +//! Usage: clar_fix +//! Replaces *reads* of the CLAR_FIXTURE_PATH macro definition in `src-file` with +//! the absolute path of `fixtures-dir`. `#ifdef`s are not affected. + +const std = @import("std"); + +const fixture_var_name = "CLAR_FIXTURE_PATH"; + +pub fn main() !void { + var arena_inst: std.heap.ArenaAllocator = .init(std.heap.page_allocator); + defer arena_inst.deinit(); + const arena = arena_inst.allocator(); + + var args = try std.process.argsWithAllocator(arena); + _ = args.skip(); + + const clar_fixture_h = args.next() orelse fatal("expected path to 'clar/fixtures.h' file", .{}); + const fixture_path: []const u8 = blk: { + const path_arg = args.next() orelse fatal("expected path to test resources directory", .{}); + + var cleaned_path: std.ArrayList(u8) = try .initCapacity(arena, std.fs.max_path_bytes + 2); + cleaned_path.appendAssumeCapacity('"'); // add string quotes + const abs_path = try std.fs.cwd().realpath(path_arg, cleaned_path.unusedCapacitySlice()); + cleaned_path.items.len += abs_path.len; + cleaned_path.appendAssumeCapacity('"'); + + // clar expects the fixture path to only have posix seperators or else some tests will break + for (cleaned_path.items) |*c| { + if (c.* == '\\') c.* = '/'; + } + break :blk cleaned_path.items; + }; + + const file = try std.fs.cwd().openFile(clar_fixture_h, .{ .mode = .read_write }); + defer file.close(); + + var buf: [1024]u8 = undefined; + var src: std.ArrayList(u8) = src: { + var file_reader = file.reader(&buf); + + const file_size = try file_reader.getSize(); + const to_add = fixture_path.len -| fixture_var_name.len; + var src: std.Io.Writer.Allocating = try .initCapacity(arena, file_size + to_add); + + _ = try file_reader.interface.streamRemaining(&src.writer); + break :src src.toArrayList(); + }; + + const i = std.mem.indexOf( + u8, + src.items, + "return fixture_path(CLAR_FIXTURE_PATH, fixture_name);", + ) orelse return; + + const start = i + "return fixture_path(".len; + src.replaceRangeAssumeCapacity(start, fixture_var_name.len, fixture_path); + + try file.seekTo(0); + var writer = file.writer(&buf); // buf is safe to reuse since file_reader is out of scope + try writer.interface.writeAll(src.items); + try writer.interface.flush(); +} + +fn fatal(comptime fmt: []const u8, args: anytype) noreturn { + std.log.err(fmt, args); + std.process.exit(1); +} From 8acaf302455ed885d1ed96decd7525a3f1a31530 Mon Sep 17 00:00:00 2001 From: Bahanur Enis Date: Mon, 19 Jan 2026 12:44:44 +0100 Subject: [PATCH 17/19] test: fix integration tests fail Signed-off-by: Bahanur Enis --- src/lib/Feature.zig | 1 - src/lib/sparse.zig | 4 +-- src/main.zig | 2 +- test/integration.zig | 14 ++++++++- test/sparse_feature_test.zig | 57 ++++++++++++++++++++---------------- 5 files changed, 47 insertions(+), 31 deletions(-) diff --git a/src/lib/Feature.zig b/src/lib/Feature.zig index ce7b3f0..616e1c4 100644 --- a/src/lib/Feature.zig +++ b/src/lib/Feature.zig @@ -16,7 +16,6 @@ pub fn new(o: struct { slices: ?[]Slice = null, }) !Feature { const dup = try o.alloc.dupe(u8, o.name); - log.debug("helloo/n", .{}); var f = Feature{ .name = dup, .ref_name = if (o.ref_name) |r| try o.alloc.dupe(u8, r) else try asFeatureRefName(o.alloc, dup), diff --git a/src/lib/sparse.zig b/src/lib/sparse.zig index 9a1ab4b..e5da203 100644 --- a/src/lib/sparse.zig +++ b/src/lib/sparse.zig @@ -30,7 +30,7 @@ pub fn feature( slice_name: ?[]const u8, target: []const u8, ) !void { - var gpa = std.heap.GeneralPurposeAllocator(.{}){}; + var gpa: std.heap.GeneralPurposeAllocator(.{}) = .init; defer std.debug.assert(gpa.deinit() == .ok); const allocator = gpa.allocator(); try LibGit.init(); @@ -44,7 +44,7 @@ pub fn feature( const _slice = if (slice_name) |s| s else constants.LAST_SLICE_NAME_POINTER; - // once sparse branchinde olup olmadigimizi kontrol edelim + // Check if we are at sparse slice branch // git show-ref --branches --head # butun branchleri ve suan ki HEAD i gormemizi // sagliyor var maybe_active_feature = try Feature.activeFeature(.{ diff --git a/src/main.zig b/src/main.zig index 59a0818..0d10225 100644 --- a/src/main.zig +++ b/src/main.zig @@ -25,7 +25,7 @@ pub fn logFn( } pub fn main() !void { - var gpa = std.heap.GeneralPurposeAllocator(.{}){}; + var gpa: std.heap.GeneralPurposeAllocator(.{}) = .init; defer std.debug.assert(gpa.deinit() == .ok); const allocator = gpa.allocator(); diff --git a/test/integration.zig b/test/integration.zig index 6b45fe6..9587015 100644 --- a/test/integration.zig +++ b/test/integration.zig @@ -68,7 +68,7 @@ pub const IntegrationTest = union(enum) { }; pub fn main() !void { - var gpa = std.heap.GeneralPurposeAllocator(.{}){}; + var gpa: std.heap.GeneralPurposeAllocator(.{}) = .init; defer std.debug.assert(gpa.deinit() == .ok); const allocator = gpa.allocator(); const args = try std.process.argsAlloc(allocator); @@ -76,12 +76,16 @@ pub fn main() !void { std.testing.log_level = .debug; const repo_dir = try std.fs.path.join(allocator, &.{ build_options.output_dir, "sparse_test_repo" }); + log.debug("test::main:: repo dir {s}", .{repo_dir}); defer allocator.free(repo_dir); } test "Create Sparse Feature with only feature name" { const test_allocator = std.testing.allocator; const args = try std.process.argsAlloc(test_allocator); + for (args) |arg| { + log.debug("benis:::Create Sparse Feature with only name:: arg - {s}", .{arg}); + } defer std.process.argsFree(test_allocator, args); const integration: IntegrationTest = undefined; @@ -96,12 +100,20 @@ test "Create Sparse Feature with only feature name" { defer data.free(test_allocator); // set a feature name data.feature_name = "hellofeature"; + log.debug( + "test::data feature_name: {s}, repodir: {s} ,", + .{ + data.feature_name.?, + data.repo_dir.?, + }, + ); const rr_feature_step = feature_integration.run( test_allocator, SparseFeatureTestData, data, sparse_feature_test.createFeatureStep, ); + if (!rr_feature_step.feature.status()) { log.err("Test Failed with exit_code {d} {any}", .{ rr_feature_step.feature.exit_code, diff --git a/test/sparse_feature_test.zig b/test/sparse_feature_test.zig index 8eb1dcb..a3e364f 100644 --- a/test/sparse_feature_test.zig +++ b/test/sparse_feature_test.zig @@ -46,10 +46,7 @@ pub const SparseFeatureTest = struct { std.testing.log_level = .debug; const rr_temp_dir = try system.system(.{ .allocator = alloc, - .args = &.{ - "mktemp", - "-d", - }, + .args = &.{ "mktemp", "-d", "-p", ".zig-cache/tmp" }, }); defer alloc.free(rr_temp_dir.stdout); defer alloc.free(rr_temp_dir.stderr); @@ -93,24 +90,25 @@ pub const SparseFeatureTest = struct { data: TestData, ) !void { _ = self; + _ = alloc; std.testing.log_level = .debug; log.info("repo_dir {s}\n", .{data.repo_dir.?}); - const rr_temp_dir = try system.system(.{ - .allocator = alloc, - .args = &.{ - "rm", - "-r", - data.repo_dir.?, - }, - }); - log.info("stdout {s}\n", .{rr_temp_dir.stdout}); - defer alloc.free(rr_temp_dir.stdout); - defer alloc.free(rr_temp_dir.stderr); - - try std.testing.expect(rr_temp_dir.term.Exited == 0); - try std.testing.expect(std.mem.eql(u8, rr_temp_dir.stderr, "")); - try std.testing.expect(std.mem.eql(u8, rr_temp_dir.stdout, "")); + // const rr_temp_dir = try system.system(.{ + // .allocator = alloc, + // .args = &.{ + // "rm", + // "-r", + // data.repo_dir.?, + // }, + // }); + // log.info("stdout {s}\n", .{rr_temp_dir.stdout}); + // defer alloc.free(rr_temp_dir.stdout); + // defer alloc.free(rr_temp_dir.stderr); + // + // try std.testing.expect(rr_temp_dir.term.Exited == 0); + // try std.testing.expect(std.mem.eql(u8, rr_temp_dir.stderr, "")); + // try std.testing.expect(std.mem.eql(u8, rr_temp_dir.stdout, "")); } pub fn run( self: SparseFeatureTest, @@ -135,20 +133,28 @@ pub fn createFeatureStep(alloc: Allocator, data: TestData) IntegrationTestResult }, }, }; + // Resolve sparse exe path to absolute path (needed because test runs in temp dir) + const sparse_exe_abs = std.fs.cwd().realpathAlloc(alloc, build_options.sparse_exe_path) catch { + test_result.feature.error_context.?.err = IntegrationTestError.TERM_EXIT_FAILED; + return test_result; + }; + defer alloc.free(sparse_exe_abs); + createCommitOnTarget(alloc, data) catch { test_result.feature.error_context.?.err = IntegrationTestError.TERM_EXIT_FAILED; return test_result; }; - // run sparse feature [feature_name] --to = null const rr_sparse_feature = system.system(.{ .allocator = alloc, .args = &.{ - build_options.sparse_exe_path, + sparse_exe_abs, "feature", data.feature_name.?, }, .cwd = data.repo_dir.?, - }) catch { + }) catch |e| { + log.debug("createFeatureStep::: sparse exe path:{s} ", .{sparse_exe_abs}); + log.debug("createFeatureStep::: error:{any} ", .{e}); test_result.feature.error_context.?.err = IntegrationTestError.TERM_EXIT_FAILED; return test_result; }; @@ -166,13 +172,13 @@ pub fn createFeatureStep(alloc: Allocator, data: TestData) IntegrationTestResult return test_result; }; log.debug( - "sparse::feature::test:: git show ref stderr:{s}\n", - .{rr_git_show_ref.stdout}, + "sparse::feature::test:: git show ref stdout:{s}, stderr: {s}\n", + .{ rr_git_show_ref.stdout, rr_git_show_ref.stderr }, ); defer alloc.free(rr_git_show_ref.stdout); defer alloc.free(rr_git_show_ref.stderr); - // Parsing git-show-ref + //Parsing git-show-ref const sparce_slice = parseGitShowRefResult( alloc, rr_git_show_ref.stdout, @@ -188,7 +194,6 @@ pub fn createFeatureStep(alloc: Allocator, data: TestData) IntegrationTestResult return test_result; }; - log.debug(":: My Sparse Slice {any}\n", .{sparce_slice}); if (test_result.feature.error_context.?.err == null) { test_result.feature.exit_code = 0; } From 3fb635f66ce57915539606197c7a5ea4971e6d67 Mon Sep 17 00:00:00 2001 From: Bahanur Enis Date: Mon, 19 Jan 2026 16:30:53 +0100 Subject: [PATCH 18/19] test: update integration tests Signed-off-by: Bahanur Enis --- test/integration.zig | 2 +- test/sparse_feature_test.zig | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/integration.zig b/test/integration.zig index 9587015..72abdee 100644 --- a/test/integration.zig +++ b/test/integration.zig @@ -75,6 +75,7 @@ pub fn main() !void { defer std.process.argsFree(allocator, args); std.testing.log_level = .debug; + // maybe we should use it this approach in build.zig? const repo_dir = try std.fs.path.join(allocator, &.{ build_options.output_dir, "sparse_test_repo" }); log.debug("test::main:: repo dir {s}", .{repo_dir}); defer allocator.free(repo_dir); @@ -118,7 +119,6 @@ test "Create Sparse Feature with only feature name" { log.err("Test Failed with exit_code {d} {any}", .{ rr_feature_step.feature.exit_code, rr_feature_step.feature.error_context.?.err, - //rr_feature_step.feature.error_context.?.err_msg.?, }); } try feature_integration.teardown(test_allocator, data); diff --git a/test/sparse_feature_test.zig b/test/sparse_feature_test.zig index a3e364f..ef2bb76 100644 --- a/test/sparse_feature_test.zig +++ b/test/sparse_feature_test.zig @@ -179,7 +179,7 @@ pub fn createFeatureStep(alloc: Allocator, data: TestData) IntegrationTestResult defer alloc.free(rr_git_show_ref.stderr); //Parsing git-show-ref - const sparce_slice = parseGitShowRefResult( + _ = parseGitShowRefResult( alloc, rr_git_show_ref.stdout, data.feature_name.?, From ff203fd6c21a20d52622979a5b1e2c960ab37599 Mon Sep 17 00:00:00 2001 From: Bahanur Enis Date: Mon, 19 Jan 2026 17:12:45 +0100 Subject: [PATCH 19/19] refactor: delete stdout flush Signed-off-by: Bahanur Enis --- src/lib/sparse.zig | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/lib/sparse.zig b/src/lib/sparse.zig index e5da203..9cee193 100644 --- a/src/lib/sparse.zig +++ b/src/lib/sparse.zig @@ -444,7 +444,7 @@ pub fn status(o: struct { try stdout.print("│ 📊 Total slices: \x1b[1m{d}\x1b[0m\n", .{slices.len}); try stdout.print("│\n", .{}); try stdout.print("└─ \x1b[2mℹ Note: Cannot check merge status without a target reference\x1b[0m\n\n", .{}); - try stdout.flush(); + //try stdout.flush(); return; } @@ -629,7 +629,7 @@ fn updateGoodWeather(o: struct { .args = &.{ "--oneline", "--decorate", "--graph", log_range }, }) catch |err| { try stdout.print("Unable to show commit log: {}\n", .{err}); - try stdout.flush(); + //try stdout.flush(); return err; }; defer o.alloc.free(log_result.stdout); @@ -735,7 +735,7 @@ fn handleUpdateInProgress(alloc: std.mem.Allocator, state: *State.Update) !void try updateGoodWeather(.{ .alloc = alloc, .feature = f, .state = state }); } else { try stdout.print("❌ Unable to find feature to continue update\n", .{}); - try stdout.flush(); + //try stdout.flush(); return Error.UNABLE_TO_DETECT_CURRENT_FEATURE; } return Error.UNABLE_TO_DETECT_CURRENT_FEATURE; @@ -786,13 +786,13 @@ fn handleUpdateInProgress(alloc: std.mem.Allocator, state: *State.Update) !void } else { try stdout.print("❌ Unable to find feature to continue update\n", .{}); } - try stdout.flush(); + //try stdout.flush(); }, .Complete => { log.debug("update:: failed when complete command is called before, no need to continue updating", .{}); try stdout.print("✓ Update already completed - nothing to continue\n", .{}); try state.delete(); - try stdout.flush(); + //try stdout.flush(); }, } } @@ -906,7 +906,7 @@ fn displayGitNotesInfo(alloc: std.mem.Allocator, stdout: *std.Io.Writer, slices: if (notes_found) { try stdout.print("│ \x1b[2m💡 Team tip: Push notes with 'git push origin refs/notes/commits'\x1b[0m\n", .{}); } - try stdout.flush(); + //try stdout.flush(); } test {