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.
- package/LICENSE +5 -0
- package/README.md +113 -0
- package/bin/zuzu +17 -0
- package/bin/zuzu-build-browser-bundle +57 -0
- package/bin/zuzu-generate-browser-stdlib +584 -0
- package/bin/zuzu-js +23 -0
- package/bin/zuzu-js-compile +152 -0
- package/bin/zuzu-js-electron +19 -0
- package/dist/zuzu-browser-worker.js +45574 -0
- package/dist/zuzu-browser.js +45362 -0
- package/lib/browser-bundle-entry.js +160 -0
- package/lib/browser-gui-renderer.js +387 -0
- package/lib/browser-runtime.js +167 -0
- package/lib/browser-worker-entry.js +413 -0
- package/lib/browser-ztests/runner.html +103 -0
- package/lib/browser-ztests/runner.js +369 -0
- package/lib/cli.js +350 -0
- package/lib/collections.js +367 -0
- package/lib/compiler.js +303 -0
- package/lib/electron/launcher.js +70 -0
- package/lib/electron/main.js +956 -0
- package/lib/electron/preload.js +80 -0
- package/lib/electron/renderer.html +122 -0
- package/lib/electron/renderer.js +24 -0
- package/lib/execution-metadata.js +18 -0
- package/lib/gui/dom-renderer.js +778 -0
- package/lib/host/browser-host.js +278 -0
- package/lib/host/capabilities.js +47 -0
- package/lib/host/electron-host.js +15 -0
- package/lib/host/node-host.js +74 -0
- package/lib/paths.js +150 -0
- package/lib/runtime-entrypoints.js +60 -0
- package/lib/runtime-helpers.js +886 -0
- package/lib/runtime.js +3529 -0
- package/lib/tap.js +37 -0
- package/lib/transpiler-new/ast.js +23 -0
- package/lib/transpiler-new/codegen.js +2455 -0
- package/lib/transpiler-new/errors.js +28 -0
- package/lib/transpiler-new/index.js +26 -0
- package/lib/transpiler-new/lexer.js +834 -0
- package/lib/transpiler-new/parser.js +2332 -0
- package/lib/transpiler-new/validate-bindings.js +326 -0
- package/lib/transpiler-utils.js +95 -0
- package/lib/transpiler.js +33 -0
- package/lib/zuzu.js +53 -0
- package/modules/javascript.js +193 -0
- package/modules/std/archive.js +603 -0
- package/modules/std/clib.js +338 -0
- package/modules/std/data/csv.js +1331 -0
- package/modules/std/data/json.js +531 -0
- package/modules/std/data/xml.js +441 -0
- package/modules/std/data/yaml.js +256 -0
- package/modules/std/db-worker.js +250 -0
- package/modules/std/db.js +664 -0
- package/modules/std/digest/_hash.js +443 -0
- package/modules/std/digest/md5.js +26 -0
- package/modules/std/digest/sha.js +72 -0
- package/modules/std/eval.js +10 -0
- package/modules/std/gui/objects.js +1519 -0
- package/modules/std/internals.js +571 -0
- package/modules/std/io/socks-worker.js +318 -0
- package/modules/std/io/socks.js +186 -0
- package/modules/std/io.js +475 -0
- package/modules/std/marshal/cbor.js +463 -0
- package/modules/std/marshal/graph.js +1624 -0
- package/modules/std/marshal.js +87 -0
- package/modules/std/math/bignum.js +91 -0
- package/modules/std/math.js +79 -0
- package/modules/std/net/dns.js +306 -0
- package/modules/std/net/http.js +820 -0
- package/modules/std/net/smtp.js +943 -0
- package/modules/std/net/url.js +109 -0
- package/modules/std/proc.js +602 -0
- package/modules/std/secure.js +3724 -0
- package/modules/std/string/base64.js +138 -0
- package/modules/std/string.js +299 -0
- package/modules/std/task.js +914 -0
- package/modules/std/time.js +579 -0
- package/modules/std/tui.js +188 -0
- package/modules/std/worker-thread.js +246 -0
- package/modules/std/worker.js +790 -0
- package/package.json +67 -0
- package/stdlib/modules/javascript.zzm +99 -0
- package/stdlib/modules/perl.zzm +105 -0
- package/stdlib/modules/std/archive.zzm +132 -0
- package/stdlib/modules/std/cache/lru.zzm +174 -0
- package/stdlib/modules/std/clib.zzm +112 -0
- package/stdlib/modules/std/colour.zzm +220 -0
- package/stdlib/modules/std/config.zzm +818 -0
- package/stdlib/modules/std/data/cbor.zzm +497 -0
- package/stdlib/modules/std/data/csv.zzm +285 -0
- package/stdlib/modules/std/data/ini.zzm +472 -0
- package/stdlib/modules/std/data/json/schema/core.zzm +573 -0
- package/stdlib/modules/std/data/json/schema/format.zzm +581 -0
- package/stdlib/modules/std/data/json/schema/model.zzm +255 -0
- package/stdlib/modules/std/data/json/schema/output.zzm +272 -0
- package/stdlib/modules/std/data/json/schema/relative_pointer.zzm +299 -0
- package/stdlib/modules/std/data/json/schema/validation.zzm +1503 -0
- package/stdlib/modules/std/data/json/schema.zzm +306 -0
- package/stdlib/modules/std/data/json.zzm +102 -0
- package/stdlib/modules/std/data/kdl/json.zzm +460 -0
- package/stdlib/modules/std/data/kdl/xml.zzm +387 -0
- package/stdlib/modules/std/data/kdl.zzm +1631 -0
- package/stdlib/modules/std/data/toml.zzm +756 -0
- package/stdlib/modules/std/data/toon.zzm +1017 -0
- package/stdlib/modules/std/data/xml/escape.zzm +156 -0
- package/stdlib/modules/std/data/xml.zzm +276 -0
- package/stdlib/modules/std/data/yaml.zzm +94 -0
- package/stdlib/modules/std/db.zzm +173 -0
- package/stdlib/modules/std/defer.zzm +75 -0
- package/stdlib/modules/std/digest/crc32.zzm +196 -0
- package/stdlib/modules/std/digest/md5.zzm +54 -0
- package/stdlib/modules/std/digest/sha.zzm +83 -0
- package/stdlib/modules/std/dump.zzm +317 -0
- package/stdlib/modules/std/eval.zzm +63 -0
- package/stdlib/modules/std/getopt.zzm +432 -0
- package/stdlib/modules/std/gui/dialogue.zzm +592 -0
- package/stdlib/modules/std/gui/objects.zzm +123 -0
- package/stdlib/modules/std/gui.zzm +1914 -0
- package/stdlib/modules/std/internals.zzm +139 -0
- package/stdlib/modules/std/io/socks.zzm +139 -0
- package/stdlib/modules/std/io.zzm +157 -0
- package/stdlib/modules/std/lingua/en.zzm +347 -0
- package/stdlib/modules/std/log.zzm +169 -0
- package/stdlib/modules/std/mail.zzm +2726 -0
- package/stdlib/modules/std/marshal.zzm +138 -0
- package/stdlib/modules/std/math/bignum.zzm +98 -0
- package/stdlib/modules/std/math/range.zzm +116 -0
- package/stdlib/modules/std/math/roman.zzm +156 -0
- package/stdlib/modules/std/math.zzm +141 -0
- package/stdlib/modules/std/net/dns.zzm +93 -0
- package/stdlib/modules/std/net/http.zzm +278 -0
- package/stdlib/modules/std/net/smtp.zzm +257 -0
- package/stdlib/modules/std/net/url.zzm +69 -0
- package/stdlib/modules/std/path/jsonpointer.zzm +526 -0
- package/stdlib/modules/std/path/kdl.zzm +1003 -0
- package/stdlib/modules/std/path/simple.zzm +520 -0
- package/stdlib/modules/std/path/z/context.zzm +147 -0
- package/stdlib/modules/std/path/z/evaluate.zzm +549 -0
- package/stdlib/modules/std/path/z/functions.zzm +874 -0
- package/stdlib/modules/std/path/z/lexer.zzm +490 -0
- package/stdlib/modules/std/path/z/node.zzm +1455 -0
- package/stdlib/modules/std/path/z/operators.zzm +445 -0
- package/stdlib/modules/std/path/z/parser.zzm +359 -0
- package/stdlib/modules/std/path/z.zzm +403 -0
- package/stdlib/modules/std/path/zz/functions.zzm +828 -0
- package/stdlib/modules/std/path/zz/operators.zzm +1036 -0
- package/stdlib/modules/std/path/zz.zzm +100 -0
- package/stdlib/modules/std/proc.zzm +155 -0
- package/stdlib/modules/std/result.zzm +149 -0
- package/stdlib/modules/std/secure.zzm +606 -0
- package/stdlib/modules/std/string/base64.zzm +66 -0
- package/stdlib/modules/std/string/quoted_printable.zzm +485 -0
- package/stdlib/modules/std/string.zzm +179 -0
- package/stdlib/modules/std/task.zzm +221 -0
- package/stdlib/modules/std/template/z.zzm +531 -0
- package/stdlib/modules/std/template/zz.zzm +62 -0
- package/stdlib/modules/std/time.zzm +188 -0
- package/stdlib/modules/std/tui.zzm +89 -0
- package/stdlib/modules/std/uuid.zzm +223 -0
- package/stdlib/modules/std/web/session.zzm +388 -0
- package/stdlib/modules/std/web/static.zzm +329 -0
- package/stdlib/modules/std/web.zzm +1942 -0
- package/stdlib/modules/std/worker.zzm +202 -0
- package/stdlib/modules/std/zuzuzoo.zzm +3960 -0
- package/stdlib/modules/test/more.zzm +528 -0
- 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
|