Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a global manager for configuring runtimes #141

Merged
merged 13 commits into from
Jul 23, 2024
2 changes: 2 additions & 0 deletions pytest_pyodide/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from importlib.metadata import PackageNotFoundError, version

from .config import get_global_config
from .decorator import copy_files_to_pyodide, run_in_pyodide
from .runner import (
NodeRunner,
Expand Down Expand Up @@ -30,4 +31,5 @@
"run_in_pyodide",
"copy_files_to_pyodide",
"spawn_web_server",
"get_global_config",
]
61 changes: 61 additions & 0 deletions pytest_pyodide/config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
"""
Stores the global runtime configuration related to the pytest_pyodide package.
"""

from typing import Literal

RUNTIMES = Literal["chrome", "firefox", "node", "safari"]

_global_load_pyodide_script = """
let pyodide = await loadPyodide({ fullStdLib: false, jsglobals : self });
"""


class Config:
def __init__(self):
# Flags to be passed to the browser or runtime.
self.flags: dict[RUNTIMES, list[str]] = {
"chrome": ["--js-flags=--expose-gc"],
"firefox": [],
"node": [],
"safari": [],
}

# The script to be executed to load the Pyodide.
self.load_pyodide_script: dict[RUNTIMES, str] = {
"chrome": _global_load_pyodide_script,
"firefox": _global_load_pyodide_script,
"node": _global_load_pyodide_script,
"safari": _global_load_pyodide_script,
}

# The script to be executed to initialize the runtime.
self.initialize_script: str = "pyodide.runPython('');"

def set_flag(self, runtime: RUNTIMES, flags: list[str]):
self.flags[runtime] = flags

def get_flag(self, runtime: RUNTIMES) -> list[str]:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn't this be get_flags / set_flags plural?

return self.flags[runtime]

def set_load_pyodide_script(self, runtime: RUNTIMES, load_pyodide_script: str):
self.load_pyodide_script[runtime] = load_pyodide_script

def get_load_pyodide_script(self, runtime: RUNTIMES) -> str:
return self.load_pyodide_script[runtime]

def set_initialize_script(self, initialize_script: str):
self.initialize_script = initialize_script

def get_initialize_script(self) -> str:
return self.initialize_script


SINGLETON = Config()


def get_global_config() -> Config:
"""
Return the singleton config object.
"""
return SINGLETON
8 changes: 4 additions & 4 deletions pytest_pyodide/fixture.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,8 @@

import pytest

from .config import get_global_config
from .runner import (
CHROME_FLAGS,
FIREFOX_FLAGS,
NodeRunner,
PlaywrightChromeRunner,
PlaywrightFirefoxRunner,
Expand Down Expand Up @@ -41,13 +40,14 @@ def _playwright_browsers(request):
)

runtimes = pytest.pyodide_runtimes
cfg = get_global_config()

with sync_playwright() as p:
browsers: dict[str, Any] = {}
supported_browsers: dict[str, tuple[str, list[str]]] = {
# browser name: (attr_name, flags)
"firefox": ("firefox", FIREFOX_FLAGS),
"chrome": ("chromium", CHROME_FLAGS),
"firefox": ("firefox", cfg.get_flag("firefox")),
"chrome": ("chromium", cfg.get_flag("chrome")),
# TODO: enable webkit
# "webkit": (),
}
Expand Down
35 changes: 18 additions & 17 deletions pytest_pyodide/runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,7 @@
import pexpect
import pytest

CHROME_FLAGS: list[str] = ["--js-flags=--expose-gc"]
FIREFOX_FLAGS: list[str] = []
NODE_FLAGS: list[str] = []

from .config import RUNTIMES, get_global_config

TEST_SETUP_CODE = """
Error.stackTraceLimit = Infinity;
Expand Down Expand Up @@ -89,8 +86,6 @@
};
""".strip()

INITIALIZE_SCRIPT = "pyodide.runPython('');"


class JavascriptException(Exception):
def __init__(self, msg, stack):
Expand All @@ -105,10 +100,17 @@ def __str__(self):


class _BrowserBaseRunner:
browser = ""
browser: RUNTIMES = "" # type: ignore[assignment]
script_timeout = 20
JavascriptException = JavascriptException

# A common script that runs after pyodide is loaded
POST_LOAD_PYODIDE_SCRIPT = """
self.pyodide = pyodide;
globalThis.pyodide = pyodide;
pyodide._api.inTestHoist = true; // improve some error messages for tests
"""

def __init__(
self,
server_port,
Expand All @@ -121,13 +123,16 @@ def __init__(
*args,
**kwargs,
):
self._config = get_global_config()

self.server_port = server_port
self.server_hostname = server_hostname
self.base_url = f"http://{self.server_hostname}:{self.server_port}"
self.server_log = server_log
self.script_type = script_type
self.dist_dir = dist_dir
self.driver = self.get_driver(jspi)

self.set_script_timeout(self.script_timeout)
self.prepare_driver()
self.javascript_setup()
Expand Down Expand Up @@ -171,12 +176,8 @@ def javascript_setup(self):

def load_pyodide(self):
self.run_js(
"""
let pyodide = await loadPyodide({ fullStdLib: false, jsglobals : self });
self.pyodide = pyodide;
globalThis.pyodide = pyodide;
pyodide._api.inTestHoist = true; // improve some error messages for tests
"""
self._config.get_load_pyodide_script(self.browser)
+ self.POST_LOAD_PYODIDE_SCRIPT
)

def initialize_pyodide(self):
Expand All @@ -201,7 +202,7 @@ def initialize_pyodide(self):
}
"""
)
self.run_js(INITIALIZE_SCRIPT)
self.run_js(self._config.get_initialize_script())
from .decorator import initialize_decorator

initialize_decorator(self)
Expand Down Expand Up @@ -430,7 +431,7 @@ def get_driver(self, jspi=False):

options = Options()
options.add_argument("--headless")
for flag in FIREFOX_FLAGS:
for flag in self._config.get_flag("firefox"):
options.add_argument(flag)

return Firefox(service=Service(), options=options)
Expand All @@ -449,7 +450,7 @@ def get_driver(self, jspi=False):
if jspi:
options.add_argument("--enable-features=WebAssemblyExperimentalJSPI")
options.add_argument("--enable-experimental-webassembly-features")
for flag in CHROME_FLAGS:
for flag in self._config.get_flag("chrome"):
options.add_argument(flag)
return Chrome(options=options)

Expand Down Expand Up @@ -544,7 +545,7 @@ def init_node(self, jspi=False):
f"Node version {node_version} is too old, please use node >= 18"
)

extra_args = NODE_FLAGS[:]
extra_args = self._config.get_flag("node")[:]
# Node v14 require the --experimental-wasm-bigint which
# produces errors on later versions
if jspi:
Expand Down
25 changes: 25 additions & 0 deletions tests/test_config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
from pytest_pyodide.config import Config, get_global_config


def test_config():
c = Config()

runtimes = ["chrome", "firefox", "node", "safari"]

for runtime in runtimes:
c.get_flag(runtime)
c.get_load_pyodide_script(runtime)

c.set_flag(runtime, ["--headless"])
assert c.get_flag(runtime) == ["--headless"]

c.set_load_pyodide_script(runtime, "console.log('hello')")
assert c.get_load_pyodide_script(runtime) == "console.log('hello')"

c.get_initialize_script()
c.set_initialize_script("console.log('hello')")
assert c.get_initialize_script() == "console.log('hello')"


def test_global_config():
assert get_global_config() is get_global_config()
Loading