tests/ports/webassembly: Add webassembly JS tests.

Signed-off-by: Damien George <damien@micropython.org>
pull/13583/head
Damien George 2024-02-01 17:43:25 +11:00
rodzic e41b571a29
commit c1513a078d
43 zmienionych plików z 757 dodań i 1 usunięć

Wyświetl plik

@ -0,0 +1,8 @@
import(process.argv[2]).then((mp) => {
mp.loadMicroPython().then((py) => {
py.runPython("1");
py.runPython("print('hello')");
py.runPython("import sys; print(f'hello from {sys.platform}')");
py.runPython("import collections; print(collections.OrderedDict)");
});
});

Wyświetl plik

@ -0,0 +1,3 @@
hello
hello from webassembly
<class 'OrderedDict'>

Wyświetl plik

@ -0,0 +1,9 @@
const mp = await (await import(process.argv[2])).loadMicroPython();
mp.FS.mkdir("/lib/");
mp.FS.writeFile("/lib/testmod.py", "x = 1; print(__name__, x)");
mp.runPython("import testmod");
mp.runPython("import sys; sys.modules.clear()");
const testmod = mp.pyimport("testmod");
console.log("testmod:", testmod, testmod.x);

Wyświetl plik

@ -0,0 +1,3 @@
testmod 1
testmod 1
testmod: PyProxy { _ref: 3 } 1

Wyświetl plik

@ -0,0 +1,43 @@
// Test passing floats between JavaScript and Python.
const mp = await (await import(process.argv[2])).loadMicroPython();
globalThis.a = 1 / 2;
globalThis.b = Infinity;
globalThis.c = NaN;
mp.runPython(`
import js
# Test retrieving floats from JS.
print(js.a)
print(js.b)
print(js.c)
# Test calling JS which returns a float.
r = js.Math.random()
print(type(r), 0 < r < 1)
x = 1 / 2
y = float("inf")
z = float("nan")
# Test passing floats to a JS function.
js.console.log(x)
js.console.log(x, y)
js.console.log(x, y, z)
`);
// Test retrieving floats from Python.
console.log(mp.globals.get("x"));
console.log(mp.globals.get("y"));
console.log(mp.globals.get("z"));
// Test passing floats to a Python function.
const mp_print = mp.pyimport("builtins").print;
mp_print(globalThis.a);
mp_print(globalThis.a, globalThis.b);
mp_print(globalThis.a, globalThis.b, globalThis.c);
// Test calling Python which returns a float.
console.log(mp.pyimport("math").sqrt(0.16));

Wyświetl plik

@ -0,0 +1,14 @@
0.5
inf
nan
<class 'float'> True
0.5
0.5 Infinity
0.5 Infinity NaN
0.5
Infinity
NaN
0.5
0.5 inf
0.5 inf nan
0.4

Wyświetl plik

@ -0,0 +1,17 @@
// Test calling JavaScript functions from Python.
const mp = await (await import(process.argv[2])).loadMicroPython();
globalThis.f = (a, b, c, d, e) => {
console.log(a, b, c, d, e);
};
mp.runPython(`
import js
js.f()
js.f(1)
js.f(1, 2)
js.f(1, 2, 3)
js.f(1, 2, 3, 4)
js.f(1, 2, 3, 4, 5)
js.f(1, 2, 3, 4, 5, 6)
`);

Wyświetl plik

@ -0,0 +1,7 @@
undefined undefined undefined undefined undefined
1 undefined undefined undefined undefined
1 2 undefined undefined undefined
1 2 3 undefined undefined
1 2 3 4 undefined
1 2 3 4 5
1 2 3 4 5

Wyświetl plik

@ -0,0 +1,13 @@
// Test accessing Python globals dict via mp.globals.
const mp = await (await import(process.argv[2])).loadMicroPython();
mp.runPython("x = 1");
console.log(mp.globals.get("x"));
mp.globals.set("y", 2);
mp.runPython("print(y)");
mp.runPython("print('y' in globals())");
mp.globals.delete("y");
mp.runPython("print('y' in globals())");

Wyświetl plik

@ -0,0 +1,4 @@
1
2
True
False

Wyświetl plik

@ -0,0 +1,15 @@
// Test expanding the MicroPython GC heap.
const mp = await (await import(process.argv[2])).loadMicroPython();
mp.runPython(`
import gc
bs = []
for i in range(24):
b = bytearray(1 << i)
bs.append(b)
gc.collect()
print(gc.mem_free())
for b in bs:
print(len(b))
`);

Wyświetl plik

@ -0,0 +1,48 @@
135241360
135241328
135241296
135241264
135241216
135241168
135241088
135240944
135240640
135240112
135239072
135237008
135232896
135224688
135208288
135175504
135109888
134978800
134716640
135216848
136217216
138218032
142219616
150222864
1
2
4
8
16
32
64
128
256
512
1024
2048
4096
8192
16384
32768
65536
131072
262144
524288
1048576
2097152
4194304
8388608

Wyświetl plik

@ -0,0 +1,15 @@
// Test jsffi.create_proxy().
const mp = await (await import(process.argv[2])).loadMicroPython();
mp.runPython(`
import jsffi
x = jsffi.create_proxy(1)
print(x)
y = jsffi.create_proxy([2])
print(y)
`);
console.log(mp.globals.get("x"));
console.log(mp.PyProxy.toJs(mp.globals.get("x")));
console.log(mp.globals.get("y"));
console.log(mp.PyProxy.toJs(mp.globals.get("y")));

Wyświetl plik

@ -0,0 +1,6 @@
1
<JsProxy 1>
1
1
PyProxy { _ref: 3 }
[ 2 ]

Wyświetl plik

@ -0,0 +1,28 @@
// Test jsffi.to_js().
const mp = await (await import(process.argv[2])).loadMicroPython();
mp.runPython(`
import jsffi
x = jsffi.to_js(1)
print(x)
y = jsffi.to_js([2])
print(y)
z = jsffi.to_js({"three":3})
print(z)
`);
const x = mp.globals.get("x");
const y = mp.globals.get("y");
const z = mp.globals.get("z");
console.log(Array.isArray(x));
console.log(x);
console.log(Array.isArray(y));
console.log(y);
console.log(Reflect.ownKeys(y));
console.log(Array.isArray(z));
console.log(z);
console.log(Reflect.ownKeys(z));

Wyświetl plik

@ -0,0 +1,11 @@
1
<JsProxy 1>
<JsProxy 2>
false
1
true
[ 2 ]
[ '0', 'length' ]
false
{ three: 3 }
[ 'three' ]

Wyświetl plik

@ -0,0 +1,33 @@
// Test overriding .new() on a JavaScript class.
const mp = await (await import(process.argv[2])).loadMicroPython();
globalThis.MyClass1 = class {
new() {
console.log("MyClass1 new");
return 1;
}
};
globalThis.MyClass2 = class {
static new() {
console.log("MyClass2 static new");
return 2;
}
new() {
console.log("MyClass2 new");
return 3;
}
};
globalThis.myClass2Instance = new globalThis.MyClass2();
mp.runPython(`
import js
print(type(js.MyClass1.new()))
print(js.MyClass1.new().new())
print(js.MyClass2.new())
print(js.myClass2Instance.new())
`);

Wyświetl plik

@ -0,0 +1,7 @@
<class 'JsProxy'>
MyClass1 new
1
MyClass2 static new
2
MyClass2 new
3

Wyświetl plik

@ -0,0 +1,23 @@
// Test polyfill of a method on a built-in.
// Implement Promise.withResolvers, and make sure it has a unique name so
// the test below is guaranteed to use this version.
Promise.withResolversCustom = function withResolversCustom() {
let a;
let b;
const c = new this((resolve, reject) => {
a = resolve;
b = reject;
});
return { resolve: a, reject: b, promise: c };
};
const mp = await (await import(process.argv[2])).loadMicroPython();
mp.runPython(`
from js import Promise
deferred = Promise.withResolversCustom()
deferred.promise.then(print)
deferred.resolve('OK')
`);

Wyświetl plik

@ -0,0 +1,30 @@
// Test `delete <py-obj>.<attr>` on the JavaScript side, which tests PyProxy.deleteProperty.
const mp = await (await import(process.argv[2])).loadMicroPython();
mp.runPython(`
class A:
pass
x = A()
x.foo = 1
y = []
`);
const x = mp.globals.get("x");
const y = mp.globals.get("y");
// Should pass.
// biome-ignore lint/performance/noDelete: test delete statement
delete x.foo;
mp.runPython(`
print(hasattr(x, "foo"))
`);
// Should fail, can't delete attributes on MicroPython lists.
try {
// biome-ignore lint/performance/noDelete: test delete statement
delete y.sort;
} catch (error) {
console.log(error.message);
}

Wyświetl plik

@ -0,0 +1,2 @@
False
'deleteProperty' on proxy: trap returned falsish for property 'sort'

Wyświetl plik

@ -0,0 +1,34 @@
// Test passing a Python dict into JavaScript, it should act like a JS object.
const mp = await (await import(process.argv[2])).loadMicroPython();
mp.runPython(`
x = {"a": 1, "b": 2}
`);
const x = mp.globals.get("x");
// Test has, get, keys/iteration.
console.log("a" in x, "b" in x, "c" in x);
console.log(x.a, x.b);
for (const k in x) {
console.log(k, x[k]);
}
console.log(Object.keys(x));
console.log(Reflect.ownKeys(x));
// Test set.
x.c = 3;
console.log(Object.keys(x));
// Test delete.
// biome-ignore lint/performance/noDelete: test delete statement
delete x.b;
console.log(Object.keys(x));
// Make sure changes on the JavaScript side are reflected in Python.
mp.runPython(`
print(x["a"])
print("b" in x)
print(x["c"])
`);

Wyświetl plik

@ -0,0 +1,11 @@
true true false
1 2
a 1
b 2
[ 'a', 'b' ]
[ 'a', 'b' ]
[ 'a', 'c', 'b' ]
[ 'a', 'c' ]
1
False
3

Wyświetl plik

@ -0,0 +1,11 @@
// Test `<attr> in <py-obj>` on the JavaScript side, which tests PyProxy.has.
const mp = await (await import(process.argv[2])).loadMicroPython();
mp.runPython(`
x = []
`);
const x = mp.globals.get("x");
console.log("no_exist" in x);
console.log("sort" in x);

Wyświetl plik

@ -0,0 +1,2 @@
false
true

Wyświetl plik

@ -0,0 +1,11 @@
// Test `Reflect.ownKeys(<py-obj>)` on the JavaScript side, which tests PyProxy.ownKeys.
const mp = await (await import(process.argv[2])).loadMicroPython();
mp.runPython(`
x = []
y = {"a": 1}
`);
console.log(Reflect.ownKeys(mp.globals.get("x")));
console.log(Reflect.ownKeys(mp.globals.get("y")));

Wyświetl plik

@ -0,0 +1,9 @@
[
'append', 'clear',
'copy', 'count',
'extend', 'index',
'insert', 'pop',
'remove', 'reverse',
'sort'
]
[ 'a' ]

Wyświetl plik

@ -0,0 +1,27 @@
// Test `<py-obj>.<attr> = <value>` on the JavaScript side, which tests PyProxy.set.
const mp = await (await import(process.argv[2])).loadMicroPython();
mp.runPython(`
class A:
pass
x = A()
y = []
`);
const x = mp.globals.get("x");
const y = mp.globals.get("y");
// Should pass.
x.foo = 1;
mp.runPython(`
print(x.foo)
`);
// Should fail, can't set attributes on MicroPython lists.
try {
y.bar = 1;
} catch (error) {
console.log(error.message);
}

Wyświetl plik

@ -0,0 +1,2 @@
1
'set' on proxy: trap returned falsish for property 'bar'

Wyświetl plik

@ -0,0 +1,20 @@
// Test PyProxy.toJs().
const mp = await (await import(process.argv[2])).loadMicroPython();
mp.runPython(`
a = 1
b = (1, 2, 3)
c = [None, True, 1.2]
d = {"one": 1, "tuple": b, "list": c}
`);
const py_a = mp.globals.get("a");
const py_b = mp.globals.get("b");
const py_c = mp.globals.get("c");
const py_d = mp.globals.get("d");
console.log(py_a instanceof mp.PyProxy, mp.PyProxy.toJs(py_a));
console.log(py_b instanceof mp.PyProxy, mp.PyProxy.toJs(py_b));
console.log(py_c instanceof mp.PyProxy, mp.PyProxy.toJs(py_c));
console.log(py_d instanceof mp.PyProxy, mp.PyProxy.toJs(py_d));

Wyświetl plik

@ -0,0 +1,4 @@
false 1
true [ 1, 2, 3 ]
true [ null, true, 1.2 ]
true { tuple: [ 1, 2, 3 ], one: 1, list: [ null, true, 1.2 ] }

Wyświetl plik

@ -0,0 +1,6 @@
import(process.argv[2]).then((mp) => {
mp.loadMicroPython().then((py) => {
py.registerJsModule("js_module", { y: 2 });
py.runPython("import js_module; print(js_module); print(js_module.y)");
});
});

Wyświetl plik

@ -0,0 +1,2 @@
<JsProxy 1>
2

Wyświetl plik

@ -0,0 +1,115 @@
// Test runPythonAsync() and top-level await in Python.
const mp = await (await import(process.argv[2])).loadMicroPython();
/**********************************************************/
// Using only promise objects, no await's.
console.log("= TEST 1 ==========");
globalThis.p = new Promise((resolve, reject) => {
resolve(123);
});
console.log(1);
mp.runPython(`
import js
print(js.p)
print("py 1")
print(js.p.then(lambda x: print("resolved", x)))
print("py 2")
`);
console.log(2);
// Let the promise resolve.
await globalThis.p;
console.log(3);
/**********************************************************/
// Using setTimeout to resolve the promise.
console.log("= TEST 2 ==========");
globalThis.p = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(123);
console.log("setTimeout resolved");
}, 100);
});
console.log(1);
mp.runPython(`
import js
print(js.p)
print("py 1")
print(js.p.then(lambda x: print("resolved", x)))
print("py 2")
`);
console.log(2);
// Let the promise resolve.
await globalThis.p;
console.log(3);
/**********************************************************/
// Using setTimeout and await within Python.
console.log("= TEST 3 ==========");
globalThis.p = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(123);
console.log("setTimeout resolved");
}, 100);
});
console.log(1);
const ret3 = await mp.runPythonAsync(`
import js
print("py 1")
print("resolved value:", await js.p)
print("py 2")
`);
console.log(2, ret3);
/**********************************************************/
// Multiple setTimeout's and await's within Python.
console.log("= TEST 4 ==========");
globalThis.p1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(123);
console.log("setTimeout A resolved");
}, 100);
});
globalThis.p2 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(456);
console.log("setTimeout B resolved");
}, 200);
});
console.log(1);
const ret4 = await mp.runPythonAsync(`
import js
print("py 1")
print("resolved value:", await js.p1)
print("py 2")
print("resolved value:", await js.p1)
print("py 3")
print("resolved value:", await js.p2)
print("py 4")
`);
console.log(2, ret4);

Wyświetl plik

@ -0,0 +1,38 @@
= TEST 1 ==========
1
<JsProxy 1>
py 1
<JsProxy 4>
py 2
2
resolved 123
3
= TEST 2 ==========
1
<JsProxy 5>
py 1
<JsProxy 8>
py 2
2
setTimeout resolved
resolved 123
3
= TEST 3 ==========
1
py 1
setTimeout resolved
resolved value: 123
py 2
2 null
= TEST 4 ==========
1
py 1
setTimeout A resolved
resolved value: 123
py 2
resolved value: 123
py 3
setTimeout B resolved
resolved value: 456
py 4
2 null

Wyświetl plik

@ -0,0 +1,42 @@
// Test runPythonAsync() and top-level await in Python, with multi-level awaits.
const mp = await (await import(process.argv[2])).loadMicroPython();
// simulate a 2-step resolution of the string "OK".
await mp.runPythonAsync(`
import js
def _timeout(resolve, _):
js.setTimeout(resolve, 100)
def _fetch():
return js.Promise.new(_timeout)
async def _text(promise):
if not promise._response:
print("_text await start")
await promise
print("_text awaited end")
ret = await promise._response.text()
return ret
class _Response:
async def text(self):
print('_Response.text start')
await js.Promise.new(_timeout)
print('_Response.text end')
return "OK"
def _response(promise):
promise._response = _Response()
return promise._response
def fetch(url):
promise = _fetch().then(lambda *_: _response(promise))
promise._response = None
promise.text = lambda: _text(promise)
return promise
print(await fetch("config.json").text())
print(await (await fetch("config.json")).text())
`);

Wyświetl plik

@ -0,0 +1,8 @@
_text await start
_text awaited end
_Response.text start
_Response.text end
OK
_Response.text start
_Response.text end
OK

Wyświetl plik

@ -0,0 +1,24 @@
// Test "this" behaviour.
const mp = await (await import(process.argv[2])).loadMicroPython();
// "this" should be undefined.
globalThis.func0 = function () {
console.log("func0", this);
};
mp.runPython("import js; js.func0()");
globalThis.func1 = function (a) {
console.log("func1", a, this);
};
mp.runPython("import js; js.func1(123)");
globalThis.func2 = function (a, b) {
console.log("func2", a, b, this);
};
mp.runPython("import js; js.func2(123, 456)");
globalThis.func3 = function (a, b, c) {
console.log("func3", a, b, c, this);
};
mp.runPython("import js; js.func3(123, 456, 789)");

Wyświetl plik

@ -0,0 +1,4 @@
func0 undefined
func1 123 undefined
func2 123 456 undefined
func3 123 456 789 undefined

Wyświetl plik

@ -0,0 +1,38 @@
import(process.argv[2]).then((mp) => {
mp.loadMicroPython().then((py) => {
globalThis.jsadd = (x, y) => {
return x + y;
};
py.runPython("import js; print(js); print(js.jsadd(4, 9))");
py.runPython(
"def set_timeout_callback():\n print('set_timeout_callback')",
);
py.runPython("import js; js.setTimeout(set_timeout_callback, 100)");
py.runPython("obj = js.Object(a=1)");
console.log("main", py.pyimport("__main__").obj);
console.log("=======");
py.runPython(`
from js import Array, Promise, Reflect
def callback(resolve, reject):
resolve('OK1')
p = Reflect.construct(Promise, Array(callback))
p.then(print)
`);
console.log("=======");
py.runPython(`
from js import Promise
def callback(resolve, reject):
resolve('OK2')
p = Promise.new(callback)
p.then(print)
`);
});
});

Wyświetl plik

@ -0,0 +1,8 @@
<module 'js'>
13
main { a: 1 }
=======
=======
OK1
OK2
set_timeout_callback

Wyświetl plik

@ -1142,7 +1142,7 @@ the last matching regex is used:
"ports/qemu-arm",
)
elif args.target == "webassembly":
test_dirs += ("float",)
test_dirs += ("float", "ports/webassembly")
else:
# run tests from these directories
test_dirs = args.test_dirs