zuzu-js 0.1.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.
Files changed (167) hide show
  1. package/LICENSE +5 -0
  2. package/README.md +113 -0
  3. package/bin/zuzu +17 -0
  4. package/bin/zuzu-build-browser-bundle +57 -0
  5. package/bin/zuzu-generate-browser-stdlib +584 -0
  6. package/bin/zuzu-js +23 -0
  7. package/bin/zuzu-js-compile +152 -0
  8. package/bin/zuzu-js-electron +19 -0
  9. package/dist/zuzu-browser-worker.js +45574 -0
  10. package/dist/zuzu-browser.js +45362 -0
  11. package/lib/browser-bundle-entry.js +160 -0
  12. package/lib/browser-gui-renderer.js +387 -0
  13. package/lib/browser-runtime.js +167 -0
  14. package/lib/browser-worker-entry.js +413 -0
  15. package/lib/browser-ztests/runner.html +103 -0
  16. package/lib/browser-ztests/runner.js +369 -0
  17. package/lib/cli.js +350 -0
  18. package/lib/collections.js +367 -0
  19. package/lib/compiler.js +303 -0
  20. package/lib/electron/launcher.js +70 -0
  21. package/lib/electron/main.js +956 -0
  22. package/lib/electron/preload.js +80 -0
  23. package/lib/electron/renderer.html +122 -0
  24. package/lib/electron/renderer.js +24 -0
  25. package/lib/execution-metadata.js +18 -0
  26. package/lib/gui/dom-renderer.js +778 -0
  27. package/lib/host/browser-host.js +278 -0
  28. package/lib/host/capabilities.js +47 -0
  29. package/lib/host/electron-host.js +15 -0
  30. package/lib/host/node-host.js +74 -0
  31. package/lib/paths.js +150 -0
  32. package/lib/runtime-entrypoints.js +60 -0
  33. package/lib/runtime-helpers.js +886 -0
  34. package/lib/runtime.js +3529 -0
  35. package/lib/tap.js +37 -0
  36. package/lib/transpiler-new/ast.js +23 -0
  37. package/lib/transpiler-new/codegen.js +2455 -0
  38. package/lib/transpiler-new/errors.js +28 -0
  39. package/lib/transpiler-new/index.js +26 -0
  40. package/lib/transpiler-new/lexer.js +834 -0
  41. package/lib/transpiler-new/parser.js +2332 -0
  42. package/lib/transpiler-new/validate-bindings.js +326 -0
  43. package/lib/transpiler-utils.js +95 -0
  44. package/lib/transpiler.js +33 -0
  45. package/lib/zuzu.js +53 -0
  46. package/modules/javascript.js +193 -0
  47. package/modules/std/archive.js +603 -0
  48. package/modules/std/clib.js +338 -0
  49. package/modules/std/data/csv.js +1331 -0
  50. package/modules/std/data/json.js +531 -0
  51. package/modules/std/data/xml.js +441 -0
  52. package/modules/std/data/yaml.js +256 -0
  53. package/modules/std/db-worker.js +250 -0
  54. package/modules/std/db.js +664 -0
  55. package/modules/std/digest/_hash.js +443 -0
  56. package/modules/std/digest/md5.js +26 -0
  57. package/modules/std/digest/sha.js +72 -0
  58. package/modules/std/eval.js +10 -0
  59. package/modules/std/gui/objects.js +1519 -0
  60. package/modules/std/internals.js +571 -0
  61. package/modules/std/io/socks-worker.js +318 -0
  62. package/modules/std/io/socks.js +186 -0
  63. package/modules/std/io.js +475 -0
  64. package/modules/std/marshal/cbor.js +463 -0
  65. package/modules/std/marshal/graph.js +1624 -0
  66. package/modules/std/marshal.js +87 -0
  67. package/modules/std/math/bignum.js +91 -0
  68. package/modules/std/math.js +79 -0
  69. package/modules/std/net/dns.js +306 -0
  70. package/modules/std/net/http.js +820 -0
  71. package/modules/std/net/smtp.js +943 -0
  72. package/modules/std/net/url.js +109 -0
  73. package/modules/std/proc.js +602 -0
  74. package/modules/std/secure.js +3724 -0
  75. package/modules/std/string/base64.js +138 -0
  76. package/modules/std/string.js +299 -0
  77. package/modules/std/task.js +914 -0
  78. package/modules/std/time.js +579 -0
  79. package/modules/std/tui.js +188 -0
  80. package/modules/std/worker-thread.js +246 -0
  81. package/modules/std/worker.js +790 -0
  82. package/package.json +67 -0
  83. package/stdlib/modules/javascript.zzm +99 -0
  84. package/stdlib/modules/perl.zzm +105 -0
  85. package/stdlib/modules/std/archive.zzm +132 -0
  86. package/stdlib/modules/std/cache/lru.zzm +174 -0
  87. package/stdlib/modules/std/clib.zzm +112 -0
  88. package/stdlib/modules/std/colour.zzm +220 -0
  89. package/stdlib/modules/std/config.zzm +818 -0
  90. package/stdlib/modules/std/data/cbor.zzm +497 -0
  91. package/stdlib/modules/std/data/csv.zzm +285 -0
  92. package/stdlib/modules/std/data/ini.zzm +472 -0
  93. package/stdlib/modules/std/data/json/schema/core.zzm +573 -0
  94. package/stdlib/modules/std/data/json/schema/format.zzm +581 -0
  95. package/stdlib/modules/std/data/json/schema/model.zzm +255 -0
  96. package/stdlib/modules/std/data/json/schema/output.zzm +272 -0
  97. package/stdlib/modules/std/data/json/schema/relative_pointer.zzm +299 -0
  98. package/stdlib/modules/std/data/json/schema/validation.zzm +1503 -0
  99. package/stdlib/modules/std/data/json/schema.zzm +306 -0
  100. package/stdlib/modules/std/data/json.zzm +102 -0
  101. package/stdlib/modules/std/data/kdl/json.zzm +460 -0
  102. package/stdlib/modules/std/data/kdl/xml.zzm +387 -0
  103. package/stdlib/modules/std/data/kdl.zzm +1631 -0
  104. package/stdlib/modules/std/data/toml.zzm +756 -0
  105. package/stdlib/modules/std/data/toon.zzm +1017 -0
  106. package/stdlib/modules/std/data/xml/escape.zzm +156 -0
  107. package/stdlib/modules/std/data/xml.zzm +276 -0
  108. package/stdlib/modules/std/data/yaml.zzm +94 -0
  109. package/stdlib/modules/std/db.zzm +173 -0
  110. package/stdlib/modules/std/defer.zzm +75 -0
  111. package/stdlib/modules/std/digest/crc32.zzm +196 -0
  112. package/stdlib/modules/std/digest/md5.zzm +54 -0
  113. package/stdlib/modules/std/digest/sha.zzm +83 -0
  114. package/stdlib/modules/std/dump.zzm +317 -0
  115. package/stdlib/modules/std/eval.zzm +63 -0
  116. package/stdlib/modules/std/getopt.zzm +432 -0
  117. package/stdlib/modules/std/gui/dialogue.zzm +592 -0
  118. package/stdlib/modules/std/gui/objects.zzm +123 -0
  119. package/stdlib/modules/std/gui.zzm +1914 -0
  120. package/stdlib/modules/std/internals.zzm +139 -0
  121. package/stdlib/modules/std/io/socks.zzm +139 -0
  122. package/stdlib/modules/std/io.zzm +157 -0
  123. package/stdlib/modules/std/lingua/en.zzm +347 -0
  124. package/stdlib/modules/std/log.zzm +169 -0
  125. package/stdlib/modules/std/mail.zzm +2726 -0
  126. package/stdlib/modules/std/marshal.zzm +138 -0
  127. package/stdlib/modules/std/math/bignum.zzm +98 -0
  128. package/stdlib/modules/std/math/range.zzm +116 -0
  129. package/stdlib/modules/std/math/roman.zzm +156 -0
  130. package/stdlib/modules/std/math.zzm +141 -0
  131. package/stdlib/modules/std/net/dns.zzm +93 -0
  132. package/stdlib/modules/std/net/http.zzm +278 -0
  133. package/stdlib/modules/std/net/smtp.zzm +257 -0
  134. package/stdlib/modules/std/net/url.zzm +69 -0
  135. package/stdlib/modules/std/path/jsonpointer.zzm +526 -0
  136. package/stdlib/modules/std/path/kdl.zzm +1003 -0
  137. package/stdlib/modules/std/path/simple.zzm +520 -0
  138. package/stdlib/modules/std/path/z/context.zzm +147 -0
  139. package/stdlib/modules/std/path/z/evaluate.zzm +549 -0
  140. package/stdlib/modules/std/path/z/functions.zzm +874 -0
  141. package/stdlib/modules/std/path/z/lexer.zzm +490 -0
  142. package/stdlib/modules/std/path/z/node.zzm +1455 -0
  143. package/stdlib/modules/std/path/z/operators.zzm +445 -0
  144. package/stdlib/modules/std/path/z/parser.zzm +359 -0
  145. package/stdlib/modules/std/path/z.zzm +403 -0
  146. package/stdlib/modules/std/path/zz/functions.zzm +828 -0
  147. package/stdlib/modules/std/path/zz/operators.zzm +1036 -0
  148. package/stdlib/modules/std/path/zz.zzm +100 -0
  149. package/stdlib/modules/std/proc.zzm +155 -0
  150. package/stdlib/modules/std/result.zzm +149 -0
  151. package/stdlib/modules/std/secure.zzm +606 -0
  152. package/stdlib/modules/std/string/base64.zzm +66 -0
  153. package/stdlib/modules/std/string/quoted_printable.zzm +485 -0
  154. package/stdlib/modules/std/string.zzm +179 -0
  155. package/stdlib/modules/std/task.zzm +221 -0
  156. package/stdlib/modules/std/template/z.zzm +531 -0
  157. package/stdlib/modules/std/template/zz.zzm +62 -0
  158. package/stdlib/modules/std/time.zzm +188 -0
  159. package/stdlib/modules/std/tui.zzm +89 -0
  160. package/stdlib/modules/std/uuid.zzm +223 -0
  161. package/stdlib/modules/std/web/session.zzm +388 -0
  162. package/stdlib/modules/std/web/static.zzm +329 -0
  163. package/stdlib/modules/std/web.zzm +1942 -0
  164. package/stdlib/modules/std/worker.zzm +202 -0
  165. package/stdlib/modules/std/zuzuzoo.zzm +3960 -0
  166. package/stdlib/modules/test/more.zzm +528 -0
  167. package/stdlib/modules/test/parser.zzm +209 -0
@@ -0,0 +1,528 @@
1
+ =encoding utf8
2
+
3
+ =head1 NAME
4
+
5
+ test/more - Write unit tests and integration tests in ZuzuScript.
6
+
7
+ =head1 SYNOPSIS
8
+
9
+ from test/more import *;
10
+ from my/project import frobnicate;
11
+
12
+ is(frobnicate(21), 42, "frobinated 21 correctly");
13
+ is(frobnicate(null), null, "frobinate on null input");
14
+
15
+ done_testing();
16
+
17
+ =cut
18
+
19
+
20
+ let Number _COUNT := 0;
21
+ let Number _PASSED := 0;
22
+ let Number _FAILED := 0;
23
+ let Number _LEVEL := 0;
24
+ let Number _PLAN := -1;
25
+ let Number _TODO_FAILED := 0;
26
+ let Number _TODO_PASSED := 0;
27
+
28
+ let TODO;
29
+
30
+ function _directive () {
31
+ if ( TODO ) {
32
+ if ( TODO instanceof String ) {
33
+ return ` # TODO ${TODO}`;
34
+ }
35
+ else {
36
+ return " # TODO";
37
+ }
38
+ }
39
+ return "";
40
+ }
41
+
42
+ function _indent () {
43
+ let i := _LEVEL;
44
+ let str := "";
45
+ while ( i > 0 ) {
46
+ str _= " ";
47
+ i--;
48
+ }
49
+
50
+ return str;
51
+ }
52
+
53
+ class BailOutException extends Exception;
54
+ class SkipAllException extends Exception;
55
+
56
+ function _module_name_is_valid ( String module ) {
57
+ return module ~ /^[A-Za-z_][A-Za-z0-9_]*(\/[A-Za-z_][A-Za-z0-9_]*)*$/;
58
+ }
59
+
60
+ function _module_is_available ( String module ) {
61
+ from std/eval import eval;
62
+
63
+ try {
64
+ eval( `do { from ${module} import *; }; true;` );
65
+ return true;
66
+ }
67
+ catch {
68
+ return false;
69
+ }
70
+ }
71
+
72
+ function _capability_is_available ( String capability ) {
73
+ if ( not( capability ~ /^[A-Za-z_][A-Za-z0-9_]*$/ ) ) {
74
+ die `Invalid capability name: ${capability}`;
75
+ }
76
+
77
+ let deny_key := `deny_${capability}`;
78
+ if ( deny_key in __system__ ) {
79
+ return not __system__.get(deny_key);
80
+ }
81
+
82
+ return false;
83
+ }
84
+
85
+ =head1 IMPLEMENTATION SUPPORT
86
+
87
+ This module is supported by all implementations of ZuzuScript.
88
+
89
+ =head1 DESCRIPTION
90
+
91
+ C<< test/more >> is a module for test-driven development. It can be used
92
+ for writing unit tests and integration tests. It generates output in TAP
93
+ L<https://testanything.org/>.
94
+
95
+ This module should feel familiar to anybody who has used C<< Test::More >>
96
+ for Perl, though there are some minor differences.
97
+
98
+ =head2 Functions
99
+
100
+ =over
101
+
102
+ =item C<< pass(String name?) >>
103
+
104
+ Indicates the named test has passed.
105
+
106
+ =cut
107
+
108
+ function pass ( String name? ) {
109
+ ++_PASSED;
110
+ ++_COUNT;
111
+ ++_TODO_PASSED if TODO;
112
+ if ( name ≡ null ) {
113
+ say `${_indent()}ok ${_COUNT}${_directive()}`;
114
+ }
115
+ else {
116
+ say `${_indent()}ok ${_COUNT} - ${name}${_directive()}`;
117
+ }
118
+
119
+ return true;
120
+ }
121
+
122
+ =item C<< fail(String name?) >>
123
+
124
+ Indicates the named test has failed.
125
+
126
+ =cut
127
+
128
+ function fail ( String name ) {
129
+ ++_FAILED;
130
+ ++_COUNT;
131
+ ++_TODO_FAILED if TODO;
132
+ if ( name ≡ null ) {
133
+ say `${_indent()}not ok ${_COUNT}${_directive()}`;
134
+ }
135
+ else {
136
+ say `${_indent()}not ok ${_COUNT} - ${name}${_directive()}`;
137
+ }
138
+
139
+ return false;
140
+ }
141
+
142
+ =item C<< ok(expr, String name?) >>
143
+
144
+ Passes the named test only if C<expr> is truthy.
145
+
146
+ =cut
147
+
148
+ function ok ( expr, String name? ) {
149
+ if (expr) {
150
+ return pass(name);
151
+ }
152
+ else {
153
+ return fail(name);
154
+ }
155
+ }
156
+
157
+ function _coerced_equal ( got, expected ) {
158
+ if ( got ≡ expected ) {
159
+ return true;
160
+ }
161
+
162
+ if ( got instanceof Boolean and expected instanceof Number ) {
163
+ return ( got ? 1: 0 ) = expected;
164
+ }
165
+ if ( got instanceof Number and expected instanceof Boolean ) {
166
+ return got = ( expected ? 1: 0 );
167
+ }
168
+
169
+ return false;
170
+ }
171
+
172
+ =item C<< is(got, expected, String name?) >>
173
+
174
+ Passes the named test only if C<< got ≡ expected >>.
175
+
176
+ =cut
177
+
178
+ function is ( got, expected, String name? ) {
179
+
180
+ if ( not ok( _coerced_equal( got, expected ), name ) ) {
181
+ from std/dump import Dumper;
182
+ say `${_indent()}# expected: ${Dumper.dump(expected)}`;
183
+ say `${_indent()}# got: ${Dumper.dump(got)}`;
184
+ return false;
185
+ }
186
+
187
+ return true;
188
+ }
189
+
190
+ =item C<< isnt(got, unexpected, String name?) >>
191
+
192
+ Passes the named test only if C<< got ≢ expected >>.
193
+
194
+ =cut
195
+
196
+ function isnt ( got, unexpected, String name? ) {
197
+ return ok( not _coerced_equal( got, unexpected ), name );
198
+ }
199
+
200
+ =item C<< like(got, expected_re, String name?) >>
201
+
202
+ Passes the named test only if C<< got ~ expected >>.
203
+
204
+ =cut
205
+
206
+ function like ( got, Regexp expected_re, String name? ) {
207
+
208
+ if ( not ok( got ~ expected_re, name ) ) {
209
+ say `${_indent()}# expected (regexp): ${expected_re}`;
210
+ say `${_indent()}# got: ${got}`;
211
+ return false;
212
+ }
213
+
214
+ return true;
215
+ }
216
+
217
+ =item C<< unlike(got, unexpected_re, String name?) >>
218
+
219
+ Fails the named test only if C<< got ~ expected >>.
220
+
221
+ =cut
222
+
223
+ function unlike ( got, Regexp unexpected_re, String name? ) {
224
+
225
+ if ( not ok( not( got ~ unexpected_re ), name ) ) {
226
+ say `${_indent()}# unexpected (regexp): ${unexpected_re}`;
227
+ say `${_indent()}# got: ${got}`;
228
+ return false;
229
+ }
230
+
231
+ return true;
232
+ }
233
+
234
+ =item C<< diag(diagnostic) >>
235
+
236
+ Outputs the diagnostic.
237
+
238
+ =cut
239
+
240
+ function diag (diagnostic) {
241
+ say `${_indent()}# ${diagnostic}`;
242
+ return true;
243
+ }
244
+
245
+ =item C<< explain(value) >>
246
+
247
+ Combines diag with Dumper.dump();
248
+
249
+ =cut
250
+
251
+ function explain (value) {
252
+ from std/dump import Dumper;
253
+ return diag( Dumper.dump(value) );
254
+ }
255
+
256
+ =item C<< bail_out(String message?) >>
257
+
258
+ Bail out of running further tests.
259
+
260
+ =cut
261
+
262
+ function bail_out ( String message? ) {
263
+ let e;
264
+ if ( message ≡ null ) {
265
+ e := new BailOutException( message: "Bail out!" );
266
+ }
267
+ else {
268
+ e := new BailOutException( message: `Bail out! ${message}` );
269
+ }
270
+ throw e;
271
+ }
272
+
273
+ =item C<< skip_all(String reason) >>
274
+
275
+ Skips the whole test file by emitting a TAP skip-all plan and exiting
276
+ successfully when the runtime provides C<std/proc>.
277
+
278
+ This is intended for guards at the beginning of a test script, before
279
+ any tests have run. Browser-like hosts without C<std/proc> use a
280
+ special exception fallback that the browser ztest runner treats as a
281
+ skip.
282
+
283
+ =cut
284
+
285
+ function skip_all ( String reason ) {
286
+ die "Cannot skip all tests after tests have already run" if _COUNT > 0;
287
+ die "Cannot skip all tests in a subtest" if _LEVEL > 0;
288
+ _PLAN := 0;
289
+ say `1..${_PLAN} # SKIP: ${reason}`;
290
+
291
+ from std/proc try import Proc;
292
+ Proc.exit(0) if Proc ≢ null;
293
+ throw new SkipAllException( message: reason );
294
+ }
295
+
296
+ =item C<< requires_module(String module) >>
297
+
298
+ Skips the whole test file if the named module cannot be loaded.
299
+
300
+ =cut
301
+
302
+ function requires_module ( String module ) {
303
+ if ( not _module_name_is_valid(module) ) {
304
+ die `Invalid module name: ${module}`;
305
+ }
306
+
307
+ if ( not _module_is_available(module) ) {
308
+ skip_all( `module ${module} is unavailable` );
309
+ }
310
+
311
+ return true;
312
+ }
313
+
314
+ =item C<< requires_capability(String capability) >>
315
+
316
+ Skips the whole test file if the named runtime capability is not
317
+ available.
318
+
319
+ =cut
320
+
321
+ function requires_capability ( String capability ) {
322
+ if ( not _capability_is_available(capability) ) {
323
+ skip_all( `capability ${capability} is unavailable` );
324
+ }
325
+
326
+ return true;
327
+ }
328
+
329
+ =item C<< author_testing() >>
330
+
331
+ Skips the whole test file unless the C<AUTHOR_TESTING> environment
332
+ variable is set to a true value.
333
+
334
+ =cut
335
+
336
+ function author_testing () {
337
+ requires_capability( "proc" );
338
+ from std/proc try import Env;
339
+ if ( Env and Env.get( "AUTHOR_TESTING", false ) ) {
340
+ return true;
341
+ }
342
+ skip_all( "requires AUTHOR_TESTING=1" );
343
+ }
344
+
345
+ =item C<< extended_testing() >>
346
+
347
+ Skips the whole test file unless the C<EXTENDED_TESTING> environment
348
+ variable is set to a true value.
349
+
350
+ =cut
351
+
352
+ function extended_testing () {
353
+ requires_capability( "proc" );
354
+ from std/proc try import Env;
355
+ if ( Env and Env.get( "EXTENDED_TESTING", false ) ) {
356
+ return true;
357
+ }
358
+ skip_all( "requires EXTENDED_TESTING=1" );
359
+ }
360
+
361
+ =item C<< plan(Number n) >>
362
+
363
+ Indicate the number of tests you intend on running, before running any
364
+ tests.
365
+
366
+ done_testing() can later check the number.
367
+
368
+ =cut
369
+
370
+ function plan ( Number n ) {
371
+ die "Must plan() before running any tests" if _COUNT > 0;
372
+ die "Cannot plan() in a subtest" if _LEVEL > 0;
373
+ die "Must plan() at least one test" if n < 1;
374
+ _PLAN := n;
375
+ say `1..${_PLAN}`;
376
+ }
377
+
378
+ =item C<< done_testing(Number n?) >>
379
+
380
+ Indicates that testing is finished.
381
+
382
+ You may optionally provide the expected total number of tests, and the
383
+ function will throw an exception if that doesn't match the number of
384
+ tests which were actually run.
385
+
386
+ Will also throw an error if you called plan() earlier and the actual
387
+ number of tests run differs from your plan.
388
+
389
+ =cut
390
+
391
+ function done_testing ( Number n? ) {
392
+ die "Unexpected done_testing() in subtest" if _LEVEL > 0;
393
+ die `Expected ${n} tests, but ran ${_COUNT}` if n ≢ null and n ≠ _COUNT;
394
+
395
+ function maybe_fail_count ( should_die ) {
396
+ if ( _TODO_PASSED > 0 ) {
397
+ warn `Passed ${_TODO_PASSED} TODO tests!`;
398
+ }
399
+ if ( _FAILED > 0 ) {
400
+ let msg := `Failed ${_FAILED} tests`;
401
+ msg _= ` (${_TODO_FAILED} todo, ${_FAILED - _TODO_FAILED} true fails)` if _TODO_FAILED > 0;
402
+ die msg if should_die and ( _FAILED > _TODO_FAILED );
403
+ warn msg;
404
+ }
405
+ }
406
+
407
+ if ( _PLAN < 0 ) {
408
+ say `1..${_COUNT}`;
409
+ }
410
+ else if ( _PLAN ≠ _COUNT ) {
411
+ maybe_fail_count(false);
412
+ die `Planned ${_PLAN} tests, but ran ${_COUNT}`;
413
+ }
414
+
415
+ maybe_fail_count(true);
416
+ return true;
417
+ }
418
+
419
+ =item C<< subtest(String name, Function f) >>
420
+
421
+ Calls f() as a subtest.
422
+
423
+ Don't use done_testing() within the function.
424
+
425
+ =cut
426
+
427
+ function subtest ( String name, Function f ) {
428
+ let orig_context := {
429
+ count: _COUNT,
430
+ passed: _PASSED,
431
+ failed: _FAILED,
432
+ level: _LEVEL,
433
+ todo_passed: _TODO_PASSED,
434
+ todo_failed: _TODO_FAILED,
435
+ };
436
+
437
+ function restore_context () {
438
+ _COUNT := orig_context{count};
439
+ _PASSED := orig_context{passed};
440
+ _FAILED := orig_context{failed};
441
+ _LEVEL := orig_context{level};
442
+ _TODO_PASSED := orig_context{todo_passed};
443
+ _TODO_FAILED := orig_context{todo_failed};
444
+ }
445
+
446
+ _COUNT := 0;
447
+ _PASSED := 0;
448
+ _FAILED := 0;
449
+ _TODO_PASSED := 0;
450
+ _TODO_FAILED := 0;
451
+ _LEVEL++;
452
+ let is_ok := true;
453
+
454
+ try {
455
+ diag( `Subtest: ${name}` );
456
+ f();
457
+ say `${_indent()}1..${_COUNT}`;
458
+ is_ok := false if _FAILED > _TODO_FAILED;
459
+ restore_context();
460
+ }
461
+ catch ( BailOutException e ) {
462
+ throw e;
463
+ }
464
+ catch ( Exception e ) {
465
+ is_ok := false;
466
+ restore_context();
467
+ }
468
+
469
+ return ok( is_ok, name );
470
+ }
471
+
472
+ =back
473
+
474
+ =item C<< todo(Boolean c, String reason, Function f) >>
475
+
476
+ Runs the tests in the given function, but if condition c is true then
477
+ first sets TODO to true.
478
+
479
+ You can manually set and unset the TODO variable instead.
480
+
481
+ =cut
482
+
483
+ function todo ( Boolean c, String reason, Function f ) {
484
+ let orig := TODO;
485
+ TODO := reason if c;
486
+ try {
487
+ f();
488
+ }
489
+ catch {
490
+ }
491
+ TODO := orig;
492
+ return not c;
493
+ }
494
+
495
+ =back
496
+
497
+ =item C<< exception(Function f) >>
498
+
499
+ Calls f() and returns any exception thrown.
500
+
501
+ Returns null if no exception was thrown.
502
+
503
+ =cut
504
+
505
+ function exception ( Function f ) {
506
+ try {
507
+ f();
508
+ return null;
509
+ }
510
+ catch ( BailOutException e ) {
511
+ throw e;
512
+ }
513
+ catch ( Exception e ) {
514
+ return e;
515
+ }
516
+ }
517
+
518
+ =back
519
+
520
+ =head1 COPYRIGHT AND LICENCE
521
+
522
+ B<< test/more >> is copyright Toby Inkster.
523
+
524
+ It is free software; you may redistribute it and/or modify it under
525
+ the terms of either the Artistic License 1.0 or the GNU General Public
526
+ License version 2.
527
+
528
+ =cut
@@ -0,0 +1,209 @@
1
+ =encoding utf8
2
+
3
+ =head1 NAME
4
+
5
+ test/parser - Parse TAP output and summarize test counts.
6
+
7
+ =head1 SYNOPSIS
8
+
9
+ from test/parser import parse;
10
+
11
+ let tap := "ok 1 - alpha\nnot ok 2 - beta\n1..2\n";
12
+ let summary := parse( tap );
13
+
14
+ # Top-level counts
15
+ say( summary{top_level}{passed} );
16
+
17
+ # All assertions, including nested subtests
18
+ say( summary{assertions}{failed} );
19
+
20
+ # Top-level plan, if present
21
+ say( summary{planned} );
22
+
23
+ =head1 IMPLEMENTATION SUPPORT
24
+
25
+ This module is supported by all implementations of ZuzuScript.
26
+
27
+ =head1 DESCRIPTION
28
+
29
+ C<test/parser> parses lines in TAP (Test Anything Protocol) format and
30
+ returns a summary dictionary.
31
+
32
+ The summary tracks:
33
+
34
+ =over
35
+
36
+ =item * Top-level assertion outcomes.
37
+
38
+ A top-level assertion is any C<ok ...> or C<not ok ...> line with no
39
+ subtest indentation. A subtest block therefore counts as one
40
+ top-level test via the parent assertion line.
41
+
42
+ =item * Total assertion outcomes across all levels.
43
+
44
+ This includes assertions inside subtests, including nested subtests.
45
+
46
+ =item * Planned top-level test count.
47
+
48
+ This is the final number from a top-level C<1..N> plan line, or
49
+ C<null> if no top-level plan is present.
50
+
51
+ =item * Parsed test assertion rows.
52
+
53
+ Each parsed assertion is appended to C<tests>, including whether it is
54
+ at top level and its parsed number/description metadata.
55
+
56
+ =back
57
+
58
+ Outcome buckets are mutually exclusive and reported as C<passed>,
59
+ C<failed>, C<todo>, and C<skipped>. If an assertion line has a TODO or
60
+ SKIP directive, it is counted in that bucket rather than pass/fail.
61
+
62
+ =head1 EXPORTS
63
+
64
+ =head2 Functions
65
+
66
+ =over
67
+
68
+ =item C<< parse(String tap) >>
69
+
70
+ Parameters: C<tap> is TAP output text. Returns: C<Dict>. Parses a TAP
71
+ string and returns a summary dictionary.
72
+
73
+ =item C<< parse_lines(Array lines) >>
74
+
75
+ Parameters: C<lines> is an array of TAP lines. Returns: C<Dict>. Parses
76
+ TAP from already-split lines and returns the same summary dictionary as
77
+ C<parse>.
78
+
79
+ =back
80
+
81
+ =cut
82
+
83
+ from std/string import split, substr, trim;
84
+
85
+ function _new_counts () {
86
+ return {
87
+ passed: 0,
88
+ failed: 0,
89
+ todo: 0,
90
+ skipped: 0,
91
+ total: 0,
92
+ };
93
+ }
94
+
95
+ function _strip_indent ( String line ) {
96
+ let rest := line;
97
+ while ( rest ~ /^ / ) {
98
+ rest := substr( rest, 4 );
99
+ }
100
+ return rest;
101
+ }
102
+
103
+ function _bucket_for_assertion ( String line ) {
104
+ if ( line ~ /#\s*todo\b/i ) {
105
+ return "todo";
106
+ }
107
+ if ( line ~ /#\s*skip\b/i ) {
108
+ return "skipped";
109
+ }
110
+ if ( line ~ /^ok\b/ ) {
111
+ return "passed";
112
+ }
113
+ return "failed";
114
+ }
115
+
116
+ function _count_assertion ( Dict counts, String bucket ) {
117
+ counts.set( "total", counts.get( "total", 0 ) + 1 );
118
+ if ( bucket ≡ "passed" ) {
119
+ counts.set( "passed", counts.get( "passed", 0 ) + 1 );
120
+ }
121
+ else if ( bucket ≡ "failed" ) {
122
+ counts.set( "failed", counts.get( "failed", 0 ) + 1 );
123
+ }
124
+ else if ( bucket ≡ "todo" ) {
125
+ counts.set( "todo", counts.get( "todo", 0 ) + 1 );
126
+ }
127
+ else if ( bucket ≡ "skipped" ) {
128
+ counts.set( "skipped", counts.get( "skipped", 0 ) + 1 );
129
+ }
130
+ }
131
+
132
+ function _parse_test_row ( String body, String bucket, Boolean is_top_level ) {
133
+ let ok := body ~ /^ok\b/;
134
+ let rest := ok ? substr( body, 3 ): substr( body, 7 );
135
+ let number := null;
136
+ let description := "";
137
+
138
+ let parts := split( rest, " - ", 2 );
139
+ if ( parts.length() > 0 ) {
140
+ number := trim( parts [0] ) + 0;
141
+ }
142
+ if ( parts.length() > 1 ) {
143
+ let desc_parts := split( parts [1], " #", 2 );
144
+ description := trim( desc_parts [0] );
145
+ }
146
+
147
+ return {
148
+ ok: ok,
149
+ number: number,
150
+ description: description,
151
+ bucket: bucket,
152
+ top_level: is_top_level,
153
+ raw: body,
154
+ };
155
+ }
156
+
157
+ function parse_lines ( Array lines ) {
158
+ let top_level := _new_counts();
159
+ let assertions := _new_counts();
160
+ let planned := null;
161
+ let tests := [];
162
+
163
+ for ( let line in lines ) {
164
+ if ( line ≡ "" ) {
165
+ next;
166
+ }
167
+
168
+ let is_top_level := not ( line ~ /^ / );
169
+ let body := _strip_indent( line );
170
+
171
+ if ( body ~ /^\d+\.\.\d+\b/ ) {
172
+ if ( is_top_level ) {
173
+ let parts := split( body, "..", 2 );
174
+ planned := parts.get( 1, "" ) + 0;
175
+ }
176
+ next;
177
+ }
178
+
179
+ if ( body ~ /^ok\b/ or body ~ /^not ok\b/ ) {
180
+ let bucket := _bucket_for_assertion( body );
181
+ _count_assertion( assertions, bucket );
182
+ if ( is_top_level ) {
183
+ _count_assertion( top_level, bucket );
184
+ }
185
+ tests.push( _parse_test_row( body, bucket, is_top_level ) );
186
+ }
187
+ }
188
+
189
+ return {
190
+ top_level: top_level,
191
+ assertions: assertions,
192
+ planned: planned,
193
+ tests: tests,
194
+ };
195
+ }
196
+
197
+ function parse ( String tap ) {
198
+ return parse_lines( split( tap, /\r?\n/ ) );
199
+ }
200
+
201
+ =head1 COPYRIGHT AND LICENCE
202
+
203
+ B<< test/parser >> is copyright Toby Inkster.
204
+
205
+ It is free software; you may redistribute it and/or modify it under
206
+ the terms of either the Artistic License 1.0 or the GNU General Public
207
+ License version 2.
208
+
209
+ =cut