also looking at this
Add deterministic test sharding for distributed CI
#22
10 files
+322
-5
| @@ -8,6 +8,7 @@ pub const ConfigFormat = enum { json }; | ||
| 8 | 8 | /// actually honor. Defaults intentionally match `CLIOptions`. |
| 9 | 9 | pub const TestConfig = struct { |
| 10 | 10 | test_options: TestOptions = .{}, |
| 11 | sharding: ShardingOptions = .{}, | |
| 11 | 12 | parallel: ParallelOptions = .{}, |
| 12 | 13 | reporter: ReporterOptions = .{}, |
| 13 | 14 | snapshot: SnapshotOptions = .{}, |
| @@ -17,6 +18,11 @@ pub const TestConfig = struct { | ||
| 17 | 18 | coverage: CoverageOptions = .{}, |
| 18 | 19 | }; |
| 19 | 20 | |
| 21 | pub const ShardingOptions = struct { | |
| 22 | index: ?usize = null, | |
| 23 | count: ?usize = null, | |
| 24 | }; | |
| 25 | ||
| 20 | 26 | pub const TestOptions = struct { |
| 21 | 27 | pattern: []const u8 = "*.test.zig", |
| 22 | 28 | test_dir: []const u8 = ".", |
| @@ -64,6 +70,7 @@ pub const CoverageOptions = struct { | ||
| 64 | 70 | |
| 65 | 71 | const FileConfig = struct { |
| 66 | 72 | @"test": TestOptions = .{}, |
| 73 | sharding: ShardingOptions = .{}, | |
| 67 | 74 | parallel: ParallelOptions = .{}, |
| 68 | 75 | reporter: ReporterOptions = .{}, |
| 69 | 76 | snapshot: SnapshotOptions = .{}, |
| @@ -80,6 +87,7 @@ pub const ConfigError = error{ | ||
| 80 | 87 | InvalidPort, |
| 81 | 88 | InvalidTimeout, |
| 82 | 89 | InvalidDebounce, |
| 90 | InvalidShard, | |
| 83 | 91 | }; |
| 84 | 92 | |
| 85 | 93 | /// Owns strings parsed from configuration files until `deinit` is called. |
| @@ -116,6 +124,7 @@ pub const ConfigLoader = struct { | ||
| 116 | 124 | ); |
| 117 | 125 | const config = TestConfig{ |
| 118 | 126 | .test_options = file_config.@"test", |
| 127 | .sharding = file_config.sharding, | |
| 119 | 128 | .parallel = file_config.parallel, |
| 120 | 129 | .reporter = file_config.reporter, |
| 121 | 130 | .snapshot = file_config.snapshot, |
| @@ -175,6 +184,13 @@ fn validate(config: TestConfig) !void { | ||
| 175 | 184 | if (timeout == 0) return ConfigError.InvalidTimeout; |
| 176 | 185 | } |
| 177 | 186 | if (config.watch.debounce_ms == 0) return ConfigError.InvalidDebounce; |
| 187 | const has_shard_index = config.sharding.index != null; | |
| 188 | const has_shard_count = config.sharding.count != null; | |
| 189 | if (has_shard_index != has_shard_count) return ConfigError.InvalidShard; | |
| 190 | if (config.sharding.count) |count| { | |
| 191 | const index = config.sharding.index.?; | |
| 192 | if (count == 0 or index == 0 or index > count) return ConfigError.InvalidShard; | |
| 193 | } | |
| 178 | 194 | } |
| 179 | 195 | |
| 180 | 196 | test "TestConfig defaults match CLI discovery defaults" { |
| @@ -183,6 +199,8 @@ test "TestConfig defaults match CLI discovery defaults" { | ||
| 183 | 199 | try std.testing.expectEqualStrings(".", config.test_options.test_dir); |
| 184 | 200 | try std.testing.expect(config.test_options.recursive); |
| 185 | 201 | try std.testing.expect(config.test_options.timeout == null); |
| 202 | try std.testing.expect(config.sharding.index == null); | |
| 203 | try std.testing.expect(config.sharding.count == null); | |
| 186 | 204 | try std.testing.expect(!config.parallel.enabled); |
| 187 | 205 | try std.testing.expectEqualStrings("spec", config.reporter.reporter); |
| 188 | 206 | } |
| @@ -200,6 +218,11 @@ test "configuration validation rejects invalid runtime values" { | ||
| 200 | 218 | invalid.reporter.reporter = "spec"; |
| 201 | 219 | invalid.parallel.jobs = 0; |
| 202 | 220 | try std.testing.expectError(ConfigError.InvalidJobs, validate(invalid)); |
| 221 | invalid.parallel.jobs = null; | |
| 222 | invalid.sharding.index = 2; | |
| 223 | try std.testing.expectError(ConfigError.InvalidShard, validate(invalid)); | |
| 224 | invalid.sharding.count = 1; | |
| 225 | try std.testing.expectError(ConfigError.InvalidShard, validate(invalid)); | |
| 203 | 226 | } |
| 204 | 227 | |
| 205 | 228 | test "ConfigLoader loads supported JSON and rejects unknown keys" { |
| @@ -210,6 +233,8 @@ test "ConfigLoader loads supported JSON and rejects unknown keys" { | ||
| 210 | 233 | try std.testing.expectEqualStrings("tests/fixtures", config.test_options.test_dir); |
| 211 | 234 | try std.testing.expectEqualStrings("selected test passes", config.test_options.filter.?); |
| 212 | 235 | try std.testing.expect(!config.test_options.recursive); |
| 236 | try std.testing.expectEqual(@as(?usize, 1), config.sharding.index); | |
| 237 | try std.testing.expectEqual(@as(?usize, 1), config.sharding.count); | |
| 213 | 238 | |
| 214 | 239 | try std.testing.expectError( |
| 215 | 240 | error.UnknownField, |