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/asyncio.pyj
ADDED
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
# globals: assrt
|
|
2
|
+
# vim:fileencoding=utf-8
|
|
3
|
+
#
|
|
4
|
+
# asyncio.pyj
|
|
5
|
+
# Tests for the asyncio standard library module.
|
|
6
|
+
#
|
|
7
|
+
# Most tests verify synchronous-observable behaviour (exceptions, queue state,
|
|
8
|
+
# lock/event/semaphore flags, iscoroutine/iscoroutinefunction) because the
|
|
9
|
+
# vm.runInNewContext test runner is synchronous. Async coroutine execution
|
|
10
|
+
# is tested by verifying that async functions compile correctly and return
|
|
11
|
+
# thenable Promises.
|
|
12
|
+
|
|
13
|
+
from asyncio import (
|
|
14
|
+
sleep, gather, create_task, ensure_future, run,
|
|
15
|
+
shield, wait_for, iscoroutine, iscoroutinefunction,
|
|
16
|
+
current_task, all_tasks, get_event_loop, get_running_loop, new_event_loop,
|
|
17
|
+
CancelledError, TimeoutError, InvalidStateError, RuntimeError,
|
|
18
|
+
QueueEmpty, QueueFull,
|
|
19
|
+
Lock, Event, Semaphore, BoundedSemaphore,
|
|
20
|
+
Queue, LifoQueue, PriorityQueue
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
ae = assrt.equal
|
|
24
|
+
ade = assrt.deepEqual
|
|
25
|
+
ok = assrt.ok
|
|
26
|
+
|
|
27
|
+
# ── 1. Exception classes ─────────────────────────────────────────────────────
|
|
28
|
+
|
|
29
|
+
try:
|
|
30
|
+
raise CancelledError('cancelled')
|
|
31
|
+
ok(False)
|
|
32
|
+
except CancelledError as e:
|
|
33
|
+
ae(e.message, 'cancelled')
|
|
34
|
+
|
|
35
|
+
try:
|
|
36
|
+
raise TimeoutError('timed out')
|
|
37
|
+
ok(False)
|
|
38
|
+
except TimeoutError as e:
|
|
39
|
+
ae(e.message, 'timed out')
|
|
40
|
+
|
|
41
|
+
try:
|
|
42
|
+
raise InvalidStateError('bad state')
|
|
43
|
+
ok(False)
|
|
44
|
+
except InvalidStateError as e:
|
|
45
|
+
ae(e.message, 'bad state')
|
|
46
|
+
|
|
47
|
+
try:
|
|
48
|
+
raise QueueEmpty('empty')
|
|
49
|
+
ok(False)
|
|
50
|
+
except QueueEmpty as e:
|
|
51
|
+
ae(e.message, 'empty')
|
|
52
|
+
|
|
53
|
+
try:
|
|
54
|
+
raise QueueFull('full')
|
|
55
|
+
ok(False)
|
|
56
|
+
except QueueFull as e:
|
|
57
|
+
ae(e.message, 'full')
|
|
58
|
+
|
|
59
|
+
# ── 2. sleep / gather return Promises ────────────────────────────────────────
|
|
60
|
+
|
|
61
|
+
ok(iscoroutine(sleep(0)))
|
|
62
|
+
ok(iscoroutine(sleep(0.1)))
|
|
63
|
+
ok(iscoroutine(gather()))
|
|
64
|
+
ok(iscoroutine(gather(Promise.resolve(1), Promise.resolve(2))))
|
|
65
|
+
|
|
66
|
+
# ── 3. create_task / ensure_future / run / shield pass through Promises ──────
|
|
67
|
+
|
|
68
|
+
p = Promise.resolve(42)
|
|
69
|
+
ok(iscoroutine(create_task(p)))
|
|
70
|
+
ok(iscoroutine(ensure_future(p)))
|
|
71
|
+
ok(iscoroutine(run(p)))
|
|
72
|
+
ok(iscoroutine(shield(p)))
|
|
73
|
+
|
|
74
|
+
# ── 4. iscoroutine / iscoroutinefunction ─────────────────────────────────────
|
|
75
|
+
|
|
76
|
+
ok(not iscoroutine(42))
|
|
77
|
+
ok(not iscoroutine(None))
|
|
78
|
+
ok(not iscoroutine('string'))
|
|
79
|
+
ok(not iscoroutine([]))
|
|
80
|
+
|
|
81
|
+
async def _async_fn():
|
|
82
|
+
return 1
|
|
83
|
+
|
|
84
|
+
ok(iscoroutinefunction(_async_fn))
|
|
85
|
+
|
|
86
|
+
def _sync_fn():
|
|
87
|
+
return 1
|
|
88
|
+
|
|
89
|
+
ok(not iscoroutinefunction(_sync_fn))
|
|
90
|
+
ok(not iscoroutinefunction(42))
|
|
91
|
+
ok(not iscoroutinefunction(None))
|
|
92
|
+
|
|
93
|
+
# Async functions return Promises
|
|
94
|
+
p2 = _async_fn()
|
|
95
|
+
ok(iscoroutine(p2))
|
|
96
|
+
|
|
97
|
+
# ── 5. current_task / all_tasks ───────────────────────────────────────────────
|
|
98
|
+
|
|
99
|
+
ok(current_task() is None)
|
|
100
|
+
ok(len(all_tasks()) == 0)
|
|
101
|
+
|
|
102
|
+
# ── 6. Event loop stubs ───────────────────────────────────────────────────────
|
|
103
|
+
|
|
104
|
+
loop = get_event_loop()
|
|
105
|
+
ok(loop is not None)
|
|
106
|
+
ok(loop is get_running_loop())
|
|
107
|
+
|
|
108
|
+
loop2 = new_event_loop()
|
|
109
|
+
ok(loop2 is not None)
|
|
110
|
+
ok(not loop2.is_closed())
|
|
111
|
+
ok(loop2.is_running())
|
|
112
|
+
|
|
113
|
+
p3 = Promise.resolve(99)
|
|
114
|
+
ok(iscoroutine(loop.run_until_complete(p3)))
|
|
115
|
+
ok(iscoroutine(loop.create_task(p3)))
|
|
116
|
+
|
|
117
|
+
# ── 7. Lock — synchronous state ──────────────────────────────────────────────
|
|
118
|
+
|
|
119
|
+
lock = Lock()
|
|
120
|
+
ok(not lock.locked())
|
|
121
|
+
|
|
122
|
+
# acquire() returns a Promise (since it's async def)
|
|
123
|
+
acq = lock.acquire()
|
|
124
|
+
ok(iscoroutine(acq))
|
|
125
|
+
|
|
126
|
+
# After acquiring asynchronously lock should become locked.
|
|
127
|
+
# We can test the synchronous fast-path by observing the side-effect in a
|
|
128
|
+
# chained then (fire-and-forget style; the VM won't await it but we verify
|
|
129
|
+
# the structure is correct):
|
|
130
|
+
lock2 = Lock()
|
|
131
|
+
ok(not lock2.locked())
|
|
132
|
+
|
|
133
|
+
try:
|
|
134
|
+
lock2.release()
|
|
135
|
+
ok(False)
|
|
136
|
+
except RuntimeError:
|
|
137
|
+
ok(True)
|
|
138
|
+
|
|
139
|
+
# ── 8. Event — synchronous set/clear/is_set ──────────────────────────────────
|
|
140
|
+
|
|
141
|
+
ev = Event()
|
|
142
|
+
ok(not ev.is_set())
|
|
143
|
+
|
|
144
|
+
ev.set()
|
|
145
|
+
ok(ev.is_set())
|
|
146
|
+
|
|
147
|
+
ev.clear()
|
|
148
|
+
ok(not ev.is_set())
|
|
149
|
+
|
|
150
|
+
# wait() returns a Promise
|
|
151
|
+
ok(iscoroutine(ev.wait()))
|
|
152
|
+
|
|
153
|
+
# If event is already set, wait() still returns a thenable
|
|
154
|
+
ev.set()
|
|
155
|
+
ok(iscoroutine(ev.wait()))
|
|
156
|
+
|
|
157
|
+
# ── 9. Semaphore — synchronous state ─────────────────────────────────────────
|
|
158
|
+
|
|
159
|
+
sem = Semaphore(2)
|
|
160
|
+
ok(not sem.locked())
|
|
161
|
+
|
|
162
|
+
sem.release()
|
|
163
|
+
ok(not sem.locked())
|
|
164
|
+
|
|
165
|
+
sem.release()
|
|
166
|
+
ok(not sem.locked())
|
|
167
|
+
|
|
168
|
+
# acquire() returns a Promise
|
|
169
|
+
ok(iscoroutine(sem.acquire()))
|
|
170
|
+
|
|
171
|
+
try:
|
|
172
|
+
Semaphore(-1)
|
|
173
|
+
ok(False)
|
|
174
|
+
except ValueError:
|
|
175
|
+
ok(True)
|
|
176
|
+
|
|
177
|
+
# ── 10. BoundedSemaphore ─────────────────────────────────────────────────────
|
|
178
|
+
|
|
179
|
+
# BoundedSemaphore(n) starts at value=n (same as bound).
|
|
180
|
+
# Releasing without a prior acquire immediately exceeds the bound.
|
|
181
|
+
bsem = BoundedSemaphore(1)
|
|
182
|
+
ok(not bsem.locked())
|
|
183
|
+
try:
|
|
184
|
+
bsem.release()
|
|
185
|
+
ok(False)
|
|
186
|
+
except ValueError:
|
|
187
|
+
ok(True)
|
|
188
|
+
|
|
189
|
+
# BoundedSemaphore(0) is always locked and cannot be released at all.
|
|
190
|
+
bsem0 = BoundedSemaphore(0)
|
|
191
|
+
ok(bsem0.locked())
|
|
192
|
+
try:
|
|
193
|
+
bsem0.release()
|
|
194
|
+
ok(False)
|
|
195
|
+
except ValueError:
|
|
196
|
+
ok(True)
|
|
197
|
+
|
|
198
|
+
# ── 11. Queue — synchronous put_nowait / get_nowait ──────────────────────────
|
|
199
|
+
|
|
200
|
+
q = Queue()
|
|
201
|
+
ok(q.empty())
|
|
202
|
+
ok(q.qsize() == 0)
|
|
203
|
+
ok(not q.full())
|
|
204
|
+
|
|
205
|
+
q.put_nowait('a')
|
|
206
|
+
q.put_nowait('b')
|
|
207
|
+
q.put_nowait('c')
|
|
208
|
+
ok(q.qsize() == 3)
|
|
209
|
+
ok(not q.empty())
|
|
210
|
+
|
|
211
|
+
ae(q.get_nowait(), 'a')
|
|
212
|
+
q.task_done()
|
|
213
|
+
ae(q.get_nowait(), 'b')
|
|
214
|
+
q.task_done()
|
|
215
|
+
ae(q.get_nowait(), 'c')
|
|
216
|
+
q.task_done()
|
|
217
|
+
ok(q.empty())
|
|
218
|
+
|
|
219
|
+
# QueueFull with bounded queue
|
|
220
|
+
q2 = Queue(2)
|
|
221
|
+
ok(not q2.full())
|
|
222
|
+
q2.put_nowait(1)
|
|
223
|
+
q2.put_nowait(2)
|
|
224
|
+
ok(q2.full())
|
|
225
|
+
try:
|
|
226
|
+
q2.put_nowait(3)
|
|
227
|
+
ok(False)
|
|
228
|
+
except QueueFull:
|
|
229
|
+
ok(True)
|
|
230
|
+
|
|
231
|
+
# QueueEmpty
|
|
232
|
+
q3 = Queue()
|
|
233
|
+
try:
|
|
234
|
+
q3.get_nowait()
|
|
235
|
+
ok(False)
|
|
236
|
+
except QueueEmpty:
|
|
237
|
+
ok(True)
|
|
238
|
+
|
|
239
|
+
# task_done() overflow check
|
|
240
|
+
q4 = Queue()
|
|
241
|
+
q4.put_nowait('x')
|
|
242
|
+
ae(q4.get_nowait(), 'x')
|
|
243
|
+
q4.task_done()
|
|
244
|
+
try:
|
|
245
|
+
q4.task_done()
|
|
246
|
+
ok(False)
|
|
247
|
+
except ValueError:
|
|
248
|
+
ok(True)
|
|
249
|
+
|
|
250
|
+
# put() and get() return Promises (async methods) — test on fresh queues
|
|
251
|
+
q5 = Queue()
|
|
252
|
+
put_p = q5.put('async-item') # no waiters, goes to internal queue
|
|
253
|
+
ok(iscoroutine(put_p))
|
|
254
|
+
ae(q5.get_nowait(), 'async-item')
|
|
255
|
+
q5.task_done()
|
|
256
|
+
|
|
257
|
+
q6 = Queue()
|
|
258
|
+
get_p = q6.get() # empty queue → adds a getter waiter
|
|
259
|
+
ok(iscoroutine(get_p))
|
|
260
|
+
|
|
261
|
+
# join() returns a Promise
|
|
262
|
+
q7 = Queue()
|
|
263
|
+
ok(iscoroutine(q7.join()))
|
|
264
|
+
|
|
265
|
+
# ── 12. LifoQueue — last-in first-out ordering ───────────────────────────────
|
|
266
|
+
|
|
267
|
+
lq = LifoQueue()
|
|
268
|
+
lq.put_nowait(1)
|
|
269
|
+
lq.put_nowait(2)
|
|
270
|
+
lq.put_nowait(3)
|
|
271
|
+
ae(lq.get_nowait(), 3)
|
|
272
|
+
ae(lq.get_nowait(), 2)
|
|
273
|
+
ae(lq.get_nowait(), 1)
|
|
274
|
+
|
|
275
|
+
# ── 13. PriorityQueue — lowest value dequeued first ──────────────────────────
|
|
276
|
+
|
|
277
|
+
pq = PriorityQueue()
|
|
278
|
+
pq.put_nowait(30)
|
|
279
|
+
pq.put_nowait(10)
|
|
280
|
+
pq.put_nowait(20)
|
|
281
|
+
ae(pq.get_nowait(), 10)
|
|
282
|
+
ae(pq.get_nowait(), 20)
|
|
283
|
+
ae(pq.get_nowait(), 30)
|
|
284
|
+
|
|
285
|
+
# ── 14. async def / await compiles correctly ─────────────────────────────────
|
|
286
|
+
|
|
287
|
+
async def _add_async(a, b):
|
|
288
|
+
return a + b
|
|
289
|
+
|
|
290
|
+
ok(iscoroutinefunction(_add_async))
|
|
291
|
+
p4 = _add_async(3, 4)
|
|
292
|
+
ok(iscoroutine(p4))
|
|
293
|
+
|
|
294
|
+
async def _chain():
|
|
295
|
+
x = await Promise.resolve(10)
|
|
296
|
+
y = await Promise.resolve(20)
|
|
297
|
+
return x + y
|
|
298
|
+
|
|
299
|
+
p5 = _chain()
|
|
300
|
+
ok(iscoroutine(p5))
|
|
301
|
+
|
|
302
|
+
async def _gather_test():
|
|
303
|
+
results = await gather(Promise.resolve(1), Promise.resolve(2), Promise.resolve(3))
|
|
304
|
+
return results
|
|
305
|
+
|
|
306
|
+
p6 = _gather_test()
|
|
307
|
+
ok(iscoroutine(p6))
|
package/test/base64.pyj
ADDED
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
# globals: assrt
|
|
2
|
+
# vim:fileencoding=utf-8
|
|
3
|
+
#
|
|
4
|
+
# base64.pyj
|
|
5
|
+
# Tests for the base64 standard library module.
|
|
6
|
+
|
|
7
|
+
from base64 import b64encode, b64decode, standard_b64encode, standard_b64decode
|
|
8
|
+
from base64 import urlsafe_b64encode, urlsafe_b64decode
|
|
9
|
+
from base64 import b32encode, b32decode
|
|
10
|
+
from base64 import b16encode, b16decode
|
|
11
|
+
from base64 import encodebytes, decodebytes
|
|
12
|
+
from base64 import Error
|
|
13
|
+
|
|
14
|
+
ae = assrt.equal
|
|
15
|
+
ade = assrt.deepEqual
|
|
16
|
+
ok = assrt.ok
|
|
17
|
+
throws = assrt.throws
|
|
18
|
+
|
|
19
|
+
# Helper: convert bytes to ASCII string for easy comparison
|
|
20
|
+
def _str(b):
|
|
21
|
+
return b.decode('ascii')
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
# ── 1. b64encode — basic round-trips ─────────────────────────────────────────
|
|
25
|
+
|
|
26
|
+
ae(_str(b64encode(bytes([]))), '')
|
|
27
|
+
ae(_str(b64encode(bytes([0]))), 'AA==')
|
|
28
|
+
ae(_str(b64encode(bytes([0, 0]))), 'AAA=')
|
|
29
|
+
ae(_str(b64encode(bytes([0, 0, 0]))), 'AAAA')
|
|
30
|
+
ae(_str(b64encode(bytes([77, 97, 110]))), 'TWFu') # 'Man'
|
|
31
|
+
|
|
32
|
+
# Known Python vectors
|
|
33
|
+
ae(_str(b64encode(bytes([104, 101, 108, 108, 111]))), 'aGVsbG8=') # 'hello'
|
|
34
|
+
ae(_str(b64encode(bytes([104, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100]))), 'aGVsbG8gd29ybGQ=') # 'hello world'
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
# ── 2. b64decode ──────────────────────────────────────────────────────────────
|
|
38
|
+
|
|
39
|
+
_dec1 = b64decode('TWFu')
|
|
40
|
+
ade(list(_dec1), [77, 97, 110])
|
|
41
|
+
|
|
42
|
+
_dec2 = b64decode('aGVsbG8=')
|
|
43
|
+
ade(list(_dec2), [104, 101, 108, 108, 111])
|
|
44
|
+
|
|
45
|
+
# Empty string
|
|
46
|
+
ade(list(b64decode('')), [])
|
|
47
|
+
|
|
48
|
+
# Missing padding — Python strips whitespace and pads internally
|
|
49
|
+
_dec3 = b64decode('aGVsbG8')
|
|
50
|
+
ade(list(_dec3), [104, 101, 108, 108, 111])
|
|
51
|
+
|
|
52
|
+
# With embedded whitespace (stripped by default)
|
|
53
|
+
_dec4 = b64decode('aGVs bG8=')
|
|
54
|
+
ade(list(_dec4), [104, 101, 108, 108, 111])
|
|
55
|
+
|
|
56
|
+
# Accepts bytes input
|
|
57
|
+
_dec5 = b64decode(bytes([84, 87, 70, 117])) # b'TWFu'
|
|
58
|
+
ade(list(_dec5), [77, 97, 110])
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
# ── 3. b64encode / b64decode with altchars ────────────────────────────────────
|
|
62
|
+
|
|
63
|
+
# altchars b'-_': replaces + and /
|
|
64
|
+
_enc_alt = b64encode(bytes([251, 239, 190]), altchars=bytes([45, 95])) # b'-_'
|
|
65
|
+
_enc_std = b64encode(bytes([251, 239, 190]))
|
|
66
|
+
ok(_str(_enc_alt).indexOf('-') >= 0 or _str(_enc_alt).indexOf('_') >= 0
|
|
67
|
+
or (_str(_enc_alt) == _str(_enc_std)),
|
|
68
|
+
'altchars encoding should differ or be identical if no + or / present')
|
|
69
|
+
|
|
70
|
+
# Round-trip with altchars
|
|
71
|
+
_data_alt = bytes([200, 100, 50, 25])
|
|
72
|
+
_enc2 = b64encode(_data_alt, altchars=bytes([45, 95]))
|
|
73
|
+
_dec6 = b64decode(_enc2, altchars=bytes([45, 95]))
|
|
74
|
+
ade(list(_dec6), list(_data_alt))
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
# ── 4. standard_b64encode / standard_b64decode ────────────────────────────────
|
|
78
|
+
|
|
79
|
+
_s_enc = standard_b64encode(bytes([104, 101, 108, 108, 111]))
|
|
80
|
+
ae(_str(_s_enc), 'aGVsbG8=')
|
|
81
|
+
|
|
82
|
+
_s_dec = standard_b64decode('aGVsbG8=')
|
|
83
|
+
ade(list(_s_dec), [104, 101, 108, 108, 111])
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
# ── 5. urlsafe_b64encode / urlsafe_b64decode ──────────────────────────────────
|
|
87
|
+
|
|
88
|
+
# bytes([251, 239, 190]) encodes to '----' in URL-safe (all 6-bit groups are 62)
|
|
89
|
+
_us_enc = urlsafe_b64encode(bytes([251, 239, 190]))
|
|
90
|
+
ok(_str(_us_enc).indexOf('+') < 0, 'URL-safe should not contain +')
|
|
91
|
+
ok(_str(_us_enc).indexOf('/') < 0, 'URL-safe should not contain /')
|
|
92
|
+
ae(_str(_us_enc), '----')
|
|
93
|
+
|
|
94
|
+
# Round-trip with data that produces + and / in standard encoding
|
|
95
|
+
_us_data = bytes([251, 254, 254]) # encodes to '-_7-' in URL-safe
|
|
96
|
+
_us_enc2 = urlsafe_b64encode(_us_data)
|
|
97
|
+
ae(_str(_us_enc2), '-_7-')
|
|
98
|
+
|
|
99
|
+
# URL-safe decode
|
|
100
|
+
_us_dec = urlsafe_b64decode('-_7-')
|
|
101
|
+
ade(list(_us_dec), [251, 254, 254])
|
|
102
|
+
|
|
103
|
+
# Round-trip with general data
|
|
104
|
+
_us_rt_data = bytes([0, 1, 2, 127, 128, 255])
|
|
105
|
+
_us_rt = urlsafe_b64decode(urlsafe_b64encode(_us_rt_data))
|
|
106
|
+
ade(list(_us_rt), list(_us_rt_data))
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
# ── 6. b32encode / b32decode ─────────────────────────────────────────────────
|
|
110
|
+
|
|
111
|
+
ae(_str(b32encode(bytes([]))), '')
|
|
112
|
+
ae(_str(b32encode(bytes([102]))), 'MY======') # b'f' → 'MY======'
|
|
113
|
+
ae(_str(b32encode(bytes([102, 111]))), 'MZXQ====') # b'fo' → 'MZXQ===='
|
|
114
|
+
ae(_str(b32encode(bytes([102, 111, 111]))), 'MZXW6===') # b'foo'
|
|
115
|
+
ae(_str(b32encode(bytes([102, 111, 111, 98]))), 'MZXW6YQ=') # b'foob'
|
|
116
|
+
ae(_str(b32encode(bytes([102, 111, 111, 98, 97]))), 'MZXW6YTB') # b'fooba'
|
|
117
|
+
|
|
118
|
+
# Round-trip several lengths
|
|
119
|
+
for _rt_data in [bytes([]), bytes([42]), bytes([1, 2, 3]), bytes([255, 0, 128, 64])]:
|
|
120
|
+
ade(list(b32decode(b32encode(_rt_data))), list(_rt_data))
|
|
121
|
+
|
|
122
|
+
# casefold
|
|
123
|
+
_b32_lc = b32decode('mzxw6ytb', casefold=True)
|
|
124
|
+
ade(list(_b32_lc), list(bytes([102, 111, 111, 98, 97])))
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
# ── 7. b16encode / b16decode ──────────────────────────────────────────────────
|
|
128
|
+
|
|
129
|
+
ae(_str(b16encode(bytes([]))), '')
|
|
130
|
+
ae(_str(b16encode(bytes([0]))), '00')
|
|
131
|
+
ae(_str(b16encode(bytes([255]))), 'FF')
|
|
132
|
+
ae(_str(b16encode(bytes([0, 1, 254, 255]))), '0001FEFF')
|
|
133
|
+
ae(_str(b16encode(bytes([171]))), 'AB')
|
|
134
|
+
|
|
135
|
+
# Round-trip
|
|
136
|
+
ade(list(b16decode(b16encode(bytes([10, 20, 30, 40, 250])))), [10, 20, 30, 40, 250])
|
|
137
|
+
|
|
138
|
+
# casefold
|
|
139
|
+
ade(list(b16decode('0001feff', casefold=True)), [0, 1, 254, 255])
|
|
140
|
+
|
|
141
|
+
# Error on invalid hex
|
|
142
|
+
_b16_err = False
|
|
143
|
+
try:
|
|
144
|
+
b16decode('ZZ')
|
|
145
|
+
except Error:
|
|
146
|
+
_b16_err = True
|
|
147
|
+
ok(_b16_err, 'b16decode of non-hex should raise Error')
|
|
148
|
+
|
|
149
|
+
# Error on odd-length string
|
|
150
|
+
_b16_odd = False
|
|
151
|
+
try:
|
|
152
|
+
b16decode('A')
|
|
153
|
+
except Error:
|
|
154
|
+
_b16_odd = True
|
|
155
|
+
ok(_b16_odd, 'b16decode of odd-length string should raise Error')
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
# ── 8. encodebytes / decodebytes ──────────────────────────────────────────────
|
|
159
|
+
|
|
160
|
+
# encodebytes wraps at 76 chars per line
|
|
161
|
+
_long_data = bytes(list(range(57))) # 57 bytes → exactly 76 base64 chars (one line)
|
|
162
|
+
_enc_lines = encodebytes(_long_data)
|
|
163
|
+
_enc_lines_str = _str(_enc_lines)
|
|
164
|
+
# Each line ends with \n
|
|
165
|
+
ok(_enc_lines_str[-1:] == '\n', 'encodebytes output should end with newline')
|
|
166
|
+
|
|
167
|
+
# Decode should recover original
|
|
168
|
+
_dec_lines = decodebytes(_enc_lines)
|
|
169
|
+
ade(list(_dec_lines), list(_long_data))
|
|
170
|
+
|
|
171
|
+
# More than 57 bytes → multiple lines
|
|
172
|
+
_long2 = bytes(list(range(100)))
|
|
173
|
+
_enc2b = encodebytes(_long2)
|
|
174
|
+
ok(_str(_enc2b).split('\n').length > 2, 'long encodebytes should have multiple lines')
|
|
175
|
+
ade(list(decodebytes(_enc2b)), list(_long2))
|
|
176
|
+
|
|
177
|
+
# Empty bytes
|
|
178
|
+
ade(list(decodebytes(encodebytes(bytes([])))), [])
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
# ── 9. b64decode validate=True ────────────────────────────────────────────────
|
|
182
|
+
|
|
183
|
+
# Valid input should not raise
|
|
184
|
+
_valid = b64decode('aGVsbG8=', validate=True)
|
|
185
|
+
ade(list(_valid), [104, 101, 108, 108, 111])
|
|
186
|
+
|
|
187
|
+
# Invalid character raises Error when validate=True
|
|
188
|
+
_bad_raised = False
|
|
189
|
+
try:
|
|
190
|
+
b64decode('aGVs!G8=', validate=True)
|
|
191
|
+
except Error:
|
|
192
|
+
_bad_raised = True
|
|
193
|
+
ok(_bad_raised, 'validate=True should raise Error on non-base64 character')
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
# ── 10. b64encode returns bytes ───────────────────────────────────────────────
|
|
197
|
+
|
|
198
|
+
_enc_type = b64encode(bytes([1, 2, 3]))
|
|
199
|
+
ok(isinstance(_enc_type, bytes), 'b64encode should return bytes')
|
|
200
|
+
|
|
201
|
+
_dec_type = b64decode('AQID')
|
|
202
|
+
ok(isinstance(_dec_type, bytes), 'b64decode should return bytes')
|
package/test/baselib.pyj
CHANGED
|
@@ -159,6 +159,29 @@ a.sort()
|
|
|
159
159
|
assrt.deepEqual(a, [1,2,3,4])
|
|
160
160
|
a.sort(reverse=True)
|
|
161
161
|
assrt.deepEqual(a, [4,3,2,1])
|
|
162
|
+
|
|
163
|
+
# sort: a positional two-argument function is treated as a comparator
|
|
164
|
+
assrt.deepEqual(sorted([3, 1, 2, 10], def(x, y): return x - y;), [1, 2, 3, 10])
|
|
165
|
+
assrt.deepEqual(sorted([3, 1, 2, 10], def(x, y): return y - x;), [10, 3, 2, 1])
|
|
166
|
+
sort_cmp = [5, 2, 8, 1]
|
|
167
|
+
sort_cmp.sort(def(x, y): return x - y;)
|
|
168
|
+
assrt.deepEqual(sort_cmp, [1, 2, 5, 8])
|
|
169
|
+
# a one-argument function is still a key function
|
|
170
|
+
assrt.deepEqual(sorted([3, 1, 2], key=def(x): return -x;), [3, 2, 1])
|
|
171
|
+
# comparator path keeps ties in their original order (stable)
|
|
172
|
+
assrt.deepEqual(sorted([[1, 'a'], [1, 'b'], [0, 'c']], def(p, q): return p[0] - q[0];),
|
|
173
|
+
[[0, 'c'], [1, 'a'], [1, 'b']])
|
|
174
|
+
|
|
175
|
+
# sort: custom objects are ordered via their __lt__ method
|
|
176
|
+
class SortOrd:
|
|
177
|
+
def __init__(self, v):
|
|
178
|
+
self.v = v
|
|
179
|
+
def __lt__(self, other):
|
|
180
|
+
return self.v < other.v
|
|
181
|
+
ord_asc = sorted([SortOrd(3), SortOrd(1), SortOrd(2)])
|
|
182
|
+
assrt.deepEqual([ord_asc[0].v, ord_asc[1].v, ord_asc[2].v], [1, 2, 3])
|
|
183
|
+
ord_desc = sorted([SortOrd(3), SortOrd(1), SortOrd(2)], reverse=True)
|
|
184
|
+
assrt.deepEqual([ord_desc[0].v, ord_desc[1].v, ord_desc[2].v], [3, 2, 1])
|
|
162
185
|
assrt.deepEqual(a, a.slice())
|
|
163
186
|
assrt.ok(a is not a.slice())
|
|
164
187
|
assrt.ok(a.slice().extend is not undefined)
|