rapydscript-ns 0.9.2 → 0.9.4
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/CHANGELOG.md +28 -0
- package/PYTHON_GAPS.md +352 -0
- package/README.md +176 -32
- package/TODO.md +1 -128
- package/bin/rapydscript +70 -70
- package/language-service/index.js +242 -11
- package/memory/project_string_impl.md +43 -0
- package/package.json +1 -1
- package/release/baselib-plain-pretty.js +248 -38
- package/release/baselib-plain-ugly.js +8 -8
- package/release/compiler.js +778 -277
- package/release/signatures.json +30 -30
- package/src/ast.pyj +10 -1
- package/src/baselib-builtins.pyj +56 -2
- package/src/baselib-containers.pyj +25 -1
- package/src/baselib-errors.pyj +7 -3
- package/src/baselib-internal.pyj +51 -6
- package/src/baselib-str.pyj +18 -5
- package/src/lib/asyncio.pyj +534 -0
- package/src/lib/base64.pyj +399 -0
- package/src/lib/bisect.pyj +73 -0
- package/src/lib/collections.pyj +228 -4
- package/src/lib/csv.pyj +494 -0
- package/src/lib/heapq.pyj +98 -0
- package/src/lib/html.pyj +382 -0
- package/src/lib/http/__init__.pyj +98 -0
- package/src/lib/http/client.pyj +304 -0
- package/src/lib/http/cookies.pyj +236 -0
- package/src/lib/logging.pyj +672 -0
- package/src/lib/pprint.pyj +455 -0
- package/src/lib/pythonize.pyj +20 -20
- package/src/lib/statistics.pyj +0 -0
- package/src/lib/string.pyj +357 -0
- package/src/lib/textwrap.pyj +329 -0
- package/src/lib/urllib/__init__.pyj +14 -0
- package/src/lib/urllib/error.pyj +66 -0
- package/src/lib/urllib/parse.pyj +475 -0
- package/src/lib/urllib/request.pyj +86 -0
- package/src/monaco-language-service/analyzer.js +5 -2
- package/src/monaco-language-service/completions.js +26 -0
- package/src/monaco-language-service/diagnostics.js +203 -4
- package/src/monaco-language-service/scope.js +1 -0
- package/src/output/codegen.pyj +4 -1
- package/src/output/functions.pyj +152 -6
- package/src/output/loops.pyj +17 -2
- package/src/output/modules.pyj +1 -1
- package/src/output/operators.pyj +15 -0
- package/src/output/stream.pyj +0 -1
- package/src/parse.pyj +108 -24
- package/src/tokenizer.pyj +19 -3
- package/test/async_generators.pyj +144 -0
- package/test/asyncio.pyj +307 -0
- package/test/base64.pyj +202 -0
- package/test/baselib.pyj +23 -0
- package/test/bisect.pyj +178 -0
- package/test/chainmap.pyj +185 -0
- package/test/csv.pyj +405 -0
- package/test/float_special.pyj +64 -0
- package/test/heapq.pyj +174 -0
- package/test/html.pyj +212 -0
- package/test/http.pyj +259 -0
- package/test/imports.pyj +79 -72
- package/test/logging.pyj +356 -0
- package/test/long.pyj +130 -0
- package/test/parenthesized_with.pyj +141 -0
- package/test/pprint.pyj +232 -0
- package/test/python_compat.pyj +3 -5
- package/test/python_modulo.pyj +76 -0
- package/test/python_modulo_off.pyj +21 -0
- package/test/statistics.pyj +224 -0
- package/test/str.pyj +14 -0
- package/test/string.pyj +245 -0
- package/test/textwrap.pyj +172 -0
- package/test/type_display.pyj +48 -0
- package/test/type_enforcement.pyj +164 -0
- package/test/unit/index.js +94 -6
- package/test/unit/language-service-completions.js +121 -0
- package/test/unit/language-service-scope.js +32 -0
- package/test/unit/language-service.js +190 -5
- package/test/unit/run-language-service.js +17 -3
- package/test/unit/web-repl.js +2401 -13
- package/test/urllib.pyj +193 -0
- package/tools/compile.js +1 -1
- package/tools/embedded_compiler.js +7 -7
- package/tools/export.js +4 -2
- package/web-repl/main.js +1 -1
- package/web-repl/rapydscript.js +7 -5
- package/test/omit_function_metadata.pyj +0 -20
package/test/unit/web-repl.js
CHANGED
|
@@ -103,7 +103,6 @@ assert.deepEqual = function (a, b, message) {
|
|
|
103
103
|
// Compile RapydScript using the web-repl's own compile() path (same as browser)
|
|
104
104
|
function bundle_compile(repl, src) {
|
|
105
105
|
return repl.compile(src, {
|
|
106
|
-
omit_function_metadata: false,
|
|
107
106
|
tree_shake: false,
|
|
108
107
|
keep_baselib: true,
|
|
109
108
|
});
|
|
@@ -271,6 +270,95 @@ var TESTS = [
|
|
|
271
270
|
},
|
|
272
271
|
},
|
|
273
272
|
|
|
273
|
+
{
|
|
274
|
+
name: "bundle_chainmap_basic",
|
|
275
|
+
description: "ChainMap construction and first-map-wins lookup work in the web-repl bundle",
|
|
276
|
+
run: function () {
|
|
277
|
+
var repl = RS.web_repl();
|
|
278
|
+
var js = bundle_compile(repl, [
|
|
279
|
+
"from collections import ChainMap",
|
|
280
|
+
"defaults = {'color': 'red', 'user': 'guest'}",
|
|
281
|
+
"overrides = {'user': 'admin'}",
|
|
282
|
+
"cm = ChainMap(overrides, defaults)",
|
|
283
|
+
"assrt.equal(cm['user'], 'admin')",
|
|
284
|
+
"assrt.equal(cm['color'], 'red')",
|
|
285
|
+
"assrt.equal(len(cm), 2)",
|
|
286
|
+
"assrt.ok('color' in cm)",
|
|
287
|
+
"assrt.ok('missing' not in cm)",
|
|
288
|
+
"assrt.equal(cm.get('missing', 'fallback'), 'fallback')",
|
|
289
|
+
].join("\n"));
|
|
290
|
+
run_js(js);
|
|
291
|
+
},
|
|
292
|
+
},
|
|
293
|
+
|
|
294
|
+
{
|
|
295
|
+
name: "bundle_chainmap_writes",
|
|
296
|
+
description: "ChainMap writes, deletes and updates affect only the first map in the bundle",
|
|
297
|
+
run: function () {
|
|
298
|
+
var repl = RS.web_repl();
|
|
299
|
+
var js = bundle_compile(repl, [
|
|
300
|
+
"from collections import ChainMap",
|
|
301
|
+
"defaults = {'depth': 1}",
|
|
302
|
+
"cm = ChainMap({}, defaults)",
|
|
303
|
+
"cm['depth'] = 99",
|
|
304
|
+
"assrt.equal(cm['depth'], 99)",
|
|
305
|
+
"assrt.equal(defaults['depth'], 1)",
|
|
306
|
+
"del cm['depth']",
|
|
307
|
+
"assrt.equal(cm['depth'], 1)",
|
|
308
|
+
"cm.update({'a': 1}, b=2)",
|
|
309
|
+
"assrt.equal(cm['a'], 1)",
|
|
310
|
+
"assrt.equal(cm['b'], 2)",
|
|
311
|
+
"assrt.equal(cm.pop('a'), 1)",
|
|
312
|
+
"assrt.equal(cm.pop('depth', 'dflt'), 'dflt')",
|
|
313
|
+
"assrt.equal(cm.setdefault('new', 7), 7)",
|
|
314
|
+
"assrt.equal(cm['new'], 7)",
|
|
315
|
+
].join("\n"));
|
|
316
|
+
run_js(js);
|
|
317
|
+
},
|
|
318
|
+
},
|
|
319
|
+
|
|
320
|
+
{
|
|
321
|
+
name: "bundle_chainmap_new_child",
|
|
322
|
+
description: "ChainMap new_child and parents work in the web-repl bundle",
|
|
323
|
+
run: function () {
|
|
324
|
+
var repl = RS.web_repl();
|
|
325
|
+
var js = bundle_compile(repl, [
|
|
326
|
+
"from collections import ChainMap",
|
|
327
|
+
"base = ChainMap({'x': 1})",
|
|
328
|
+
"child = base.new_child({'x': 2, 'y': 3})",
|
|
329
|
+
"assrt.equal(child['x'], 2)",
|
|
330
|
+
"assrt.equal(child['y'], 3)",
|
|
331
|
+
"assrt.equal(base['x'], 1)",
|
|
332
|
+
"assrt.equal(child.maps.length, 2)",
|
|
333
|
+
"parents = child.parents",
|
|
334
|
+
"assrt.equal(parents['x'], 1)",
|
|
335
|
+
"assrt.equal(parents.maps.length, 1)",
|
|
336
|
+
].join("\n"));
|
|
337
|
+
run_js(js);
|
|
338
|
+
},
|
|
339
|
+
},
|
|
340
|
+
|
|
341
|
+
{
|
|
342
|
+
name: "bundle_chainmap_iteration",
|
|
343
|
+
description: "ChainMap keys/values/items, iteration order and copy work in the bundle",
|
|
344
|
+
run: function () {
|
|
345
|
+
var repl = RS.web_repl();
|
|
346
|
+
var js = bundle_compile(repl, [
|
|
347
|
+
"from collections import ChainMap",
|
|
348
|
+
"cm = ChainMap({'a': 1, 'b': 2}, {'c': 3})",
|
|
349
|
+
"assrt.deepEqual(list(cm), ['c', 'a', 'b'])",
|
|
350
|
+
"assrt.deepEqual(cm.keys(), ['c', 'a', 'b'])",
|
|
351
|
+
"assrt.deepEqual(cm.values(), [3, 1, 2])",
|
|
352
|
+
"dup = cm.copy()",
|
|
353
|
+
"dup['a'] = 100",
|
|
354
|
+
"assrt.equal(cm['a'], 1)",
|
|
355
|
+
"assrt.equal(dup['a'], 100)",
|
|
356
|
+
"assrt.equal(dup['c'], 3)",
|
|
357
|
+
].join("\n"));
|
|
358
|
+
run_js(js);
|
|
359
|
+
},
|
|
360
|
+
},
|
|
361
|
+
|
|
274
362
|
{
|
|
275
363
|
name: "bundle_operator_overloading",
|
|
276
364
|
description: "overload_operators flag works in the web-repl bundle",
|
|
@@ -1233,6 +1321,71 @@ var TESTS = [
|
|
|
1233
1321
|
},
|
|
1234
1322
|
},
|
|
1235
1323
|
|
|
1324
|
+
// ── f-string {x=} debugging format ───────────────────────────────────────
|
|
1325
|
+
|
|
1326
|
+
{
|
|
1327
|
+
name: "bundle_fstring_debug_simple",
|
|
1328
|
+
description: "f'{x=}' produces 'x=<value>' for simple variable",
|
|
1329
|
+
run: function () {
|
|
1330
|
+
var repl = RS.web_repl();
|
|
1331
|
+
var js = bundle_compile(repl, [
|
|
1332
|
+
"x = 42",
|
|
1333
|
+
"name = 'Alice'",
|
|
1334
|
+
"flag = True",
|
|
1335
|
+
"assrt.equal(f'{x=}', 'x=42')",
|
|
1336
|
+
"assrt.equal(f'{name=}', \"name=Alice\")",
|
|
1337
|
+
"assrt.equal(f'{flag=}', 'flag=true')",
|
|
1338
|
+
].join("\n"));
|
|
1339
|
+
run_js(js);
|
|
1340
|
+
},
|
|
1341
|
+
},
|
|
1342
|
+
|
|
1343
|
+
{
|
|
1344
|
+
name: "bundle_fstring_debug_expr",
|
|
1345
|
+
description: "f'{expr=}' preserves the full expression text as prefix",
|
|
1346
|
+
run: function () {
|
|
1347
|
+
var repl = RS.web_repl();
|
|
1348
|
+
var js = bundle_compile(repl, [
|
|
1349
|
+
"x = 5",
|
|
1350
|
+
"nums = [10, 20, 30]",
|
|
1351
|
+
"assrt.equal(f'{x*2=}', 'x*2=10')",
|
|
1352
|
+
"assrt.equal(f'{nums[1]=}', 'nums[1]=20')",
|
|
1353
|
+
"assrt.equal(f'{x=} and {x*2=}', 'x=5 and x*2=10')",
|
|
1354
|
+
].join("\n"));
|
|
1355
|
+
run_js(js);
|
|
1356
|
+
},
|
|
1357
|
+
},
|
|
1358
|
+
|
|
1359
|
+
{
|
|
1360
|
+
name: "bundle_fstring_debug_format_spec",
|
|
1361
|
+
description: "f'{x=:.2f}' combines debugging prefix with format spec",
|
|
1362
|
+
run: function () {
|
|
1363
|
+
var repl = RS.web_repl();
|
|
1364
|
+
var js = bundle_compile(repl, [
|
|
1365
|
+
"pi = 3.14159",
|
|
1366
|
+
"n = 1234567",
|
|
1367
|
+
"assrt.equal(f'{pi=:.2f}', 'pi=3.14')",
|
|
1368
|
+
"assrt.equal(f'{n=:,}', 'n=' + (1234567).toLocaleString())",
|
|
1369
|
+
].join("\n"));
|
|
1370
|
+
run_js(js);
|
|
1371
|
+
},
|
|
1372
|
+
},
|
|
1373
|
+
|
|
1374
|
+
{
|
|
1375
|
+
name: "bundle_fstring_debug_repr",
|
|
1376
|
+
description: "f'{x=!r}' combines debugging prefix with !r repr conversion",
|
|
1377
|
+
run: function () {
|
|
1378
|
+
var repl = RS.web_repl();
|
|
1379
|
+
var js = bundle_compile(repl, [
|
|
1380
|
+
"msg = 'hello'",
|
|
1381
|
+
"nums = [1, 2, 3]",
|
|
1382
|
+
"assrt.equal(f'{msg=!r}', 'msg=\"hello\"')",
|
|
1383
|
+
"assrt.equal(f'{nums=!r}', 'nums=[1, 2, 3]')",
|
|
1384
|
+
].join("\n"));
|
|
1385
|
+
run_js(js);
|
|
1386
|
+
},
|
|
1387
|
+
},
|
|
1388
|
+
|
|
1236
1389
|
// ── object() builtin ──────────────────────────────────────────────────────
|
|
1237
1390
|
|
|
1238
1391
|
{
|
|
@@ -2198,6 +2351,522 @@ var TESTS = [
|
|
|
2198
2351
|
},
|
|
2199
2352
|
},
|
|
2200
2353
|
|
|
2354
|
+
// ── float() special string values ────────────────────────────────────────
|
|
2355
|
+
|
|
2356
|
+
{
|
|
2357
|
+
name: "bundle_float_special_inf_nan",
|
|
2358
|
+
description: "float() accepts 'inf', '-inf', 'infinity', 'nan' (and variants) in the web-repl bundle",
|
|
2359
|
+
run: function () {
|
|
2360
|
+
var repl = RS.web_repl();
|
|
2361
|
+
var js = bundle_compile(repl, [
|
|
2362
|
+
// positive infinity
|
|
2363
|
+
"assrt.equal(float('inf'), Infinity)",
|
|
2364
|
+
"assrt.equal(float('+inf'), Infinity)",
|
|
2365
|
+
"assrt.equal(float('INF'), Infinity)",
|
|
2366
|
+
"assrt.equal(float('infinity'), Infinity)",
|
|
2367
|
+
"assrt.equal(float('+infinity'), Infinity)",
|
|
2368
|
+
"assrt.equal(float('Infinity'), Infinity)",
|
|
2369
|
+
"assrt.equal(float('INFINITY'), Infinity)",
|
|
2370
|
+
// negative infinity
|
|
2371
|
+
"assrt.equal(float('-inf'), -Infinity)",
|
|
2372
|
+
"assrt.equal(float('-infinity'), -Infinity)",
|
|
2373
|
+
"assrt.equal(float('-Infinity'), -Infinity)",
|
|
2374
|
+
// nan
|
|
2375
|
+
"assrt.ok(isNaN(float('nan')))",
|
|
2376
|
+
"assrt.ok(isNaN(float('NaN')))",
|
|
2377
|
+
"assrt.ok(isNaN(float('NAN')))",
|
|
2378
|
+
"assrt.ok(isNaN(float('+nan')))",
|
|
2379
|
+
"assrt.ok(isNaN(float('-nan')))",
|
|
2380
|
+
// whitespace stripped
|
|
2381
|
+
"assrt.equal(float(' inf '), Infinity)",
|
|
2382
|
+
"assrt.equal(float(' -inf '), -Infinity)",
|
|
2383
|
+
"assrt.ok(isNaN(float(' nan ')))",
|
|
2384
|
+
// numeric strings still work
|
|
2385
|
+
"assrt.equal(float('3.14'), 3.14)",
|
|
2386
|
+
"assrt.equal(float('-2.5'), -2.5)",
|
|
2387
|
+
// real Infinity passes through
|
|
2388
|
+
"assrt.equal(float(Infinity), Infinity)",
|
|
2389
|
+
"assrt.equal(float(-Infinity), -Infinity)",
|
|
2390
|
+
// ValueError still raised for bad strings
|
|
2391
|
+
"_err = False",
|
|
2392
|
+
"try:",
|
|
2393
|
+
" float('bad')",
|
|
2394
|
+
"except ValueError:",
|
|
2395
|
+
" _err = True",
|
|
2396
|
+
"assrt.ok(_err)",
|
|
2397
|
+
].join("\n"));
|
|
2398
|
+
run_js(js);
|
|
2399
|
+
},
|
|
2400
|
+
},
|
|
2401
|
+
|
|
2402
|
+
// ── base64 stdlib ────────────────────────────────────────────────────────
|
|
2403
|
+
|
|
2404
|
+
{
|
|
2405
|
+
name: "bundle_base64_encode_decode",
|
|
2406
|
+
description: "base64 stdlib: b64encode/b64decode round-trips in the web-repl bundle",
|
|
2407
|
+
run: function () {
|
|
2408
|
+
var repl = RS.web_repl();
|
|
2409
|
+
var js = bundle_compile(repl, [
|
|
2410
|
+
"from base64 import b64encode, b64decode",
|
|
2411
|
+
// basic encode
|
|
2412
|
+
"enc = b64encode(bytes([77, 97, 110]))",
|
|
2413
|
+
"assrt.equal(enc.decode('ascii'), 'TWFu')",
|
|
2414
|
+
// empty
|
|
2415
|
+
"assrt.equal(len(b64encode(bytes([]))), 0)",
|
|
2416
|
+
// padding variants
|
|
2417
|
+
"assrt.equal(b64encode(bytes([0])).decode('ascii'), 'AA==')",
|
|
2418
|
+
"assrt.equal(b64encode(bytes([0, 0])).decode('ascii'), 'AAA=')",
|
|
2419
|
+
"assrt.equal(b64encode(bytes([0, 0, 0])).decode('ascii'), 'AAAA')",
|
|
2420
|
+
// decode round-trip
|
|
2421
|
+
"msg = bytes([104, 101, 108, 108, 111])", // hello
|
|
2422
|
+
"assrt.deepEqual(list(b64decode(b64encode(msg))), list(msg))",
|
|
2423
|
+
// decode accepts string input
|
|
2424
|
+
"d = b64decode('TWFu')",
|
|
2425
|
+
"assrt.equal(d[0], 77)",
|
|
2426
|
+
"assrt.equal(d[1], 97)",
|
|
2427
|
+
"assrt.equal(d[2], 110)",
|
|
2428
|
+
// decode strips whitespace
|
|
2429
|
+
"assrt.deepEqual(list(b64decode('aGVs bG8=')), list(msg))",
|
|
2430
|
+
// missing padding accepted
|
|
2431
|
+
"assrt.deepEqual(list(b64decode('aGVsbG8')), list(msg))",
|
|
2432
|
+
// isinstance check
|
|
2433
|
+
"assrt.ok(isinstance(b64encode(bytes([1,2,3])), bytes))",
|
|
2434
|
+
].join("\n"));
|
|
2435
|
+
run_js(js);
|
|
2436
|
+
},
|
|
2437
|
+
},
|
|
2438
|
+
|
|
2439
|
+
{
|
|
2440
|
+
name: "bundle_base64_urlsafe",
|
|
2441
|
+
description: "base64 stdlib: URL-safe encoding and altchars in the web-repl bundle",
|
|
2442
|
+
run: function () {
|
|
2443
|
+
var repl = RS.web_repl();
|
|
2444
|
+
var js = bundle_compile(repl, [
|
|
2445
|
+
"from base64 import urlsafe_b64encode, urlsafe_b64decode, b64encode, b64decode",
|
|
2446
|
+
// URL-safe produces no + or /
|
|
2447
|
+
"enc = urlsafe_b64encode(bytes([251, 239, 190]))",
|
|
2448
|
+
"s = enc.decode('ascii')",
|
|
2449
|
+
"assrt.ok(s.indexOf('+') < 0, 'no + in URL-safe')",
|
|
2450
|
+
"assrt.ok(s.indexOf('/') < 0, 'no / in URL-safe')",
|
|
2451
|
+
// -_7- decodes to [251, 254, 254]
|
|
2452
|
+
"dec = urlsafe_b64decode('-_7-')",
|
|
2453
|
+
"assrt.equal(dec[0], 251)",
|
|
2454
|
+
"assrt.equal(dec[1], 254)",
|
|
2455
|
+
"assrt.equal(dec[2], 254)",
|
|
2456
|
+
// round-trip
|
|
2457
|
+
"data = bytes([0, 127, 128, 255])",
|
|
2458
|
+
"assrt.deepEqual(list(urlsafe_b64decode(urlsafe_b64encode(data))), list(data))",
|
|
2459
|
+
// altchars
|
|
2460
|
+
"enc_alt = b64encode(bytes([251, 254, 254]), altchars=bytes([45, 95]))",
|
|
2461
|
+
"assrt.equal(enc_alt.decode('ascii'), '-_7-')",
|
|
2462
|
+
"dec_alt = b64decode('-_7-', altchars=bytes([45, 95]))",
|
|
2463
|
+
"assrt.deepEqual(list(dec_alt), [251, 254, 254])",
|
|
2464
|
+
].join("\n"));
|
|
2465
|
+
run_js(js);
|
|
2466
|
+
},
|
|
2467
|
+
},
|
|
2468
|
+
|
|
2469
|
+
{
|
|
2470
|
+
name: "bundle_base64_b32_b16",
|
|
2471
|
+
description: "base64 stdlib: b32encode/b32decode and b16encode/b16decode in the web-repl bundle",
|
|
2472
|
+
run: function () {
|
|
2473
|
+
var repl = RS.web_repl();
|
|
2474
|
+
var js = bundle_compile(repl, [
|
|
2475
|
+
"from base64 import b32encode, b32decode, b16encode, b16decode",
|
|
2476
|
+
// b32 round-trips
|
|
2477
|
+
"b32_msg = bytes([102, 111, 111])", // 'foo'
|
|
2478
|
+
"b32_enc = b32encode(b32_msg)",
|
|
2479
|
+
"assrt.equal(b32_enc.decode('ascii'), 'MZXW6===')",
|
|
2480
|
+
"assrt.deepEqual(list(b32decode(b32_enc)), list(b32_msg))",
|
|
2481
|
+
// casefold
|
|
2482
|
+
"assrt.deepEqual(list(b32decode('mzxw6===', casefold=True)), list(b32_msg))",
|
|
2483
|
+
// b16 (hex) encoding
|
|
2484
|
+
"b16_msg = bytes([0, 1, 254, 255])",
|
|
2485
|
+
"b16_enc = b16encode(b16_msg)",
|
|
2486
|
+
"assrt.equal(b16_enc.decode('ascii'), '0001FEFF')",
|
|
2487
|
+
"assrt.deepEqual(list(b16decode(b16_enc)), list(b16_msg))",
|
|
2488
|
+
// b16 casefold
|
|
2489
|
+
"assrt.deepEqual(list(b16decode('0001feff', casefold=True)), list(b16_msg))",
|
|
2490
|
+
// b16 Error on bad input
|
|
2491
|
+
"b16_err = False",
|
|
2492
|
+
"try:",
|
|
2493
|
+
" b16decode('ZZ')",
|
|
2494
|
+
"except ValueError:",
|
|
2495
|
+
" b16_err = True",
|
|
2496
|
+
"assrt.ok(b16_err, 'b16decode of non-hex raises ValueError')",
|
|
2497
|
+
].join("\n"));
|
|
2498
|
+
run_js(js);
|
|
2499
|
+
},
|
|
2500
|
+
},
|
|
2501
|
+
|
|
2502
|
+
{
|
|
2503
|
+
name: "bundle_base64_encodebytes",
|
|
2504
|
+
description: "base64 stdlib: encodebytes/decodebytes and validate= flag in the web-repl bundle",
|
|
2505
|
+
run: function () {
|
|
2506
|
+
var repl = RS.web_repl();
|
|
2507
|
+
var js = bundle_compile(repl, [
|
|
2508
|
+
"from base64 import encodebytes, decodebytes, b64decode",
|
|
2509
|
+
// Note: catch ValueError (base64.Error subclasses ValueError).
|
|
2510
|
+
// Importing 'Error' from base64 in the web-repl context would
|
|
2511
|
+
// shadow the native JS Error constructor, causing issues.
|
|
2512
|
+
// encodebytes wraps at 76 chars per line
|
|
2513
|
+
"data = bytes(list(range(57)))",
|
|
2514
|
+
"enc = encodebytes(data)",
|
|
2515
|
+
"s = enc.decode('ascii')",
|
|
2516
|
+
"assrt.equal(s.charCodeAt(s.length - 1), 10, 'encodebytes ends with newline')",
|
|
2517
|
+
// decodebytes round-trip
|
|
2518
|
+
"assrt.deepEqual(list(decodebytes(enc)), list(data))",
|
|
2519
|
+
// validate=True raises on bad input (caught as ValueError)
|
|
2520
|
+
"b64_err = False",
|
|
2521
|
+
"try:",
|
|
2522
|
+
" b64decode('aGVs!G8=', validate=True)",
|
|
2523
|
+
"except ValueError:",
|
|
2524
|
+
" b64_err = True",
|
|
2525
|
+
"assrt.ok(b64_err, 'validate=True should raise ValueError on non-base64 char')",
|
|
2526
|
+
// validate=True passes on good input
|
|
2527
|
+
"good = b64decode('aGVsbG8=', validate=True)",
|
|
2528
|
+
"assrt.equal(good[0], 104)",
|
|
2529
|
+
].join("\n"));
|
|
2530
|
+
run_js(js);
|
|
2531
|
+
},
|
|
2532
|
+
},
|
|
2533
|
+
|
|
2534
|
+
{
|
|
2535
|
+
name: "bundle_multiline_paren_import",
|
|
2536
|
+
description: "multi-line parenthesized import with trailing comma works in the web-repl bundle",
|
|
2537
|
+
run: function () {
|
|
2538
|
+
var repl = RS.web_repl();
|
|
2539
|
+
var js = bundle_compile(repl, [
|
|
2540
|
+
"from math import (",
|
|
2541
|
+
" floor,",
|
|
2542
|
+
" ceil,",
|
|
2543
|
+
" sqrt,",
|
|
2544
|
+
")",
|
|
2545
|
+
"assrt.equal(floor(3.9), 3)",
|
|
2546
|
+
"assrt.equal(ceil(3.1), 4)",
|
|
2547
|
+
"assrt.equal(sqrt(9), 3)",
|
|
2548
|
+
].join("\n"));
|
|
2549
|
+
run_js(js);
|
|
2550
|
+
},
|
|
2551
|
+
},
|
|
2552
|
+
|
|
2553
|
+
{
|
|
2554
|
+
name: "bundle_multiline_paren_import_alias",
|
|
2555
|
+
description: "multi-line parenthesized import with aliases and trailing comma works in the web-repl bundle",
|
|
2556
|
+
run: function () {
|
|
2557
|
+
var repl = RS.web_repl();
|
|
2558
|
+
var js = bundle_compile(repl, [
|
|
2559
|
+
"from math import (",
|
|
2560
|
+
" floor as fl,",
|
|
2561
|
+
" ceil as cl,",
|
|
2562
|
+
")",
|
|
2563
|
+
"assrt.equal(fl(2.9), 2)",
|
|
2564
|
+
"assrt.equal(cl(2.1), 3)",
|
|
2565
|
+
].join("\n"));
|
|
2566
|
+
run_js(js);
|
|
2567
|
+
},
|
|
2568
|
+
},
|
|
2569
|
+
|
|
2570
|
+
// ── string stdlib ────────────────────────────────────────────────────────
|
|
2571
|
+
|
|
2572
|
+
{
|
|
2573
|
+
name: "bundle_string_constants",
|
|
2574
|
+
description: "string stdlib: character constants in the web-repl bundle",
|
|
2575
|
+
run: function () {
|
|
2576
|
+
var repl = RS.web_repl();
|
|
2577
|
+
var js = bundle_compile(repl, [
|
|
2578
|
+
"from string import (",
|
|
2579
|
+
" ascii_lowercase, ascii_uppercase, ascii_letters,",
|
|
2580
|
+
" digits, hexdigits, octdigits,",
|
|
2581
|
+
" punctuation, whitespace, printable,",
|
|
2582
|
+
")",
|
|
2583
|
+
// lengths
|
|
2584
|
+
"assrt.equal(len(ascii_lowercase), 26)",
|
|
2585
|
+
"assrt.equal(len(ascii_uppercase), 26)",
|
|
2586
|
+
"assrt.equal(len(ascii_letters), 52)",
|
|
2587
|
+
"assrt.equal(len(digits), 10)",
|
|
2588
|
+
"assrt.equal(len(hexdigits), 22)",
|
|
2589
|
+
"assrt.equal(len(octdigits), 8)",
|
|
2590
|
+
"assrt.equal(len(punctuation), 32)",
|
|
2591
|
+
"assrt.equal(len(whitespace), 6)",
|
|
2592
|
+
"assrt.equal(len(printable), len(digits) + len(ascii_letters) + len(punctuation) + len(whitespace))",
|
|
2593
|
+
// spot-checks
|
|
2594
|
+
"assrt.equal(ascii_letters, ascii_lowercase + ascii_uppercase)",
|
|
2595
|
+
"assrt.ok(digits.indexOf('5') >= 0)",
|
|
2596
|
+
"assrt.ok(hexdigits.indexOf('f') >= 0)",
|
|
2597
|
+
"assrt.ok(punctuation.indexOf('!') >= 0)",
|
|
2598
|
+
"assrt.ok(whitespace.indexOf(' ') >= 0)",
|
|
2599
|
+
"assrt.ok(printable.indexOf('A') >= 0)",
|
|
2600
|
+
].join("\n"));
|
|
2601
|
+
run_js(js);
|
|
2602
|
+
},
|
|
2603
|
+
},
|
|
2604
|
+
|
|
2605
|
+
{
|
|
2606
|
+
name: "bundle_string_template",
|
|
2607
|
+
description: "string stdlib: Template class in the web-repl bundle",
|
|
2608
|
+
run: function () {
|
|
2609
|
+
var repl = RS.web_repl();
|
|
2610
|
+
var js = bundle_compile(repl, [
|
|
2611
|
+
"from string import Template",
|
|
2612
|
+
// basic substitution
|
|
2613
|
+
"t1 = Template('Hello $name!')",
|
|
2614
|
+
"assrt.equal(t1.substitute({'name': 'World'}), 'Hello World!')",
|
|
2615
|
+
// $$ → $
|
|
2616
|
+
"assrt.equal(Template('Price: $$5').substitute({}), 'Price: $5')",
|
|
2617
|
+
// brace form
|
|
2618
|
+
"assrt.equal(Template('${x}bar').substitute({'x': 'foo'}), 'foobar')",
|
|
2619
|
+
// multiple fields
|
|
2620
|
+
"assrt.equal(Template('$a and $b').substitute({'a': '1', 'b': '2'}), '1 and 2')",
|
|
2621
|
+
// numeric value
|
|
2622
|
+
"assrt.equal(Template('n=$n').substitute({'n': 42}), 'n=42')",
|
|
2623
|
+
// safe_substitute leaves missing intact
|
|
2624
|
+
"assrt.equal(Template('$x $y').safe_substitute({'x': 'hi'}), 'hi $y')",
|
|
2625
|
+
// safe_substitute with all keys
|
|
2626
|
+
"assrt.equal(Template('$a').safe_substitute({'a': 'z'}), 'z')",
|
|
2627
|
+
// substitute raises KeyError for missing key
|
|
2628
|
+
"_t_err = False",
|
|
2629
|
+
"try:",
|
|
2630
|
+
" Template('Hello $missing').substitute({})",
|
|
2631
|
+
"except KeyError:",
|
|
2632
|
+
" _t_err = True",
|
|
2633
|
+
"assrt.ok(_t_err)",
|
|
2634
|
+
// template attribute
|
|
2635
|
+
"assrt.equal(Template('hello $x').template, 'hello $x')",
|
|
2636
|
+
// class attribute
|
|
2637
|
+
"assrt.equal(Template.delimiter, '$')",
|
|
2638
|
+
].join("\n"));
|
|
2639
|
+
run_js(js);
|
|
2640
|
+
},
|
|
2641
|
+
},
|
|
2642
|
+
|
|
2643
|
+
{
|
|
2644
|
+
name: "bundle_string_formatter",
|
|
2645
|
+
description: "string stdlib: Formatter class in the web-repl bundle",
|
|
2646
|
+
run: function () {
|
|
2647
|
+
var repl = RS.web_repl();
|
|
2648
|
+
var js = bundle_compile(repl, [
|
|
2649
|
+
"from string import Formatter",
|
|
2650
|
+
"f = Formatter()",
|
|
2651
|
+
// positional args
|
|
2652
|
+
"assrt.equal(f.format('Hello {}!', 'World'), 'Hello World!')",
|
|
2653
|
+
"assrt.equal(f.format('{0} {1}', 'a', 'b'), 'a b')",
|
|
2654
|
+
"assrt.equal(f.format('{1} {0}', 'a', 'b'), 'b a')",
|
|
2655
|
+
"assrt.equal(f.format('no fields'), 'no fields')",
|
|
2656
|
+
// format specs
|
|
2657
|
+
"assrt.equal(f.format('{:.2f}', 3.14159), '3.14')",
|
|
2658
|
+
"assrt.equal(f.format('{:d}', 42), '42')",
|
|
2659
|
+
"assrt.equal(f.format('{:x}', 255), 'ff')",
|
|
2660
|
+
// {{ }} escaping
|
|
2661
|
+
"assrt.equal(f.format('{{ }}'), '{ }')",
|
|
2662
|
+
// vformat with named kwargs
|
|
2663
|
+
"assrt.equal(f.vformat('{name}', [], {'name': 'Alice'}), 'Alice')",
|
|
2664
|
+
"assrt.equal(f.vformat('{0} {name}', ['hi'], {'name': 'Bob'}), 'hi Bob')",
|
|
2665
|
+
// convert_field
|
|
2666
|
+
"assrt.equal(f.convert_field(42, 's'), '42')",
|
|
2667
|
+
"assrt.equal(f.convert_field('x', None), 'x')",
|
|
2668
|
+
// format_field
|
|
2669
|
+
"assrt.equal(f.format_field(3.14159, '.2f'), '3.14')",
|
|
2670
|
+
"assrt.equal(f.format_field('hi', ''), 'hi')",
|
|
2671
|
+
// get_value
|
|
2672
|
+
"assrt.equal(f.get_value(0, ['a', 'b'], {}), 'a')",
|
|
2673
|
+
"assrt.equal(f.get_value('x', [], {'x': 99}), 99)",
|
|
2674
|
+
// parse
|
|
2675
|
+
"_p = f.parse('lit {0:.2f} end')",
|
|
2676
|
+
"assrt.equal(_p[0][0], 'lit ')",
|
|
2677
|
+
"assrt.equal(_p[0][1], '0')",
|
|
2678
|
+
"assrt.equal(_p[0][2], '.2f')",
|
|
2679
|
+
"assrt.equal(_p[1][0], ' end')",
|
|
2680
|
+
"assrt.equal(_p[1][1], None)",
|
|
2681
|
+
].join("\n"));
|
|
2682
|
+
run_js(js);
|
|
2683
|
+
},
|
|
2684
|
+
},
|
|
2685
|
+
|
|
2686
|
+
{
|
|
2687
|
+
name: "bundle_html_escape_unescape",
|
|
2688
|
+
description: "html stdlib: escape and unescape functions in the web-repl bundle",
|
|
2689
|
+
run: function () {
|
|
2690
|
+
var repl = RS.web_repl();
|
|
2691
|
+
var js = bundle_compile(repl, [
|
|
2692
|
+
"from html import escape, unescape",
|
|
2693
|
+
// escape basics
|
|
2694
|
+
"assrt.equal(escape('<'), '<')",
|
|
2695
|
+
"assrt.equal(escape('>'), '>')",
|
|
2696
|
+
"assrt.equal(escape('&'), '&')",
|
|
2697
|
+
"assrt.equal(escape('\"'), '"')",
|
|
2698
|
+
"assrt.equal(escape(\"'\"), ''')",
|
|
2699
|
+
// quote=False
|
|
2700
|
+
"assrt.equal(escape('A & B', quote=False), 'A & B')",
|
|
2701
|
+
"assrt.equal(escape('\"hi\"', quote=False), '\"hi\"')",
|
|
2702
|
+
// unescape named entities
|
|
2703
|
+
"assrt.equal(unescape('&'), '&')",
|
|
2704
|
+
"assrt.equal(unescape('<'), '<')",
|
|
2705
|
+
"assrt.equal(unescape('>'), '>')",
|
|
2706
|
+
"assrt.equal(unescape('"'), '\"')",
|
|
2707
|
+
"assrt.equal(unescape('©'), '\\u00a9')",
|
|
2708
|
+
"assrt.equal(unescape('€'), '\\u20ac')",
|
|
2709
|
+
// numeric references
|
|
2710
|
+
"assrt.equal(unescape('A'), 'A')",
|
|
2711
|
+
"assrt.equal(unescape('A'), 'A')",
|
|
2712
|
+
// unknown entity left intact
|
|
2713
|
+
"assrt.equal(unescape('&nosuchentity;'), '&nosuchentity;')",
|
|
2714
|
+
// round-trip
|
|
2715
|
+
"s = '<Hello & \"World\">'",
|
|
2716
|
+
"assrt.equal(unescape(escape(s)), s)",
|
|
2717
|
+
].join("\n"));
|
|
2718
|
+
run_js(js);
|
|
2719
|
+
},
|
|
2720
|
+
},
|
|
2721
|
+
|
|
2722
|
+
{
|
|
2723
|
+
name: "bundle_html_parser_basic",
|
|
2724
|
+
description: "html stdlib: HTMLParser start/end/data callbacks in the web-repl bundle",
|
|
2725
|
+
run: function () {
|
|
2726
|
+
var repl = RS.web_repl();
|
|
2727
|
+
var js = bundle_compile(repl, [
|
|
2728
|
+
"from html import HTMLParser",
|
|
2729
|
+
"class _P(HTMLParser):",
|
|
2730
|
+
" def __init__(self):",
|
|
2731
|
+
" HTMLParser.__init__(self)",
|
|
2732
|
+
" self.events = []",
|
|
2733
|
+
" def handle_starttag(self, tag, attrs):",
|
|
2734
|
+
" self.events.push(['start', tag])",
|
|
2735
|
+
" def handle_endtag(self, tag):",
|
|
2736
|
+
" self.events.push(['end', tag])",
|
|
2737
|
+
" def handle_data(self, data):",
|
|
2738
|
+
" self.events.push(['data', data])",
|
|
2739
|
+
"p = _P()",
|
|
2740
|
+
"p.feed('<p>Hello, World!</p>')",
|
|
2741
|
+
"assrt.equal(p.events[0][0], 'start')",
|
|
2742
|
+
"assrt.equal(p.events[0][1], 'p')",
|
|
2743
|
+
"assrt.equal(p.events[1][0], 'data')",
|
|
2744
|
+
"assrt.equal(p.events[1][1], 'Hello, World!')",
|
|
2745
|
+
"assrt.equal(p.events[2][0], 'end')",
|
|
2746
|
+
"assrt.equal(p.events[2][1], 'p')",
|
|
2747
|
+
// entity conversion in data
|
|
2748
|
+
"p2 = _P()",
|
|
2749
|
+
"p2.feed('<p>A & B</p>')",
|
|
2750
|
+
"assrt.equal(p2.events[1][1], 'A & B')",
|
|
2751
|
+
// tag names lowercased
|
|
2752
|
+
"p3 = _P()",
|
|
2753
|
+
"p3.feed('<DIV></DIV>')",
|
|
2754
|
+
"assrt.equal(p3.events[0][1], 'div')",
|
|
2755
|
+
].join("\n"));
|
|
2756
|
+
run_js(js);
|
|
2757
|
+
},
|
|
2758
|
+
},
|
|
2759
|
+
|
|
2760
|
+
{
|
|
2761
|
+
name: "bundle_html_parser_attrs",
|
|
2762
|
+
description: "html stdlib: HTMLParser attribute parsing in the web-repl bundle",
|
|
2763
|
+
run: function () {
|
|
2764
|
+
var repl = RS.web_repl();
|
|
2765
|
+
var js = bundle_compile(repl, [
|
|
2766
|
+
"from html import HTMLParser",
|
|
2767
|
+
"class _PA(HTMLParser):",
|
|
2768
|
+
" def __init__(self):",
|
|
2769
|
+
" HTMLParser.__init__(self)",
|
|
2770
|
+
" self.last_attrs = None",
|
|
2771
|
+
" def handle_starttag(self, tag, attrs):",
|
|
2772
|
+
" self.last_attrs = attrs",
|
|
2773
|
+
// multiple attrs
|
|
2774
|
+
"p = _PA()",
|
|
2775
|
+
"p.feed('<a href=\"http://example.com\" target=\"_blank\">')",
|
|
2776
|
+
"assrt.equal(p.last_attrs.length, 2)",
|
|
2777
|
+
"assrt.equal(p.last_attrs[0][0], 'href')",
|
|
2778
|
+
"assrt.equal(p.last_attrs[0][1], 'http://example.com')",
|
|
2779
|
+
"assrt.equal(p.last_attrs[1][0], 'target')",
|
|
2780
|
+
"assrt.equal(p.last_attrs[1][1], '_blank')",
|
|
2781
|
+
// valueless attribute
|
|
2782
|
+
"p2 = _PA()",
|
|
2783
|
+
"p2.feed('<input disabled>')",
|
|
2784
|
+
"assrt.equal(p2.last_attrs[0][0], 'disabled')",
|
|
2785
|
+
"assrt.equal(p2.last_attrs[0][1], None)",
|
|
2786
|
+
// self-closing
|
|
2787
|
+
"class _PSC(HTMLParser):",
|
|
2788
|
+
" def __init__(self):",
|
|
2789
|
+
" HTMLParser.__init__(self)",
|
|
2790
|
+
" self.events = []",
|
|
2791
|
+
" def handle_starttag(self, tag, attrs):",
|
|
2792
|
+
" self.events.push('start:' + tag)",
|
|
2793
|
+
" def handle_endtag(self, tag):",
|
|
2794
|
+
" self.events.push('end:' + tag)",
|
|
2795
|
+
"psc = _PSC()",
|
|
2796
|
+
"psc.feed('<br/>')",
|
|
2797
|
+
"assrt.equal(psc.events.length, 2)",
|
|
2798
|
+
"assrt.equal(psc.events[0], 'start:br')",
|
|
2799
|
+
"assrt.equal(psc.events[1], 'end:br')",
|
|
2800
|
+
].join("\n"));
|
|
2801
|
+
run_js(js);
|
|
2802
|
+
},
|
|
2803
|
+
},
|
|
2804
|
+
|
|
2805
|
+
{
|
|
2806
|
+
name: "bundle_html_parser_special",
|
|
2807
|
+
description: "html stdlib: HTMLParser comments, DOCTYPE, get_starttag_text in the web-repl bundle",
|
|
2808
|
+
run: function () {
|
|
2809
|
+
var repl = RS.web_repl();
|
|
2810
|
+
var js = bundle_compile(repl, [
|
|
2811
|
+
"from html import HTMLParser",
|
|
2812
|
+
// comment
|
|
2813
|
+
"class _PC(HTMLParser):",
|
|
2814
|
+
" def __init__(self):",
|
|
2815
|
+
" HTMLParser.__init__(self)",
|
|
2816
|
+
" self.comments = []",
|
|
2817
|
+
" def handle_comment(self, data):",
|
|
2818
|
+
" self.comments.push(data)",
|
|
2819
|
+
"pc = _PC()",
|
|
2820
|
+
"pc.feed('<!-- hello -->')",
|
|
2821
|
+
"assrt.equal(pc.comments.length, 1)",
|
|
2822
|
+
"assrt.equal(pc.comments[0], ' hello ')",
|
|
2823
|
+
// doctype
|
|
2824
|
+
"class _PD(HTMLParser):",
|
|
2825
|
+
" def __init__(self):",
|
|
2826
|
+
" HTMLParser.__init__(self)",
|
|
2827
|
+
" self.decls = []",
|
|
2828
|
+
" def handle_decl(self, decl):",
|
|
2829
|
+
" self.decls.push(decl)",
|
|
2830
|
+
"pd = _PD()",
|
|
2831
|
+
"pd.feed('<!DOCTYPE html>')",
|
|
2832
|
+
"assrt.equal(pd.decls[0], 'DOCTYPE html')",
|
|
2833
|
+
// get_starttag_text
|
|
2834
|
+
"class _PR(HTMLParser):",
|
|
2835
|
+
" def __init__(self):",
|
|
2836
|
+
" HTMLParser.__init__(self)",
|
|
2837
|
+
" self.raw = None",
|
|
2838
|
+
" def handle_starttag(self, tag, attrs):",
|
|
2839
|
+
" self.raw = self.get_starttag_text()",
|
|
2840
|
+
"pr = _PR()",
|
|
2841
|
+
"pr.feed('<img src=\"pic.png\">')",
|
|
2842
|
+
"assrt.equal(pr.raw, '<img src=\"pic.png\">')",
|
|
2843
|
+
].join("\n"));
|
|
2844
|
+
run_js(js);
|
|
2845
|
+
},
|
|
2846
|
+
},
|
|
2847
|
+
|
|
2848
|
+
{
|
|
2849
|
+
name: "bundle_python_modulo",
|
|
2850
|
+
description: "% operator gives Python-style modulo (sign of divisor) in the web-repl bundle",
|
|
2851
|
+
run: function () {
|
|
2852
|
+
var repl = RS.web_repl();
|
|
2853
|
+
var js = bundle_compile(repl, [
|
|
2854
|
+
"assrt.equal(-7 % 3, 2)",
|
|
2855
|
+
"assrt.equal(7 % -3, -2)",
|
|
2856
|
+
"assrt.equal(-7 % -3, -1)",
|
|
2857
|
+
"assrt.equal(7 % 3, 1)",
|
|
2858
|
+
"assrt.equal(0 % 5, 0)",
|
|
2859
|
+
"assrt.equal(-6 % 3, 0)",
|
|
2860
|
+
"assrt.equal(-7.5 % 2, 0.5)",
|
|
2861
|
+
"assrt.equal(7.5 % -2, -0.5)",
|
|
2862
|
+
"x = -7",
|
|
2863
|
+
"x %= 3",
|
|
2864
|
+
"assrt.equal(x, 2)",
|
|
2865
|
+
].join("\n"));
|
|
2866
|
+
run_js(js);
|
|
2867
|
+
},
|
|
2868
|
+
},
|
|
2869
|
+
|
|
2201
2870
|
{
|
|
2202
2871
|
name: "repl_exists_persistence",
|
|
2203
2872
|
description: "ρσ_exists accessible after baselib init — existential operator on non-SymbolRef in web-repl context",
|
|
@@ -2218,16 +2887,1727 @@ var TESTS = [
|
|
|
2218
2887
|
},
|
|
2219
2888
|
},
|
|
2220
2889
|
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2890
|
+
{
|
|
2891
|
+
name: "bundle_asyncio_exceptions_and_primitives",
|
|
2892
|
+
description: "asyncio stdlib: exception classes, Queue/Lock/Event/Semaphore synchronous state in the web-repl bundle",
|
|
2893
|
+
run: function () {
|
|
2894
|
+
var repl = RS.web_repl();
|
|
2895
|
+
var js = bundle_compile(repl, [
|
|
2896
|
+
"from asyncio import (",
|
|
2897
|
+
" CancelledError, TimeoutError, InvalidStateError, RuntimeError,",
|
|
2898
|
+
" QueueEmpty, QueueFull,",
|
|
2899
|
+
" Lock, Event, Semaphore, BoundedSemaphore, Queue",
|
|
2900
|
+
")",
|
|
2901
|
+
// Exception classes
|
|
2902
|
+
"try:",
|
|
2903
|
+
" raise CancelledError('c')",
|
|
2904
|
+
"except CancelledError as e:",
|
|
2905
|
+
" assrt.equal(e.message, 'c')",
|
|
2906
|
+
"try:",
|
|
2907
|
+
" raise TimeoutError('t')",
|
|
2908
|
+
"except TimeoutError as e:",
|
|
2909
|
+
" assrt.equal(e.message, 't')",
|
|
2910
|
+
"try:",
|
|
2911
|
+
" raise QueueEmpty('empty')",
|
|
2912
|
+
"except QueueEmpty as e:",
|
|
2913
|
+
" assrt.equal(e.message, 'empty')",
|
|
2914
|
+
"try:",
|
|
2915
|
+
" raise QueueFull('full')",
|
|
2916
|
+
"except QueueFull as e:",
|
|
2917
|
+
" assrt.equal(e.message, 'full')",
|
|
2918
|
+
// Lock
|
|
2919
|
+
"lock = Lock()",
|
|
2920
|
+
"assrt.equal(lock.locked(), False)",
|
|
2921
|
+
"try:",
|
|
2922
|
+
" lock.release()",
|
|
2923
|
+
" assrt.ok(False)",
|
|
2924
|
+
"except RuntimeError:",
|
|
2925
|
+
" assrt.ok(True)",
|
|
2926
|
+
// Event
|
|
2927
|
+
"ev = Event()",
|
|
2928
|
+
"assrt.equal(ev.is_set(), False)",
|
|
2929
|
+
"ev.set()",
|
|
2930
|
+
"assrt.equal(ev.is_set(), True)",
|
|
2931
|
+
"ev.clear()",
|
|
2932
|
+
"assrt.equal(ev.is_set(), False)",
|
|
2933
|
+
// Semaphore
|
|
2934
|
+
"sem = Semaphore(2)",
|
|
2935
|
+
"assrt.equal(sem.locked(), False)",
|
|
2936
|
+
"sem.release()",
|
|
2937
|
+
"assrt.equal(sem.locked(), False)",
|
|
2938
|
+
// BoundedSemaphore
|
|
2939
|
+
"bsem = BoundedSemaphore(1)",
|
|
2940
|
+
"try:",
|
|
2941
|
+
" bsem.release()",
|
|
2942
|
+
" assrt.ok(False)",
|
|
2943
|
+
"except ValueError:",
|
|
2944
|
+
" assrt.ok(True)",
|
|
2945
|
+
// Queue synchronous ops
|
|
2946
|
+
"q = Queue()",
|
|
2947
|
+
"assrt.equal(q.empty(), True)",
|
|
2948
|
+
"q.put_nowait('a')",
|
|
2949
|
+
"q.put_nowait('b')",
|
|
2950
|
+
"assrt.equal(q.qsize(), 2)",
|
|
2951
|
+
"assrt.equal(q.get_nowait(), 'a')",
|
|
2952
|
+
"q.task_done()",
|
|
2953
|
+
"assrt.equal(q.get_nowait(), 'b')",
|
|
2954
|
+
"q.task_done()",
|
|
2955
|
+
"assrt.equal(q.empty(), True)",
|
|
2956
|
+
].join("\n"));
|
|
2957
|
+
run_js(js);
|
|
2958
|
+
},
|
|
2959
|
+
},
|
|
2226
2960
|
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
:
|
|
2961
|
+
{
|
|
2962
|
+
name: "bundle_asyncio_coroutine_helpers",
|
|
2963
|
+
description: "asyncio stdlib: iscoroutine, iscoroutinefunction, sleep/gather/run return Promises in the web-repl bundle",
|
|
2964
|
+
run: function () {
|
|
2965
|
+
var repl = RS.web_repl();
|
|
2966
|
+
var js = bundle_compile(repl, [
|
|
2967
|
+
"from asyncio import iscoroutine, iscoroutinefunction, sleep, gather, create_task, run, shield",
|
|
2968
|
+
// sleep returns a thenable
|
|
2969
|
+
"p = sleep(0)",
|
|
2970
|
+
"assrt.ok(iscoroutine(p))",
|
|
2971
|
+
// gather returns a thenable
|
|
2972
|
+
"p2 = gather(Promise.resolve(1), Promise.resolve(2))",
|
|
2973
|
+
"assrt.ok(iscoroutine(p2))",
|
|
2974
|
+
// create_task / run pass through
|
|
2975
|
+
"p3 = Promise.resolve(99)",
|
|
2976
|
+
"assrt.ok(iscoroutine(create_task(p3)))",
|
|
2977
|
+
"assrt.ok(iscoroutine(run(p3)))",
|
|
2978
|
+
"assrt.ok(iscoroutine(shield(p3)))",
|
|
2979
|
+
// iscoroutine
|
|
2980
|
+
"assrt.ok(not iscoroutine(42))",
|
|
2981
|
+
"assrt.ok(not iscoroutine(None))",
|
|
2982
|
+
"assrt.ok(iscoroutine(sleep(0)))",
|
|
2983
|
+
// iscoroutinefunction
|
|
2984
|
+
"async def _af():",
|
|
2985
|
+
" return 1",
|
|
2986
|
+
"assrt.ok(iscoroutinefunction(_af))",
|
|
2987
|
+
"def _sf():",
|
|
2988
|
+
" return 1",
|
|
2989
|
+
"assrt.ok(not iscoroutinefunction(_sf))",
|
|
2990
|
+
// async functions return Promises
|
|
2991
|
+
"assrt.ok(iscoroutine(_af()))",
|
|
2992
|
+
].join("\n"));
|
|
2993
|
+
run_js(js);
|
|
2994
|
+
},
|
|
2995
|
+
},
|
|
2996
|
+
|
|
2997
|
+
{
|
|
2998
|
+
name: "bundle_asyncio_queue_variants",
|
|
2999
|
+
description: "asyncio stdlib: LifoQueue and PriorityQueue ordering in the web-repl bundle",
|
|
3000
|
+
run: function () {
|
|
3001
|
+
var repl = RS.web_repl();
|
|
3002
|
+
var js = bundle_compile(repl, [
|
|
3003
|
+
"from asyncio import Queue, LifoQueue, PriorityQueue, QueueFull, QueueEmpty",
|
|
3004
|
+
// Bounded Queue — QueueFull
|
|
3005
|
+
"q2 = Queue(2)",
|
|
3006
|
+
"q2.put_nowait(1)",
|
|
3007
|
+
"q2.put_nowait(2)",
|
|
3008
|
+
"assrt.equal(q2.full(), True)",
|
|
3009
|
+
"try:",
|
|
3010
|
+
" q2.put_nowait(3)",
|
|
3011
|
+
" assrt.ok(False)",
|
|
3012
|
+
"except QueueFull:",
|
|
3013
|
+
" assrt.ok(True)",
|
|
3014
|
+
// LifoQueue ordering
|
|
3015
|
+
"lq = LifoQueue()",
|
|
3016
|
+
"lq.put_nowait(1)",
|
|
3017
|
+
"lq.put_nowait(2)",
|
|
3018
|
+
"lq.put_nowait(3)",
|
|
3019
|
+
"assrt.equal(lq.get_nowait(), 3)",
|
|
3020
|
+
"assrt.equal(lq.get_nowait(), 2)",
|
|
3021
|
+
"assrt.equal(lq.get_nowait(), 1)",
|
|
3022
|
+
// PriorityQueue ordering (lowest value first)
|
|
3023
|
+
"pq = PriorityQueue()",
|
|
3024
|
+
"pq.put_nowait(30)",
|
|
3025
|
+
"pq.put_nowait(10)",
|
|
3026
|
+
"pq.put_nowait(20)",
|
|
3027
|
+
"assrt.equal(pq.get_nowait(), 10)",
|
|
3028
|
+
"assrt.equal(pq.get_nowait(), 20)",
|
|
3029
|
+
"assrt.equal(pq.get_nowait(), 30)",
|
|
3030
|
+
// QueueEmpty
|
|
3031
|
+
"q3 = Queue()",
|
|
3032
|
+
"try:",
|
|
3033
|
+
" q3.get_nowait()",
|
|
3034
|
+
" assrt.ok(False)",
|
|
3035
|
+
"except QueueEmpty:",
|
|
3036
|
+
" assrt.ok(True)",
|
|
3037
|
+
].join("\n"));
|
|
3038
|
+
run_js(js);
|
|
3039
|
+
},
|
|
3040
|
+
},
|
|
3041
|
+
|
|
3042
|
+
{
|
|
3043
|
+
name: "bundle_asyncio_async_await",
|
|
3044
|
+
description: "asyncio stdlib: async def / await compiles and returns Promise in the web-repl bundle",
|
|
3045
|
+
run: function () {
|
|
3046
|
+
var repl = RS.web_repl();
|
|
3047
|
+
var js = bundle_compile(repl, [
|
|
3048
|
+
"from asyncio import sleep, gather, iscoroutine, iscoroutinefunction, get_event_loop",
|
|
3049
|
+
// async functions compile and return Promises
|
|
3050
|
+
"async def _add(a, b):",
|
|
3051
|
+
" return a + b",
|
|
3052
|
+
"assrt.ok(iscoroutinefunction(_add))",
|
|
3053
|
+
"p = _add(2, 3)",
|
|
3054
|
+
"assrt.ok(iscoroutine(p))",
|
|
3055
|
+
// chained awaits produce a Promise
|
|
3056
|
+
"async def _chain():",
|
|
3057
|
+
" x = await Promise.resolve(10)",
|
|
3058
|
+
" y = await Promise.resolve(20)",
|
|
3059
|
+
" return x + y",
|
|
3060
|
+
"assrt.ok(iscoroutine(_chain()))",
|
|
3061
|
+
// gather of resolved Promises
|
|
3062
|
+
"async def _g():",
|
|
3063
|
+
" results = await gather(Promise.resolve(1), Promise.resolve(2))",
|
|
3064
|
+
" return results",
|
|
3065
|
+
"assrt.ok(iscoroutine(_g()))",
|
|
3066
|
+
// event loop stub
|
|
3067
|
+
"loop = get_event_loop()",
|
|
3068
|
+
"assrt.ok(loop is not None)",
|
|
3069
|
+
"assrt.ok(not loop.is_closed())",
|
|
3070
|
+
"assrt.ok(loop.is_running())",
|
|
3071
|
+
].join("\n"));
|
|
3072
|
+
run_js(js);
|
|
3073
|
+
},
|
|
3074
|
+
},
|
|
3075
|
+
|
|
3076
|
+
{
|
|
3077
|
+
name: "bundle_async_generator_shape",
|
|
3078
|
+
description: "async def with yield returns an async iterator with .next/.send/.asend (web-repl bundle)",
|
|
3079
|
+
run: function () {
|
|
3080
|
+
var repl = RS.web_repl();
|
|
3081
|
+
var js = bundle_compile(repl, [
|
|
3082
|
+
"async def aiter():",
|
|
3083
|
+
" yield 1",
|
|
3084
|
+
" yield 2",
|
|
3085
|
+
" yield 3",
|
|
3086
|
+
"it = aiter()",
|
|
3087
|
+
// The wrapper is sync — calling the async generator returns the
|
|
3088
|
+
// iterator immediately, NOT a Promise.
|
|
3089
|
+
"assrt.equal(jstype(it.next), 'function')",
|
|
3090
|
+
"assrt.equal(jstype(it.send), 'function')", // Python alias
|
|
3091
|
+
"assrt.equal(jstype(it.asend), 'function')", // async-gen alias
|
|
3092
|
+
"assrt.equal(jstype(it[v'Symbol.asyncIterator']), 'function')",
|
|
3093
|
+
// .next() returns a thenable Promise
|
|
3094
|
+
"p = it.next()",
|
|
3095
|
+
"assrt.equal(jstype(p.then), 'function')",
|
|
3096
|
+
].join("\n"));
|
|
3097
|
+
run_js(js);
|
|
3098
|
+
},
|
|
3099
|
+
},
|
|
3100
|
+
|
|
3101
|
+
{
|
|
3102
|
+
name: "bundle_async_generator_await_inside",
|
|
3103
|
+
description: "async generators may use `await` between yields (web-repl bundle)",
|
|
3104
|
+
run: function () {
|
|
3105
|
+
var repl = RS.web_repl();
|
|
3106
|
+
var js = bundle_compile(repl, [
|
|
3107
|
+
"async def gen():",
|
|
3108
|
+
" a = await Promise.resolve(10)",
|
|
3109
|
+
" yield a",
|
|
3110
|
+
" b = await Promise.resolve(20)",
|
|
3111
|
+
" yield a + b",
|
|
3112
|
+
"it = gen()",
|
|
3113
|
+
"assrt.equal(jstype(it.next), 'function')",
|
|
3114
|
+
"assrt.equal(jstype(it[v'Symbol.asyncIterator']), 'function')",
|
|
3115
|
+
// .next() is thenable even when the body awaits before yielding
|
|
3116
|
+
"assrt.equal(jstype(it.next().then), 'function')",
|
|
3117
|
+
].join("\n"));
|
|
3118
|
+
run_js(js);
|
|
3119
|
+
},
|
|
3120
|
+
},
|
|
3121
|
+
|
|
3122
|
+
{
|
|
3123
|
+
name: "bundle_async_for_compiles_and_resolves",
|
|
3124
|
+
description: "async for loop drives an async generator and resolves to expected values (web-repl bundle)",
|
|
3125
|
+
run: function () {
|
|
3126
|
+
var repl = RS.web_repl();
|
|
3127
|
+
var js = bundle_compile(repl, [
|
|
3128
|
+
"async def aiter():",
|
|
3129
|
+
" yield 'a'",
|
|
3130
|
+
" yield 'b'",
|
|
3131
|
+
" yield 'c'",
|
|
3132
|
+
"async def consume():",
|
|
3133
|
+
" out = []",
|
|
3134
|
+
" async for x in aiter():",
|
|
3135
|
+
" out.append(x)",
|
|
3136
|
+
" return out",
|
|
3137
|
+
"p = consume()",
|
|
3138
|
+
"assrt.equal(jstype(p.then), 'function')",
|
|
3139
|
+
// The resolved value is verified via a microtask callback that
|
|
3140
|
+
// throws on mismatch. Failing the assertion in a Promise chain
|
|
3141
|
+
// surfaces as an unhandled rejection — Node exits non-zero,
|
|
3142
|
+
// failing the test.
|
|
3143
|
+
"v\"p.then(function(v){ if (v.length !== 3 || v[0] !== 'a' || v[1] !== 'b' || v[2] !== 'c') throw new Error('async-for produced ' + JSON.stringify(v)); })\"",
|
|
3144
|
+
].join("\n"));
|
|
3145
|
+
run_js(js);
|
|
3146
|
+
},
|
|
3147
|
+
},
|
|
3148
|
+
|
|
3149
|
+
{
|
|
3150
|
+
name: "bundle_async_generator_class_method",
|
|
3151
|
+
description: "async generator method on a class compiles and exposes async iterator (web-repl bundle)",
|
|
3152
|
+
run: function () {
|
|
3153
|
+
var repl = RS.web_repl();
|
|
3154
|
+
var js = bundle_compile(repl, [
|
|
3155
|
+
"class Counter:",
|
|
3156
|
+
" def __init__(self, limit):",
|
|
3157
|
+
" self.limit = limit",
|
|
3158
|
+
"",
|
|
3159
|
+
" async def values(self):",
|
|
3160
|
+
" i = 0",
|
|
3161
|
+
" while i < self.limit:",
|
|
3162
|
+
" yield i",
|
|
3163
|
+
" i += 1",
|
|
3164
|
+
"",
|
|
3165
|
+
"it = Counter(3).values()",
|
|
3166
|
+
"assrt.equal(jstype(it.next), 'function')",
|
|
3167
|
+
"assrt.equal(jstype(it.send), 'function')",
|
|
3168
|
+
"assrt.equal(jstype(it.asend), 'function')",
|
|
3169
|
+
"assrt.equal(jstype(it[v'Symbol.asyncIterator']), 'function')",
|
|
3170
|
+
"assrt.equal(jstype(it.next().then), 'function')",
|
|
3171
|
+
].join("\n"));
|
|
3172
|
+
run_js(js);
|
|
3173
|
+
},
|
|
3174
|
+
},
|
|
3175
|
+
|
|
3176
|
+
{
|
|
3177
|
+
name: "bundle_urllib_parse_quote",
|
|
3178
|
+
description: "urllib.parse stdlib: quote, unquote, quote_plus, unquote_plus in the web-repl bundle",
|
|
3179
|
+
run: function () {
|
|
3180
|
+
var repl = RS.web_repl();
|
|
3181
|
+
var js = bundle_compile(repl, [
|
|
3182
|
+
"from urllib.parse import quote, unquote, quote_plus, unquote_plus",
|
|
3183
|
+
// quote: basic
|
|
3184
|
+
"assrt.equal(quote('hello world'), 'hello%20world')",
|
|
3185
|
+
"assrt.equal(quote('a/b/c'), 'a/b/c')", // '/' safe by default
|
|
3186
|
+
"assrt.equal(quote('a/b/c', safe=''), 'a%2Fb%2Fc')",
|
|
3187
|
+
"assrt.equal(quote('abc123-_.~'), 'abc123-_.~')", // RFC 3986 unreserved
|
|
3188
|
+
"assrt.equal(quote('a+b'), 'a%2Bb')",
|
|
3189
|
+
"assrt.equal(quote('!*'), '%21%2A')", // sub-delimiters encoded
|
|
3190
|
+
// unquote
|
|
3191
|
+
"assrt.equal(unquote('hello%20world'), 'hello world')",
|
|
3192
|
+
"assrt.equal(unquote('a%2Fb'), 'a/b')",
|
|
3193
|
+
"assrt.equal(unquote('a%2Bb'), 'a+b')", // '+' not decoded by unquote
|
|
3194
|
+
// quote_plus / unquote_plus
|
|
3195
|
+
"assrt.equal(quote_plus('hello world'), 'hello+world')",
|
|
3196
|
+
"assrt.equal(quote_plus('a+b'), 'a%2Bb')",
|
|
3197
|
+
"assrt.equal(unquote_plus('hello+world'), 'hello world')",
|
|
3198
|
+
"assrt.equal(unquote_plus('a%2Bb'), 'a+b')",
|
|
3199
|
+
// round-trips
|
|
3200
|
+
"s = 'a/b c+d=e&f'",
|
|
3201
|
+
"assrt.equal(unquote(quote(s, safe='')), s)",
|
|
3202
|
+
"assrt.equal(unquote_plus(quote_plus(s, safe='')), s)",
|
|
3203
|
+
].join("\n"));
|
|
3204
|
+
run_js(js);
|
|
3205
|
+
},
|
|
3206
|
+
},
|
|
3207
|
+
|
|
3208
|
+
{
|
|
3209
|
+
name: "bundle_urllib_parse_urlencode",
|
|
3210
|
+
description: "urllib.parse stdlib: urlencode, parse_qs, parse_qsl in the web-repl bundle",
|
|
3211
|
+
run: function () {
|
|
3212
|
+
var repl = RS.web_repl();
|
|
3213
|
+
var js = bundle_compile(repl, [
|
|
3214
|
+
"from urllib.parse import urlencode, parse_qs, parse_qsl",
|
|
3215
|
+
// urlencode with list of pairs
|
|
3216
|
+
"assrt.equal(urlencode([['a', '1'], ['b', '2']]), 'a=1&b=2')",
|
|
3217
|
+
"assrt.equal(urlencode([['q', 'hello world']]), 'q=hello%20world')",
|
|
3218
|
+
"assrt.equal(urlencode([]), '')",
|
|
3219
|
+
// doseq
|
|
3220
|
+
"assrt.equal(urlencode([['a', ['x', 'y']]], doseq=True), 'a=x&a=y')",
|
|
3221
|
+
// parse_qsl
|
|
3222
|
+
"pairs = parse_qsl('a=1&b=2&a=3')",
|
|
3223
|
+
"assrt.equal(pairs.length, 3)",
|
|
3224
|
+
"assrt.equal(pairs[0][0], 'a')",
|
|
3225
|
+
"assrt.equal(pairs[0][1], '1')",
|
|
3226
|
+
"assrt.equal(pairs[2][0], 'a')",
|
|
3227
|
+
"assrt.equal(pairs[2][1], '3')",
|
|
3228
|
+
"assrt.equal(parse_qsl('a=hello+world')[0][1], 'hello world')",
|
|
3229
|
+
"assrt.equal(parse_qsl('q=a%20b')[0][1], 'a b')",
|
|
3230
|
+
// parse_qs
|
|
3231
|
+
"d = parse_qs('a=1&b=2&a=3')",
|
|
3232
|
+
"assrt.equal(d['a'].length, 2)",
|
|
3233
|
+
"assrt.equal(d['a'][0], '1')",
|
|
3234
|
+
"assrt.equal(d['a'][1], '3')",
|
|
3235
|
+
"assrt.equal(d['b'][0], '2')",
|
|
3236
|
+
"assrt.equal(parse_qs('q=hello+world')['q'][0], 'hello world')",
|
|
3237
|
+
].join("\n"));
|
|
3238
|
+
run_js(js);
|
|
3239
|
+
},
|
|
3240
|
+
},
|
|
3241
|
+
|
|
3242
|
+
{
|
|
3243
|
+
name: "bundle_urllib_parse_urlparse",
|
|
3244
|
+
description: "urllib.parse stdlib: urlsplit, urlparse, urljoin, urlunsplit in the web-repl bundle",
|
|
3245
|
+
run: function () {
|
|
3246
|
+
var repl = RS.web_repl();
|
|
3247
|
+
var js = bundle_compile(repl, [
|
|
3248
|
+
"from urllib.parse import urlsplit, urlunsplit, urlparse, urlunparse, urljoin",
|
|
3249
|
+
// urlsplit
|
|
3250
|
+
"r = urlsplit('http://example.com/path?q=1#frag')",
|
|
3251
|
+
"assrt.equal(r.scheme, 'http')",
|
|
3252
|
+
"assrt.equal(r.netloc, 'example.com')",
|
|
3253
|
+
"assrt.equal(r.path, '/path')",
|
|
3254
|
+
"assrt.equal(r.query, 'q=1')",
|
|
3255
|
+
"assrt.equal(r.fragment, 'frag')",
|
|
3256
|
+
"assrt.equal(r.hostname, 'example.com')",
|
|
3257
|
+
"assrt.equal(r.port, None)",
|
|
3258
|
+
// authority with user:pass@host:port
|
|
3259
|
+
"r2 = urlsplit('https://user:pw@host:8080/p?x=1')",
|
|
3260
|
+
"assrt.equal(r2.hostname, 'host')",
|
|
3261
|
+
"assrt.equal(r2.port, 8080)",
|
|
3262
|
+
"assrt.equal(r2.username, 'user')",
|
|
3263
|
+
"assrt.equal(r2.password, 'pw')",
|
|
3264
|
+
// urlparse splits params
|
|
3265
|
+
"r3 = urlparse('http://example.com/path;params?q=1#frag')",
|
|
3266
|
+
"assrt.equal(r3.path, '/path')",
|
|
3267
|
+
"assrt.equal(r3.params, 'params')",
|
|
3268
|
+
// urlunsplit round-trip
|
|
3269
|
+
"assrt.equal(urlunsplit(('http', 'example.com', '/path', 'q=1', 'frag')), 'http://example.com/path?q=1#frag')",
|
|
3270
|
+
"assrt.equal(urlunsplit(('http', 'example.com', '/path', '', '')), 'http://example.com/path')",
|
|
3271
|
+
// urlunparse with params
|
|
3272
|
+
"assrt.equal(urlunparse(('http', 'example.com', '/path', 'par', 'q=1', 'frag')), 'http://example.com/path;par?q=1#frag')",
|
|
3273
|
+
// geturl round-trip
|
|
3274
|
+
"r4 = urlsplit('http://example.com/path?q=1#frag')",
|
|
3275
|
+
"assrt.equal(r4.geturl(), 'http://example.com/path?q=1#frag')",
|
|
3276
|
+
// urljoin
|
|
3277
|
+
"assrt.equal(urljoin('http://example.com/foo', 'bar'), 'http://example.com/bar')",
|
|
3278
|
+
"assrt.equal(urljoin('http://example.com/foo/', 'bar'), 'http://example.com/foo/bar')",
|
|
3279
|
+
"assrt.equal(urljoin('http://example.com/', '/other'), 'http://example.com/other')",
|
|
3280
|
+
"assrt.equal(urljoin('http://example.com/foo', 'http://other.com/'), 'http://other.com/')",
|
|
3281
|
+
].join("\n"));
|
|
3282
|
+
run_js(js);
|
|
3283
|
+
},
|
|
3284
|
+
},
|
|
3285
|
+
|
|
3286
|
+
{
|
|
3287
|
+
name: "bundle_urllib_error",
|
|
3288
|
+
description: "urllib.error stdlib: URLError and HTTPError exception classes in the web-repl bundle",
|
|
3289
|
+
run: function () {
|
|
3290
|
+
var repl = RS.web_repl();
|
|
3291
|
+
var js = bundle_compile(repl, [
|
|
3292
|
+
"from urllib.error import URLError, HTTPError",
|
|
3293
|
+
// URLError
|
|
3294
|
+
"caught_url = False",
|
|
3295
|
+
"try:",
|
|
3296
|
+
" raise URLError('network failure')",
|
|
3297
|
+
"except URLError as e:",
|
|
3298
|
+
" caught_url = True",
|
|
3299
|
+
" assrt.ok('network failure' in str(e.reason))",
|
|
3300
|
+
"assrt.ok(caught_url)",
|
|
3301
|
+
// HTTPError
|
|
3302
|
+
"caught_http = False",
|
|
3303
|
+
"try:",
|
|
3304
|
+
" raise HTTPError('http://example.com', 404, 'Not Found', {}, None)",
|
|
3305
|
+
"except HTTPError as e:",
|
|
3306
|
+
" caught_http = True",
|
|
3307
|
+
" assrt.equal(e.code, 404)",
|
|
3308
|
+
" assrt.equal(e.msg, 'Not Found')",
|
|
3309
|
+
" assrt.equal(e.getcode(), 404)",
|
|
3310
|
+
" assrt.equal(e.geturl(), 'http://example.com')",
|
|
3311
|
+
"assrt.ok(caught_http)",
|
|
3312
|
+
// HTTPError is caught by URLError handler
|
|
3313
|
+
"caught_as_url = False",
|
|
3314
|
+
"try:",
|
|
3315
|
+
" raise HTTPError('http://x.com', 500, 'Server Error', {}, None)",
|
|
3316
|
+
"except URLError as e:",
|
|
3317
|
+
" caught_as_url = True",
|
|
3318
|
+
" assrt.equal(e.code, 500)",
|
|
3319
|
+
"assrt.ok(caught_as_url)",
|
|
3320
|
+
].join("\n"));
|
|
3321
|
+
run_js(js);
|
|
3322
|
+
},
|
|
3323
|
+
},
|
|
3324
|
+
|
|
3325
|
+
{
|
|
3326
|
+
name: "bundle_bisect_basic",
|
|
3327
|
+
description: "bisect stdlib: bisect_left, bisect_right, bisect in the web-repl bundle",
|
|
3328
|
+
run: function () {
|
|
3329
|
+
var repl = RS.web_repl();
|
|
3330
|
+
var js = bundle_compile(repl, [
|
|
3331
|
+
"from bisect import bisect_left, bisect_right, bisect",
|
|
3332
|
+
"a = [1, 3, 5, 7, 9]",
|
|
3333
|
+
// bisect_left
|
|
3334
|
+
"assrt.equal(bisect_left(a, 0), 0)",
|
|
3335
|
+
"assrt.equal(bisect_left(a, 1), 0)",
|
|
3336
|
+
"assrt.equal(bisect_left(a, 5), 2)",
|
|
3337
|
+
"assrt.equal(bisect_left(a, 9), 4)",
|
|
3338
|
+
"assrt.equal(bisect_left(a, 10), 5)",
|
|
3339
|
+
// bisect_right
|
|
3340
|
+
"assrt.equal(bisect_right(a, 1), 1)",
|
|
3341
|
+
"assrt.equal(bisect_right(a, 5), 3)",
|
|
3342
|
+
"assrt.equal(bisect_right(a, 9), 5)",
|
|
3343
|
+
"assrt.equal(bisect_right(a, 10), 5)",
|
|
3344
|
+
// bisect alias == bisect_right
|
|
3345
|
+
"assrt.equal(bisect(a, 5), bisect_right(a, 5))",
|
|
3346
|
+
// empty list
|
|
3347
|
+
"assrt.equal(bisect_left([], 5), 0)",
|
|
3348
|
+
"assrt.equal(bisect_right([], 5), 0)",
|
|
3349
|
+
// all equal
|
|
3350
|
+
"eq = [3, 3, 3]",
|
|
3351
|
+
"assrt.equal(bisect_left(eq, 3), 0)",
|
|
3352
|
+
"assrt.equal(bisect_right(eq, 3), 3)",
|
|
3353
|
+
// lo/hi bounds
|
|
3354
|
+
"assrt.equal(bisect_left(a, 6, 1, 4), 3)",
|
|
3355
|
+
"assrt.equal(bisect_right(a, 3, 1, 4), 2)",
|
|
3356
|
+
].join("\n"));
|
|
3357
|
+
run_js(js);
|
|
3358
|
+
},
|
|
3359
|
+
},
|
|
3360
|
+
|
|
3361
|
+
{
|
|
3362
|
+
name: "bundle_bisect_insort",
|
|
3363
|
+
description: "bisect stdlib: insort_left, insort_right, insort in the web-repl bundle",
|
|
3364
|
+
run: function () {
|
|
3365
|
+
var repl = RS.web_repl();
|
|
3366
|
+
var js = bundle_compile(repl, [
|
|
3367
|
+
"from bisect import insort_left, insort_right, insort",
|
|
3368
|
+
// insort_right basic
|
|
3369
|
+
"r = [1, 3, 5, 7]",
|
|
3370
|
+
"insort_right(r, 4)",
|
|
3371
|
+
"assrt.deepEqual(r, [1, 3, 4, 5, 7])",
|
|
3372
|
+
// insort_left: new value goes to the LEFT of equal elements
|
|
3373
|
+
"l = [1, 3, 3, 5]",
|
|
3374
|
+
"insort_left(l, 3)",
|
|
3375
|
+
"assrt.equal(l[1], 3)", // new 3 at index 1
|
|
3376
|
+
"assrt.equal(l.length, 5)",
|
|
3377
|
+
// insort alias == insort_right
|
|
3378
|
+
"s = [2, 4, 6]",
|
|
3379
|
+
"insort(s, 5)",
|
|
3380
|
+
"assrt.deepEqual(s, [2, 4, 5, 6])",
|
|
3381
|
+
// build sorted list from scratch
|
|
3382
|
+
"built = []",
|
|
3383
|
+
"for v in [5, 1, 3, 2, 4]:",
|
|
3384
|
+
" insort(built, v)",
|
|
3385
|
+
"assrt.deepEqual(built, [1, 2, 3, 4, 5])",
|
|
3386
|
+
].join("\n"));
|
|
3387
|
+
run_js(js);
|
|
3388
|
+
},
|
|
3389
|
+
},
|
|
3390
|
+
|
|
3391
|
+
{
|
|
3392
|
+
name: "bundle_bisect_key",
|
|
3393
|
+
description: "bisect stdlib: key function parameter in the web-repl bundle",
|
|
3394
|
+
run: function () {
|
|
3395
|
+
var repl = RS.web_repl();
|
|
3396
|
+
var js = bundle_compile(repl, [
|
|
3397
|
+
"from bisect import bisect_left, bisect_right",
|
|
3398
|
+
// key extracts first element of each pair; x is the key value
|
|
3399
|
+
"pairs = [[1, 'a'], [3, 'b'], [5, 'c'], [7, 'd']]",
|
|
3400
|
+
"kfn = def(item): return item[0];",
|
|
3401
|
+
"assrt.equal(bisect_left(pairs, 3, 0, None, kfn), 1)",
|
|
3402
|
+
"assrt.equal(bisect_right(pairs, 3, 0, None, kfn), 2)",
|
|
3403
|
+
"assrt.equal(bisect_left(pairs, 4, 0, None, kfn), 2)",
|
|
3404
|
+
"assrt.equal(bisect_right(pairs, 4, 0, None, kfn), 2)",
|
|
3405
|
+
"assrt.equal(bisect_left(pairs, 0, 0, None, kfn), 0)",
|
|
3406
|
+
"assrt.equal(bisect_right(pairs, 8, 0, None, kfn), 4)",
|
|
3407
|
+
].join("\n"));
|
|
3408
|
+
run_js(js);
|
|
3409
|
+
},
|
|
3410
|
+
},
|
|
3411
|
+
|
|
3412
|
+
{
|
|
3413
|
+
name: "bundle_bisect_errors",
|
|
3414
|
+
description: "bisect stdlib: ValueError for negative lo in the web-repl bundle",
|
|
3415
|
+
run: function () {
|
|
3416
|
+
var repl = RS.web_repl();
|
|
3417
|
+
var js = bundle_compile(repl, [
|
|
3418
|
+
"from bisect import bisect_left, bisect_right",
|
|
3419
|
+
"caught_left = False",
|
|
3420
|
+
"try:",
|
|
3421
|
+
" bisect_left([1, 2, 3], 2, -1)",
|
|
3422
|
+
"except ValueError:",
|
|
3423
|
+
" caught_left = True",
|
|
3424
|
+
"assrt.ok(caught_left)",
|
|
3425
|
+
"caught_right = False",
|
|
3426
|
+
"try:",
|
|
3427
|
+
" bisect_right([1, 2, 3], 2, -1)",
|
|
3428
|
+
"except ValueError:",
|
|
3429
|
+
" caught_right = True",
|
|
3430
|
+
"assrt.ok(caught_right)",
|
|
3431
|
+
// strings also work
|
|
3432
|
+
"words = ['bar', 'baz', 'foo', 'qux']",
|
|
3433
|
+
"assrt.equal(bisect_left(words, 'car'), 2)",
|
|
3434
|
+
"assrt.equal(bisect_right(words, 'car'), 2)",
|
|
3435
|
+
].join("\n"));
|
|
3436
|
+
run_js(js);
|
|
3437
|
+
},
|
|
3438
|
+
},
|
|
3439
|
+
|
|
3440
|
+
// ── http stdlib ──────────────────────────────────────────────────────────
|
|
3441
|
+
|
|
3442
|
+
{
|
|
3443
|
+
name: "bundle_http_status",
|
|
3444
|
+
description: "http stdlib: HTTPStatus constants in the web-repl bundle",
|
|
3445
|
+
run: function () {
|
|
3446
|
+
var repl = RS.web_repl();
|
|
3447
|
+
var js = bundle_compile(repl, [
|
|
3448
|
+
"from http import HTTPStatus",
|
|
3449
|
+
"assrt.equal(HTTPStatus.OK, 200)",
|
|
3450
|
+
"assrt.equal(HTTPStatus.CREATED, 201)",
|
|
3451
|
+
"assrt.equal(HTTPStatus.NO_CONTENT, 204)",
|
|
3452
|
+
"assrt.equal(HTTPStatus.NOT_FOUND, 404)",
|
|
3453
|
+
"assrt.equal(HTTPStatus.INTERNAL_SERVER_ERROR, 500)",
|
|
3454
|
+
"assrt.equal(HTTPStatus.IM_A_TEAPOT, 418)",
|
|
3455
|
+
"assrt.equal(HTTPStatus.MOVED_PERMANENTLY, 301)",
|
|
3456
|
+
"assrt.equal(HTTPStatus.UNAUTHORIZED, 401)",
|
|
3457
|
+
].join("\n"));
|
|
3458
|
+
run_js(js);
|
|
3459
|
+
},
|
|
3460
|
+
},
|
|
3461
|
+
|
|
3462
|
+
{
|
|
3463
|
+
name: "bundle_http_client_basics",
|
|
3464
|
+
description: "http.client stdlib: HTTPConnection, HTTPSConnection, HTTPResponse, exceptions in the web-repl bundle",
|
|
3465
|
+
run: function () {
|
|
3466
|
+
var repl = RS.web_repl();
|
|
3467
|
+
var js = bundle_compile(repl, [
|
|
3468
|
+
"from http.client import (HTTPConnection, HTTPSConnection, HTTPResponse,",
|
|
3469
|
+
" HTTPException, NotConnected, InvalidURL,",
|
|
3470
|
+
" RemoteDisconnected, HTTP_PORT, HTTPS_PORT)",
|
|
3471
|
+
"assrt.equal(HTTP_PORT, 80)",
|
|
3472
|
+
"assrt.equal(HTTPS_PORT, 443)",
|
|
3473
|
+
// URL building
|
|
3474
|
+
"conn = HTTPConnection('example.com')",
|
|
3475
|
+
"assrt.equal(conn._build_url('/path'), 'http://example.com/path')",
|
|
3476
|
+
"conn2 = HTTPConnection('example.com', 8080)",
|
|
3477
|
+
"assrt.equal(conn2._build_url('/api'), 'http://example.com:8080/api')",
|
|
3478
|
+
"sconn = HTTPSConnection('secure.example.com')",
|
|
3479
|
+
"assrt.equal(sconn._build_url('/data'), 'https://secure.example.com/data')",
|
|
3480
|
+
// request() stores state
|
|
3481
|
+
"conn3 = HTTPConnection('api.example.com')",
|
|
3482
|
+
"conn3.request('POST', '/items', 'a=1', {'Content-Type': 'text/plain'})",
|
|
3483
|
+
"assrt.equal(conn3._method, 'POST')",
|
|
3484
|
+
"assrt.equal(conn3._path, '/items')",
|
|
3485
|
+
"assrt.equal(conn3._body, 'a=1')",
|
|
3486
|
+
"assrt.equal(conn3._headers['content-type'], 'text/plain')",
|
|
3487
|
+
// HTTPResponse accessors
|
|
3488
|
+
"hdrs = {'content-type': 'application/json'}",
|
|
3489
|
+
"resp = HTTPResponse(200, 'OK', hdrs, '{\"n\":42}', 'https://x.com/')",
|
|
3490
|
+
"assrt.equal(resp.status, 200)",
|
|
3491
|
+
"assrt.equal(resp.reason, 'OK')",
|
|
3492
|
+
"assrt.equal(resp.getheader('content-type'), 'application/json')",
|
|
3493
|
+
"assrt.equal(resp.getheader('Content-Type'), 'application/json')",
|
|
3494
|
+
"assrt.equal(resp.getheader('missing', 'def'), 'def')",
|
|
3495
|
+
"assrt.ok(resp.read() is not None)",
|
|
3496
|
+
"assrt.ok(resp.json() is not None)",
|
|
3497
|
+
// exception hierarchy
|
|
3498
|
+
"caught = False",
|
|
3499
|
+
"try:",
|
|
3500
|
+
" raise NotConnected('nc')",
|
|
3501
|
+
"except HTTPException as e:",
|
|
3502
|
+
" caught = True",
|
|
3503
|
+
"assrt.ok(caught)",
|
|
3504
|
+
].join("\n"));
|
|
3505
|
+
run_js(js);
|
|
3506
|
+
},
|
|
3507
|
+
},
|
|
3508
|
+
|
|
3509
|
+
{
|
|
3510
|
+
name: "bundle_http_cookies_basic",
|
|
3511
|
+
description: "http.cookies stdlib: SimpleCookie parsing and Morsel access in the web-repl bundle",
|
|
3512
|
+
run: function () {
|
|
3513
|
+
var repl = RS.web_repl();
|
|
3514
|
+
var js = bundle_compile(repl, [
|
|
3515
|
+
"from __python__ import overload_getitem",
|
|
3516
|
+
"from http.cookies import SimpleCookie, Morsel, CookieError",
|
|
3517
|
+
// basic parse
|
|
3518
|
+
"c = SimpleCookie()",
|
|
3519
|
+
"c.load('session=abc123; user=alice')",
|
|
3520
|
+
"assrt.ok('session' in c.keys())",
|
|
3521
|
+
"assrt.ok('user' in c.keys())",
|
|
3522
|
+
"assrt.equal(c['session'].value, 'abc123')",
|
|
3523
|
+
"assrt.equal(c['user'].value, 'alice')",
|
|
3524
|
+
// set a cookie
|
|
3525
|
+
"c2 = SimpleCookie()",
|
|
3526
|
+
"c2['token'] = 'xyz'",
|
|
3527
|
+
"assrt.ok('token' in c2.keys())",
|
|
3528
|
+
"assrt.equal(c2['token'].value, 'xyz')",
|
|
3529
|
+
// cookie attributes
|
|
3530
|
+
"c3 = SimpleCookie()",
|
|
3531
|
+
"c3['id'] = '1'",
|
|
3532
|
+
"c3['id']['path'] = '/'",
|
|
3533
|
+
"c3['id']['max-age'] = 3600",
|
|
3534
|
+
"assrt.equal(c3['id']['path'], '/')",
|
|
3535
|
+
"assrt.equal(c3['id']['max-age'], 3600)",
|
|
3536
|
+
// constructor with initial data
|
|
3537
|
+
"c4 = SimpleCookie('x=10; y=20')",
|
|
3538
|
+
"assrt.equal(c4['x'].value, '10')",
|
|
3539
|
+
"assrt.equal(c4['y'].value, '20')",
|
|
3540
|
+
].join("\n"));
|
|
3541
|
+
run_js(js);
|
|
3542
|
+
},
|
|
3543
|
+
},
|
|
3544
|
+
|
|
3545
|
+
{
|
|
3546
|
+
name: "bundle_http_cookies_output",
|
|
3547
|
+
description: "http.cookies stdlib: SimpleCookie.output, Morsel.OutputString in the web-repl bundle",
|
|
3548
|
+
run: function () {
|
|
3549
|
+
var repl = RS.web_repl();
|
|
3550
|
+
var js = bundle_compile(repl, [
|
|
3551
|
+
"from __python__ import overload_getitem",
|
|
3552
|
+
"from http.cookies import SimpleCookie, Morsel, CookieError",
|
|
3553
|
+
// Morsel.OutputString
|
|
3554
|
+
"m = Morsel()",
|
|
3555
|
+
"m.set('token', 'abc', 'abc')",
|
|
3556
|
+
"m._attrs['path'] = '/'",
|
|
3557
|
+
"m._attrs['max-age'] = '3600'",
|
|
3558
|
+
"s = m.OutputString()",
|
|
3559
|
+
"assrt.ok('token=abc' in s)",
|
|
3560
|
+
"assrt.ok('Path=/' in s)",
|
|
3561
|
+
"assrt.ok('Max-Age=3600' in s)",
|
|
3562
|
+
// Morsel.output with header
|
|
3563
|
+
"out = m.output()",
|
|
3564
|
+
"assrt.ok('Set-Cookie: token=abc' in out)",
|
|
3565
|
+
// SimpleCookie.output
|
|
3566
|
+
"c = SimpleCookie()",
|
|
3567
|
+
"c['a'] = '1'",
|
|
3568
|
+
"c['b'] = '2'",
|
|
3569
|
+
"full = c.output()",
|
|
3570
|
+
"assrt.ok('Set-Cookie: a=1' in full)",
|
|
3571
|
+
"assrt.ok('Set-Cookie: b=2' in full)",
|
|
3572
|
+
// CookieError
|
|
3573
|
+
"caught = False",
|
|
3574
|
+
"try:",
|
|
3575
|
+
" raise CookieError('bad')",
|
|
3576
|
+
"except CookieError as e:",
|
|
3577
|
+
" caught = True",
|
|
3578
|
+
"assrt.ok(caught)",
|
|
3579
|
+
].join("\n"));
|
|
3580
|
+
run_js(js);
|
|
3581
|
+
},
|
|
3582
|
+
},
|
|
3583
|
+
|
|
3584
|
+
// ── csv stdlib ────────────────────────────────────────────────────────
|
|
3585
|
+
|
|
3586
|
+
{
|
|
3587
|
+
name: "bundle_csv_reader_basic",
|
|
3588
|
+
description: "csv stdlib: reader parses CSV rows and writer produces CSV text in the web-repl bundle",
|
|
3589
|
+
run: function () {
|
|
3590
|
+
var repl = RS.web_repl();
|
|
3591
|
+
var js = bundle_compile(repl, [
|
|
3592
|
+
"import csv",
|
|
3593
|
+
"from io import StringIO",
|
|
3594
|
+
// reader — list input
|
|
3595
|
+
"rows = []",
|
|
3596
|
+
"for row in csv.reader(['a,b,c', '1,2,3']):",
|
|
3597
|
+
" rows.push(row)",
|
|
3598
|
+
"assrt.equal(rows.length, 2)",
|
|
3599
|
+
"assrt.deepEqual(rows[0], ['a', 'b', 'c'])",
|
|
3600
|
+
"assrt.deepEqual(rows[1], ['1', '2', '3'])",
|
|
3601
|
+
// reader — quoted field
|
|
3602
|
+
"rows2 = []",
|
|
3603
|
+
"for row in csv.reader(['\"hello, world\",foo']):",
|
|
3604
|
+
" rows2.push(row)",
|
|
3605
|
+
"assrt.equal(rows2[0][0], 'hello, world')",
|
|
3606
|
+
"assrt.equal(rows2[0][1], 'foo')",
|
|
3607
|
+
// writer
|
|
3608
|
+
"sio = StringIO()",
|
|
3609
|
+
"w = csv.writer(sio)",
|
|
3610
|
+
"w.writerow(['name', 'age'])",
|
|
3611
|
+
"w.writerow(['Alice', 30])",
|
|
3612
|
+
"out = sio.getvalue()",
|
|
3613
|
+
"assrt.ok('name,age' in out)",
|
|
3614
|
+
"assrt.ok('Alice,30' in out)",
|
|
3615
|
+
// round-trip
|
|
3616
|
+
"buf = StringIO()",
|
|
3617
|
+
"w2 = csv.writer(buf)",
|
|
3618
|
+
"w2.writerow(['x', 'needs, quoting', 'y'])",
|
|
3619
|
+
"buf.seek(0)",
|
|
3620
|
+
"rt = []",
|
|
3621
|
+
"for row in csv.reader(buf):",
|
|
3622
|
+
" rt.push(row)",
|
|
3623
|
+
"assrt.deepEqual(rt[0], ['x', 'needs, quoting', 'y'])",
|
|
3624
|
+
].join("\n"));
|
|
3625
|
+
run_js(js);
|
|
3626
|
+
},
|
|
3627
|
+
},
|
|
3628
|
+
|
|
3629
|
+
{
|
|
3630
|
+
name: "bundle_csv_dictreader",
|
|
3631
|
+
description: "csv stdlib: DictReader reads rows as dicts with automatic fieldnames in the web-repl bundle",
|
|
3632
|
+
run: function () {
|
|
3633
|
+
var repl = RS.web_repl();
|
|
3634
|
+
var js = bundle_compile(repl, [
|
|
3635
|
+
"import csv",
|
|
3636
|
+
// DictReader — fieldnames from first row
|
|
3637
|
+
"rows = []",
|
|
3638
|
+
"for row in csv.DictReader(['name,age', 'Alice,30', 'Bob,25']):",
|
|
3639
|
+
" rows.push(row)",
|
|
3640
|
+
"assrt.equal(rows.length, 2)",
|
|
3641
|
+
"assrt.equal(rows[0]['name'], 'Alice')",
|
|
3642
|
+
"assrt.equal(rows[0]['age'], '30')",
|
|
3643
|
+
"assrt.equal(rows[1]['name'], 'Bob')",
|
|
3644
|
+
// DictReader — provided fieldnames
|
|
3645
|
+
"rows2 = []",
|
|
3646
|
+
"for row in csv.DictReader(['Alice,30', 'Bob,25'], fieldnames=['name','age']):",
|
|
3647
|
+
" rows2.push(row)",
|
|
3648
|
+
"assrt.equal(rows2.length, 2)",
|
|
3649
|
+
"assrt.equal(rows2[0]['name'], 'Alice')",
|
|
3650
|
+
// DictReader — restval for missing field
|
|
3651
|
+
"rows3 = []",
|
|
3652
|
+
"for row in csv.DictReader(['name,age,city', 'Alice,30'], restval='?'):",
|
|
3653
|
+
" rows3.push(row)",
|
|
3654
|
+
"assrt.equal(rows3[0]['city'], '?')",
|
|
3655
|
+
// DictReader — empty input yields no rows
|
|
3656
|
+
"empty_count = 0",
|
|
3657
|
+
"for row in csv.DictReader([]):",
|
|
3658
|
+
" empty_count += 1",
|
|
3659
|
+
"assrt.equal(empty_count, 0)",
|
|
3660
|
+
].join("\n"));
|
|
3661
|
+
run_js(js);
|
|
3662
|
+
},
|
|
3663
|
+
},
|
|
3664
|
+
|
|
3665
|
+
{
|
|
3666
|
+
name: "bundle_csv_dictwriter",
|
|
3667
|
+
description: "csv stdlib: DictWriter writes header and rows from dicts in the web-repl bundle",
|
|
3668
|
+
run: function () {
|
|
3669
|
+
var repl = RS.web_repl();
|
|
3670
|
+
var js = bundle_compile(repl, [
|
|
3671
|
+
"import csv",
|
|
3672
|
+
"from io import StringIO",
|
|
3673
|
+
// DictWriter — writeheader + writerow
|
|
3674
|
+
"sio = StringIO()",
|
|
3675
|
+
"dw = csv.DictWriter(sio, ['name', 'score'])",
|
|
3676
|
+
"dw.writeheader()",
|
|
3677
|
+
"dw.writerow({'name': 'Eve', 'score': '99'})",
|
|
3678
|
+
"dw.writerow({'name': 'Frank', 'score': '88'})",
|
|
3679
|
+
"out = sio.getvalue()",
|
|
3680
|
+
"assrt.ok('name,score' in out)",
|
|
3681
|
+
"assrt.ok('Eve,99' in out)",
|
|
3682
|
+
"assrt.ok('Frank,88' in out)",
|
|
3683
|
+
// round-trip DictWriter → DictReader
|
|
3684
|
+
"buf = StringIO()",
|
|
3685
|
+
"dw2 = csv.DictWriter(buf, ['x', 'y'])",
|
|
3686
|
+
"dw2.writeheader()",
|
|
3687
|
+
"dw2.writerow({'x': 'hello', 'y': 'world'})",
|
|
3688
|
+
"buf.seek(0)",
|
|
3689
|
+
"rt = []",
|
|
3690
|
+
"for row in csv.DictReader(buf):",
|
|
3691
|
+
" rt.push(row)",
|
|
3692
|
+
"assrt.equal(rt.length, 1)",
|
|
3693
|
+
"assrt.equal(rt[0]['x'], 'hello')",
|
|
3694
|
+
"assrt.equal(rt[0]['y'], 'world')",
|
|
3695
|
+
].join("\n"));
|
|
3696
|
+
run_js(js);
|
|
3697
|
+
},
|
|
3698
|
+
},
|
|
3699
|
+
|
|
3700
|
+
{
|
|
3701
|
+
name: "bundle_csv_dialects",
|
|
3702
|
+
description: "csv stdlib: dialect options, register_dialect, list_dialects, field_size_limit in the web-repl bundle",
|
|
3703
|
+
run: function () {
|
|
3704
|
+
var repl = RS.web_repl();
|
|
3705
|
+
var js = bundle_compile(repl, [
|
|
3706
|
+
"import csv",
|
|
3707
|
+
"from io import StringIO",
|
|
3708
|
+
// excel-tab dialect
|
|
3709
|
+
"rows = []",
|
|
3710
|
+
"for row in csv.reader(['a\\tb\\tc'], dialect='excel-tab'):",
|
|
3711
|
+
" rows.push(row)",
|
|
3712
|
+
"assrt.deepEqual(rows[0], ['a', 'b', 'c'])",
|
|
3713
|
+
// QUOTE_ALL
|
|
3714
|
+
"sio = StringIO()",
|
|
3715
|
+
"w = csv.writer(sio, quoting=csv.QUOTE_ALL)",
|
|
3716
|
+
"w.writerow(['x', 'y'])",
|
|
3717
|
+
"assrt.equal(sio.getvalue(), '\"x\",\"y\"\\r\\n')",
|
|
3718
|
+
// register_dialect / list_dialects / unregister_dialect
|
|
3719
|
+
"csv.register_dialect('pipes', delimiter='|')",
|
|
3720
|
+
"dialects = csv.list_dialects()",
|
|
3721
|
+
"assrt.ok(dialects.indexOf('pipes') >= 0)",
|
|
3722
|
+
"sio2 = StringIO()",
|
|
3723
|
+
"w2 = csv.writer(sio2, dialect='pipes')",
|
|
3724
|
+
"w2.writerow(['a', 'b'])",
|
|
3725
|
+
"assrt.equal(sio2.getvalue(), 'a|b\\r\\n')",
|
|
3726
|
+
"csv.unregister_dialect('pipes')",
|
|
3727
|
+
"assrt.ok(csv.list_dialects().indexOf('pipes') < 0)",
|
|
3728
|
+
// field_size_limit
|
|
3729
|
+
"old = csv.field_size_limit(65536)",
|
|
3730
|
+
"assrt.equal(old, 131072)",
|
|
3731
|
+
"assrt.equal(csv.field_size_limit(), 65536)",
|
|
3732
|
+
"csv.field_size_limit(131072)",
|
|
3733
|
+
].join("\n"));
|
|
3734
|
+
run_js(js);
|
|
3735
|
+
},
|
|
3736
|
+
},
|
|
3737
|
+
|
|
3738
|
+
{
|
|
3739
|
+
name: "bundle_textwrap_wrap",
|
|
3740
|
+
description: "textwrap stdlib: wrap, fill, shorten in the web-repl bundle",
|
|
3741
|
+
run: function () {
|
|
3742
|
+
var repl = RS.web_repl();
|
|
3743
|
+
var js = bundle_compile(repl, [
|
|
3744
|
+
"from textwrap import wrap, fill, shorten",
|
|
3745
|
+
// basic wrap
|
|
3746
|
+
"r = wrap('one two three four five', 10)",
|
|
3747
|
+
"assrt.equal(r.length, 3)",
|
|
3748
|
+
"assrt.equal(r[0], 'one two')",
|
|
3749
|
+
"assrt.equal(r[1], 'three four')",
|
|
3750
|
+
"assrt.equal(r[2], 'five')",
|
|
3751
|
+
// short text fits on one line
|
|
3752
|
+
"assrt.deepEqual(wrap('hello', 20), ['hello'])",
|
|
3753
|
+
// empty string
|
|
3754
|
+
"assrt.deepEqual(wrap('', 10), [])",
|
|
3755
|
+
// fill joins with newlines
|
|
3756
|
+
"assrt.equal(fill('one two three', 8), 'one two\\nthree')",
|
|
3757
|
+
// fill with indents
|
|
3758
|
+
"assrt.equal(fill('one two three four', 12, initial_indent='> ', subsequent_indent=' '), '> one two\\n three four')",
|
|
3759
|
+
// shorten — fits
|
|
3760
|
+
"assrt.equal(shorten('hello world', 20), 'hello world')",
|
|
3761
|
+
// shorten — truncates; 'one two three' (13) + ' [...]' (6) = 19 ≤ 20
|
|
3762
|
+
"assrt.equal(shorten('one two three four five', 20), 'one two three [...]')",
|
|
3763
|
+
// shorten — custom placeholder
|
|
3764
|
+
"assrt.equal(shorten('hello world foo bar', 14, placeholder='...'), 'hello world...')",
|
|
3765
|
+
// shorten — normalises whitespace
|
|
3766
|
+
"assrt.equal(shorten('hello world', 20), 'hello world')",
|
|
3767
|
+
].join("\n"));
|
|
3768
|
+
run_js(js);
|
|
3769
|
+
},
|
|
3770
|
+
},
|
|
3771
|
+
|
|
3772
|
+
{
|
|
3773
|
+
name: "bundle_textwrap_dedent",
|
|
3774
|
+
description: "textwrap stdlib: dedent in the web-repl bundle",
|
|
3775
|
+
run: function () {
|
|
3776
|
+
var repl = RS.web_repl();
|
|
3777
|
+
var js = bundle_compile(repl, [
|
|
3778
|
+
"from textwrap import dedent",
|
|
3779
|
+
// common indent removed
|
|
3780
|
+
"assrt.equal(dedent(' hello\\n world'), 'hello\\nworld')",
|
|
3781
|
+
// no common indent
|
|
3782
|
+
"assrt.equal(dedent('hello\\n world'), 'hello\\n world')",
|
|
3783
|
+
// empty lines ignored when computing margin
|
|
3784
|
+
"assrt.equal(dedent(' hello\\n\\n world'), 'hello\\n\\nworld')",
|
|
3785
|
+
// partial common indent: ' ' vs ' ' → margin ' '
|
|
3786
|
+
"assrt.equal(dedent(' foo\\n bar'), ' foo\\nbar')",
|
|
3787
|
+
// leading blank line then indented
|
|
3788
|
+
"assrt.equal(dedent('\\n foo\\n bar'), '\\nfoo\\nbar')",
|
|
3789
|
+
// tab-based indent
|
|
3790
|
+
"assrt.equal(dedent('\\thello\\n\\tworld'), 'hello\\nworld')",
|
|
3791
|
+
].join("\n"));
|
|
3792
|
+
run_js(js);
|
|
3793
|
+
},
|
|
3794
|
+
},
|
|
3795
|
+
|
|
3796
|
+
{
|
|
3797
|
+
name: "bundle_textwrap_indent",
|
|
3798
|
+
description: "textwrap stdlib: indent with and without predicate in the web-repl bundle",
|
|
3799
|
+
run: function () {
|
|
3800
|
+
var repl = RS.web_repl();
|
|
3801
|
+
var js = bundle_compile(repl, [
|
|
3802
|
+
"from textwrap import indent",
|
|
3803
|
+
// basic
|
|
3804
|
+
"assrt.equal(indent('hello\\nworld', ' '), ' hello\\n world')",
|
|
3805
|
+
// empty lines not indented by default
|
|
3806
|
+
"assrt.equal(indent('hello\\n\\nworld', ' '), ' hello\\n\\n world')",
|
|
3807
|
+
// whitespace-only line not indented
|
|
3808
|
+
"assrt.equal(indent('hello\\n \\nworld', '> '), '> hello\\n \\n> world')",
|
|
3809
|
+
// custom predicate: indent all lines
|
|
3810
|
+
"_pred = def(line): return True;",
|
|
3811
|
+
"assrt.equal(indent('hello\\n\\nworld', '> ', _pred), '> hello\\n> \\n> world')",
|
|
3812
|
+
// predicate: only lines starting with '#'
|
|
3813
|
+
"_ph = def(line): return line.startsWith('#');",
|
|
3814
|
+
"assrt.equal(indent('# a\\ncode\\n# b', '!! ', _ph), '!! # a\\ncode\\n!! # b')",
|
|
3815
|
+
].join("\n"));
|
|
3816
|
+
run_js(js);
|
|
3817
|
+
},
|
|
3818
|
+
},
|
|
3819
|
+
|
|
3820
|
+
{
|
|
3821
|
+
name: "bundle_textwrap_textwrapper",
|
|
3822
|
+
description: "textwrap stdlib: TextWrapper class with options in the web-repl bundle",
|
|
3823
|
+
run: function () {
|
|
3824
|
+
var repl = RS.web_repl();
|
|
3825
|
+
var js = bundle_compile(repl, [
|
|
3826
|
+
"from textwrap import TextWrapper",
|
|
3827
|
+
// basic wrap and fill
|
|
3828
|
+
"tw = TextWrapper(width=10)",
|
|
3829
|
+
"assrt.deepEqual(tw.wrap('one two three'), ['one two', 'three'])",
|
|
3830
|
+
"assrt.equal(tw.fill('one two three'), 'one two\\nthree')",
|
|
3831
|
+
// max_lines with placeholder
|
|
3832
|
+
// width=15, max_lines=2, placeholder=' ...' (4 chars)
|
|
3833
|
+
// line 1: 'alpha beta' (10) fits; line 2 truncated to 'gamma delta ...' (15)
|
|
3834
|
+
"tw2 = TextWrapper(width=15, max_lines=2, placeholder=' ...')",
|
|
3835
|
+
"r2 = tw2.wrap('alpha beta gamma delta epsilon')",
|
|
3836
|
+
"assrt.equal(r2.length, 2)",
|
|
3837
|
+
"assrt.equal(r2[0], 'alpha beta')",
|
|
3838
|
+
"assrt.equal(r2[1], 'gamma delta ...')",
|
|
3839
|
+
// break_long_words=False
|
|
3840
|
+
"tw3 = TextWrapper(width=5, break_long_words=False)",
|
|
3841
|
+
"r3 = tw3.wrap('superlongword short')",
|
|
3842
|
+
"assrt.equal(r3[0], 'superlongword')",
|
|
3843
|
+
"assrt.equal(r3[1], 'short')",
|
|
3844
|
+
// fix_sentence_endings
|
|
3845
|
+
"tw4 = TextWrapper(width=70, fix_sentence_endings=True)",
|
|
3846
|
+
"r4 = tw4.wrap('end of sentence. New sentence.')",
|
|
3847
|
+
"assrt.equal(r4[0], 'end of sentence. New sentence.')",
|
|
3848
|
+
].join("\n"));
|
|
3849
|
+
run_js(js);
|
|
3850
|
+
},
|
|
3851
|
+
},
|
|
3852
|
+
|
|
3853
|
+
// ── logging ──────────────────────────────────────────────────────────────
|
|
3854
|
+
|
|
3855
|
+
{
|
|
3856
|
+
name: "bundle_logging_basic",
|
|
3857
|
+
description: "logging stdlib: StreamHandler with custom stream in the web-repl bundle",
|
|
3858
|
+
run: function () {
|
|
3859
|
+
var repl = RS.web_repl();
|
|
3860
|
+
var js = bundle_compile(repl, [
|
|
3861
|
+
"from logging import Logger, StreamHandler, Formatter, DEBUG, INFO, WARNING, ERROR",
|
|
3862
|
+
"class _Buf:",
|
|
3863
|
+
" def __init__(self):",
|
|
3864
|
+
" self.lines = []",
|
|
3865
|
+
" def write(self, s):",
|
|
3866
|
+
" self.lines.push(s)",
|
|
3867
|
+
"_buf = _Buf()",
|
|
3868
|
+
"_h = StreamHandler(_buf)",
|
|
3869
|
+
"_h.setFormatter(Formatter('%(levelname)s:%(name)s:%(message)s'))",
|
|
3870
|
+
"_h.setLevel(DEBUG)",
|
|
3871
|
+
"_log = Logger('myapp')",
|
|
3872
|
+
"_log.addHandler(_h)",
|
|
3873
|
+
"_log.setLevel(DEBUG)",
|
|
3874
|
+
"_log.propagate = False",
|
|
3875
|
+
"_log.debug('dbg')",
|
|
3876
|
+
"_log.info('hi')",
|
|
3877
|
+
"_log.warning('warn')",
|
|
3878
|
+
"_log.error('err')",
|
|
3879
|
+
"assrt.equal(_buf.lines.length, 4)",
|
|
3880
|
+
"assrt.equal(_buf.lines[0], 'DEBUG:myapp:dbg\\n')",
|
|
3881
|
+
"assrt.equal(_buf.lines[1], 'INFO:myapp:hi\\n')",
|
|
3882
|
+
"assrt.equal(_buf.lines[2], 'WARNING:myapp:warn\\n')",
|
|
3883
|
+
"assrt.equal(_buf.lines[3], 'ERROR:myapp:err\\n')",
|
|
3884
|
+
].join("\n"));
|
|
3885
|
+
run_js(js);
|
|
3886
|
+
},
|
|
3887
|
+
},
|
|
3888
|
+
|
|
3889
|
+
{
|
|
3890
|
+
name: "bundle_logging_levels",
|
|
3891
|
+
description: "logging stdlib: level constants, getLevelName, addLevelName, %-format args",
|
|
3892
|
+
run: function () {
|
|
3893
|
+
var repl = RS.web_repl();
|
|
3894
|
+
var js = bundle_compile(repl, [
|
|
3895
|
+
"from logging import getLevelName, addLevelName, DEBUG, INFO, WARNING, ERROR, CRITICAL, NOTSET",
|
|
3896
|
+
"assrt.equal(NOTSET, 0)",
|
|
3897
|
+
"assrt.equal(DEBUG, 10)",
|
|
3898
|
+
"assrt.equal(INFO, 20)",
|
|
3899
|
+
"assrt.equal(WARNING, 30)",
|
|
3900
|
+
"assrt.equal(ERROR, 40)",
|
|
3901
|
+
"assrt.equal(CRITICAL, 50)",
|
|
3902
|
+
"assrt.equal(getLevelName(DEBUG), 'DEBUG')",
|
|
3903
|
+
"assrt.equal(getLevelName(WARNING), 'WARNING')",
|
|
3904
|
+
"assrt.equal(getLevelName(42), 'Level 42')",
|
|
3905
|
+
"assrt.equal(getLevelName('ERROR'), ERROR)",
|
|
3906
|
+
"assrt.equal(getLevelName('WARN'), WARNING)",
|
|
3907
|
+
"addLevelName(15, 'VERBOSE')",
|
|
3908
|
+
"assrt.equal(getLevelName(15), 'VERBOSE')",
|
|
3909
|
+
"assrt.equal(getLevelName('VERBOSE'), 15)",
|
|
3910
|
+
// %-format args via Logger
|
|
3911
|
+
"from logging import Logger, StreamHandler, Formatter",
|
|
3912
|
+
"class _B:",
|
|
3913
|
+
" def __init__(self):",
|
|
3914
|
+
" self.out = []",
|
|
3915
|
+
" def write(self, s):",
|
|
3916
|
+
" self.out.push(s)",
|
|
3917
|
+
"_b = _B()",
|
|
3918
|
+
"_h2 = StreamHandler(_b)",
|
|
3919
|
+
"_h2.setFormatter(Formatter('%(message)s'))",
|
|
3920
|
+
"_l2 = Logger('fmt')",
|
|
3921
|
+
"_l2.addHandler(_h2)",
|
|
3922
|
+
"_l2.setLevel(DEBUG)",
|
|
3923
|
+
"_l2.propagate = False",
|
|
3924
|
+
"_l2.info('x=%d y=%s', 7, 'foo')",
|
|
3925
|
+
"assrt.equal(_b.out[0], 'x=7 y=foo\\n')",
|
|
3926
|
+
].join("\n"));
|
|
3927
|
+
run_js(js);
|
|
3928
|
+
},
|
|
3929
|
+
},
|
|
3930
|
+
|
|
3931
|
+
{
|
|
3932
|
+
name: "bundle_logging_hierarchy",
|
|
3933
|
+
description: "logging stdlib: parent/child logger hierarchy and propagation",
|
|
3934
|
+
run: function () {
|
|
3935
|
+
var repl = RS.web_repl();
|
|
3936
|
+
var js = bundle_compile(repl, [
|
|
3937
|
+
"from logging import Logger, StreamHandler, Formatter, DEBUG, INFO",
|
|
3938
|
+
"class _B:",
|
|
3939
|
+
" def __init__(self):",
|
|
3940
|
+
" self.out = []",
|
|
3941
|
+
" def write(self, s):",
|
|
3942
|
+
" self.out.push(s)",
|
|
3943
|
+
"_b = _B()",
|
|
3944
|
+
"_h = StreamHandler(_b)",
|
|
3945
|
+
"_h.setFormatter(Formatter('%(name)s:%(message)s'))",
|
|
3946
|
+
"_parent = Logger('app')",
|
|
3947
|
+
"_parent.addHandler(_h)",
|
|
3948
|
+
"_parent.setLevel(DEBUG)",
|
|
3949
|
+
"_parent.propagate = False",
|
|
3950
|
+
"_child = Logger('app.sub')",
|
|
3951
|
+
"_child.parent = _parent",
|
|
3952
|
+
"_child.setLevel(DEBUG)",
|
|
3953
|
+
"_child.propagate = True",
|
|
3954
|
+
"_child.info('from child')",
|
|
3955
|
+
"assrt.equal(_b.out.length, 1)",
|
|
3956
|
+
"assrt.equal(_b.out[0], 'app.sub:from child\\n')",
|
|
3957
|
+
// child with propagate=False should NOT reach parent handler
|
|
3958
|
+
"_b2 = _B()",
|
|
3959
|
+
"_h2 = StreamHandler(_b2)",
|
|
3960
|
+
"_h2.setFormatter(Formatter('%(message)s'))",
|
|
3961
|
+
"_child2 = Logger('app.sub2')",
|
|
3962
|
+
"_child2.parent = _parent",
|
|
3963
|
+
"_child2.addHandler(_h2)",
|
|
3964
|
+
"_child2.setLevel(DEBUG)",
|
|
3965
|
+
"_child2.propagate = False",
|
|
3966
|
+
"_child2.info('isolated')",
|
|
3967
|
+
"assrt.equal(_b.out.length, 1)", // parent buffer unchanged
|
|
3968
|
+
"assrt.equal(_b2.out[0], 'isolated\\n')",
|
|
3969
|
+
].join("\n"));
|
|
3970
|
+
run_js(js);
|
|
3971
|
+
},
|
|
3972
|
+
},
|
|
3973
|
+
|
|
3974
|
+
{
|
|
3975
|
+
name: "bundle_logging_basicconfig",
|
|
3976
|
+
description: "logging stdlib: basicConfig sets up root logger with custom stream",
|
|
3977
|
+
run: function () {
|
|
3978
|
+
var repl = RS.web_repl();
|
|
3979
|
+
var js = bundle_compile(repl, [
|
|
3980
|
+
"import logging",
|
|
3981
|
+
"class _B:",
|
|
3982
|
+
" def __init__(self):",
|
|
3983
|
+
" self.out = []",
|
|
3984
|
+
" def write(self, s):",
|
|
3985
|
+
" self.out.push(s)",
|
|
3986
|
+
"_b = _B()",
|
|
3987
|
+
"logging.basicConfig(level=logging.DEBUG, format='%(levelname)s:%(message)s', stream=_b)",
|
|
3988
|
+
"logging.debug('d')",
|
|
3989
|
+
"logging.info('i')",
|
|
3990
|
+
"logging.warning('w')",
|
|
3991
|
+
"assrt.equal(_b.out.length, 3)",
|
|
3992
|
+
"assrt.equal(_b.out[0], 'DEBUG:d\\n')",
|
|
3993
|
+
"assrt.equal(_b.out[1], 'INFO:i\\n')",
|
|
3994
|
+
"assrt.equal(_b.out[2], 'WARNING:w\\n')",
|
|
3995
|
+
// disable() suppresses messages at or below the given level
|
|
3996
|
+
"logging.disable(logging.ERROR)",
|
|
3997
|
+
"logging.warning('suppressed')",
|
|
3998
|
+
"logging.error('also suppressed')",
|
|
3999
|
+
"assrt.equal(_b.out.length, 3)",
|
|
4000
|
+
"logging.disable(logging.NOTSET)", // reset
|
|
4001
|
+
].join("\n"));
|
|
4002
|
+
run_js(js);
|
|
4003
|
+
},
|
|
4004
|
+
},
|
|
4005
|
+
|
|
4006
|
+
// ── pprint stdlib ────────────────────────────────────────────────────────
|
|
4007
|
+
|
|
4008
|
+
{
|
|
4009
|
+
name: "bundle_pprint_basic",
|
|
4010
|
+
description: "pprint stdlib: pformat of atoms and short containers in the web-repl bundle",
|
|
4011
|
+
run: function () {
|
|
4012
|
+
var repl = RS.web_repl();
|
|
4013
|
+
var js = bundle_compile(repl, [
|
|
4014
|
+
"from pprint import pformat",
|
|
4015
|
+
// atoms -> repr()
|
|
4016
|
+
"assrt.equal(pformat(None), 'None')",
|
|
4017
|
+
"assrt.equal(pformat(True), 'True')",
|
|
4018
|
+
"assrt.equal(pformat(42), '42')",
|
|
4019
|
+
"assrt.equal(pformat('hi'), '\"hi\"')",
|
|
4020
|
+
// empty containers
|
|
4021
|
+
"assrt.equal(pformat([]), '[]')",
|
|
4022
|
+
"assrt.equal(pformat({}), '{}')",
|
|
4023
|
+
"assrt.equal(pformat(set()), 'set()')",
|
|
4024
|
+
// short containers fit on one line
|
|
4025
|
+
"assrt.equal(pformat([1, 2, 3]), '[1, 2, 3]')",
|
|
4026
|
+
"assrt.equal(pformat({'a': 1, 'b': 2}), '{\"a\": 1, \"b\": 2}')",
|
|
4027
|
+
"assrt.equal(pformat(frozenset([1])), 'frozenset({1})')",
|
|
4028
|
+
].join("\n"));
|
|
4029
|
+
run_js(js);
|
|
4030
|
+
},
|
|
4031
|
+
},
|
|
4032
|
+
|
|
4033
|
+
{
|
|
4034
|
+
name: "bundle_pprint_wrap",
|
|
4035
|
+
description: "pprint stdlib: wide containers break across lines in the web-repl bundle",
|
|
4036
|
+
run: function () {
|
|
4037
|
+
var repl = RS.web_repl();
|
|
4038
|
+
var js = bundle_compile(repl, [
|
|
4039
|
+
"from pprint import pformat",
|
|
4040
|
+
// wide list breaks one element per line
|
|
4041
|
+
"assrt.equal(pformat([1, 2, 3], width=5), '[1,\\n 2,\\n 3]')",
|
|
4042
|
+
// wide dict breaks, keys sorted by default
|
|
4043
|
+
"assrt.equal(pformat({'name': 'Al', 'age': 3}, width=12), '{\"age\": 3,\\n \"name\": \"Al\"}')",
|
|
4044
|
+
// indent parameter widens inner indentation
|
|
4045
|
+
"assrt.equal(pformat([1, 2, 3], width=5, indent=4), '[ 1,\\n 2,\\n 3]')",
|
|
4046
|
+
// single-element containers never break
|
|
4047
|
+
"assrt.equal(pformat([42], width=1), '[42]')",
|
|
4048
|
+
].join("\n"));
|
|
4049
|
+
run_js(js);
|
|
4050
|
+
},
|
|
4051
|
+
},
|
|
4052
|
+
|
|
4053
|
+
{
|
|
4054
|
+
name: "bundle_pprint_features",
|
|
4055
|
+
description: "pprint stdlib: depth, compact, sort_dicts, and pp() in the web-repl bundle",
|
|
4056
|
+
run: function () {
|
|
4057
|
+
var repl = RS.web_repl();
|
|
4058
|
+
var js = bundle_compile(repl, [
|
|
4059
|
+
"from pprint import pformat, pp",
|
|
4060
|
+
// depth limits expansion
|
|
4061
|
+
"assrt.equal(pformat([1, [2, [3]]], depth=1), '[1, [...]]')",
|
|
4062
|
+
"assrt.equal(pformat([1, [2, [3]]], depth=2), '[1, [2, [...]]]')",
|
|
4063
|
+
// sort_dicts=False keeps insertion order (ρσ_dict preserves it)
|
|
4064
|
+
"d = dict()",
|
|
4065
|
+
"d.set('c', 1)",
|
|
4066
|
+
"d.set('a', 2)",
|
|
4067
|
+
"assrt.equal(pformat(d, sort_dicts=False), '{\"c\": 1, \"a\": 2}')",
|
|
4068
|
+
"assrt.equal(pformat(d, sort_dicts=True), '{\"a\": 2, \"c\": 1}')",
|
|
4069
|
+
// compact packs multiple items per line
|
|
4070
|
+
"compact_out = pformat([1, 2, 3, 4, 5, 6, 7, 8], compact=True, width=15)",
|
|
4071
|
+
"assrt.equal(compact_out, '[1, 2, 3, 4, 5,\\n 6, 7, 8]')",
|
|
4072
|
+
// pp() writes to a custom stream and defaults sort_dicts=False
|
|
4073
|
+
"class _C:",
|
|
4074
|
+
" def __init__(self):",
|
|
4075
|
+
" self.parts = []",
|
|
4076
|
+
" def write(self, s):",
|
|
4077
|
+
" self.parts.push(s)",
|
|
4078
|
+
"col = _C()",
|
|
4079
|
+
"pp(d, stream=col)",
|
|
4080
|
+
"assrt.equal(col.parts.join(''), '{\"c\": 1, \"a\": 2}\\n')",
|
|
4081
|
+
].join("\n"));
|
|
4082
|
+
run_js(js);
|
|
4083
|
+
},
|
|
4084
|
+
},
|
|
4085
|
+
|
|
4086
|
+
{
|
|
4087
|
+
name: "bundle_pprint_safe",
|
|
4088
|
+
description: "pprint stdlib: saferepr, isreadable, isrecursive, PrettyPrinter in the web-repl bundle",
|
|
4089
|
+
run: function () {
|
|
4090
|
+
var repl = RS.web_repl();
|
|
4091
|
+
var js = bundle_compile(repl, [
|
|
4092
|
+
"from pprint import saferepr, isreadable, isrecursive, PrettyPrinter",
|
|
4093
|
+
// saferepr of normal structures == single-line repr
|
|
4094
|
+
"assrt.equal(saferepr([1, 2, 3]), '[1, 2, 3]')",
|
|
4095
|
+
"assrt.equal(saferepr({'a': 1}), '{\"a\": 1}')",
|
|
4096
|
+
// isreadable / isrecursive on plain structures
|
|
4097
|
+
"assrt.ok(isreadable([1, 2, 3]))",
|
|
4098
|
+
"assrt.ok(not isrecursive([1, 2, 3]))",
|
|
4099
|
+
// self-referential list -> recursion detected and marked
|
|
4100
|
+
"cyclic = [1, 2]",
|
|
4101
|
+
"cyclic.push(cyclic)",
|
|
4102
|
+
"assrt.ok(isrecursive(cyclic))",
|
|
4103
|
+
"assrt.ok(not isreadable(cyclic))",
|
|
4104
|
+
"assrt.ok(saferepr(cyclic).indexOf('<Recursion on') >= 0)",
|
|
4105
|
+
// PrettyPrinter class: pformat + instance predicates
|
|
4106
|
+
"p = PrettyPrinter(indent=2, width=8)",
|
|
4107
|
+
"assrt.equal(p.pformat([10, 20, 30]), '[ 10,\\n 20,\\n 30]')",
|
|
4108
|
+
"assrt.ok(p.isrecursive(cyclic))",
|
|
4109
|
+
// invalid args raise ValueError
|
|
4110
|
+
"caught = False",
|
|
4111
|
+
"try:",
|
|
4112
|
+
" PrettyPrinter(depth=0)",
|
|
4113
|
+
"except ValueError:",
|
|
4114
|
+
" caught = True",
|
|
4115
|
+
"assrt.ok(caught)",
|
|
4116
|
+
].join("\n"));
|
|
4117
|
+
run_js(js);
|
|
4118
|
+
},
|
|
4119
|
+
},
|
|
4120
|
+
|
|
4121
|
+
{
|
|
4122
|
+
name: "bundle_type_enforcement_basic",
|
|
4123
|
+
description: "type_enforcement: max args, missing required, type annotations",
|
|
4124
|
+
run: function () {
|
|
4125
|
+
var repl = RS.web_repl();
|
|
4126
|
+
var js = bundle_compile(repl, [
|
|
4127
|
+
"from __python__ import type_enforcement",
|
|
4128
|
+
"def add(a: int, b: int):",
|
|
4129
|
+
" return a + b",
|
|
4130
|
+
// correct call
|
|
4131
|
+
"assrt.equal(add(2, 3), 5)",
|
|
4132
|
+
// too many positional args
|
|
4133
|
+
"caught = False",
|
|
4134
|
+
"try:",
|
|
4135
|
+
" add(1, 2, 3)",
|
|
4136
|
+
"except TypeError:",
|
|
4137
|
+
" caught = True",
|
|
4138
|
+
"assrt.ok(caught)",
|
|
4139
|
+
// missing required arg
|
|
4140
|
+
"caught2 = False",
|
|
4141
|
+
"try:",
|
|
4142
|
+
" add(1)",
|
|
4143
|
+
"except TypeError:",
|
|
4144
|
+
" caught2 = True",
|
|
4145
|
+
"assrt.ok(caught2)",
|
|
4146
|
+
// type mismatch
|
|
4147
|
+
"caught3 = False",
|
|
4148
|
+
"try:",
|
|
4149
|
+
" add('x', 2)",
|
|
4150
|
+
"except TypeError:",
|
|
4151
|
+
" caught3 = True",
|
|
4152
|
+
"assrt.ok(caught3)",
|
|
4153
|
+
].join("\n"));
|
|
4154
|
+
run_js(js);
|
|
4155
|
+
},
|
|
4156
|
+
},
|
|
4157
|
+
|
|
4158
|
+
{
|
|
4159
|
+
name: "bundle_type_enforcement_posonly",
|
|
4160
|
+
description: "type_enforcement: positional-only args cannot be passed as kwargs",
|
|
4161
|
+
run: function () {
|
|
4162
|
+
var repl = RS.web_repl();
|
|
4163
|
+
var js = bundle_compile(repl, [
|
|
4164
|
+
"from __python__ import type_enforcement",
|
|
4165
|
+
"def sub(a, b, /):",
|
|
4166
|
+
" return a - b",
|
|
4167
|
+
// valid positional call
|
|
4168
|
+
"assrt.equal(sub(10, 3), 7)",
|
|
4169
|
+
// posonly passed as kwarg → TypeError
|
|
4170
|
+
"caught = False",
|
|
4171
|
+
"try:",
|
|
4172
|
+
" sub(a=10, b=3)",
|
|
4173
|
+
"except TypeError:",
|
|
4174
|
+
" caught = True",
|
|
4175
|
+
"assrt.ok(caught)",
|
|
4176
|
+
// mixed: posonly + default normal
|
|
4177
|
+
"def greet(name, /, greeting='Hello'):",
|
|
4178
|
+
" return greeting + ' ' + name",
|
|
4179
|
+
"assrt.equal(greet('Alice'), 'Hello Alice')",
|
|
4180
|
+
"assrt.equal(greet('Bob', greeting='Hi'), 'Hi Bob')",
|
|
4181
|
+
"caught2 = False",
|
|
4182
|
+
"try:",
|
|
4183
|
+
" greet(name='Carol')",
|
|
4184
|
+
"except TypeError:",
|
|
4185
|
+
" caught2 = True",
|
|
4186
|
+
"assrt.ok(caught2)",
|
|
4187
|
+
].join("\n"));
|
|
4188
|
+
run_js(js);
|
|
4189
|
+
},
|
|
4190
|
+
},
|
|
4191
|
+
|
|
4192
|
+
{
|
|
4193
|
+
name: "bundle_type_enforcement_kwonly",
|
|
4194
|
+
description: "type_enforcement: keyword-only args must be supplied by name",
|
|
4195
|
+
run: function () {
|
|
4196
|
+
var repl = RS.web_repl();
|
|
4197
|
+
var js = bundle_compile(repl, [
|
|
4198
|
+
"from __python__ import type_enforcement",
|
|
4199
|
+
"def notify(msg, *, urgent):",
|
|
4200
|
+
" return msg + ('!' if urgent else '.')",
|
|
4201
|
+
// correct kwonly usage
|
|
4202
|
+
"assrt.equal(notify('hi', urgent=True), 'hi!')",
|
|
4203
|
+
"assrt.equal(notify('hi', urgent=False), 'hi.')",
|
|
4204
|
+
// missing required kwonly → TypeError
|
|
4205
|
+
"caught = False",
|
|
4206
|
+
"try:",
|
|
4207
|
+
" notify('hello')",
|
|
4208
|
+
"except TypeError:",
|
|
4209
|
+
" caught = True",
|
|
4210
|
+
"assrt.ok(caught)",
|
|
4211
|
+
// optional kwonly with default — no error when omitted
|
|
4212
|
+
"def fmt(val, *, prefix=''):",
|
|
4213
|
+
" return prefix + str(val)",
|
|
4214
|
+
"assrt.equal(fmt(42), '42')",
|
|
4215
|
+
"assrt.equal(fmt(42, prefix='>> '), '>> 42')",
|
|
4216
|
+
].join("\n"));
|
|
4217
|
+
run_js(js);
|
|
4218
|
+
},
|
|
4219
|
+
},
|
|
4220
|
+
|
|
4221
|
+
{
|
|
4222
|
+
name: "bundle_type_enforcement_class",
|
|
4223
|
+
description: "type_enforcement: class method arg enforcement",
|
|
4224
|
+
run: function () {
|
|
4225
|
+
var repl = RS.web_repl();
|
|
4226
|
+
var js = bundle_compile(repl, [
|
|
4227
|
+
"from __python__ import type_enforcement",
|
|
4228
|
+
"class Vec:",
|
|
4229
|
+
" def __init__(self, x: int, y: int):",
|
|
4230
|
+
" self.x = x",
|
|
4231
|
+
" self.y = y",
|
|
4232
|
+
" def scale(self, factor: int, /, *, clamp=False):",
|
|
4233
|
+
" v = self.x * factor",
|
|
4234
|
+
" return min(v, 100) if clamp else v",
|
|
4235
|
+
"v = Vec(3, 4)",
|
|
4236
|
+
"assrt.equal(v.scale(2), 6)",
|
|
4237
|
+
"assrt.equal(v.scale(50, clamp=True), 100)",
|
|
4238
|
+
// type mismatch in __init__
|
|
4239
|
+
"caught = False",
|
|
4240
|
+
"try:",
|
|
4241
|
+
" Vec('a', 1)",
|
|
4242
|
+
"except TypeError:",
|
|
4243
|
+
" caught = True",
|
|
4244
|
+
"assrt.ok(caught)",
|
|
4245
|
+
// factor is posonly → cannot be kwarg
|
|
4246
|
+
"caught2 = False",
|
|
4247
|
+
"try:",
|
|
4248
|
+
" v.scale(factor=2)",
|
|
4249
|
+
"except TypeError:",
|
|
4250
|
+
" caught2 = True",
|
|
4251
|
+
"assrt.ok(caught2)",
|
|
4252
|
+
].join("\n"));
|
|
4253
|
+
run_js(js);
|
|
4254
|
+
},
|
|
4255
|
+
},
|
|
4256
|
+
|
|
4257
|
+
{
|
|
4258
|
+
name: "bundle_heapq_push_pop",
|
|
4259
|
+
description: "heapq stdlib: heappush and heappop in the web-repl bundle",
|
|
4260
|
+
run: function () {
|
|
4261
|
+
var repl = RS.web_repl();
|
|
4262
|
+
var js = bundle_compile(repl, [
|
|
4263
|
+
"from heapq import heappush, heappop",
|
|
4264
|
+
// push in arbitrary order, pop should return sorted
|
|
4265
|
+
"h = []",
|
|
4266
|
+
"heappush(h, 3)",
|
|
4267
|
+
"heappush(h, 1)",
|
|
4268
|
+
"heappush(h, 4)",
|
|
4269
|
+
"heappush(h, 1)",
|
|
4270
|
+
"heappush(h, 5)",
|
|
4271
|
+
"assrt.equal(heappop(h), 1)",
|
|
4272
|
+
"assrt.equal(heappop(h), 1)",
|
|
4273
|
+
"assrt.equal(heappop(h), 3)",
|
|
4274
|
+
"assrt.equal(heappop(h), 4)",
|
|
4275
|
+
"assrt.equal(heappop(h), 5)",
|
|
4276
|
+
"assrt.equal(h.length, 0)",
|
|
4277
|
+
// heappop on empty raises IndexError
|
|
4278
|
+
"caught = False",
|
|
4279
|
+
"try:",
|
|
4280
|
+
" heappop([])",
|
|
4281
|
+
"except IndexError:",
|
|
4282
|
+
" caught = True",
|
|
4283
|
+
"assrt.ok(caught)",
|
|
4284
|
+
].join("\n"));
|
|
4285
|
+
run_js(js);
|
|
4286
|
+
},
|
|
4287
|
+
},
|
|
4288
|
+
|
|
4289
|
+
{
|
|
4290
|
+
name: "bundle_heapq_heapify",
|
|
4291
|
+
description: "heapq stdlib: heapify in the web-repl bundle",
|
|
4292
|
+
run: function () {
|
|
4293
|
+
var repl = RS.web_repl();
|
|
4294
|
+
var js = bundle_compile(repl, [
|
|
4295
|
+
"from heapq import heapify, heappop",
|
|
4296
|
+
// heapify puts min at root
|
|
4297
|
+
"x = [5, 3, 8, 1, 2, 4]",
|
|
4298
|
+
"heapify(x)",
|
|
4299
|
+
"assrt.equal(x[0], 1)",
|
|
4300
|
+
// heapify + repeated heappop gives sorted order
|
|
4301
|
+
"data = [5, 3, 8, 1, 2, 4]",
|
|
4302
|
+
"heapify(data)",
|
|
4303
|
+
"result = []",
|
|
4304
|
+
"while data.length > 0:",
|
|
4305
|
+
" result.push(heappop(data))",
|
|
4306
|
+
"assrt.deepEqual(result, [1, 2, 3, 4, 5, 8])",
|
|
4307
|
+
// negative numbers
|
|
4308
|
+
"neg = [-3, -1, -4, -1, -5]",
|
|
4309
|
+
"heapify(neg)",
|
|
4310
|
+
"neg_out = []",
|
|
4311
|
+
"while neg.length > 0:",
|
|
4312
|
+
" neg_out.push(heappop(neg))",
|
|
4313
|
+
"assrt.deepEqual(neg_out, [-5, -4, -3, -1, -1])",
|
|
4314
|
+
].join("\n"));
|
|
4315
|
+
run_js(js);
|
|
4316
|
+
},
|
|
4317
|
+
},
|
|
4318
|
+
|
|
4319
|
+
{
|
|
4320
|
+
name: "bundle_heapq_nsmallest_nlargest",
|
|
4321
|
+
description: "heapq stdlib: nsmallest and nlargest in the web-repl bundle",
|
|
4322
|
+
run: function () {
|
|
4323
|
+
var repl = RS.web_repl();
|
|
4324
|
+
var js = bundle_compile(repl, [
|
|
4325
|
+
"from heapq import nsmallest, nlargest",
|
|
4326
|
+
"data = [3, 1, 4, 1, 5, 9, 2, 6]",
|
|
4327
|
+
"assrt.deepEqual(nsmallest(3, data), [1, 1, 2])",
|
|
4328
|
+
"assrt.deepEqual(nlargest(3, data), [9, 6, 5])",
|
|
4329
|
+
"assrt.deepEqual(nsmallest(0, data), [])",
|
|
4330
|
+
"assrt.deepEqual(nlargest(0, data), [])",
|
|
4331
|
+
// n > len returns all sorted
|
|
4332
|
+
"assrt.deepEqual(nsmallest(100, [3, 1, 2]), [1, 2, 3])",
|
|
4333
|
+
"assrt.deepEqual(nlargest(100, [3, 1, 2]), [3, 2, 1])",
|
|
4334
|
+
// key function
|
|
4335
|
+
"pairs = [[3, 'c'], [1, 'a'], [4, 'd'], [1, 'b'], [5, 'e']]",
|
|
4336
|
+
"kfn = def(p): return p[0];",
|
|
4337
|
+
"sm = nsmallest(2, pairs, key=kfn)",
|
|
4338
|
+
"assrt.equal(sm[0][0], 1)",
|
|
4339
|
+
"assrt.equal(sm[1][0], 1)",
|
|
4340
|
+
"lg = nlargest(2, pairs, key=kfn)",
|
|
4341
|
+
"assrt.equal(lg[0][0], 5)",
|
|
4342
|
+
"assrt.equal(lg[1][0], 4)",
|
|
4343
|
+
].join("\n"));
|
|
4344
|
+
run_js(js);
|
|
4345
|
+
},
|
|
4346
|
+
},
|
|
4347
|
+
|
|
4348
|
+
{
|
|
4349
|
+
name: "bundle_heapq_replace_pushpop",
|
|
4350
|
+
description: "heapq stdlib: heapreplace and heappushpop in the web-repl bundle",
|
|
4351
|
+
run: function () {
|
|
4352
|
+
var repl = RS.web_repl();
|
|
4353
|
+
var js = bundle_compile(repl, [
|
|
4354
|
+
"from heapq import heapify, heappop, heapreplace, heappushpop",
|
|
4355
|
+
// heapreplace: returns old min, inserts new item
|
|
4356
|
+
"r = [1, 3, 5, 7, 9]",
|
|
4357
|
+
"heapify(r)",
|
|
4358
|
+
"old = heapreplace(r, 4)",
|
|
4359
|
+
"assrt.equal(old, 1)",
|
|
4360
|
+
"assrt.equal(r[0], 3)",
|
|
4361
|
+
// heapreplace on empty raises IndexError
|
|
4362
|
+
"caught_rep = False",
|
|
4363
|
+
"try:",
|
|
4364
|
+
" heapreplace([], 1)",
|
|
4365
|
+
"except IndexError:",
|
|
4366
|
+
" caught_rep = True",
|
|
4367
|
+
"assrt.ok(caught_rep)",
|
|
4368
|
+
// heappushpop: item > root — returns root
|
|
4369
|
+
"pp = [1, 3, 5]",
|
|
4370
|
+
"heapify(pp)",
|
|
4371
|
+
"assrt.equal(heappushpop(pp, 2), 1)",
|
|
4372
|
+
"assrt.equal(pp[0], 2)",
|
|
4373
|
+
// heappushpop: item <= root — returns item unchanged
|
|
4374
|
+
"pp2 = [5, 7, 9]",
|
|
4375
|
+
"heapify(pp2)",
|
|
4376
|
+
"assrt.equal(heappushpop(pp2, 4), 4)",
|
|
4377
|
+
"assrt.equal(pp2[0], 5)",
|
|
4378
|
+
].join("\n"));
|
|
4379
|
+
run_js(js);
|
|
4380
|
+
},
|
|
4381
|
+
},
|
|
4382
|
+
|
|
4383
|
+
// ── parenthesized with (Python 3.10+) ────────────────────────────────────
|
|
4384
|
+
|
|
4385
|
+
{
|
|
4386
|
+
name: "bundle_paren_with_single",
|
|
4387
|
+
description: "parenthesized with: single clause with alias compiles and runs in the web-repl bundle",
|
|
4388
|
+
run: function () {
|
|
4389
|
+
var repl = RS.web_repl();
|
|
4390
|
+
var js = bundle_compile(repl, [
|
|
4391
|
+
"class _CM:",
|
|
4392
|
+
" def __init__(self, val):",
|
|
4393
|
+
" self.val = val",
|
|
4394
|
+
" def __enter__(self):",
|
|
4395
|
+
" return self.val",
|
|
4396
|
+
" def __exit__(self):",
|
|
4397
|
+
" pass",
|
|
4398
|
+
"with (_CM(42) as x):",
|
|
4399
|
+
" assrt.equal(x, 42)",
|
|
4400
|
+
].join("\n"));
|
|
4401
|
+
run_js(js);
|
|
4402
|
+
},
|
|
4403
|
+
},
|
|
4404
|
+
|
|
4405
|
+
{
|
|
4406
|
+
name: "bundle_paren_with_multi",
|
|
4407
|
+
description: "parenthesized with: multi-clause LIFO exit order compiles and runs in the web-repl bundle",
|
|
4408
|
+
run: function () {
|
|
4409
|
+
var repl = RS.web_repl();
|
|
4410
|
+
var js = bundle_compile(repl, [
|
|
4411
|
+
"log = []",
|
|
4412
|
+
"class _CM:",
|
|
4413
|
+
" def __init__(self, name):",
|
|
4414
|
+
" self.name = name",
|
|
4415
|
+
" def __enter__(self):",
|
|
4416
|
+
" log.push('enter:' + self.name)",
|
|
4417
|
+
" return self",
|
|
4418
|
+
" def __exit__(self):",
|
|
4419
|
+
" log.push('exit:' + self.name)",
|
|
4420
|
+
"with (_CM('a') as a, _CM('b') as b):",
|
|
4421
|
+
" log.push('body')",
|
|
4422
|
+
"assrt.equal(log[0], 'enter:a')",
|
|
4423
|
+
"assrt.equal(log[1], 'enter:b')",
|
|
4424
|
+
"assrt.equal(log[2], 'body')",
|
|
4425
|
+
"assrt.equal(log[3], 'exit:b')",
|
|
4426
|
+
"assrt.equal(log[4], 'exit:a')",
|
|
4427
|
+
].join("\n"));
|
|
4428
|
+
run_js(js);
|
|
4429
|
+
},
|
|
4430
|
+
},
|
|
4431
|
+
|
|
4432
|
+
{
|
|
4433
|
+
name: "bundle_paren_with_multiline",
|
|
4434
|
+
description: "parenthesized with: multi-line trailing-comma form compiles and runs in the web-repl bundle",
|
|
4435
|
+
run: function () {
|
|
4436
|
+
var repl = RS.web_repl();
|
|
4437
|
+
var js = bundle_compile(repl, [
|
|
4438
|
+
"log = []",
|
|
4439
|
+
"class _CM:",
|
|
4440
|
+
" def __init__(self, name):",
|
|
4441
|
+
" self.name = name",
|
|
4442
|
+
" def __enter__(self):",
|
|
4443
|
+
" log.push('enter:' + self.name)",
|
|
4444
|
+
" return self",
|
|
4445
|
+
" def __exit__(self):",
|
|
4446
|
+
" log.push('exit:' + self.name)",
|
|
4447
|
+
"with (",
|
|
4448
|
+
" _CM('x') as x,",
|
|
4449
|
+
" _CM('y') as y,",
|
|
4450
|
+
"):",
|
|
4451
|
+
" log.push('body')",
|
|
4452
|
+
"assrt.equal(log[0], 'enter:x')",
|
|
4453
|
+
"assrt.equal(log[1], 'enter:y')",
|
|
4454
|
+
"assrt.equal(log[2], 'body')",
|
|
4455
|
+
"assrt.equal(log[3], 'exit:y')",
|
|
4456
|
+
"assrt.equal(log[4], 'exit:x')",
|
|
4457
|
+
].join("\n"));
|
|
4458
|
+
run_js(js);
|
|
4459
|
+
},
|
|
4460
|
+
},
|
|
4461
|
+
|
|
4462
|
+
{
|
|
4463
|
+
name: "bundle_paren_with_trailing_comma",
|
|
4464
|
+
description: "parenthesized with: trailing comma accepted in the web-repl bundle",
|
|
4465
|
+
run: function () {
|
|
4466
|
+
var repl = RS.web_repl();
|
|
4467
|
+
var js = bundle_compile(repl, [
|
|
4468
|
+
"class _CM:",
|
|
4469
|
+
" def __init__(self, val):",
|
|
4470
|
+
" self.val = val",
|
|
4471
|
+
" def __enter__(self):",
|
|
4472
|
+
" return self.val",
|
|
4473
|
+
" def __exit__(self):",
|
|
4474
|
+
" pass",
|
|
4475
|
+
"with (_CM(7) as v,):",
|
|
4476
|
+
" assrt.equal(v, 7)",
|
|
4477
|
+
].join("\n"));
|
|
4478
|
+
run_js(js);
|
|
4479
|
+
},
|
|
4480
|
+
},
|
|
4481
|
+
|
|
4482
|
+
// ── statistics ───────────────────────────────────────────────────────────
|
|
4483
|
+
|
|
4484
|
+
{
|
|
4485
|
+
name: "bundle_statistics_averages",
|
|
4486
|
+
description: "statistics stdlib: averages and central location in the web-repl bundle",
|
|
4487
|
+
run: function () {
|
|
4488
|
+
var repl = RS.web_repl();
|
|
4489
|
+
var js = bundle_compile(repl, [
|
|
4490
|
+
"from statistics import mean, fmean, median, median_low, median_high",
|
|
4491
|
+
"from statistics import mode, multimode, harmonic_mean, geometric_mean",
|
|
4492
|
+
"assrt.equal(mean([1, 2, 3, 4]), 2.5)",
|
|
4493
|
+
"assrt.equal(mean([1, 2, 3]), 2)",
|
|
4494
|
+
"assrt.equal(fmean([1, 2, 3, 4]), 2.5)",
|
|
4495
|
+
"assrt.equal(median([1, 3, 5]), 3)",
|
|
4496
|
+
"assrt.equal(median([1, 3, 5, 7]), 4)",
|
|
4497
|
+
"assrt.equal(median([5, 1, 3]), 3)", // unsorted input
|
|
4498
|
+
"assrt.equal(median_low([1, 3, 5, 7]), 3)",
|
|
4499
|
+
"assrt.equal(median_high([1, 3, 5, 7]), 5)",
|
|
4500
|
+
"assrt.equal(mode([1, 1, 2, 3, 3, 3]), 3)",
|
|
4501
|
+
"assrt.equal(mode(['x', 'y', 'x']), 'x')",
|
|
4502
|
+
"assrt.deepEqual(multimode([1, 1, 2, 2, 3]), [1, 2])",
|
|
4503
|
+
"assrt.ok(Math.abs(harmonic_mean([40, 60]) - 48.0) < 1e-9)",
|
|
4504
|
+
"assrt.ok(Math.abs(geometric_mean([2, 8]) - 4.0) < 1e-9)",
|
|
4505
|
+
].join("\n"));
|
|
4506
|
+
run_js(js);
|
|
4507
|
+
},
|
|
4508
|
+
},
|
|
4509
|
+
|
|
4510
|
+
{
|
|
4511
|
+
name: "bundle_statistics_spread",
|
|
4512
|
+
description: "statistics stdlib: variance, stdev and quantiles in the web-repl bundle",
|
|
4513
|
+
run: function () {
|
|
4514
|
+
var repl = RS.web_repl();
|
|
4515
|
+
var js = bundle_compile(repl, [
|
|
4516
|
+
"from statistics import variance, pvariance, stdev, pstdev, quantiles",
|
|
4517
|
+
"assrt.equal(variance([1, 2, 3, 4, 5]), 2.5)",
|
|
4518
|
+
"assrt.equal(pvariance([1, 2, 3, 4, 5]), 2.0)",
|
|
4519
|
+
"assrt.ok(Math.abs(stdev([1, 2, 3, 4, 5]) - Math.sqrt(2.5)) < 1e-9)",
|
|
4520
|
+
"assrt.ok(Math.abs(pstdev([1, 2, 3, 4, 5]) - Math.sqrt(2.0)) < 1e-9)",
|
|
4521
|
+
"assrt.deepEqual(quantiles([1, 2, 3, 4]), [1.25, 2.5, 3.75])",
|
|
4522
|
+
"q = quantiles([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], n=10)",
|
|
4523
|
+
"assrt.equal(q.length, 9)",
|
|
4524
|
+
].join("\n"));
|
|
4525
|
+
run_js(js);
|
|
4526
|
+
},
|
|
4527
|
+
},
|
|
4528
|
+
|
|
4529
|
+
{
|
|
4530
|
+
name: "bundle_statistics_relations",
|
|
4531
|
+
description: "statistics stdlib: covariance, correlation, linear_regression in the bundle",
|
|
4532
|
+
run: function () {
|
|
4533
|
+
var repl = RS.web_repl();
|
|
4534
|
+
var js = bundle_compile(repl, [
|
|
4535
|
+
"from statistics import covariance, correlation, linear_regression",
|
|
4536
|
+
"x = [1, 2, 3, 4, 5, 6, 7, 8, 9]",
|
|
4537
|
+
"y = [1, 2, 3, 1, 2, 3, 1, 2, 3]",
|
|
4538
|
+
"assrt.ok(Math.abs(covariance(x, y) - 0.75) < 1e-9)",
|
|
4539
|
+
"assrt.ok(Math.abs(correlation(x, x) - 1.0) < 1e-9)",
|
|
4540
|
+
"lr = linear_regression([1, 2, 3, 4, 5], [2, 4, 6, 8, 10])",
|
|
4541
|
+
"assrt.ok(Math.abs(lr.slope - 2.0) < 1e-9)",
|
|
4542
|
+
"assrt.ok(Math.abs(lr.intercept - 0.0) < 1e-9)",
|
|
4543
|
+
].join("\n"));
|
|
4544
|
+
run_js(js);
|
|
4545
|
+
},
|
|
4546
|
+
},
|
|
4547
|
+
|
|
4548
|
+
{
|
|
4549
|
+
name: "bundle_statistics_normaldist",
|
|
4550
|
+
description: "statistics stdlib: NormalDist in the web-repl bundle",
|
|
4551
|
+
run: function () {
|
|
4552
|
+
var repl = RS.web_repl();
|
|
4553
|
+
var js = bundle_compile(repl, [
|
|
4554
|
+
"from statistics import NormalDist",
|
|
4555
|
+
"nd = NormalDist(100, 15)",
|
|
4556
|
+
"assrt.equal(nd.mean, 100)",
|
|
4557
|
+
"assrt.equal(nd.stdev, 15)",
|
|
4558
|
+
"assrt.equal(nd.variance, 225)",
|
|
4559
|
+
"assrt.ok(Math.abs(nd.cdf(100) - 0.5) < 1e-6)",
|
|
4560
|
+
"assrt.equal(nd.inv_cdf(0.5), 100)",
|
|
4561
|
+
"assrt.equal(nd.zscore(115), 1.0)",
|
|
4562
|
+
"fs = NormalDist.from_samples([1, 2, 3, 4, 5])",
|
|
4563
|
+
"assrt.ok(Math.abs(fs.mean - 3.0) < 1e-9)",
|
|
4564
|
+
// operator overloading dispatches to __add__
|
|
4565
|
+
"combined = nd + NormalDist(50, 20)",
|
|
4566
|
+
"assrt.equal(combined.mean, 150)",
|
|
4567
|
+
"assrt.equal(combined.stdev, 25)",
|
|
4568
|
+
].join("\n"));
|
|
4569
|
+
run_js(js);
|
|
4570
|
+
},
|
|
4571
|
+
},
|
|
4572
|
+
|
|
4573
|
+
// ── list sort: comparator detection + __lt__ dispatch ────────────────────
|
|
4574
|
+
|
|
4575
|
+
{
|
|
4576
|
+
name: "bundle_sort_comparator_and_lt",
|
|
4577
|
+
description: "list sort: positional comparator and __lt__ dispatch in the web-repl bundle",
|
|
4578
|
+
run: function () {
|
|
4579
|
+
var repl = RS.web_repl();
|
|
4580
|
+
var js = bundle_compile(repl, [
|
|
4581
|
+
// a positional two-argument function is treated as a comparator
|
|
4582
|
+
"assrt.deepEqual(sorted([3, 1, 2, 10], def(x, y): return x - y;), [1, 2, 3, 10])",
|
|
4583
|
+
"m = [5, 2, 8, 1]",
|
|
4584
|
+
"m.sort(def(x, y): return y - x;)",
|
|
4585
|
+
"assrt.deepEqual(m, [8, 5, 2, 1])",
|
|
4586
|
+
// a one-argument function is still a key function
|
|
4587
|
+
"assrt.deepEqual(sorted([3, 1, 2], key=def(x): return -x;), [3, 2, 1])",
|
|
4588
|
+
// custom objects are ordered through their __lt__ method
|
|
4589
|
+
"class Ord:",
|
|
4590
|
+
" def __init__(self, v):",
|
|
4591
|
+
" self.v = v",
|
|
4592
|
+
" def __lt__(self, other):",
|
|
4593
|
+
" return self.v < other.v",
|
|
4594
|
+
"s = sorted([Ord(3), Ord(1), Ord(2)])",
|
|
4595
|
+
"assrt.deepEqual([s[0].v, s[1].v, s[2].v], [1, 2, 3])",
|
|
4596
|
+
].join("\n"));
|
|
4597
|
+
run_js(js);
|
|
4598
|
+
},
|
|
4599
|
+
},
|
|
4600
|
+
|
|
4601
|
+
];
|
|
4602
|
+
|
|
4603
|
+
// ---------------------------------------------------------------------------
|
|
4604
|
+
// Runner
|
|
4605
|
+
// ---------------------------------------------------------------------------
|
|
4606
|
+
|
|
4607
|
+
function run_tests(filter) {
|
|
4608
|
+
var tests = filter
|
|
4609
|
+
? TESTS.filter(function (t) { return t.name === filter; })
|
|
4610
|
+
: TESTS;
|
|
2231
4611
|
|
|
2232
4612
|
if (tests.length === 0) {
|
|
2233
4613
|
console.error(colored("No test found: " + filter, "red"));
|
|
@@ -2245,12 +4625,20 @@ function run_tests(filter) {
|
|
|
2245
4625
|
}
|
|
2246
4626
|
});
|
|
2247
4627
|
|
|
4628
|
+
var passed = tests.length - failures.length;
|
|
2248
4629
|
console.log("");
|
|
2249
4630
|
if (failures.length) {
|
|
2250
|
-
console.log(colored(
|
|
2251
|
-
|
|
2252
|
-
|
|
4631
|
+
console.log(colored("Failed tests:", "red"));
|
|
4632
|
+
failures.forEach(function (name) {
|
|
4633
|
+
console.log(colored(" ✗ " + name, "red"));
|
|
4634
|
+
});
|
|
4635
|
+
console.log("");
|
|
2253
4636
|
}
|
|
4637
|
+
var summary = "web-repl tests — " +
|
|
4638
|
+
colored("passed: " + passed, "green") + " " +
|
|
4639
|
+
(failures.length ? colored("failed: " + failures.length, "red") : colored("failed: 0", "green")) +
|
|
4640
|
+
" total: " + tests.length;
|
|
4641
|
+
console.log(summary);
|
|
2254
4642
|
process.exit(failures.length ? 1 : 0);
|
|
2255
4643
|
}
|
|
2256
4644
|
|