@voxgig/sdkgen 4.3.0 → 4.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/voxgig-sdkgen +1 -1
- package/package.json +1 -1
- package/project/.sdk/src/cmp/zig/Config_zig.ts +1 -1
- package/project/.sdk/tm/c/tests/primary_corpus_test.c +565 -0
- package/project/.sdk/tm/c/utility/prepare_method.c +3 -1
- package/project/.sdk/tm/c/utility/transform_request.c +4 -3
- package/project/.sdk/tm/clojure/src/sdk/core.clj +55 -5
- package/project/.sdk/tm/clojure/test/sdk/test/primary.clj +171 -1
- package/project/.sdk/tm/clojure/test/sdk/test/struct_corpus.clj +13 -2
- package/project/.sdk/tm/clojure/test/sdk/test_runner.clj +2 -0
- package/project/.sdk/tm/csharp/test/CustomUtilityTest.cs +104 -0
- package/project/.sdk/tm/csharp/utility/MakeOptions.cs +82 -2
- package/project/.sdk/tm/elixir/lib/projectname/utility.ex +50 -2
- package/project/.sdk/tm/elixir/test/primary_utility_test.exs +18 -200
- package/project/.sdk/tm/elixir/test/support/struct_corpus.ex +413 -2
- package/project/.sdk/tm/kotlin/utility/MakeOptions.kt +56 -2
- package/project/.sdk/tm/lua/utility/make_options.lua +22 -2
- package/project/.sdk/tm/ocaml/Makefile +15 -4
- package/project/.sdk/tm/ocaml/sdk_runtime.ml +8 -1
- package/project/.sdk/tm/ocaml/test/corpus_runner.ml +185 -0
- package/project/.sdk/tm/ocaml/test/primary_utility_test.ml +238 -0
- package/project/.sdk/tm/ocaml/test/struct_corpus.ml +5 -160
- package/project/.sdk/tm/scala/Makefile +1 -0
- package/project/.sdk/tm/scala/sdktest/PrimaryCorpus.scala +294 -0
- package/project/.sdk/tm/scala/sdktest/StructCorpus.scala +0 -0
- package/project/.sdk/tm/scala/utility/Make.scala +49 -2
- package/project/.sdk/tm/scala/utility/Prepare.scala +3 -1
- package/project/.sdk/tm/swift/Sources/ProjectNameSDK/utility/MakeOptions.swift +21 -3
- package/project/.sdk/tm/ts/src/feature/test/TestFeature.ts +13 -2
- package/project/.sdk/tm/zig/core/utility.zig +8 -1
- package/project/.sdk/tm/zig/test/primary_utility_test.zig +445 -0
- package/project/.sdk/tm/zig/test/struct_runner.zig +238 -0
- package/project/sdkgen-package.json +1 -1
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
// against subject functions, mirroring the TS/Go runner pattern.
|
|
3
3
|
|
|
4
4
|
const std = @import("std");
|
|
5
|
+
const regex = @import("../utility/voxgigstruct/regex.zig");
|
|
5
6
|
const voxgig_struct = @import("voxgig-struct");
|
|
6
7
|
|
|
7
8
|
const Allocator = std.mem.Allocator;
|
|
@@ -20,6 +21,22 @@ pub const Subject = *const fn (StdJsonValue) StdJsonValue;
|
|
|
20
21
|
// Subject that takes an allocator and our JsonValue type.
|
|
21
22
|
pub const AllocSubject = *const fn (Allocator, JsonValue) JsonValue;
|
|
22
23
|
|
|
24
|
+
/// A subject that receives the whole corpus ENTRY and returns std.json.
|
|
25
|
+
///
|
|
26
|
+
/// `published` is an out-parameter for the context the subject built. std.json
|
|
27
|
+
/// maps cannot be mutated in place safely from here — a put may reallocate the
|
|
28
|
+
/// backing store and the caller would never see it — so a section that needs a
|
|
29
|
+
/// `match: ctx.*` assertion hands its ctx back through this instead.
|
|
30
|
+
pub const EntrySubject = *const fn (
|
|
31
|
+
Allocator,
|
|
32
|
+
StdJsonValue,
|
|
33
|
+
*?StdJsonValue,
|
|
34
|
+
/// The SDK's own error message. Zig errors carry no payload, so
|
|
35
|
+
/// @errorName gives the tag ("Sdk") and never the text the corpus matches
|
|
36
|
+
/// on. A subject that fails writes ctx.pending_err's message here.
|
|
37
|
+
*?[]const u8,
|
|
38
|
+
) anyerror!StdJsonValue;
|
|
39
|
+
|
|
23
40
|
pub const Spec = struct {
|
|
24
41
|
data: StdJsonValue,
|
|
25
42
|
|
|
@@ -35,6 +52,8 @@ pub const RunPack = struct {
|
|
|
35
52
|
spec: Spec,
|
|
36
53
|
allocator: Allocator,
|
|
37
54
|
file_data: []const u8,
|
|
55
|
+
/// Cases actually executed, so a suite can assert it ran something.
|
|
56
|
+
ran: usize = 0,
|
|
38
57
|
parsed: std.json.Parsed(StdJsonValue),
|
|
39
58
|
|
|
40
59
|
/// Run all entries in testspec.set against the subject function (no alloc, std types).
|
|
@@ -125,6 +144,101 @@ pub const RunPack = struct {
|
|
|
125
144
|
}
|
|
126
145
|
}
|
|
127
146
|
|
|
147
|
+
/// Drive a section whose entries carry ctx/args/match/err.
|
|
148
|
+
///
|
|
149
|
+
/// The subject receives the whole ENTRY, not the resolved args, because an
|
|
150
|
+
/// args-style section has to publish the context it builds back onto the
|
|
151
|
+
/// entry — a `match: ctx.*` assertion reads it from there.
|
|
152
|
+
pub fn runsetEntry(self: *RunPack, testspec: StdJsonValue, subject: EntrySubject) !void {
|
|
153
|
+
const set_val = switch (testspec) {
|
|
154
|
+
.object => |obj| obj.get("set") orelse return,
|
|
155
|
+
else => return,
|
|
156
|
+
};
|
|
157
|
+
if (set_val != .array) return;
|
|
158
|
+
|
|
159
|
+
var failures: usize = 0;
|
|
160
|
+
|
|
161
|
+
for (set_val.array.items, 0..) |entry_val, entry_idx| {
|
|
162
|
+
if (entry_val != .object) continue;
|
|
163
|
+
const entry = entry_val.object;
|
|
164
|
+
|
|
165
|
+
var arena = std.heap.ArenaAllocator.init(self.allocator);
|
|
166
|
+
defer arena.deinit();
|
|
167
|
+
const alloc = arena.allocator();
|
|
168
|
+
|
|
169
|
+
const err_field = entry.get("err");
|
|
170
|
+
|
|
171
|
+
self.ran += 1;
|
|
172
|
+
var published: ?StdJsonValue = null;
|
|
173
|
+
var errmsg: ?[]const u8 = null;
|
|
174
|
+
|
|
175
|
+
const res = subject(alloc, entry_val, &published, &errmsg) catch |e| {
|
|
176
|
+
// An expected error: match its text, then any `match` block.
|
|
177
|
+
if (err_field) |ef| {
|
|
178
|
+
const msg = errmsg orelse @errorName(e);
|
|
179
|
+
if (ef == .bool and ef.bool) continue;
|
|
180
|
+
if (matchval(alloc, ef, StdJsonValue{ .string = msg })) continue;
|
|
181
|
+
std.debug.print("\n ERROR MATCH [entry {d}]: [{s}] <=> [{s}]\n", .{
|
|
182
|
+
entry_idx, fmtJson(ef), msg,
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
failures += 1;
|
|
186
|
+
continue;
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
if (err_field != null) {
|
|
190
|
+
std.debug.print("\n expected an error [entry {d}]\n", .{entry_idx});
|
|
191
|
+
failures += 1;
|
|
192
|
+
continue;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
var matched = false;
|
|
196
|
+
|
|
197
|
+
if (entry.get("match")) |m| {
|
|
198
|
+
var subj = std.json.ObjectMap.init(alloc);
|
|
199
|
+
subj.put("out", res) catch {};
|
|
200
|
+
if (entry.get("in")) |v| subj.put("in", v) catch {};
|
|
201
|
+
if (published) |p| {
|
|
202
|
+
subj.put("ctx", p) catch {};
|
|
203
|
+
} else if (entry.get("ctx")) |v| {
|
|
204
|
+
subj.put("ctx", v) catch {};
|
|
205
|
+
}
|
|
206
|
+
const subjval = StdJsonValue{ .object = subj };
|
|
207
|
+
|
|
208
|
+
var path = std.ArrayList([]const u8).init(alloc);
|
|
209
|
+
doMatch(alloc, m, subjval, &path) catch {
|
|
210
|
+
std.debug.print(" [entry {d}]\n", .{entry_idx});
|
|
211
|
+
failures += 1;
|
|
212
|
+
continue;
|
|
213
|
+
};
|
|
214
|
+
matched = true;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const raw_out = entry.get("out");
|
|
218
|
+
|
|
219
|
+
if (raw_out) |o| {
|
|
220
|
+
if (jsonEqual(o, res)) continue;
|
|
221
|
+
if (o == .string and std.mem.eql(u8, o.string, NULLMARK) and res == .null) continue;
|
|
222
|
+
std.debug.print("\n FAIL [entry {d}]: expected {s} got {s}\n", .{
|
|
223
|
+
entry_idx, fmtJson(o), fmtJson(res),
|
|
224
|
+
});
|
|
225
|
+
failures += 1;
|
|
226
|
+
} else if (!matched) {
|
|
227
|
+
// NO `out` MEANS EXPECT NULL, not "assert nothing". The
|
|
228
|
+
// reference sets a missing out to the null marker before
|
|
229
|
+
// running, so an entry like {"ctx": {"opname": "bad"}} asserts
|
|
230
|
+
// that the utility yields nothing.
|
|
231
|
+
if (res == .null) continue;
|
|
232
|
+
std.debug.print("\n FAIL [entry {d}]: expected null got {s}\n", .{
|
|
233
|
+
entry_idx, fmtJson(res),
|
|
234
|
+
});
|
|
235
|
+
failures += 1;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
if (0 < failures) return error.SectionFailed;
|
|
240
|
+
}
|
|
241
|
+
|
|
128
242
|
pub fn deinit(self: *RunPack) void {
|
|
129
243
|
self.parsed.deinit();
|
|
130
244
|
self.allocator.free(self.file_data);
|
|
@@ -136,6 +250,29 @@ pub const Flags = struct {
|
|
|
136
250
|
undef_as_null: bool = true,
|
|
137
251
|
};
|
|
138
252
|
|
|
253
|
+
/// Load test.json and return a named top-level spec section.
|
|
254
|
+
///
|
|
255
|
+
/// makeRunner is this with "struct" baked in. The primary-utility suite needs
|
|
256
|
+
/// "primary" from the same file, and duplicating the load would give the two
|
|
257
|
+
/// suites separate copies of a corpus that is meant to be shared.
|
|
258
|
+
pub fn makeRunnerFor(allocator: Allocator, section: []const u8) !RunPack {
|
|
259
|
+
const data = try std.fs.cwd().readFileAlloc(allocator, TEST_JSON_FILE, 10 * 1024 * 1024);
|
|
260
|
+
const parsed = try std.json.parseFromSlice(StdJsonValue, allocator, data, .{});
|
|
261
|
+
const root = parsed.value;
|
|
262
|
+
|
|
263
|
+
const spec_val = switch (root) {
|
|
264
|
+
.object => |obj| obj.get(section) orelse return error.NoSectionInTestJson,
|
|
265
|
+
else => return error.TestJsonNotObject,
|
|
266
|
+
};
|
|
267
|
+
|
|
268
|
+
return RunPack{
|
|
269
|
+
.spec = Spec{ .data = spec_val },
|
|
270
|
+
.allocator = allocator,
|
|
271
|
+
.file_data = data,
|
|
272
|
+
.parsed = parsed,
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
|
|
139
276
|
/// Load test.json and return the "struct" spec.
|
|
140
277
|
pub fn makeRunner(allocator: Allocator) !RunPack {
|
|
141
278
|
const path = TEST_JSON_FILE;
|
|
@@ -156,6 +293,107 @@ pub fn makeRunner(allocator: Allocator) !RunPack {
|
|
|
156
293
|
};
|
|
157
294
|
}
|
|
158
295
|
|
|
296
|
+
// ---- Corpus matching: matchval / doMatch ----
|
|
297
|
+
//
|
|
298
|
+
// The primary corpus asserts with `match` as often as with `out`, and its
|
|
299
|
+
// error expectations are regexes. runsetAlloc resolved only `in`, had no
|
|
300
|
+
// match support and silently skipped any entry carrying `err` — so it could
|
|
301
|
+
// drive the struct corpus but not this one. Semantics are the reference's:
|
|
302
|
+
// exact equality first, then __UNDEF__/__EXISTS__ markers, then a /regex/, and
|
|
303
|
+
// finally a case-insensitive substring.
|
|
304
|
+
|
|
305
|
+
fn stringifyAlloc(alloc: Allocator, v: StdJsonValue) []const u8 {
|
|
306
|
+
var buf = std.ArrayList(u8).init(alloc);
|
|
307
|
+
if (v == .string) return v.string;
|
|
308
|
+
std.json.stringify(v, .{}, buf.writer()) catch return "";
|
|
309
|
+
return buf.items;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
pub fn matchval(alloc: Allocator, check: StdJsonValue, base: StdJsonValue) bool {
|
|
313
|
+
if (jsonEqual(check, base)) return true;
|
|
314
|
+
|
|
315
|
+
if (check == .string) {
|
|
316
|
+
const c = check.string;
|
|
317
|
+
if (std.mem.eql(u8, c, UNDEFMARK)) return base == .null;
|
|
318
|
+
if (std.mem.eql(u8, c, EXISTSMARK)) return base != .null;
|
|
319
|
+
|
|
320
|
+
const basestr = stringifyAlloc(alloc, base);
|
|
321
|
+
|
|
322
|
+
// A /pattern/ is a regex over the stringified base.
|
|
323
|
+
if (2 <= c.len and c[0] == '/' and c[c.len - 1] == '/') {
|
|
324
|
+
const pat = c[1 .. c.len - 1];
|
|
325
|
+
var re = regex.compile(alloc, pat) orelse return false;
|
|
326
|
+
defer re.deinit();
|
|
327
|
+
return re.isMatch(basestr);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// Otherwise a case-insensitive substring, as the reference does.
|
|
331
|
+
const lc_base = std.ascii.allocLowerString(alloc, basestr) catch return false;
|
|
332
|
+
const lc_check = std.ascii.allocLowerString(alloc, c) catch return false;
|
|
333
|
+
return std.mem.indexOf(u8, lc_base, lc_check) != null;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
return false;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
fn getPath(base: StdJsonValue, path: []const []const u8) StdJsonValue {
|
|
340
|
+
var cur = base;
|
|
341
|
+
for (path) |seg| {
|
|
342
|
+
switch (cur) {
|
|
343
|
+
.object => |o| cur = o.get(seg) orelse return StdJsonValue{ .null = {} },
|
|
344
|
+
.array => |a| {
|
|
345
|
+
const idx = std.fmt.parseInt(usize, seg, 10) catch return StdJsonValue{ .null = {} };
|
|
346
|
+
if (idx >= a.items.len) return StdJsonValue{ .null = {} };
|
|
347
|
+
cur = a.items[idx];
|
|
348
|
+
},
|
|
349
|
+
else => return StdJsonValue{ .null = {} },
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
return cur;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/// Walk `check`; every LEAF must matchval against the same path in `base`.
|
|
356
|
+
pub fn doMatch(
|
|
357
|
+
alloc: Allocator,
|
|
358
|
+
check: StdJsonValue,
|
|
359
|
+
base: StdJsonValue,
|
|
360
|
+
path: *std.ArrayList([]const u8),
|
|
361
|
+
) !void {
|
|
362
|
+
switch (check) {
|
|
363
|
+
.object => |o| {
|
|
364
|
+
var it = o.iterator();
|
|
365
|
+
while (it.next()) |kv| {
|
|
366
|
+
try path.append(kv.key_ptr.*);
|
|
367
|
+
try doMatch(alloc, kv.value_ptr.*, base, path);
|
|
368
|
+
_ = path.pop();
|
|
369
|
+
}
|
|
370
|
+
},
|
|
371
|
+
.array => |a| {
|
|
372
|
+
for (a.items, 0..) |item, i| {
|
|
373
|
+
const seg = try std.fmt.allocPrint(alloc, "{d}", .{i});
|
|
374
|
+
try path.append(seg);
|
|
375
|
+
try doMatch(alloc, item, base, path);
|
|
376
|
+
_ = path.pop();
|
|
377
|
+
}
|
|
378
|
+
},
|
|
379
|
+
else => {
|
|
380
|
+
const baseval = getPath(base, path.items);
|
|
381
|
+
if (!matchval(alloc, check, baseval)) {
|
|
382
|
+
std.debug.print("\n MATCH: {s}: [{s}] <=> [{s}]\n", .{
|
|
383
|
+
joinPath(alloc, path.items),
|
|
384
|
+
fmtJson(check),
|
|
385
|
+
fmtJson(baseval),
|
|
386
|
+
});
|
|
387
|
+
return error.MatchMismatch;
|
|
388
|
+
}
|
|
389
|
+
},
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
fn joinPath(alloc: Allocator, parts: []const []const u8) []const u8 {
|
|
394
|
+
return std.mem.join(alloc, ".", parts) catch "?";
|
|
395
|
+
}
|
|
396
|
+
|
|
159
397
|
// ---- Result comparison (operates on std.json.Value) ----
|
|
160
398
|
|
|
161
399
|
fn checkResult(expected: StdJsonValue, result: StdJsonValue) !void {
|