2018-03-30 14:51:13 +00:00
|
|
|
"""
|
|
|
|
Various common utilities for testing.
|
|
|
|
"""
|
|
|
|
import pathlib
|
2022-02-21 22:27:03 +00:00
|
|
|
import sys
|
2021-03-22 08:16:16 +00:00
|
|
|
|
2022-02-21 22:27:03 +00:00
|
|
|
import pytest
|
2022-01-24 01:47:04 +00:00
|
|
|
|
2022-05-22 04:11:30 +00:00
|
|
|
pytest_plugins = ("pytest_asyncio",)
|
|
|
|
|
2019-06-19 18:26:08 +00:00
|
|
|
ROOT_PATH = pathlib.Path(__file__).parents[0].resolve()
|
2022-04-11 23:01:40 +00:00
|
|
|
DIST_PATH = ROOT_PATH / "dist"
|
2018-10-02 08:39:25 +00:00
|
|
|
|
2022-05-08 07:52:08 +00:00
|
|
|
sys.path.append(str(ROOT_PATH / "pyodide-test-runner"))
|
2021-05-03 18:51:11 +00:00
|
|
|
sys.path.append(str(ROOT_PATH / "pyodide-build"))
|
2021-09-21 06:46:44 +00:00
|
|
|
sys.path.append(str(ROOT_PATH / "src" / "py"))
|
2018-10-04 11:24:57 +00:00
|
|
|
|
2022-05-08 07:52:08 +00:00
|
|
|
from pyodide_test_runner.fixture import ( # noqa: F401
|
|
|
|
script_type,
|
|
|
|
selenium,
|
|
|
|
selenium_common,
|
|
|
|
selenium_context_manager,
|
|
|
|
selenium_esm,
|
|
|
|
selenium_module_scope,
|
|
|
|
selenium_standalone,
|
|
|
|
selenium_standalone_noload,
|
|
|
|
selenium_standalone_noload_common,
|
|
|
|
selenium_webworker_standalone,
|
|
|
|
web_server_main,
|
|
|
|
web_server_secondary,
|
|
|
|
)
|
|
|
|
from pyodide_test_runner.utils import maybe_skip_test
|
|
|
|
from pyodide_test_runner.utils import package_is_built as _package_is_built
|
2018-10-04 11:24:57 +00:00
|
|
|
|
|
|
|
|
2021-03-22 08:16:16 +00:00
|
|
|
def pytest_addoption(parser):
|
|
|
|
group = parser.getgroup("general")
|
|
|
|
group.addoption(
|
2022-04-11 23:01:40 +00:00
|
|
|
"--dist-dir",
|
2021-03-22 08:16:16 +00:00
|
|
|
action="store",
|
2022-04-11 23:01:40 +00:00
|
|
|
default=DIST_PATH,
|
|
|
|
help="Path to the dist directory",
|
2021-03-22 08:16:16 +00:00
|
|
|
)
|
|
|
|
group.addoption(
|
|
|
|
"--run-xfail",
|
|
|
|
action="store_true",
|
|
|
|
help="If provided, tests marked as xfail will be run",
|
|
|
|
)
|
|
|
|
|
2020-06-28 18:24:40 +00:00
|
|
|
|
2021-03-22 08:16:16 +00:00
|
|
|
def pytest_configure(config):
|
2021-04-19 11:39:22 +00:00
|
|
|
"""Monkey patch the function cwd_relative_nodeid
|
|
|
|
|
|
|
|
returns the description of a test for the short summary table. Monkey patch
|
|
|
|
it to reduce the verbosity of the test names in the table. This leaves
|
|
|
|
enough room to see the information about the test failure in the summary.
|
2021-03-22 08:16:16 +00:00
|
|
|
"""
|
2022-05-08 07:52:08 +00:00
|
|
|
global CONFIG
|
|
|
|
|
2021-03-22 08:16:16 +00:00
|
|
|
old_cwd_relative_nodeid = config.cwd_relative_nodeid
|
2018-03-30 14:51:13 +00:00
|
|
|
|
2021-03-22 08:16:16 +00:00
|
|
|
def cwd_relative_nodeid(*args):
|
|
|
|
result = old_cwd_relative_nodeid(*args)
|
|
|
|
result = result.replace("src/tests/", "")
|
|
|
|
result = result.replace("packages/", "")
|
|
|
|
result = result.replace("::test_", "::")
|
|
|
|
return result
|
|
|
|
|
|
|
|
config.cwd_relative_nodeid = cwd_relative_nodeid
|
2018-03-30 14:51:13 +00:00
|
|
|
|
2022-05-08 07:52:08 +00:00
|
|
|
pytest.pyodide_dist_dir = config.getoption("--dist-dir")
|
|
|
|
|
2018-03-30 14:51:13 +00:00
|
|
|
|
2021-11-21 17:26:33 +00:00
|
|
|
def pytest_collection_modifyitems(config, items):
|
|
|
|
"""Called after collect is completed.
|
|
|
|
Parameters
|
|
|
|
----------
|
|
|
|
config : pytest config
|
|
|
|
items : list of collected items
|
|
|
|
"""
|
|
|
|
for item in items:
|
2022-05-08 07:52:08 +00:00
|
|
|
maybe_skip_test(item, config.getoption("--dist-dir"), delayed=True)
|
2021-07-20 08:48:27 +00:00
|
|
|
|
2018-07-09 19:09:49 +00:00
|
|
|
|
2021-03-24 23:32:26 +00:00
|
|
|
@pytest.hookimpl(hookwrapper=True)
|
|
|
|
def pytest_runtest_call(item):
|
|
|
|
"""We want to run extra verification at the start and end of each test to
|
|
|
|
check that we haven't leaked memory. According to pytest issue #5044, it's
|
|
|
|
not possible to "Fail" a test from a fixture (no matter what you do, pytest
|
|
|
|
sets the test status to "Error"). The approach suggested there is hook
|
|
|
|
pytest_runtest_call as we do here. To get access to the selenium fixture, we
|
2022-03-08 05:51:20 +00:00
|
|
|
imitate the definition of pytest_pyfunc_call:
|
2021-03-24 23:32:26 +00:00
|
|
|
https://github.com/pytest-dev/pytest/blob/6.2.2/src/_pytest/python.py#L177
|
|
|
|
|
|
|
|
Pytest issue #5044:
|
|
|
|
https://github.com/pytest-dev/pytest/issues/5044
|
|
|
|
"""
|
2022-05-08 07:52:08 +00:00
|
|
|
browser = None
|
2021-08-01 14:12:14 +00:00
|
|
|
for fixture in item._fixtureinfo.argnames:
|
|
|
|
if fixture.startswith("selenium"):
|
2022-05-08 07:52:08 +00:00
|
|
|
browser = item.funcargs[fixture]
|
2021-08-01 14:12:14 +00:00
|
|
|
break
|
2022-05-08 07:52:08 +00:00
|
|
|
if browser and browser.pyodide_loaded:
|
2021-06-07 07:23:47 +00:00
|
|
|
trace_pyproxies = pytest.mark.skip_pyproxy_check.mark not in item.own_markers
|
2021-07-10 22:05:29 +00:00
|
|
|
trace_hiwire_refs = (
|
|
|
|
trace_pyproxies
|
|
|
|
and pytest.mark.skip_refcount_check.mark not in item.own_markers
|
|
|
|
)
|
2021-09-20 21:51:31 +00:00
|
|
|
yield from extra_checks_test_wrapper(
|
2022-05-08 07:52:08 +00:00
|
|
|
browser, trace_hiwire_refs, trace_pyproxies
|
2021-05-28 21:19:32 +00:00
|
|
|
)
|
2021-03-24 23:32:26 +00:00
|
|
|
else:
|
|
|
|
yield
|
|
|
|
|
|
|
|
|
2022-05-08 07:52:08 +00:00
|
|
|
def extra_checks_test_wrapper(browser, trace_hiwire_refs, trace_pyproxies):
|
2021-09-20 21:51:31 +00:00
|
|
|
"""Extra conditions for test to pass:
|
|
|
|
1. No explicit request for test to fail
|
|
|
|
2. No leaked JsRefs
|
|
|
|
3. No leaked PyProxys
|
|
|
|
"""
|
2022-05-08 07:52:08 +00:00
|
|
|
browser.clear_force_test_fail()
|
|
|
|
init_num_keys = browser.get_num_hiwire_keys()
|
2021-05-28 21:19:32 +00:00
|
|
|
if trace_pyproxies:
|
2022-05-08 07:52:08 +00:00
|
|
|
browser.enable_pyproxy_tracing()
|
|
|
|
init_num_proxies = browser.get_num_proxies()
|
2021-03-24 23:32:26 +00:00
|
|
|
a = yield
|
2021-07-20 08:48:27 +00:00
|
|
|
try:
|
|
|
|
# If these guys cause a crash because the test really screwed things up,
|
|
|
|
# we override the error message with the better message returned by
|
|
|
|
# a.result() in the finally block.
|
2022-05-08 07:52:08 +00:00
|
|
|
browser.disable_pyproxy_tracing()
|
|
|
|
browser.restore_state()
|
2021-07-20 08:48:27 +00:00
|
|
|
finally:
|
|
|
|
# if there was an error in the body of the test, flush it out by calling
|
|
|
|
# get_result (we don't want to override the error message by raising a
|
|
|
|
# different error here.)
|
|
|
|
a.get_result()
|
2022-05-08 07:52:08 +00:00
|
|
|
if browser.force_test_fail:
|
2021-09-20 21:51:31 +00:00
|
|
|
raise Exception("Test failure explicitly requested but no error was raised.")
|
2021-07-10 22:05:29 +00:00
|
|
|
if trace_pyproxies and trace_hiwire_refs:
|
2022-05-08 07:52:08 +00:00
|
|
|
delta_proxies = browser.get_num_proxies() - init_num_proxies
|
|
|
|
delta_keys = browser.get_num_hiwire_keys() - init_num_keys
|
2021-07-10 22:05:29 +00:00
|
|
|
assert (delta_proxies, delta_keys) == (0, 0) or delta_keys < 0
|
2021-06-07 07:23:47 +00:00
|
|
|
if trace_hiwire_refs:
|
2022-05-08 07:52:08 +00:00
|
|
|
delta_keys = browser.get_num_hiwire_keys() - init_num_keys
|
2021-07-10 22:05:29 +00:00
|
|
|
assert delta_keys <= 0
|
2021-03-24 23:32:26 +00:00
|
|
|
|
|
|
|
|
2022-05-08 07:52:08 +00:00
|
|
|
def package_is_built(package_name):
|
|
|
|
return _package_is_built(package_name, pytest.pyodide_dist_dir)
|
2022-05-09 01:43:26 +00:00
|
|
|
|
|
|
|
|
|
|
|
import ast
|
|
|
|
from copy import deepcopy
|
|
|
|
from typing import Any
|
|
|
|
|
|
|
|
from _pytest.assertion.rewrite import AssertionRewritingHook, rewrite_asserts
|
|
|
|
from _pytest.python import (
|
|
|
|
pytest_pycollect_makemodule as orig_pytest_pycollect_makemodule,
|
|
|
|
)
|
|
|
|
|
|
|
|
# Handling for pytest assertion rewrites
|
|
|
|
|
|
|
|
|
|
|
|
# First we find the pytest rewrite config. It's an attribute of the pytest
|
|
|
|
# assertion rewriting meta_path_finder, so we locate that to get the config.
|
|
|
|
|
|
|
|
|
|
|
|
def _get_pytest_rewrite_config() -> Any:
|
|
|
|
for meta_path_finder in sys.meta_path:
|
|
|
|
if isinstance(meta_path_finder, AssertionRewritingHook):
|
|
|
|
break
|
|
|
|
else:
|
|
|
|
return None
|
|
|
|
return meta_path_finder.config
|
|
|
|
|
|
|
|
|
|
|
|
# Now we need to parse the ast of the files, rewrite the ast, and store the
|
|
|
|
# original and rewritten ast into dictionaries. `run_in_pyodide` will look the
|
|
|
|
# ast up in the appropriate dictionary depending on whether or not it is using
|
|
|
|
# pytest assert rewrites.
|
|
|
|
|
|
|
|
REWRITE_CONFIG = _get_pytest_rewrite_config()
|
|
|
|
del _get_pytest_rewrite_config
|
|
|
|
|
|
|
|
ORIGINAL_MODULE_ASTS: dict[str, ast.Module] = {}
|
|
|
|
REWRITTEN_MODULE_ASTS: dict[str, ast.Module] = {}
|
|
|
|
|
|
|
|
|
2022-05-21 20:35:02 +00:00
|
|
|
def pytest_pycollect_makemodule(
|
|
|
|
module_path: pathlib.Path, path: Any, parent: Any
|
|
|
|
) -> None:
|
2022-05-09 01:43:26 +00:00
|
|
|
source = module_path.read_bytes()
|
|
|
|
strfn = str(module_path)
|
|
|
|
tree = ast.parse(source, filename=strfn)
|
|
|
|
ORIGINAL_MODULE_ASTS[strfn] = tree
|
|
|
|
tree2 = deepcopy(tree)
|
|
|
|
rewrite_asserts(tree2, source, strfn, REWRITE_CONFIG)
|
|
|
|
REWRITTEN_MODULE_ASTS[strfn] = tree2
|
|
|
|
orig_pytest_pycollect_makemodule(module_path, parent)
|