Eliminate pyproxy leaks (#1616)

This commit is contained in:
Hood Chatham 2021-06-07 00:23:47 -07:00 committed by GitHub
parent 1b8e8b3486
commit c05c1cac78
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 420 additions and 196 deletions

View File

@ -66,7 +66,7 @@ jobs:
command: |
ccache -z
# The following packages are currently used in the main pyodide test suite
PYODIDE_PACKAGES="micropip,pyparsing,pytz,packaging,kiwisolver,Jinja2" make
PYODIDE_PACKAGES="micropip,pyparsing,pytz,packaging,Jinja2" make
ccache -s
- run:

View File

@ -215,5 +215,5 @@ minimal :
debug :
EXTRA_CFLAGS+=" -D DEBUG_F" \
PYODIDE_PACKAGES+=", micropip, pyparsing, pytz, packaging, kiwisolver, " \
PYODIDE_PACKAGES+=", micropip, pyparsing, pytz, packaging, " \
make

View File

@ -99,7 +99,10 @@ class SeleniumWrapper:
self.javascript_setup()
if load_pyodide:
self.run_js(
"window.pyodide = await loadPyodide({ indexURL : './', fullStdLib: false });"
"""
window.pyodide = await loadPyodide({ indexURL : './', fullStdLib: false });
pyodide.runPython("");
"""
)
self.save_state()
self.script_timeout = script_timeout
@ -362,7 +365,7 @@ def pytest_runtest_call(item):
selenium = item.funcargs["selenium_standalone"]
if selenium:
trace_hiwire_refs = pytest.mark.skip_refcount_check.mark not in item.own_markers
trace_pyproxies = pytest.mark.trace_pyproxies.mark in item.own_markers
trace_pyproxies = pytest.mark.skip_pyproxy_check.mark not in item.own_markers
yield from test_wrapper_check_for_memory_leaks(
selenium, trace_hiwire_refs, trace_pyproxies
)
@ -382,12 +385,12 @@ def test_wrapper_check_for_memory_leaks(selenium, trace_hiwire_refs, trace_pypro
# get_result (we don't want to override the error message by raising a
# different error here.)
a.get_result()
if trace_hiwire_refs:
delta_keys = selenium.get_num_hiwire_keys() - init_num_keys
assert delta_keys == 0
if trace_pyproxies:
delta_proxies = selenium.get_num_proxies() - init_num_proxies
assert delta_proxies == 0
if trace_hiwire_refs:
delta_keys = selenium.get_num_hiwire_keys() - init_num_keys
assert delta_keys == 0
@contextlib.contextmanager

View File

@ -2,6 +2,7 @@ import pytest
@pytest.mark.skip_refcount_check
@pytest.mark.skip_pyproxy_check
def test_matplotlib(selenium_standalone):
selenium = selenium_standalone
selenium.load_package("matplotlib")
@ -16,6 +17,7 @@ def test_matplotlib(selenium_standalone):
@pytest.mark.skip_refcount_check
@pytest.mark.skip_pyproxy_check
def test_svg(selenium):
selenium.load_package("matplotlib")
selenium.run("from matplotlib import pyplot as plt")
@ -29,6 +31,7 @@ def test_svg(selenium):
assert content.startswith("<?xml")
@pytest.mark.skip_pyproxy_check
def test_pdf(selenium):
selenium.load_package("matplotlib")
selenium.run("from matplotlib import pyplot as plt")

View File

@ -3,17 +3,24 @@ import pytest
def test_numpy(selenium):
selenium.load_package("numpy")
selenium.run("import numpy")
selenium.run("x = numpy.ones((32, 64))")
assert selenium.run_js("return pyodide.globals.get('x').toJs().length == 32")
selenium.run(
"""
import numpy
x = numpy.ones((32, 64))
"""
)
selenium.run_js(
"""
let xpy = pyodide.runPython('x');
window.x = xpy.toJs();
xpy.destroy();
"""
)
assert selenium.run_js("return x.length === 32")
for i in range(32):
assert selenium.run_js(
f"return pyodide.globals.get('x').toJs()[{i}].length == 64"
)
assert selenium.run_js(f"return x[{i}].length == 64")
for j in range(64):
assert selenium.run_js(
f"return pyodide.globals.get('x').toJs()[{i}][{j}] == 1"
)
assert selenium.run_js(f"return x[{i}][{j}] == 1")
def test_typed_arrays(selenium):
@ -30,7 +37,6 @@ def test_typed_arrays(selenium):
("Float32Array", "float32"),
("Float64Array", "float64"),
):
print(jstype, npytype)
selenium.run_js(f"window.array = new {jstype}([1, 2, 3, 4]);\n")
assert selenium.run(
"from js import array\n"
@ -40,6 +46,7 @@ def test_typed_arrays(selenium):
)
@pytest.mark.skip_pyproxy_check
@pytest.mark.parametrize("order", ("C", "F"))
@pytest.mark.parametrize(
"dtype",
@ -106,6 +113,7 @@ def test_python2js_numpy_dtype(selenium, order, dtype):
assert selenium.run("np.array([True, False])") == [True, False]
@pytest.mark.skip_pyproxy_check
def test_py2js_buffer_clear_error_flag(selenium):
selenium.load_package("numpy")
selenium.run("import numpy as np")
@ -119,6 +127,7 @@ def test_py2js_buffer_clear_error_flag(selenium):
)
@pytest.mark.skip_pyproxy_check
@pytest.mark.parametrize(
"dtype",
(
@ -146,8 +155,8 @@ def test_python2js_numpy_scalar(selenium, dtype):
assert (
selenium.run_js(
"""
return pyodide.globals.get('x') == 1
"""
return pyodide.globals.get('x') == 1
"""
)
is True
)
@ -166,6 +175,7 @@ def test_python2js_numpy_scalar(selenium, dtype):
)
@pytest.mark.skip_pyproxy_check
def test_runpythonasync_numpy(selenium_standalone):
selenium_standalone.run_async(
"""
@ -194,6 +204,7 @@ def test_runwebworker_numpy(selenium_webworker_standalone):
assert output == "[0. 0. 0. 0. 0.]"
@pytest.mark.skip_pyproxy_check
def test_get_buffer(selenium):
selenium.run_js(
"""
@ -224,6 +235,7 @@ def test_get_buffer(selenium):
)
@pytest.mark.skip_pyproxy_check
@pytest.mark.parametrize(
"arg",
[
@ -323,6 +335,11 @@ def test_get_buffer_error_messages(selenium):
import numpy as np
x = np.ones(2, dtype=np.float16)
`);
pyodide.pyimport("x").getBuffer();
let x = pyodide.runPython("x");
try {
x.getBuffer();
} finally {
x.destroy();
}
"""
)

View File

@ -56,19 +56,26 @@ def run_in_pyodide(
# containing the source. This results in a more helpful
# traceback
selenium.run_js(
"""pyodide._module.pyodide_py.eval_code.callKwargs(
"""
let eval_code = pyodide._module.pyodide_py.eval_code;
try {{
eval_code.callKwargs(
{{
source : {!r},
globals : pyodide._module.globals,
filename : {!r}
}}
)""".format(
)
}} finally {{
eval_code.destroy();
}}
""".format(
_run_in_pyodide_get_source(f), inspect.getsourcefile(f)
)
)
# When invoking the function, use the default filename <eval>
selenium.run_js(
"""pyodide._module.pyodide_py.eval_code("{}()", pyodide._module.globals)""".format(
"""pyodide.runPython("{}()", pyodide._module.globals)""".format(
f.__name__
)
)

View File

@ -3,8 +3,8 @@ norecursedirs = build cpython emsdk/
addopts = --doctest-modules
markers =
skip_refcount_check: Dont run refcount checks
skip_pyproxy_check: Dont run pyproxy allocation checks
driver_timeout: Set script timeout in WebDriver
trace_pyproxies: Trace pyproxy allocations
[bumpversion]
current_version = 0.16.1

View File

@ -131,7 +131,16 @@ export async function loadPackagesFromImports(
messageCallback,
errorCallback
) {
let imports = Module.pyodide_py.find_imports(code).toJs();
let find_imports = Module.pyodide_py.find_imports;
let imports;
let pyimports;
try {
pyimports = find_imports(code);
imports = pyimports.toJs();
} finally {
find_imports.destroy();
pyimports && pyimports.destroy();
}
if (imports.length === 0) {
return;
}
@ -213,7 +222,12 @@ Module.runPythonAsync = runPythonAsync;
* @param {object} module Javascript object backing the module
*/
export function registerJsModule(name, module) {
Module.pyodide_py.register_js_module(name, module);
let register_js_module = Module.pyodide_py.register_js_module;
try {
register_js_module(name, module);
} finally {
register_js_module.destroy();
}
}
/**
@ -228,7 +242,12 @@ export function registerJsModule(name, module) {
* @param {string} name Name of the Javascript module to remove
*/
export function unregisterJsModule(name) {
Module.pyodide_py.unregister_js_module(name);
let unregister_js_module = Module.pyodide_py.unregister_js_module;
try {
unregister_js_module(name);
} finally {
unregister_js_module.destroy();
}
}
/**

View File

@ -16,7 +16,8 @@ def test_await_jsproxy(selenium):
global resolve
resolve = res
from js import Promise
p = Promise.new(prom)
from pyodide import create_once_callable
p = Promise.new(create_once_callable(prom))
async def temp():
x = await p
return x + 7
@ -45,6 +46,7 @@ def test_then_jsproxy(selenium):
reject = rej
from js import Promise
from pyodide import create_once_callable
result = None
err = None
finally_occurred = False
@ -65,7 +67,7 @@ def test_then_jsproxy(selenium):
selenium.run(
"""
p = Promise.new(prom)
p = Promise.new(create_once_callable(prom))
p.then(onfulfilled, onrejected)
resolve(10)
"""
@ -81,7 +83,7 @@ def test_then_jsproxy(selenium):
selenium.run(
"""
p = Promise.new(prom)
p = Promise.new(create_once_callable(prom))
p.then(onfulfilled, onrejected)
reject(10)
"""
@ -97,7 +99,7 @@ def test_then_jsproxy(selenium):
selenium.run(
"""
p = Promise.new(prom)
p = Promise.new(create_once_callable(prom))
p.catch(onrejected)
resolve(10)
"""
@ -107,7 +109,7 @@ def test_then_jsproxy(selenium):
selenium.run(
"""
p = Promise.new(prom)
p = Promise.new(create_once_callable(prom))
p.catch(onrejected)
reject(10)
"""
@ -122,7 +124,7 @@ def test_then_jsproxy(selenium):
selenium.run(
"""
p = Promise.new(prom)
p = Promise.new(create_once_callable(prom))
p.finally_(onfinally)
resolve(10)
"""
@ -137,7 +139,7 @@ def test_then_jsproxy(selenium):
selenium.run(
"""
p = Promise.new(prom)
p = Promise.new(create_once_callable(prom))
p.finally_(onfinally)
reject(10)
"""
@ -245,7 +247,8 @@ def test_eval_code_await_jsproxy(selenium):
global resolve
resolve = res
from js import Promise
p = Promise.new(prom)
from pyodide import create_once_callable
p = Promise.new(create_once_callable(prom))
from pyodide._base import eval_code_async
c = eval_code_async(
'''
@ -350,9 +353,11 @@ def test_await_pyproxy_eval_async(selenium):
assert (
selenium.run_js(
"""
let c = pyodide._module.pyodide_py._base.eval_code_async("1+1");
let eval_code_async = pyodide.pyodide_py.eval_code_async;
let c = eval_code_async("1+1");
let result = await c;
c.destroy();
eval_code_async.destroy();
return result;
"""
)
@ -363,8 +368,11 @@ def test_await_pyproxy_eval_async(selenium):
selenium.run_js(
"""
let finally_occurred = false;
let c = pyodide._module.pyodide_py._base.eval_code_async("1+1");
let eval_code_async = pyodide.pyodide_py.eval_code_async;
let c = eval_code_async("1+1");
let result = await c.finally(() => { finally_occurred = true; });
c.destroy();
eval_code_async.destroy();
return [result, finally_occurred];
"""
)
@ -376,12 +384,15 @@ def test_await_pyproxy_eval_async(selenium):
"""
let finally_occurred = false;
let err_occurred = false;
let c = pyodide._module.pyodide_py._base.eval_code_async("raise ValueError('hi')");
let eval_code_async = pyodide.pyodide_py.eval_code_async;
let c = eval_code_async("raise ValueError('hi')");
try {
let result = await c.finally(() => { finally_occurred = true; });
} catch(e){
err_occurred = e.constructor.name === "PythonError";
}
eval_code_async.destroy();
c.destroy();
return [finally_occurred, err_occurred];
"""
)
@ -390,18 +401,26 @@ def test_await_pyproxy_eval_async(selenium):
assert selenium.run_js(
"""
let c = pyodide._module.pyodide_py._base.eval_code_async("raise ValueError('hi')");
return await c.catch(e => e.constructor.name === "PythonError");
let eval_code_async = pyodide.pyodide_py.eval_code_async;
let c = eval_code_async("raise ValueError('hi')");
eval_code_async.destroy();
try {
return await c.catch(e => e.constructor.name === "PythonError");
} finally {
c.destroy();
}
"""
)
assert selenium.run_js(
"""
let c = pyodide._module.pyodide_py._base.eval_code_async(`
let eval_code_async = pyodide.pyodide_py.eval_code_async;
let c = eval_code_async(`
from js import fetch
await (await fetch('packages.json')).json()
`);
let packages = await c;
eval_code_async.destroy();
c.destroy();
return (!!packages.dependencies) && (!!packages.import_name_to_package_name);
"""
@ -409,8 +428,11 @@ def test_await_pyproxy_eval_async(selenium):
assert selenium.run_js(
"""
let c = pyodide._module.pyodide_py._base.eval_code_async("1+1");
let eval_code_async = pyodide.pyodide_py.eval_code_async;
let c = eval_code_async("1+1");
await c;
eval_code_async.destroy();
c.destroy();
let err_occurred = false;
try {
// Triggers: cannot await already awaited coroutine
@ -426,11 +448,11 @@ def test_await_pyproxy_eval_async(selenium):
def test_await_pyproxy_async_def(selenium):
assert selenium.run_js(
"""
let packages = await pyodide.runPython(`
let packages = await pyodide.runPythonAsync(`
from js import fetch
async def temp():
return await (await fetch('packages.json')).json()
temp()
await temp()
`);
return (!!packages.dependencies) && (!!packages.import_name_to_package_name);
"""

View File

@ -176,20 +176,20 @@ def test_interactive_console(selenium, safe_selenium_sys_redirections):
selenium.run("shell.push('x = 5')")
selenium.run("shell.push('x')")
selenium.run_js("await pyodide.runPython('shell.run_complete');")
selenium.run_js("await pyodide.runPythonAsync('await shell.run_complete');")
assert selenium.run("result") == 5
selenium.run("shell.push('x ** 2')")
selenium.run_js("await pyodide.runPython('shell.run_complete');")
selenium.run_js("await pyodide.runPythonAsync('await shell.run_complete');")
assert selenium.run("result") == 25
selenium.run("shell.push('def f(x):')")
selenium.run("shell.push(' return x*x + 1')")
selenium.run("shell.push('')")
selenium.run("shell.push('[f(x) for x in range(5)]')")
selenium.run_js("await pyodide.runPython('shell.run_complete');")
assert selenium.run("result") == [1, 2, 5, 10, 17]
selenium.run("shell.push('str([f(x) for x in range(5)])')")
selenium.run_js("await pyodide.runPythonAsync('await shell.run_complete');")
assert selenium.run("result") == str([1, 2, 5, 10, 17])
selenium.run("shell.push('def factorial(n):')")
selenium.run("shell.push(' if n < 2:')")
@ -198,13 +198,13 @@ def test_interactive_console(selenium, safe_selenium_sys_redirections):
selenium.run("shell.push(' return n * factorial(n - 1)')")
selenium.run("shell.push('')")
selenium.run("shell.push('factorial(10)')")
selenium.run_js("await pyodide.runPython('shell.run_complete');")
selenium.run_js("await pyodide.runPythonAsync('await shell.run_complete');")
assert selenium.run("result") == 3628800
# with package load
selenium.run("shell.push('import pytz')")
selenium.run("shell.push('pytz.utc.zone')")
selenium.run_js("await pyodide.runPython('shell.run_complete');")
selenium.run_js("await pyodide.runPythonAsync('await shell.run_complete');")
assert selenium.run("result") == "UTC"

View File

@ -8,11 +8,14 @@ def test_jsproxy_dir(selenium):
"""
window.a = { x : 2, y : "9" };
window.b = function(){};
return pyodide.runPython(`
let pyresult = pyodide.runPython(`
from js import a
from js import b
[dir(a), dir(b)]
`).toJs();
`);
let result = pyresult.toJs();
pyresult.destroy();
return result;
"""
)
jsproxy_items = set(
@ -67,10 +70,13 @@ def test_jsproxy_getattr(selenium):
selenium.run_js(
"""
window.a = { x : 2, y : "9", typeof : 7 };
return pyodide.runPython(`
let pyresult = pyodide.runPython(`
from js import a
[ a.x, a.y, a.typeof ]
`).toJs();
`);
let result = pyresult.toJs();
pyresult.destroy();
return result;
"""
)
== [2, "9", "object"]
@ -211,12 +217,15 @@ def test_jsproxy_call(selenium):
selenium.run_js(
"""
window.f = function(){ return arguments.length; };
return pyodide.runPython(
let pyresult = pyodide.runPython(
`
from js import f
[f(*range(n)) for n in range(10)]
`
).toJs();
);
let result = pyresult.toJs();
pyresult.destroy();
return result;
"""
)
== list(range(10))
@ -302,7 +311,9 @@ def test_import_invocation():
def temp():
print("okay?")
js.setTimeout(temp, 100)
from pyodide import create_once_callable
js.setTimeout(create_once_callable(temp), 100)
js.fetch("packages.json")
@ -398,7 +409,7 @@ def test_unregister_jsmodule(selenium):
raises = TestCase().assertRaises
with raises(ImportError):
import a
`)
`);
"""
)
@ -529,6 +540,7 @@ def test_mixins_feature_presence(selenium):
}
test_object(o, keys_expected);
}
test_object.destroy();
"""
)
@ -553,7 +565,7 @@ def test_mixins_calls(selenium):
};
testObjects.awaitable = { then(cb){ cb(7); } };
let result = await pyodide.runPythonAsync(`
let pyresult = await pyodide.runPythonAsync(`
from js import testObjects as obj
result = []
result.append(["iterable1", list(iter(obj.iterable)), [3, 5, 7]])
@ -577,7 +589,9 @@ def test_mixins_calls(selenium):
result.append(["awaitable", await obj.awaitable, 7])
result
`);
return result.toJs();
let result = pyresult.toJs();
pyresult.destroy();
return result;
"""
)
for [desc, a, b] in result:
@ -779,6 +793,7 @@ def test_memory_leaks(selenium):
from js import a
repr(a)
[*a]
None
`);
"""
)

View File

@ -196,7 +196,12 @@ def test_hiwire_is_promise(selenium):
assert not selenium.run_js(
"""
return pyodide._module.hiwire.isPromise(pyodide.globals);
let d = pyodide.runPython("{}");
try {
return pyodide._module.hiwire.isPromise(d);
} finally {
d.destroy();
}
"""
)
@ -238,7 +243,6 @@ def test_run_python_async_toplevel_await(selenium):
)
@pytest.mark.trace_pyproxies
def test_run_python_proxy_leak(selenium):
selenium.run_js(
"""
@ -424,6 +428,7 @@ def test_docstrings_b(selenium):
@pytest.mark.skip_refcount_check
@pytest.mark.skip_pyproxy_check
def test_restore_state(selenium):
selenium.run_js(
"""

View File

@ -2,61 +2,68 @@
import pytest
def test_pyproxy(selenium):
selenium.run(
def test_pyproxy_class(selenium):
selenium.run_js(
"""
class Foo:
bar = 42
def get_value(self, value):
return value * 64
f = Foo()
pyodide.runPython(`
class Foo:
bar = 42
def get_value(self, value):
return value * 64
f = Foo()
`);
window.f = pyodide.globals.get('f');
assert(() => f.type === "Foo");
let f_get_value = f.get_value
assert(() => f_get_value(2) === 128);
f_get_value.destroy();
assert(() => f.bar === 42);
assert(() => 'bar' in f);
f.baz = 32;
assert(() => f.baz === 32);
pyodide.runPython(`assert hasattr(f, 'baz')`)
window.f_props = Object.getOwnPropertyNames(f);
delete f.baz
pyodide.runPython(`assert not hasattr(f, 'baz')`)
assert(() => f.toString().startsWith("<Foo"));
f.destroy();
"""
)
selenium.run_js("window.f = pyodide.globals.get('f')")
assert selenium.run_js("return f.type") == "Foo"
assert selenium.run_js("return f.get_value(2)") == 128
assert selenium.run_js("return f.bar") == 42
assert selenium.run_js("return ('bar' in f)")
selenium.run_js("f.baz = 32")
assert selenium.run("f.baz") == 32
assert set(selenium.run_js("return Object.getOwnPropertyNames(f)")) > set(
[
"__class__",
"__delattr__",
"__dict__",
"__dir__",
"__doc__",
"__eq__",
"__format__",
"__ge__",
"__getattribute__",
"__gt__",
"__hash__",
"__init__",
"__init_subclass__",
"__le__",
"__lt__",
"__module__",
"__ne__",
"__new__",
"__reduce__",
"__reduce_ex__",
"__repr__",
"__setattr__",
"__sizeof__",
"__str__",
"__subclasshook__",
"__weakref__",
"bar",
"baz",
"get_value",
]
)
assert selenium.run("hasattr(f, 'baz')")
selenium.run_js("delete pyodide.globals.get('f').baz")
assert not selenium.run("hasattr(f, 'baz')")
assert selenium.run_js("return pyodide.globals.get('f').toString()").startswith(
"<Foo"
assert (
set(
[
"__class__",
"__delattr__",
"__dict__",
"__dir__",
"__doc__",
"__eq__",
"__format__",
"__ge__",
"__getattribute__",
"__gt__",
"__hash__",
"__init__",
"__init_subclass__",
"__le__",
"__lt__",
"__module__",
"__ne__",
"__new__",
"__reduce__",
"__reduce_ex__",
"__repr__",
"__setattr__",
"__sizeof__",
"__str__",
"__subclasshook__",
"__weakref__",
"bar",
"baz",
"get_value",
]
).difference(selenium.run_js("return f_props"))
== set()
)
@ -72,6 +79,7 @@ def test_pyproxy_clone(selenium):
)
@pytest.mark.skip_pyproxy_check
def test_pyproxy_refcount(selenium):
result = selenium.run_js(
"""
@ -139,7 +147,9 @@ def test_pyproxy_destroy(selenium):
selenium.run_js(
"""
let f = pyodide.globals.get('f');
assert(()=> f.get_value(1) === 64);
let f_get_value = f.get_value;
assert(()=> f_get_value(1) === 64);
f_get_value.destroy();
f.destroy();
f.get_value();
"""
@ -155,7 +165,9 @@ def test_pyproxy_iter(selenium):
yield i
test()
`);
return [c.type, [...c]];
let result = [c.type, [...c]];
c.destroy();
return result;
"""
)
assert ty == "generator"
@ -163,11 +175,13 @@ def test_pyproxy_iter(selenium):
[ty, l] = selenium.run_js(
"""
c = pyodide.runPython(`
let c = pyodide.runPython(`
from collections import ChainMap
ChainMap({"a" : 2, "b" : 3})
`);
return [c.type, [...c]];
let result = [c.type, [...c]];
c.destroy();
return result;
"""
)
assert ty == "ChainMap"
@ -189,6 +203,7 @@ def test_pyproxy_iter(selenium):
result.push(value);
({done, value} = c.next(value + 1));
}
c.destroy();
function* test(){
let acc = 0;
@ -336,8 +351,8 @@ def test_pyproxy_mixins(selenium):
class AwaitIter(Await, Iter): pass
class AwaitNext(Await, Next): pass
[NoImpls(), Await(), Iter(), Next(), AwaitIter(), AwaitNext()]
from pyodide import to_js
to_js([NoImpls(), Await(), Iter(), Next(), AwaitIter(), AwaitNext()])
`);
let name_proxy = {noimpls, awaitable, iterable, iterator, awaititerable, awaititerator};
let result = {};
@ -353,6 +368,7 @@ def test_pyproxy_mixins(selenium):
impls[name] = key in x;
}
result[name] = impls;
x.destroy();
}
return result;
"""
@ -391,16 +407,26 @@ def test_pyproxy_mixins2(selenium):
assert(() => get_method.prototype === undefined);
assert(() => !("length" in get_method));
assert(() => !("name" in get_method));
get_method.destroy();
let d = pyodide.runPython("{}");
assert(() => d.$get.type === "builtin_function_or_method");
let d_get = d.$get;
assert(() => d_get.type === "builtin_function_or_method");
assert(() => d.get.type === undefined);
assert(() => d.set.type === undefined);
d_get.destroy();
d.destroy();
"""
)
def test_pyproxy_mixins3(selenium):
selenium.run_js(
"""
let [Test, t] = pyodide.runPython(`
class Test: pass
[Test, Test()]
from pyodide import to_js
to_js([Test, Test()])
`);
assert(() => Test.prototype === undefined);
assert(() => !("name" in Test));
@ -426,14 +452,23 @@ def test_pyproxy_mixins2(selenium):
assertThrows( () => Test.$$ = 7, "TypeError", /^Cannot set read only field/);
assertThrows( () => delete Test.$$, "TypeError", /^Cannot delete read only field/);
Test.destroy();
t.destroy();
"""
)
def test_pyproxy_mixins4(selenium):
selenium.run_js(
"""
[Test, t] = pyodide.runPython(`
class Test:
caller="fifty"
prototype="prototype"
name="me"
length=7
[Test, Test()]
from pyodide import to_js
to_js([Test, Test()])
`);
assert(() => Test.$prototype === "prototype");
assert(() => Test.prototype === undefined);
@ -444,20 +479,38 @@ def test_pyproxy_mixins2(selenium):
assert(() => t.prototype === "prototype");
assert(() => t.name==="me");
assert(() => t.length === 7);
Test.destroy();
t.destroy();
"""
)
def test_pyproxy_mixins5(selenium):
selenium.run_js(
"""
[Test, t] = pyodide.runPython(`
class Test:
def __len__(self):
return 9
[Test, Test()]
from pyodide import to_js
to_js([Test, Test()])
`);
assert(() => !("length" in Test));
assert(() => t.length === 9);
t.length = 10;
assert(() => t.length === 9);
assert(() => t.$length === 10);
let t__len__ = t.__len__;
assert(() => t__len__() === 9);
t__len__.destroy();
Test.destroy();
t.destroy();
"""
)
def test_pyproxy_mixins6(selenium):
selenium.run_js(
"""
let l = pyodide.runPython(`
l = [5, 6, 7] ; l
`);
@ -473,10 +526,12 @@ def test_pyproxy_mixins2(selenium):
assert len(l) == 2 and l[1] == 7
`);
assert(() => l.length === 2 && l.get(1) === 7);
l.destroy();
"""
)
@pytest.mark.skip_pyproxy_check
def test_pyproxy_gc(selenium):
if selenium.browser != "chrome":
pytest.skip("No gc exposed")
@ -526,6 +581,7 @@ def test_pyproxy_gc(selenium):
d.get();
get_ref_count(2);
d.get();
d.destroy()
"""
)
selenium.driver.execute_cdp_cmd("HeapProfiler.collectGarbage", {})
@ -541,6 +597,7 @@ def test_pyproxy_gc(selenium):
assert dict(a) == {0: 2, 1: 3, 2: 4, 3: 2, "destructor_ran": True}
@pytest.mark.skip_pyproxy_check
def test_pyproxy_gc_destroy(selenium):
if selenium.browser != "chrome":
pytest.skip("No gc exposed")
@ -604,8 +661,9 @@ def test_pyproxy_copy(selenium):
let a = pyodide.runPython(`d = { 1 : 2}; d`);
let b = pyodide.runPython(`d`);
result.push(a.get(1));
a.destroy();
result.push(b.get(1));
a.destroy();
b.destroy();
return result;
"""
)
@ -613,6 +671,7 @@ def test_pyproxy_copy(selenium):
assert result[1] == 2
@pytest.mark.skip_pyproxy_check
def test_errors(selenium):
selenium.run_js(
"""
@ -652,6 +711,7 @@ def test_errors(selenium):
)
@pytest.mark.skip_pyproxy_check
def test_fatal_error(selenium_standalone):
"""Inject fatal errors in all the reasonable entrypoints"""
selenium_standalone.run_js(
@ -766,14 +826,18 @@ def test_pyproxy_call(selenium):
msg = r"TypeError: f\(\) got multiple values for argument 'x'"
with pytest.raises(selenium.JavascriptException, match=msg):
selenium.run_js("f.callKwargs(76, {x : 6})")
selenium.run_js("f.destroy()")
def test_pyproxy_name_clash(selenium):
selenium.run_js(
"""
let d = pyodide.runPython("{'a' : 2}");
let d_get = d.$get;
assert(() => d.get('a') === 2);
assert(() => d.$get('b', 3) === 3);
assert(() => d_get('b', 3) === 3);
d_get.destroy();
d.destroy();
let t = pyodide.runPython(`
class Test:
@ -781,7 +845,9 @@ def test_pyproxy_name_clash(selenium):
return 7
Test()
`);
assert(() => t.$destroy() === 7);
let t_dest = t.$destroy;
assert(() => t_dest() === 7);
t_dest.destroy();
t.destroy();
assertThrows(() => t.$destroy, "Error", "Object has already been destroyed");
"""

View File

@ -37,9 +37,13 @@ def test_import_js(selenium):
def test_globals_get_multiple(selenium):
"""See #1151"""
selenium.run("v = 0.123")
selenium.run_js("pyodide.globals.get('v')")
selenium.run_js("pyodide.globals.get('v')")
selenium.run_js(
"""
pyodide.runPython("v = 0.123");
pyodide.globals.get('v')
pyodide.globals.get('v')
"""
)
def test_open_url(selenium, httpserver):
@ -63,10 +67,10 @@ def test_load_package_after_convert_string(selenium):
"""
See #93.
"""
selenium.run("import sys\n" "x = sys.version")
selenium.run_js("let x = pyodide.globals.get('x');\n" "console.log(x);")
selenium.load_package("kiwisolver")
selenium.run("import kiwisolver")
selenium.run("import sys; x = sys.version")
selenium.run_js("let x = pyodide.runPython('x'); console.log(x);")
selenium.load_package("pytz")
selenium.run("import pytz")
def test_version_info(selenium):
@ -131,15 +135,18 @@ def test_runpythonasync_exception_after_import(selenium_standalone):
def test_py(selenium_standalone):
selenium_standalone.run(
selenium_standalone.run_js(
"""
def func():
return 42
pyodide.runPython(`
def func():
return 42
`);
let func = pyodide.globals.get('func');
assert(() => func() === 42);
func.destroy();
"""
)
assert selenium_standalone.run_js("return pyodide.globals.get('func')()") == 42
def test_eval_nothing(selenium):
assert selenium.run("# comment") is None

View File

@ -1,5 +1,6 @@
# See also test_pyproxy, test_jsproxy, and test_python.
import pytest
from pyodide_build.testing import run_in_pyodide
from hypothesis import given, settings, assume, strategies
from hypothesis.strategies import text, from_type
from conftest import selenium_context_manager
@ -213,10 +214,14 @@ def test_python2js(selenium):
assert selenium.run_js('return pyodide.runPython("\'碘化物\'") === "碘化物"')
assert selenium.run_js('return pyodide.runPython("\'🐍\'") === "🐍"')
assert selenium.run_js(
"let x = pyodide.runPython(\"b'bytes'\").toJs();\n"
"return (x instanceof window.Uint8Array) && "
"(x.length === 5) && "
"(x[0] === 98)"
"""
let xpy = pyodide.runPython("b'bytes'");
let x = xpy.toJs();
xpy.destroy();
return (x instanceof window.Uint8Array) &&
(x.length === 5) &&
(x[0] === 98)
"""
)
assert selenium.run_js(
"""
@ -234,13 +239,17 @@ def test_python2js(selenium):
let typename = proxy.type;
let x = proxy.toJs();
proxy.destroy();
return (typename === "dict") && (x.constructor.name === "Map") && (x.get(42) === 64)
return (typename === "dict") && (x.constructor.name === "Map") && (x.get(42) === 64);
"""
)
assert selenium.run_js(
"""
let x = pyodide.runPython("open('/foo.txt', 'wb')")
return (x.tell() === 0)
let x_tell = x.tell;
let result = x_tell();
x.destroy();
x_tell.destroy();
return result === 0;
"""
)
@ -392,6 +401,7 @@ def test_js2python(selenium):
assert selenium.run("bool(t.jsobject) == True")
assert selenium.run("bool(t.jsarray0) == False")
assert selenium.run("bool(t.jsarray1) == True")
selenium.run_js("test_objects.jspython.destroy()")
def test_js2python_bool(selenium):
@ -467,37 +477,43 @@ def test_array_buffer(selenium):
def assert_js_to_py_to_js(selenium, name):
selenium.run_js(f"window.obj = {name};")
selenium.run("from js import obj")
assert selenium.run_js("return pyodide.globals.get('obj') === obj;")
assert selenium.run_js(
"""
let pyobj = pyodide.globals.get("obj");
return pyobj === obj;
"""
)
def assert_py_to_js_to_py(selenium, name):
selenium.run_js(f"window.obj = pyodide.globals.get('{name}');")
assert selenium.run(
selenium.run_js(
f"""
from js import obj
obj is {name}
window.obj = pyodide.runPython('{name}');
pyodide.runPython(`
from js import obj
assert obj is {name}
`);
obj.destroy();
"""
)
def test_recursive_list_to_js(selenium_standalone):
selenium_standalone.run(
"""
x = []
x.append(x)
"""
)
selenium_standalone.run_js("x = pyodide.globals.get('x').toJs();")
@run_in_pyodide
def test_recursive_list_to_js():
x = []
x.append(x)
from pyodide import to_js
to_js(x)
def test_recursive_dict_to_js(selenium_standalone):
selenium_standalone.run(
"""
x = {}
x[0] = x
"""
)
selenium_standalone.run_js("x = pyodide.globals.get('x').toJs();")
@run_in_pyodide
def test_recursive_dict_to_js():
x = {}
x[0] = x
from pyodide import to_js
to_js(x)
def test_list_js2py2js(selenium):
@ -589,9 +605,9 @@ def test_javascript_error_back_to_js(selenium):
)
== "JsException"
)
assert selenium.run_js(
selenium.run_js(
"""
return pyodide.globals.get("py_err") === err;
assert(() => pyodide.globals.get("py_err") === err);
"""
)
@ -606,7 +622,7 @@ def test_memoryview_conversion(selenium):
)
selenium.run_js(
"""
pyodide.globals.get("a")
pyodide.runPython("a").destroy()
// Implicit assertion: this doesn't leave python error indicator set
// (automatically checked in conftest.py)
"""
@ -614,7 +630,7 @@ def test_memoryview_conversion(selenium):
selenium.run_js(
"""
pyodide.globals.get("b")
pyodide.runPython("b").destroy()
// Implicit assertion: this doesn't leave python error indicator set
// (automatically checked in conftest.py)
"""
@ -642,42 +658,54 @@ def test_python2js_with_depth(selenium):
for(let i = 0; i < 4; i++){
assert(() => proxies[i] == result_proxies[i]);
}
x.destroy();
for(let px of proxies){
px.destroy();
}
"""
)
def test_tojs(selenium):
def test_tojs1(selenium):
assert selenium.run_js(
"""
pyodide.runPython("a = [1, 2, 3]");
let res = pyodide.globals.get("a").toJs();
let respy = pyodide.runPython("[1, 2, 3]");
let res = respy.toJs();
respy.destroy();
return (Array.isArray(res)) && JSON.stringify(res) === "[1,2,3]";
"""
)
def test_tojs2(selenium):
assert selenium.run_js(
"""
pyodide.runPython("a = (1, 2, 3)");
let res = pyodide.globals.get("a").toJs();
let respy = pyodide.runPython("(1, 2, 3)");
let res = respy.toJs();
respy.destroy();
return (Array.isArray(res)) && JSON.stringify(res) === "[1,2,3]";
"""
)
def test_tojs3(selenium):
assert selenium.run_js(
"""
pyodide.runPython("a = [(1,2), (3,4), [5, 6], { 2 : 3, 4 : 9}]")
let res = pyodide.globals.get("a").toJs();
let respy = pyodide.runPython("[(1,2), (3,4), [5, 6], { 2 : 3, 4 : 9}]")
let res = respy.toJs();
respy.destroy();
return Array.isArray(res) && \
JSON.stringify(res) === `[[1,2],[3,4],[5,6],{}]` && \
JSON.stringify(Array.from(res[3].entries())) === "[[2,3],[4,9]]";
"""
)
def test_tojs4(selenium):
selenium.run_js(
"""
pyodide.runPython("a = [1,[2,[3,[4,[5,[6,[7]]]]]]]")
let a = pyodide.globals.get("a");
let a = pyodide.runPython("[1,[2,[3,[4,[5,[6,[7]]]]]]]")
for(let i=0; i < 7; i++){
let x = a.toJs(i);
for(let j=0; j < i; j++){
@ -685,14 +713,17 @@ def test_tojs(selenium):
x = x[1];
}
assert(() => pyodide.isPyProxy(x), `i: ${i}, j: ${i}`);
x.destroy();
}
a.destroy()
"""
)
def test_tojs5(selenium):
selenium.run_js(
"""
pyodide.runPython("a = [1, (2, (3, [4, (5, (6, [7]))]))]")
let a = pyodide.globals.get("a");
let a = pyodide.runPython("[1, (2, (3, [4, (5, (6, [7]))]))]")
for(let i=0; i < 7; i++){
let x = a.toJs(i);
for(let j=0; j < i; j++){
@ -700,20 +731,25 @@ def test_tojs(selenium):
x = x[1];
}
assert(() => pyodide.isPyProxy(x), `i: ${i}, j: ${i}`);
x.destroy();
}
a.destroy()
"""
)
def test_tojs6(selenium):
selenium.run_js(
"""
pyodide.runPython(`
let respy = pyodide.runPython(`
a = [1, 2, 3, 4, 5]
b = [a, a, a, a, a]
c = [b, b, b, b, b]
[b, b, b, b, b]
`);
let total_refs = pyodide._module.hiwire.num_keys();
let res = pyodide.globals.get("c").toJs();
let res = respy.toJs();
let new_total_refs = pyodide._module.hiwire.num_keys();
respy.destroy();
assert(() => total_refs === new_total_refs);
assert(() => res[0] === res[1]);
assert(() => res[0][0] === res[1][1]);
@ -721,17 +757,21 @@ def test_tojs(selenium):
"""
)
def test_tojs7(selenium):
selenium.run_js(
"""
pyodide.runPython(`
let respy = pyodide.runPython(`
a = [["b"]]
b = [1,2,3, a[0]]
a[0].append(b)
a.append(b)
a
`);
let total_refs = pyodide._module.hiwire.num_keys();
let res = pyodide.globals.get("a").toJs();
let res = respy.toJs();
let new_total_refs = pyodide._module.hiwire.num_keys();
respy.destroy();
assert(() => total_refs === new_total_refs);
assert(() => res[0][0] === "b");
assert(() => res[1][2] === 3);
@ -739,13 +779,18 @@ def test_tojs(selenium):
assert(() => res[0][1] === res[1]);
"""
)
@pytest.mark.skip_pyproxy_check
def test_tojs8(selenium):
msg = "pyodide.ConversionError"
with pytest.raises(selenium.JavascriptException, match=msg):
selenium.run_js(
"""
pyodide.runPython(`
{ (2,2) : 0 }
`).toJs()
from pyodide import to_js
to_js({ (2,2) : 0 })
`);
"""
)
@ -753,18 +798,22 @@ def test_tojs(selenium):
selenium.run_js(
"""
pyodide.runPython(`
{ (2,2) }
`).toJs()
from pyodide import to_js
to_js({ (2,2) })
`);
"""
)
def test_tojs9(selenium):
assert (
set(
selenium.run_js(
"""
return Array.from(pyodide.runPython(`
{ 1, "1" }
`).toJs().values())
from pyodide import to_js
to_js({ 1, "1" })
`).values())
"""
)
)
@ -776,8 +825,9 @@ def test_tojs(selenium):
selenium.run_js(
"""
return Array.from(pyodide.runPython(`
{ 1 : 7, "1" : 9 }
`).toJs().entries())
from pyodide import to_js
to_js({ 1 : 7, "1" : 9 })
`).entries())
"""
)
)

View File

@ -1,3 +1,6 @@
import pytest
def run_with_resolve(selenium, code):
selenium.run_js(
f"""
@ -25,6 +28,7 @@ def test_asyncio_sleep(selenium):
print('sleeping done')
resolve()
asyncio.ensure_future(sleep_task())
None
""",
)
@ -47,6 +51,7 @@ def test_return_result(selenium):
import asyncio
fut = asyncio.ensure_future(foo(998))
fut.add_done_callback(check_result)
""",
)
@ -85,6 +90,7 @@ def test_await_js_promise(selenium):
resolve()
import asyncio
asyncio.ensure_future(fetch_task())
None
""",
)
@ -101,6 +107,7 @@ def test_call_soon(selenium):
raise Exception("Expected arg == 'bar'...")
import asyncio
asyncio.get_event_loop().call_soon(foo, 'bar')
None
""",
)
@ -122,6 +129,7 @@ def test_contextvars(selenium):
raise Exception(f"Expected request_id.get() == '123', got {request_id.get()!r}")
import asyncio
asyncio.get_event_loop().call_soon(func_ctx, context=ctx)
None
""",
)
@ -141,10 +149,12 @@ def test_asyncio_exception(selenium):
resolve()
import asyncio
asyncio.ensure_future(capture_exception())
None
""",
)
@pytest.mark.skip_pyproxy_check
def test_run_in_executor(selenium):
# If run_in_executor tries to actually use ThreadPoolExecutor, it will throw
# an error since we can't start threads