diff --git a/.circleci/config.yml b/.circleci/config.yml index c2a587fb6..598380093 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -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: diff --git a/Makefile b/Makefile index aa9069584..9aa4010c0 100644 --- a/Makefile +++ b/Makefile @@ -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 diff --git a/conftest.py b/conftest.py index 665f34816..dd994af84 100644 --- a/conftest.py +++ b/conftest.py @@ -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 diff --git a/packages/matplotlib/test_matplotlib.py b/packages/matplotlib/test_matplotlib.py index f64e5be82..1debad884 100644 --- a/packages/matplotlib/test_matplotlib.py +++ b/packages/matplotlib/test_matplotlib.py @@ -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(" selenium.run_js( - """pyodide._module.pyodide_py.eval_code("{}()", pyodide._module.globals)""".format( + """pyodide.runPython("{}()", pyodide._module.globals)""".format( f.__name__ ) ) diff --git a/setup.cfg b/setup.cfg index 60f44ea16..9eaff9851 100644 --- a/setup.cfg +++ b/setup.cfg @@ -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 diff --git a/src/js/api.js b/src/js/api.js index d4198ddd2..f6ca822eb 100644 --- a/src/js/api.js +++ b/src/js/api.js @@ -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(); + } } /** diff --git a/src/tests/test_asyncio.py b/src/tests/test_asyncio.py index bf5c4c92e..5e9dc5d08 100644 --- a/src/tests/test_asyncio.py +++ b/src/tests/test_asyncio.py @@ -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); """ diff --git a/src/tests/test_console.py b/src/tests/test_console.py index dcb2d8d83..c19d58b07 100644 --- a/src/tests/test_console.py +++ b/src/tests/test_console.py @@ -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" diff --git a/src/tests/test_jsproxy.py b/src/tests/test_jsproxy.py index 38080266c..7c679c2fc 100644 --- a/src/tests/test_jsproxy.py +++ b/src/tests/test_jsproxy.py @@ -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 `); """ ) diff --git a/src/tests/test_pyodide.py b/src/tests/test_pyodide.py index 97e4e74f5..64214e5b7 100644 --- a/src/tests/test_pyodide.py +++ b/src/tests/test_pyodide.py @@ -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( """ diff --git a/src/tests/test_pyproxy.py b/src/tests/test_pyproxy.py index 67e7ae94c..887febc35 100644 --- a/src/tests/test_pyproxy.py +++ b/src/tests/test_pyproxy.py @@ -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(" 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( - " 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"); """ diff --git a/src/tests/test_python.py b/src/tests/test_python.py index e8afa4f6f..f38a8a42d 100644 --- a/src/tests/test_python.py +++ b/src/tests/test_python.py @@ -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 diff --git a/src/tests/test_typeconversions.py b/src/tests/test_typeconversions.py index dae509155..101d1cfbd 100644 --- a/src/tests/test_typeconversions.py +++ b/src/tests/test_typeconversions.py @@ -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()) """ ) ) diff --git a/src/tests/test_webloop.py b/src/tests/test_webloop.py index dfaf9f334..3537c9114 100644 --- a/src/tests/test_webloop.py +++ b/src/tests/test_webloop.py @@ -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