497 lines
16 KiB
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());
|
|
}
|