Skip to content

Commit 597041e

Browse files
authored
Extract generic test definition helpers (#39)
1 parent 413ae40 commit 597041e

3 files changed

Lines changed: 87 additions & 32 deletions

File tree

PLAN.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -569,7 +569,7 @@ Exit criteria:
569569

570570
- M0 is complete as the Zig `0.16.0` runtime scaffold.
571571
- M1 has started on stacked branches with native Zig artifact-first parser slices.
572-
- M1A has started with behavior-preserving `src/project/types.zig`, `src/project/selector.zig`, `src/project/manifest.zig`, `src/project/util.zig`, `src/project/config.zig`, `src/project/fs.zig`, `src/project/jinja.zig`, `src/project/resolve.zig`, and `src/project/parse.zig` extractions. `src/project/manifest.zig` owns selected-resource JSON and partial `manifest.json` writing with native tests for selected JSON shape, JSON escaping, exposure dependency ordering, disabled-resource filtering, and graph-map output. `src/project/util.zig` owns shared display, membership, append-dedup, string sorting, and narrow YAML scalar/list helpers. `src/project/config.zig` owns `dbt_project.yml` loading and project path/docs config parsing. `src/project/fs.zig` owns deterministic resource file discovery and Linux directory traversal helpers. `src/project/jinja.zig` owns lexical Jinja call, parenthesis, quoted string, and literal argument helpers while graph-aware scanning remains in `src/project.zig`. `src/project/resolve.zig` owns graph lookup/count helpers, low-level ref/source resolution helpers, and dependency-map mutation for refs, sources, and known macro dependencies. `src/project/parse.zig` owns narrow parser scalar helpers for YAML booleans, JSON-compatible scalar classification, generic-test YAML item names, and generic-test identity/name/hash helpers with native tests. `src/project.zig` remains the public parser/list facade and still owns most loader, resource parser, scanner, and resolver orchestration code until follow-up extractions move those pieces behind focused internal modules.
572+
- M1A has started with behavior-preserving `src/project/types.zig`, `src/project/selector.zig`, `src/project/manifest.zig`, `src/project/util.zig`, `src/project/config.zig`, `src/project/fs.zig`, `src/project/jinja.zig`, `src/project/resolve.zig`, and `src/project/parse.zig` extractions. `src/project/manifest.zig` owns selected-resource JSON and partial `manifest.json` writing with native tests for selected JSON shape, JSON escaping, exposure dependency ordering, disabled-resource filtering, and graph-map output. `src/project/util.zig` owns shared display, membership, append-dedup, string sorting, and narrow YAML scalar/list helpers. `src/project/config.zig` owns `dbt_project.yml` loading and project path/docs config parsing. `src/project/fs.zig` owns deterministic resource file discovery and Linux directory traversal helpers. `src/project/jinja.zig` owns lexical Jinja call, parenthesis, quoted string, and literal argument helpers while graph-aware scanning remains in `src/project.zig`. `src/project/resolve.zig` owns graph lookup/count helpers, low-level ref/source resolution helpers, and dependency-map mutation for refs, sources, and known macro dependencies. `src/project/parse.zig` owns narrow parser scalar helpers for YAML booleans, JSON-compatible scalar classification, generic-test YAML item names, generic-test definition construction/cloning, and generic-test identity/name/hash helpers with native tests. `src/project.zig` remains the public parser/list facade and still owns most loader, resource parser, scanner, and resolver orchestration code until follow-up extractions move those pieces behind focused internal modules.
573573
- `dxt parse` now targets the supported Tier 0 subset: project name/model paths/seed paths/macro paths, project and package model path configs for literal `+materialized`, `+tags`, and model/seed `+docs.node_color`, root-project model config overrides for installed packages, SQL model discovery, CSV seed discovery, installed package SQL model and CSV seed discovery from `dbt_packages`, source discovery, installed package source discovery, exposure discovery, installed package exposure discovery, project macro discovery, installed package macro discovery from `dbt_packages`, macro property YAML for project macro descriptions and arguments, project and package docs block discovery, literal `ref` to models or seeds, two-argument package refs, package-local refs in installed package models and exposures, unique installed-package fallback for unqualified refs, literal `source`, package-local sources in installed package models, unique installed-package fallback for unqualified sources, literal `doc` in project and package descriptions, inline `config(materialized=..., tags=...)`, known project/package-qualified/package-local macro call dependencies, narrow project and package YAML model properties for scalar descriptions, simple columns, tags, materialization, disabled SQL models, dbt-shaped `unique`, `not_null`, `accepted_values`, and `relationships` generic test nodes, model/test `refs` and `sources` artifact fields, dependency maps, and deterministic partial `manifest.json`. The manifest includes the v12 top-level maps needed by the M1 artifact shape and is covered by a pinned local dbt Manifest v12 schema slice. YAML generic test arguments are currently supported for scalar values plus inline and block lists required by public Jaffle Shop DuckDB-style tests.
574574
- `dxt ls` now lists dbt-selectable resources from the same parser graph with stable text/JSON output and basic name/FQN wildcards, tag wildcards, slash-aware `path:` wildcards, exact `package:`/`package:this`, `source:` wildcards including package-qualified source selectors, `exposure:` wildcards, `resource_type:`, `test_type:generic`, config materialization, comma intersection, whitespace union, multi-argument selector lists, repeated selector flags, leading/trailing `+` graph expansion, and exact exclude filters; macros are emitted in artifacts but not exposed as `ls` resources.
575575
- Synthetic fixtures cover one model, model refs, seed refs, source refs, exposure refs to models and sources, combined source/model YAML, inline config/tag selection, config materialization selection, comma-intersection selection, YAML model properties and columns, emitted `unique`, `not_null`, `accepted_values`, and `relationships` generic test nodes, project macro artifacts and macro properties, configured `macro-paths` replacing the default macro directory, installed package macros with package-qualified calls and package-local macro calls, installed package models, seeds, sources, docs, exposures, package YAML model properties, root package config overrides, and package-qualified/package-local refs/sources, macro calls recorded in model and macro `depends_on.macros`, docs blocks with literal `doc` descriptions, disabled models, disabled ref diagnostics, unmatched model-property warnings, duplicate model and docs diagnostics, unsupported dynamic ref/doc diagnostics, missing doc diagnostics, malformed docs block diagnostics, unresolved package macro diagnostics, and unsupported unknown macro-call diagnostics.

src/project.zig

Lines changed: 3 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,11 @@ const splitKeyValue = util.splitKeyValue;
4646
const parseInlineStringList = util.parseInlineStringList;
4747
const dupTrimmedScalar = util.dupTrimmedScalar;
4848
const sortStrings = util.sortStrings;
49+
const appendGenericTestDef = project_parse.appendGenericTestDef;
50+
const appendGenericTestDefClone = project_parse.appendGenericTestDefClone;
4951
const parseBool = project_parse.parseBool;
5052
const genericTestUniqueId = project_parse.genericTestUniqueId;
53+
const parseInlineGenericTestList = project_parse.parseInlineGenericTestList;
5154
const parseJsonScalar = project_parse.parseJsonScalar;
5255
const synthesizeGenericTestNames = project_parse.synthesizeGenericTestNames;
5356
const testNameFromYamlItem = project_parse.testNameFromYamlItem;
@@ -1504,37 +1507,6 @@ fn parseTagList(allocator: std.mem.Allocator, text: []const u8, tags: *std.Array
15041507
}
15051508
}
15061509

1507-
fn parseInlineGenericTestList(allocator: std.mem.Allocator, value: []const u8, out: *std.ArrayList(GenericTestDef)) !void {
1508-
const trimmed = std.mem.trim(u8, value, " \t");
1509-
if (trimmed.len < 2 or trimmed[0] != '[' or trimmed[trimmed.len - 1] != ']') {
1510-
_ = try appendGenericTestDef(allocator, out, try dupTrimmedScalar(allocator, trimmed));
1511-
return;
1512-
}
1513-
var pieces = std.mem.splitScalar(u8, trimmed[1 .. trimmed.len - 1], ',');
1514-
while (pieces.next()) |piece| {
1515-
const item = std.mem.trim(u8, piece, " \t");
1516-
if (item.len != 0) _ = try appendGenericTestDef(allocator, out, try dupTrimmedScalar(allocator, item));
1517-
}
1518-
}
1519-
1520-
fn appendGenericTestDef(allocator: std.mem.Allocator, tests: *std.ArrayList(GenericTestDef), test_name: []const u8) !usize {
1521-
try tests.append(allocator, .{ .name = test_name });
1522-
return tests.items.len - 1;
1523-
}
1524-
1525-
fn appendGenericTestDefClone(graph: *Graph, tests: *std.ArrayList(GenericTestDef), source: GenericTestDef) !void {
1526-
var cloned = GenericTestDef{
1527-
.name = source.name,
1528-
.relationship_to = source.relationship_to,
1529-
.relationship_field = source.relationship_field,
1530-
};
1531-
errdefer cloned.accepted_values.deinit(graph.allocator);
1532-
for (source.accepted_values.items) |value| {
1533-
try cloned.accepted_values.append(graph.allocator, value);
1534-
}
1535-
try tests.append(graph.allocator, cloned);
1536-
}
1537-
15381510
fn appendMacroArgumentClone(graph: *Graph, arguments: *std.ArrayList(MacroArgument), source: MacroArgument) !void {
15391511
for (arguments.items) |*existing| {
15401512
if (std.mem.eql(u8, existing.name, source.name)) {

src/project/parse.zig

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ const util = @import("util.zig");
44

55
const JsonScalar = types.JsonScalar;
66
const GenericTestDef = types.GenericTestDef;
7+
const Graph = types.Graph;
78
const dupTrimmedScalar = util.dupTrimmedScalar;
89

910
pub fn parseBool(value: []const u8) !bool {
@@ -65,6 +66,37 @@ pub fn testNameFromYamlItem(allocator: std.mem.Allocator, value: []const u8) ![]
6566
return try dupTrimmedScalar(allocator, trimmed[0..colon]);
6667
}
6768

69+
pub fn parseInlineGenericTestList(allocator: std.mem.Allocator, value: []const u8, out: *std.ArrayList(GenericTestDef)) !void {
70+
const trimmed = std.mem.trim(u8, value, " \t");
71+
if (trimmed.len < 2 or trimmed[0] != '[' or trimmed[trimmed.len - 1] != ']') {
72+
_ = try appendGenericTestDef(allocator, out, try dupTrimmedScalar(allocator, trimmed));
73+
return;
74+
}
75+
var pieces = std.mem.splitScalar(u8, trimmed[1 .. trimmed.len - 1], ',');
76+
while (pieces.next()) |piece| {
77+
const item = std.mem.trim(u8, piece, " \t");
78+
if (item.len != 0) _ = try appendGenericTestDef(allocator, out, try dupTrimmedScalar(allocator, item));
79+
}
80+
}
81+
82+
pub fn appendGenericTestDef(allocator: std.mem.Allocator, tests: *std.ArrayList(GenericTestDef), test_name: []const u8) !usize {
83+
try tests.append(allocator, .{ .name = test_name });
84+
return tests.items.len - 1;
85+
}
86+
87+
pub fn appendGenericTestDefClone(graph: *Graph, tests: *std.ArrayList(GenericTestDef), source: GenericTestDef) !void {
88+
var cloned = GenericTestDef{
89+
.name = source.name,
90+
.relationship_to = source.relationship_to,
91+
.relationship_field = source.relationship_field,
92+
};
93+
errdefer cloned.accepted_values.deinit(graph.allocator);
94+
for (source.accepted_values.items) |value| {
95+
try cloned.accepted_values.append(graph.allocator, value);
96+
}
97+
try tests.append(graph.allocator, cloned);
98+
}
99+
68100
pub const GenericTestNames = struct {
69101
full: []const u8,
70102
compiled: []const u8,
@@ -264,6 +296,57 @@ test "testNameFromYamlItem reads scalar and mapping test names" {
264296
try std.testing.expectError(error.UnsupportedYaml, testNameFromYamlItem(allocator, " "));
265297
}
266298

299+
test "parseInlineGenericTestList reads scalar and inline generic tests" {
300+
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
301+
defer arena.deinit();
302+
const allocator = arena.allocator();
303+
304+
var tests: std.ArrayList(GenericTestDef) = .empty;
305+
defer tests.deinit(allocator);
306+
try parseInlineGenericTestList(allocator, "[unique, \"not_null\", ]", &tests);
307+
try std.testing.expectEqual(@as(usize, 2), tests.items.len);
308+
try std.testing.expectEqualStrings("unique", tests.items[0].name);
309+
try std.testing.expectEqualStrings("not_null", tests.items[1].name);
310+
311+
var scalar: std.ArrayList(GenericTestDef) = .empty;
312+
defer scalar.deinit(allocator);
313+
try parseInlineGenericTestList(allocator, "'accepted_values'", &scalar);
314+
try std.testing.expectEqual(@as(usize, 1), scalar.items.len);
315+
try std.testing.expectEqualStrings("accepted_values", scalar.items[0].name);
316+
}
317+
318+
test "appendGenericTestDefClone copies nested accepted values list" {
319+
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
320+
defer arena.deinit();
321+
const allocator = arena.allocator();
322+
323+
var source_values: std.ArrayList([]const u8) = .empty;
324+
defer source_values.deinit(allocator);
325+
try source_values.append(allocator, "placed");
326+
try source_values.append(allocator, "returned");
327+
328+
const source = GenericTestDef{
329+
.name = "accepted_values",
330+
.accepted_values = source_values,
331+
.relationship_to = "ref('customers')",
332+
.relationship_field = "id",
333+
};
334+
var graph = Graph{ .allocator = allocator, .project_name = "demo" };
335+
var clones: std.ArrayList(GenericTestDef) = .empty;
336+
defer clones.deinit(allocator);
337+
338+
try appendGenericTestDefClone(&graph, &clones, source);
339+
try source_values.append(allocator, "cancelled");
340+
341+
try std.testing.expectEqual(@as(usize, 1), clones.items.len);
342+
try std.testing.expectEqualStrings("accepted_values", clones.items[0].name);
343+
try std.testing.expectEqualStrings("ref('customers')", clones.items[0].relationship_to);
344+
try std.testing.expectEqualStrings("id", clones.items[0].relationship_field);
345+
try std.testing.expectEqual(@as(usize, 2), clones.items[0].accepted_values.items.len);
346+
try std.testing.expectEqualStrings("placed", clones.items[0].accepted_values.items[0]);
347+
try std.testing.expectEqualStrings("returned", clones.items[0].accepted_values.items[1]);
348+
}
349+
267350
test "synthesizeGenericTestNames preserves short generic test identities" {
268351
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
269352
defer arena.deinit();

0 commit comments

Comments
 (0)