Backlog/lib/zargs/tests/test_registry.zig

497 lines
16 KiB
Zig

const std = @import("std");
const RegistryModule = @import("ArgumentRegistry");
const ArgumentRegistry = RegistryModule.ArgumentRegistry;
const metadata = @import("metadata");
const ArgumentTypeModule = @import("ArgumentType");
const ArgumentType = ArgumentTypeModule.ArgumentType;
const ParsedValue = ArgumentTypeModule.ParsedValue;
test "ArgumentRegistry: init and deinit" {
var registry = ArgumentRegistry.init(std.testing.allocator);
defer registry.deinit();
// Registry should be initialized with empty maps
try std.testing.expectEqual(@as(usize, 0), registry.arguments.count());
try std.testing.expectEqual(@as(usize, 0), registry.modules_by_arg.count());
try std.testing.expectEqual(@as(usize, 0), registry.registered_types.count());
try std.testing.expectEqual(@as(usize, 0), registry.parsed_values.count());
}
test "ArgumentRegistry: deinit cleans up memory" {
var registry = ArgumentRegistry.init(std.testing.allocator);
// Add some data
try registry.registered_types.put("TestType", {});
var list = std.ArrayListUnmanaged([]const u8){};
try list.append(std.testing.allocator, "module1");
try registry.modules_by_arg.put("test-arg", list);
// This should not leak
registry.deinit();
}
test "ArgumentRegistry: isTypeRegistered" {
var registry = ArgumentRegistry.init(std.testing.allocator);
defer registry.deinit();
const TestStruct = struct { value: u32 };
const OtherStruct = struct { other: bool };
try std.testing.expect(!registry.isTypeRegistered(TestStruct));
try std.testing.expect(!registry.isTypeRegistered(OtherStruct));
}
test "ArgumentRegistry: markTypeRegistered" {
var registry = ArgumentRegistry.init(std.testing.allocator);
defer registry.deinit();
const TestStruct = struct { value: u32 };
try std.testing.expect(!registry.isTypeRegistered(TestStruct));
try registry.markTypeRegistered(TestStruct);
try std.testing.expect(registry.isTypeRegistered(TestStruct));
}
test "ArgumentRegistry: markTypeRegistered multiple types" {
var registry = ArgumentRegistry.init(std.testing.allocator);
defer registry.deinit();
const TestStruct1 = struct { value: u32 };
const TestStruct2 = struct { other: bool };
try registry.markTypeRegistered(TestStruct1);
try registry.markTypeRegistered(TestStruct2);
try std.testing.expect(registry.isTypeRegistered(TestStruct1));
try std.testing.expect(registry.isTypeRegistered(TestStruct2));
}
test "ArgumentRegistry: markTypeRegistered idempotent" {
var registry = ArgumentRegistry.init(std.testing.allocator);
defer registry.deinit();
const TestStruct = struct { value: u32 };
try registry.markTypeRegistered(TestStruct);
try registry.markTypeRegistered(TestStruct); // Should not error
try std.testing.expect(registry.isTypeRegistered(TestStruct));
}
test "ArgumentRegistry: isHelpRequested default false" {
var registry = ArgumentRegistry.init(std.testing.allocator);
defer registry.deinit();
try std.testing.expectEqual(false, registry.isHelpRequested());
}
test "ArgumentRegistry: isHelpRequested can be set" {
var registry = ArgumentRegistry.init(std.testing.allocator);
defer registry.deinit();
registry.help_requested = true;
try std.testing.expectEqual(true, registry.isHelpRequested());
}
test "ArgumentRegistry: getArgument with empty registry" {
var registry = ArgumentRegistry.init(std.testing.allocator);
defer registry.deinit();
try std.testing.expectEqual(@as(?*const metadata.ArgumentMetadata, null), registry.getArgument("verbose"));
}
test "ArgumentRegistry: getArgument after insertion" {
var registry = ArgumentRegistry.init(std.testing.allocator);
defer registry.deinit();
const arg_meta = metadata.ArgumentMetadata{
.field_name = "verbose",
.arg_name = "verbose",
.arg_type = .bool,
.help = "Verbose output",
};
try registry.arguments.put("verbose", arg_meta);
const found = registry.getArgument("verbose");
try std.testing.expect(found != null);
try std.testing.expectEqualStrings("verbose", found.?.field_name);
try std.testing.expectEqualStrings("Verbose output", found.?.help);
}
test "ArgumentRegistry: getModulesForArg empty" {
var registry = ArgumentRegistry.init(std.testing.allocator);
defer registry.deinit();
try std.testing.expectEqual(@as(?std.ArrayListUnmanaged([]const u8), null), registry.getModulesForArg("test"));
}
test "ArgumentRegistry: getModulesForArg with modules" {
var registry = ArgumentRegistry.init(std.testing.allocator);
defer registry.deinit();
var list = std.ArrayListUnmanaged([]const u8){};
try list.append(std.testing.allocator, "module1");
try list.append(std.testing.allocator, "module2");
try registry.modules_by_arg.put("verbose", list);
const found = registry.getModulesForArg("verbose");
try std.testing.expect(found != null);
try std.testing.expectEqual(@as(usize, 2), found.?.items.len);
}
test "ArgumentRegistry: getParsedValue empty" {
var registry = ArgumentRegistry.init(std.testing.allocator);
defer registry.deinit();
try std.testing.expectEqual(@as(?ParsedValue, null), registry.getParsedValue("verbose"));
}
test "ArgumentRegistry: storeParsedValue and retrieve" {
var registry = ArgumentRegistry.init(std.testing.allocator);
defer registry.deinit();
const value = ParsedValue{ .bool = true };
try registry.storeParsedValue("verbose", value);
const found = registry.getParsedValue("verbose");
try std.testing.expect(found != null);
try std.testing.expectEqual(true, found.?.bool);
}
test "ArgumentRegistry: storeParsedValue multiple values" {
var registry = ArgumentRegistry.init(std.testing.allocator);
defer registry.deinit();
try registry.storeParsedValue("verbose", .{ .bool = true });
try registry.storeParsedValue("count", .{ .u32 = 42 });
const verbose = registry.getParsedValue("verbose");
const count = registry.getParsedValue("count");
try std.testing.expect(verbose != null);
try std.testing.expect(count != null);
try std.testing.expectEqual(true, verbose.?.bool);
try std.testing.expectEqual(@as(u32, 42), count.?.u32);
}
test "ArgumentRegistry: storeParsedValue overwrites" {
var registry = ArgumentRegistry.init(std.testing.allocator);
defer registry.deinit();
try registry.storeParsedValue("count", .{ .u32 = 10 });
try registry.storeParsedValue("count", .{ .u32 = 20 });
const found = registry.getParsedValue("count");
try std.testing.expectEqual(@as(u32, 20), found.?.u32);
}
test "ArgumentRegistry: deinit frees parsed string values" {
var registry = ArgumentRegistry.init(std.testing.allocator);
const str = try std.testing.allocator.dupe(u8, "test string");
const value = ParsedValue{ .string = str };
try registry.storeParsedValue("name", value);
// deinit should free the string
registry.deinit();
}
test "ArgumentRegistry: deinit frees parsed enum values" {
var registry = ArgumentRegistry.init(std.testing.allocator);
const name = try std.testing.allocator.dupe(u8, "debug");
const value = ParsedValue{
.enum_type = .{
.name = name,
.value = 0,
},
};
try registry.storeParsedValue("log-level", value);
// deinit should free the enum name
registry.deinit();
}
test "ArgumentRegistry: multiple operations" {
var registry = ArgumentRegistry.init(std.testing.allocator);
defer registry.deinit();
const TestStruct = struct { verbose: bool, count: u32 };
// Mark type as registered
try registry.markTypeRegistered(TestStruct);
try std.testing.expect(registry.isTypeRegistered(TestStruct));
// Store some metadata
const arg_meta = metadata.ArgumentMetadata{
.field_name = "verbose",
.arg_name = "verbose",
.arg_type = .bool,
};
try registry.arguments.put("verbose", arg_meta);
// Store a module list
var list = std.ArrayListUnmanaged([]const u8){};
try list.append(std.testing.allocator, "TestModule");
try registry.modules_by_arg.put("verbose", list);
// Store a parsed value
try registry.storeParsedValue("verbose", .{ .bool = true });
// Verify everything
try std.testing.expect(registry.getArgument("verbose") != null);
try std.testing.expect(registry.getModulesForArg("verbose") != null);
try std.testing.expect(registry.getParsedValue("verbose") != null);
}
// ============================================================================
// Registration Tests
// ============================================================================
test "ArgumentRegistry: registerMetadata simple struct" {
var registry = ArgumentRegistry.init(std.testing.allocator);
defer registry.deinit();
const TestStruct = struct {
verbose: bool,
count: u32,
};
try registry.registerMetadata(TestStruct, "TestModule");
// Should have registered both arguments
try std.testing.expect(registry.hasArgument("verbose"));
try std.testing.expect(registry.hasArgument("count"));
try std.testing.expectEqual(@as(usize, 2), registry.argumentCount());
}
test "ArgumentRegistry: registerMetadata with short flags" {
var registry = ArgumentRegistry.init(std.testing.allocator);
defer registry.deinit();
const TestStruct = struct {
verbose: bool,
pub const meta = .{
.verbose = .{
.short = 'v',
.help = "Verbose output",
},
};
};
try registry.registerMetadata(TestStruct, "TestModule");
// Should have registered both long and short forms
try std.testing.expect(registry.hasArgument("verbose"));
try std.testing.expect(registry.hasArgument("v"));
}
test "ArgumentRegistry: registerMetadata with camelCase" {
var registry = ArgumentRegistry.init(std.testing.allocator);
defer registry.deinit();
const TestStruct = struct {
outputFile: []const u8,
};
try registry.registerMetadata(TestStruct, "TestModule");
// TODO: Field names aren't converted to kebab-case yet, using direct name
try std.testing.expect(registry.hasArgument("outputFile"));
try std.testing.expect(!registry.hasArgument("output-file"));
}
test "ArgumentRegistry: registerMetadata skips duplicate type" {
var registry = ArgumentRegistry.init(std.testing.allocator);
defer registry.deinit();
const TestStruct = struct {
verbose: bool,
};
try registry.registerMetadata(TestStruct, "Module1");
try registry.registerMetadata(TestStruct, "Module2"); // Should skip
// Should only have one instance
try std.testing.expectEqual(@as(usize, 1), registry.argumentCount());
}
test "ArgumentRegistry: registerMetadata compatible collision" {
var registry = ArgumentRegistry.init(std.testing.allocator);
defer registry.deinit();
const Module1 = struct {
verbose: bool,
};
const Module2 = struct {
verbose: bool,
};
try registry.registerMetadata(Module1, "Module1");
try registry.registerMetadata(Module2, "Module2");
// Both should register successfully (compatible types)
const arg = registry.getArgument("verbose");
try std.testing.expect(arg != null);
try std.testing.expectEqual(ArgumentType.bool, arg.?.arg_type);
// Both modules should be listed
const modules = registry.getModulesForArg("verbose");
try std.testing.expect(modules != null);
try std.testing.expectEqual(@as(usize, 2), modules.?.items.len);
}
test "ArgumentRegistry: registerMetadata incompatible collision" {
var registry = ArgumentRegistry.init(std.testing.allocator);
defer registry.deinit();
const Module1 = struct {
verbose: bool,
};
const Module2 = struct {
verbose: u32, // Different type!
};
try registry.registerMetadata(Module1, "Module1");
// Should fail with incompatible type error
try std.testing.expectError(
error.IncompatibleArgumentType,
registry.registerMetadata(Module2, "Module2")
);
}
test "ArgumentRegistry: registerMetadata short flag collision compatible" {
var registry = ArgumentRegistry.init(std.testing.allocator);
defer registry.deinit();
const Module1 = struct {
verbose: bool,
pub const meta = .{
.verbose = .{ .short = 'v' },
};
};
const Module2 = struct {
validate: bool,
pub const meta = .{
.validate = .{ .short = 'v' },
};
};
try registry.registerMetadata(Module1, "Module1");
try registry.registerMetadata(Module2, "Module2");
// Both should work (same type)
try std.testing.expect(registry.hasArgument("verbose"));
try std.testing.expect(registry.hasArgument("validate"));
try std.testing.expect(registry.hasArgument("v"));
}
test "ArgumentRegistry: registerMetadata short flag collision incompatible" {
var registry = ArgumentRegistry.init(std.testing.allocator);
defer registry.deinit();
const Module1 = struct {
verbose: bool,
pub const meta = .{
.verbose = .{ .short = 'v' },
};
};
const Module2 = struct {
value: u32,
pub const meta = .{
.value = .{ .short = 'v' },
};
};
try registry.registerMetadata(Module1, "Module1");
// Should fail due to incompatible short flag
try std.testing.expectError(
error.IncompatibleArgumentType,
registry.registerMetadata(Module2, "Module2")
);
}
test "ArgumentRegistry: registerMetadata with optional fields" {
var registry = ArgumentRegistry.init(std.testing.allocator);
defer registry.deinit();
const TestStruct = struct {
verbose: bool,
count: ?u32,
};
try registry.registerMetadata(TestStruct, "TestModule");
// Both should be registered
const verbose_arg = registry.getArgument("verbose");
const count_arg = registry.getArgument("count");
try std.testing.expect(verbose_arg != null);
try std.testing.expect(count_arg != null);
// verbose is required (non-optional)
try std.testing.expectEqual(true, verbose_arg.?.required);
// count is not required (optional)
try std.testing.expectEqual(false, count_arg.?.required);
}
test "ArgumentRegistry: registerMetadata with enum" {
var registry = ArgumentRegistry.init(std.testing.allocator);
defer registry.deinit();
const LogLevel = enum { debug, info, warn, @"error" };
const TestStruct = struct {
logLevel: LogLevel,
};
try registry.registerMetadata(TestStruct, "TestModule");
// TODO: Field names aren't converted to kebab-case yet, using direct name
const arg = registry.getArgument("logLevel");
try std.testing.expect(arg != null);
try std.testing.expectEqual(ArgumentType.enum_type, arg.?.arg_type);
// TODO: Re-enable when enum value extraction is fixed
// try std.testing.expectEqual(@as(usize, 4), arg.?.enum_values.len);
}
test "ArgumentRegistry: hasArgument" {
var registry = ArgumentRegistry.init(std.testing.allocator);
defer registry.deinit();
try std.testing.expect(!registry.hasArgument("verbose"));
const TestStruct = struct { verbose: bool };
try registry.registerMetadata(TestStruct, "Module");
try std.testing.expect(registry.hasArgument("verbose"));
}
test "ArgumentRegistry: argumentCount" {
var registry = ArgumentRegistry.init(std.testing.allocator);
defer registry.deinit();
try std.testing.expectEqual(@as(usize, 0), registry.argumentCount());
const TestStruct = struct {
verbose: bool,
count: u32,
output: []const u8,
};
try registry.registerMetadata(TestStruct, "Module");
try std.testing.expectEqual(@as(usize, 3), registry.argumentCount());
}