diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index a0bbfd5b..a64abba5 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -2,7 +2,7 @@ default_language_version: python: "3.10" repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: "v4.4.0" + rev: "v6.0.0" hooks: - id: check-added-large-files - id: check-case-conflict @@ -15,19 +15,19 @@ repos: - id: mixed-line-ending - id: trailing-whitespace - - repo: https://github.com/charliermarsh/ruff-pre-commit - rev: "v0.9.7" + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: "v0.15.20" hooks: - id: ruff args: [--fix] - - repo: https://github.com/psf/black - rev: "23.1.0" + - repo: https://github.com/psf/black-pre-commit-mirror + rev: "26.5.1" hooks: - id: black - repo: https://github.com/pre-commit/mirrors-mypy - rev: "v1.15.0" + rev: "v2.1.0" hooks: - id: mypy exclude: (setup.py|^tests|conftest.py) @@ -41,12 +41,12 @@ repos: - pytest - repo: https://github.com/shellcheck-py/shellcheck-py - rev: "v0.9.0.2" + rev: "v0.11.0.1" hooks: - id: shellcheck - repo: https://github.com/codespell-project/codespell - rev: "v2.2.4" + rev: "v2.4.2" hooks: - id: codespell args: ["-L", "te,slowy,aray,ba,nd,classs,crate,feld,lits"] diff --git a/examples/test_install_package.py b/examples/test_install_package.py index 388c93a7..39dc19c6 100644 --- a/examples/test_install_package.py +++ b/examples/test_install_package.py @@ -10,15 +10,11 @@ def test_install_from_custom_server(selenium_standalone): url = base_url + "snowballstemmer-2.2.0-py2.py3-none-any.whl" selenium = selenium_standalone - selenium.run_js( - f""" + selenium.run_js(f""" await pyodide.loadPackage({url!r}); - """ - ) - selenium.run( - """ + """) + selenium.run(""" import snowballstemmer stemmer = snowballstemmer.stemmer('english') assert stemmer.stemWords(["university"]) == ["univers"] - """ - ) + """) diff --git a/pyproject.toml b/pyproject.toml index 42e4f9c9..04a570f1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -93,9 +93,12 @@ lint.select = [ "RET505", "RET506", ] -lint.ignore = ["E402", "E501", "E731", "E741"] -# line-length = 219 # E501: Recommended goal is 88 to match black -target-version = "py312" +lint.ignore = [ + "E402", # import not at top of file + "E501", # line too long + "PLC0415", # import outside top level +] +target-version = "py311" [tool.ruff.lint.isort] diff --git a/pytest_pyodide/config.py b/pytest_pyodide/config.py index 0ed7c6b8..b08e0a4b 100644 --- a/pytest_pyodide/config.py +++ b/pytest_pyodide/config.py @@ -52,8 +52,8 @@ def set_initialize_script(self, initialize_script: str): def get_initialize_script(self) -> str: return self.initialize_script - def add_node_extra_globals(self, l: Iterable[str]): - self.node_extra_globals.extend(l) + def add_node_extra_globals(self, extra_globals: Iterable[str]): + self.node_extra_globals.extend(extra_globals) def get_node_extra_globals(self) -> Sequence[str]: return self.node_extra_globals diff --git a/pytest_pyodide/copy_files_to_pyodide.py b/pytest_pyodide/copy_files_to_pyodide.py index 72ae2744..6ae48df1 100644 --- a/pytest_pyodide/copy_files_to_pyodide.py +++ b/pytest_pyodide/copy_files_to_pyodide.py @@ -92,8 +92,7 @@ def copy_files_to_emscripten_fs( # fetch all files into the pyodide # n.b. this might be slow for big packages - selenium.run( - """ + selenium.run(""" import os from pathlib import Path from pyodide.http import pyfetch @@ -107,37 +106,28 @@ async def _fetch_file(src,dest): byte_data = await response.bytes() fp.write(byte_data) - """ - ) + """) for file, dest in new_files: _copied_files[selenium].append((file, dest)) file_url = base_url + str(file.relative_to(base_path).as_posix()) if file.suffix == ".whl" and install_wheels: # wheel - install the wheel on the pyodide side before # any fetches (and don't copy it) - selenium.run_async( - f""" + selenium.run_async(f""" all_wheels.append("{file_url}") - """ - ) + """) else: # add file to fetches - selenium.run_async( - f""" + selenium.run_async(f""" all_fetches.append(_fetch_file("{file_url}",Path("{dest}"))) - """ - ) + """) # install all wheels with micropip - selenium.run_async( - """ + selenium.run_async(""" import micropip await micropip.install(all_wheels) - """ - ) + """) # fetch everything all at once - selenium.run_async( - """ + selenium.run_async(""" import asyncio, os, os.path await asyncio.gather(*all_fetches) - """ - ) + """) diff --git a/pytest_pyodide/decorator.py b/pytest_pyodide/decorator.py index 9c66ccd4..03d29771 100644 --- a/pytest_pyodide/decorator.py +++ b/pytest_pyodide/decorator.py @@ -27,22 +27,17 @@ class SeleniumType(Protocol): JavascriptException: type browser: str - def load_package(self, pkgs: str | list[str]): - ... + def load_package(self, pkgs: str | list[str]): ... - def run_async(self, code: str): - ... + def run_async(self, code: str): ... - def run_js(self, code: str): - ... + def run_js(self, code: str): ... class _ReadableFileobj(Protocol): - def read(self, __n: int) -> bytes: - ... + def read(self, __n: int) -> bytes: ... - def readline(self) -> bytes: - ... + def readline(self) -> bytes: ... class Unpickler(pickle.Unpickler): @@ -86,11 +81,9 @@ def __del__(self): return ptr = self.ptr self.ptr = None - self.selenium.run_js( - f""" + self.selenium.run_js(f""" pyodide._module._Py_DecRef({ptr}); - """ - ) + """) def _encode(obj: Any) -> str: @@ -246,12 +239,10 @@ def (, arg1, arg2, arg3): # Make onwards call with two args: # 1. # 2. all other arguments in a tuple - func_body = ast.parse( - """\ + func_body = ast.parse("""\ __tracebackhide__ = True; \ return run(selenium_arg_name, (arg1, arg2, ...)) \ - """.strip() - ).body + """.strip()).body onwards_call = func_body[1].value # type: ignore[attr-defined] onwards_call.func = ast.Name(id=run_id, ctx=ast.Load()) onwards_call.args[0].id = selenium_arg_name # Set variable name @@ -293,8 +284,7 @@ def initialize_decorator(selenium): _decorator_in_pyodide = ( Path(__file__).parent / "_decorator_in_pyodide.py" ).read_text() - selenium.run( - f""" + selenium.run(f""" def temp(): _decorator_in_pyodide = '''{_decorator_in_pyodide}''' from importlib.machinery import ModuleSpec @@ -315,8 +305,7 @@ def temp(): sys.modules[modname] = mod temp() del temp - """ - ) + """) def _locate_funcdef( @@ -547,24 +536,20 @@ def __init__( def _get_code_prelude(self): """Start coverage with the coverage_args passed from the host""" - return dedent( - f""" + return dedent(f""" from pytest_pyodide.decorator import start_coverage coverage = start_coverage({_encode(self._coverage_args)!r}) - """ - ) + """) def _get_code_epilogue(self): """Stop coverage and append the data to the result""" - return dedent( - """ + return dedent(""" from pytest_pyodide.decorator import end_coverage coverage_outdata = end_coverage(coverage) result = (*result, coverage_outdata) - """ - ) + """) - def _process_extra(self, coverage_out_binary): # type:ignore[override] + def _process_extra(self, coverage_out_binary): # type: ignore[override] """Write coverage data to the file system""" _get_coverage_path().write_bytes(b64decode(coverage_out_binary)) diff --git a/pytest_pyodide/doctest.py b/pytest_pyodide/doctest.py index 95a024a9..faf0b428 100644 --- a/pytest_pyodide/doctest.py +++ b/pytest_pyodide/doctest.py @@ -69,7 +69,7 @@ def collect(self): """Call super and then if test includes the RUN_IN_PYODIDE option on the first line, make one copy for each Pyodide runtime environment """ - for item in super().collect(): # type:ignore[misc] + for item in super().collect(): # type: ignore[misc] pyodide_test = RUN_IN_PYODIDE in item.dtest.examples[0].options item.dtest.pyodide_test = pyodide_test if not pyodide_test: @@ -140,7 +140,7 @@ def run_doctest_in_pyodide_outer( # So we just take the DocTestRunner apart and put it back together inside # Pyodide. optionflags = self.optionflags - continue_on_failure = self.continue_on_failure # type:ignore[attr-defined] + continue_on_failure = self.continue_on_failure # type: ignore[attr-defined] return run_doctest_in_pyodide_inner( selenium, optionflags, continue_on_failure, test, compileflags, out, clear_globs diff --git a/pytest_pyodide/fixture.py b/pytest_pyodide/fixture.py index a74c9687..c571b7ad 100644 --- a/pytest_pyodide/fixture.py +++ b/pytest_pyodide/fixture.py @@ -161,7 +161,7 @@ def wrapper(*args, **kwargs): kwargs[orig_name] = kwargs.pop(new_name) return f(*args, **kwargs) - wrapper.__signature__ = new_sig # type:ignore[attr-defined] + wrapper.__signature__ = new_sig # type: ignore[attr-defined] return wrapper return use_variant @@ -295,10 +295,11 @@ def selenium_context_manager(selenium_module_scope): @pytest.fixture def selenium(request, selenium_module_scope): - with selenium_context_manager( - selenium_module_scope - ) as selenium, set_webdriver_script_timeout( - selenium, script_timeout=parse_driver_timeout(request.node) + with ( + selenium_context_manager(selenium_module_scope) as selenium, + set_webdriver_script_timeout( + selenium, script_timeout=parse_driver_timeout(request.node) + ), ): yield selenium @@ -337,10 +338,11 @@ def selenium_worker_context_manager(selenium_worker_module_scope): @pytest.fixture def selenium_worker(request, selenium_worker_module_scope): - with selenium_worker_context_manager( - selenium_worker_module_scope - ) as selenium, set_webdriver_script_timeout( - selenium, script_timeout=parse_driver_timeout(request.node) + with ( + selenium_worker_context_manager(selenium_worker_module_scope) as selenium, + set_webdriver_script_timeout( + selenium, script_timeout=parse_driver_timeout(request.node) + ), ): yield selenium @@ -368,15 +370,18 @@ def selenium_jspi_inner( pytest.skip(f"jspi not supported in {runtime}") if request.config.option.runner.lower() == "playwright": pytest.skip("jspi not supported with playwright") - with selenium_common( - request, - runtime, - web_server_main, - browsers=playwright_browsers, - jspi=True, - worker=worker, - ) as selenium, set_webdriver_script_timeout( - selenium, script_timeout=parse_driver_timeout(request.node) + with ( + selenium_common( + request, + runtime, + web_server_main, + browsers=playwright_browsers, + jspi=True, + worker=worker, + ) as selenium, + set_webdriver_script_timeout( + selenium, script_timeout=parse_driver_timeout(request.node) + ), ): yield selenium diff --git a/pytest_pyodide/run_tests_inside_pyodide.py b/pytest_pyodide/run_tests_inside_pyodide.py index d3c79c8c..6523eeb7 100644 --- a/pytest_pyodide/run_tests_inside_pyodide.py +++ b/pytest_pyodide/run_tests_inside_pyodide.py @@ -127,8 +127,7 @@ def run_test_in_pyodide(node_tree_id, selenium, ignore_fail=False): "-o", "junit_logging=out-err", ] - ret_xml = selenium.run_async( - f""" + ret_xml = selenium.run_async(f""" import pytest retcode = pytest.main({all_args}) @@ -136,8 +135,7 @@ def run_test_in_pyodide(node_tree_id, selenium, ignore_fail=False): with open("test_output.xml","r") as f: output_xml=f.read() output_xml - """ - ) + """) # get the error from junitxml root = ET.fromstring(ret_xml) fails = root.findall("*/testcase[failure]") diff --git a/pytest_pyodide/runner.py b/pytest_pyodide/runner.py index e2ad81a2..bc48f971 100644 --- a/pytest_pyodide/runner.py +++ b/pytest_pyodide/runner.py @@ -177,8 +177,7 @@ def load_pyodide(self): ) def initialize_pyodide(self): - self.run_js( - """ + self.run_js(""" let isPyProxy; if(pyodide.ffi) { isPyProxy = (o) => o instanceof pyodide.ffi.PyProxy; @@ -196,8 +195,7 @@ def initialize_pyodide(self): result.destroy(); return converted_result; } - """ - ) + """) self.run_js(self._config.get_initialize_script()) from .decorator import initialize_decorator @@ -218,21 +216,17 @@ def clean_logs(self): self.run_js("self.logs = []", pyodide_checks=False) def run(self, code): - return self.run_js( - f""" + return self.run_js(f""" let result = pyodide.runPython({code!r}); return pyodide.$handleTestResult(result); - """ - ) + """) def run_async(self, code): - return self.run_js( - f""" + return self.run_js(f""" await pyodide.loadPackagesFromImports({code!r}) let result = await pyodide.runPythonAsync({code!r}); return pyodide.$handleTestResult(result); - """ - ) + """) def run_js(self, code, pyodide_checks=True): """Run JavaScript code and check for pyodide errors""" @@ -271,13 +265,11 @@ def save_state(self): self.run_js("self.__savedState = pyodide._api.saveState();") def restore_state(self): - self.run_js( - """ + self.run_js(""" if(self.__savedState){ pyodide._api.restoreState(self.__savedState) } - """ - ) + """) def get_num_proxies(self): return self.run_js("return pyodide._module.pyproxy_alloc_map.size") @@ -332,8 +324,7 @@ def load_package(self, packages): # single ``RuntimeError`` raised at the call site so load failures # are always reported immediately and with the problematic package # reference in the message. - result = self.run_js( - f""" + result = self.run_js(f""" const __errors = []; try {{ await pyodide.loadPackage({packages!r}, {{ @@ -343,8 +334,7 @@ def load_package(self, packages): __errors.push(e.message || String(e)); }} return __errors; - """ - ) + """) if result: raise RuntimeError( "pyodide.loadPackage({!r}) reported errors:\n {}".format( diff --git a/pytest_pyodide/server.py b/pytest_pyodide/server.py index f92adca9..6a85939f 100644 --- a/pytest_pyodide/server.py +++ b/pytest_pyodide/server.py @@ -126,7 +126,7 @@ def run_web_server(q, log_filepath, dist_dir, extra_headers, handler_cls): handler_cls = functools.partial(DefaultHandler, extra_headers=extra_headers) with socketserver.TCPServer(("", 0), handler_cls) as httpd: - host, port = httpd.server_address + host, port = httpd.server_address # type: ignore[misc] print(f"Starting webserver at http://{host}:{port}") # type: ignore[str-bytes-safe] httpd.server_name = "test-server" # type: ignore[attr-defined] httpd.server_port = port # type: ignore[attr-defined] diff --git a/tests/test_copy_files.py b/tests/test_copy_files.py index 817cf379..96f1fc49 100644 --- a/tests/test_copy_files.py +++ b/tests/test_copy_files.py @@ -11,8 +11,7 @@ def test_non_recursive_decorator(selenium): should_exist = f"non_recursive_test/{Path(__file__).name}" should_not_exist = "non_recursive_test/datafiles/in_pyodide_tests.py" - selenium.run( - f""" + selenium.run(f""" with open("{should_exist}") as f: txt = f.read() assert(txt.find("test_non_recursive_decorator")!=-1) @@ -21,8 +20,7 @@ def test_non_recursive_decorator(selenium): assert("File {should_not_exist} was copied") except IOError: pass - """ - ) + """) @copy_files_to_pyodide( @@ -31,16 +29,14 @@ def test_non_recursive_decorator(selenium): def test_recursive_decorator(selenium): should_exist = f"recursive_test/{Path(__file__).name}" should_also_exist = "recursive_test/datafiles/in_pyodide_tests.py" - selenium.run( - f""" + selenium.run(f""" with open("{should_exist}") as f: assert(txt.find("test_non_recursive_decorator")!=-1) try: open("{should_also_exist}","r") except IOError: assert("File {should_also_exist} was not copied") - """ - ) + """) # possible test cases = list of a)tuple, b)path, c) folder name, d) glob pattern @@ -54,8 +50,7 @@ def test_recursive_decorator(selenium): ) def test_copy_files_decorator(selenium): auto_target_path = Path(__file__).relative_to(Path.cwd()) - selenium.run( - f""" + selenium.run(f""" with open("test.py") as f: txt = f.read() assert(txt.find("test_copy_files_decorator test 1")!=-1) @@ -71,8 +66,7 @@ def test_copy_files_decorator(selenium): with open("python_only/test_copy_files.py") as f: txt = f.read() assert(txt.find("test_copy_files_decorator test 4")!=-1) - """ - ) + """) @copy_files_to_pyodide([(__file__, "test2.py")]) diff --git a/tests/test_doctest.py b/tests/test_doctest.py index 923fc43e..d0c2d1d0 100644 --- a/tests/test_doctest.py +++ b/tests/test_doctest.py @@ -85,16 +85,14 @@ def pytest_fixture_setup(self, fixturedef, request): result.getfailures()[0] captured = capsys.readouterr() # The indentation is different here in Python 3.12 vs Python 3.13 vs Python 3.14... - expected = dedent( - """ + expected = dedent(""" 003 >>> from js import Object # doctest: +RUN_IN_PYODIDE 004 >>> 1 == 2 Expected: True Got: False - """ - ).strip() + """).strip() def normalize(s): return re.sub(r" +>", " >", s) diff --git a/tests/test_fixture.py b/tests/test_fixture.py index c0dca907..02007400 100644 --- a/tests/test_fixture.py +++ b/tests/test_fixture.py @@ -70,9 +70,7 @@ def test_rename_fixture2(myfixture): def test_has_standalone_fixture(pytester): from textwrap import dedent - pytester.makepyfile( - dedent( - """ + pytester.makepyfile(dedent(""" from pytest_pyodide.fixture import rename_fixture @rename_fixture("selenium", "selenium_standalone") @@ -82,9 +80,7 @@ def test_example1(selenium): @rename_fixture("selenium_standalone", "selenium_standalone1") def test_example2(selenium_standalone): pass - """ - ) - ) + """)) node = pytester.getpathnode("test_has_standalone_fixture.py") r = node.collect() t1, t2 = r diff --git a/tests/test_jsassert.py b/tests/test_jsassert.py index 6d7a94b6..3b49cf38 100644 --- a/tests/test_jsassert.py +++ b/tests/test_jsassert.py @@ -1,6 +1,5 @@ def test_assert(selenium): - selenium.run_js( - r""" + selenium.run_js(r""" let shouldPass; shouldPass = true; assert(() => shouldPass, "blah"); @@ -17,13 +16,11 @@ def test_assert(selenium): if(!threw){ throw new Error("Didn't throw!"); } - """ - ) + """) def test_assert_throws(selenium): - selenium.run_js( - r""" + selenium.run_js(r""" let shouldPass; let threw; assertThrows(() => { throw new TypeError("aaabbbccc") }, "TypeError", "bbc"); @@ -62,5 +59,4 @@ def test_assert_throws(selenium): assert(() => e.message.endsWith(`expected error message to match pattern /a..d/ got:\nblah`)); } assert(() => threw); - """ - ) + """) diff --git a/tests/test_options.py b/tests/test_options.py index 930e5c41..c82df758 100644 --- a/tests/test_options.py +++ b/tests/test_options.py @@ -9,13 +9,11 @@ def test_dist_dir(pytester): dist_dir = str(Path("dist").resolve()) - pytester.makepyfile( - f""" + pytester.makepyfile(f""" import pytest def test_option(request): assert str(request.config.getoption("--dist-dir", "")) == {dist_dir!r} - """ - ) + """) result = pytester.runpytest("--dist-dir", "dist") result.assert_outcomes(passed=1) @@ -23,13 +21,11 @@ def test_option(request): @pytest.mark.parametrize("runner", ["selenium", "playwright"]) def test_runner(pytester, runner): - pytester.makepyfile( - f""" + pytester.makepyfile(f""" import pytest def test_option(request): assert request.config.getoption("--runner") == {runner!r} - """ - ) + """) result = pytester.runpytest("--runner", runner) result.assert_outcomes(passed=1) @@ -38,13 +34,11 @@ def test_option(request): def test_invalid_runner(pytester): runner = "blah" - pytester.makepyfile( - f""" + pytester.makepyfile(f""" import pytest def test_option(request): assert request.config.getoption("--runner") == {runner!r} - """ - ) + """) result = pytester.runpytest("--runner", runner) @@ -61,13 +55,11 @@ def test_option(request): def test_invalid_runtime(pytester, _runtime): _runtime.split(",") - pytester.makepyfile( - """ + pytester.makepyfile(""" import pytest def test_option(): assert True - """ - ) + """) # TODO: catch internal errors directly? with pytest.raises(ValueError, match="Pytest terminal summary report not found"): @@ -92,9 +84,7 @@ def test_filter_runtimes(_runtime, expected): def test_options_pytester(pytester): - pytester.makepyfile( - dedent( - """ + pytester.makepyfile(dedent(""" import pytest from pathlib import Path @@ -102,9 +92,7 @@ def test_options_pytester(): assert pytest.pyodide_run_host_test == True assert pytest.pyodide_runtimes == set(["chrome","firefox","safari","node"]) assert pytest.pyodide_dist_dir == Path("some_weird_dir").resolve() - """ - ) - ) + """)) run_host = pytest.pyodide_run_host_test runtimes = pytest.pyodide_runtimes dist_dir = pytest.pyodide_dist_dir diff --git a/tests/test_run_in_pyodide_coverage.py b/tests/test_run_in_pyodide_coverage.py index 5ed9da9a..448b4d6e 100644 --- a/tests/test_run_in_pyodide_coverage.py +++ b/tests/test_run_in_pyodide_coverage.py @@ -105,7 +105,7 @@ def exercise_factorial(selenium): check=True, ) assert result.returncode == 0 - assert result.stdout == "Wrote JSON report to coverage.json\n" + assert (tmp_path / "coverage.json").exists() coverage_json = json.loads((tmp_path / "coverage.json").read_text()) file = coverage_json["files"][str(DUMMY_PKG_FILE.absolute())] diff --git a/tests/test_selenium_worker.py b/tests/test_selenium_worker.py index 9d783840..a4f45742 100644 --- a/tests/test_selenium_worker.py +++ b/tests/test_selenium_worker.py @@ -90,12 +90,10 @@ def test_selenium_worker_run_python(selenium_worker): def test_selenium_worker_run_python_async(selenium_worker): - selenium_worker.run_async( - """ + selenium_worker.run_async(""" import asyncio await asyncio.sleep(0) - """ - ) + """) def test_selenium_worker_js_exception(selenium_worker): diff --git a/tests/test_webworker.py b/tests/test_webworker.py index 30aad469..37f78208 100644 --- a/tests/test_webworker.py +++ b/tests/test_webworker.py @@ -1,9 +1,7 @@ def test_run_webworker(selenium_webworker_standalone): selenium = selenium_webworker_standalone - output = selenium.run_webworker( - """ + output = selenium.run_webworker(""" import sys sys.version - """ - ) + """) assert isinstance(output, str)