8000 Enable worker tests (#1757) · pyscript/pyscript@abfc687 · GitHub
[go: up one dir, main page]

Skip to content

Commit abfc687

Browse files
authored
Enable worker tests (#1757)
This PR re-enables tests on `worker`s. Highlights: * by default, each test is run twice: the main thread version uses `<script type="py">`, the worker version automatically turn the tags into `<script type="py" worker>` * you can tweak the settings per-class by using the `@with_execution_thread` decorator. In particular, `@with_execution_thread(None)` is for those tests which don't care about it (e.g., `test_py_config.py`) * inside each class, there might be some test which should be run only in the main thread (because it doesn't make sense to test it in a worker). For those, I introduced the `@only_main` decorator * we might introduce `@only_worker` in the future, if needed * `@skip_worker` is for those tests which currently pass on main but not on workers. These are meant to be temporary, and eventually they should all be fixed During the process, I tweaked/improved/fixed/deleted some of the existing tests. Some of them were at risk of being flaky and I made them more robust, others depended on some very precise implementation detail, and I made them more generic (for example, `test_image_renders_correctly` relied on pillow to render an image with a very specific string of bytes, and it broke due to the recent upgrade to pyodide 0.24.1) I also renamed all the skip messages to start with `NEXT`, so that they are easier to grep.
1 parent 3ac2ac0 commit abfc687

18 files changed

+166
-509
lines changed

pyscript.core/src/stdlib/pyscript/display.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,9 @@
1010
"_repr_html_": "text/html",
1111
"_repr_markdown_": "text/markdown",
1212
"_repr_svg_": "image/svg+xml",
13-
"_repr_png_": "image/png",
1413
"_repr_pdf_": "application/pdf",
1514
"_repr_jpeg_": "image/jpeg",
15+
"_repr_png_": "image/png",
1616
"_repr_latex": "text/latex",
1717
"_repr_json_": "application/json",
1818
"_repr_javascript_": "application/javascript",

pyscript.core/tests/integration/support.py

Lines changed: 49 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -70,9 +70,9 @@ def execution_thread(self, request):
7070
for value in values:
7171
assert value in ("main", "worker")
7272

73-
@pytest.fixture(params=params_with_marks(values))
74-
def execution_thread(self, request):
75-
return request.param
73+
@pytest.fixture(params=params_with_marks(values))
74+
def execution_thread(self, request):
75+
return request.param
7676

7777
def with_execution_thread_decorator(cls):
7878
cls.execution_thread = execution_thread
@@ -104,6 +104,20 @@ def decorated(self, *args):
104104
return decorator
105105

106106

107+
def only_main(fn):
108+
"""
109+
Decorator to mark a test which make sense only in the main thread
110+
"""
111+
112+
@functools.wraps(fn)
113+
def decorated(self, *args):
114+
if self.execution_thread == "worker":
115+
return
116+
return fn(self, *args)
117+
118+
return decorated
119+
120+
107121
def filter_inner_text(text, exclude=None):
108122
return "\n".join(filter_page_content(text.splitlines(), exclude=exclude))
109123

@@ -126,7 +140,7 @@ def filter_page_content(lines, exclude=None):
126140

127141

128142
@pytest.mark.usefixtures("init")
129-
@with_execution_thread("main") # , "worker") # XXX re-enable workers eventually
143+
@with_execution_thread("main", "worker")
130144
class PyScriptTest:
131145
"""
132146
Base class to write PyScript integration tests, based on playwright.
@@ -179,7 +193,7 @@ def init(self, request, tmpdir, logger, page, execution_thread):
179193
# create a symlink to BUILD inside tmpdir
180194
tmpdir.join("build").mksymlinkto(BUILD)
181195
self.tmpdir.chdir()
182-
self.tmpdir.join('favicon.ico').write("")
196+
self.tmpdir.join("favicon.ico").write("")
183197
self.logger = logger
184198
self.execution_thread = execution_thread
185199
self.dev_server = None
@@ -376,7 +390,12 @@ def goto(self, path):
376390
self.page.goto(url, timeout=0)
377391

378392
def wait_for_console(
379-
self, text, *, match_substring=False, timeout=None, check_js_errors=True
393+
self,
394+
text,
395+
*,
396+
match_substring=False,
397+
timeout=None,
398+
check_js_errors=True,
380399
):
381400
"""
382401
Wait until the given message appear in the console. If the message was
@@ -440,9 +459,15 @@ def wait_for_pyscript(self, *, timeout=None, check_js_errors=True):
440459
If check_js_errors is True (the default), it also checks that no JS
441460
errors were raised during the waiting.
442461
"""
443-
# this is printed by interpreter.ts:Interpreter.initialize
462+
scripts = (
463+
self.page.locator("script[type=py]").all()
464+
+ self.page.locator("py-script").all()
465+
)
466+
n_scripts = len(scripts)
467+
468+
# this is printed by core.js:onAfterRun
444469
elapsed_ms = self.wait_for_console(
445-
"[pyscript/main] PyScript Ready",
470+
"---py:all-done---",
446471
timeout 93D4 =timeout,
447472
check_js_errors=check_js_errors,
448473
)
@@ -453,54 +478,13 @@ def wait_for_pyscript(self, *, timeout=None, check_js_errors=True):
453478
# events aren't being triggered in the tests.
454479
self.page.wait_for_timeout(100)
455480

456-
def _parse_py_config(self, doc):
457-
configs = re.findall("<py-config>(.*?)</py-config>", doc, flags=re.DOTALL)
458-
configs = [cfg.strip() for cfg in configs]
459-
if len(configs) == 0:
460-
return None
461-
elif len(configs) == 1:
462-
return toml.loads(configs[0])
463-
else:
464-
raise AssertionError("Too many <py-config>")
465-
466-
def _inject_execution_thread_config(self, snippet, execution_thread):
467-
"""
468-
If snippet already contains a py-config, let's try to inject
469-
execution_thread automatically. Note that this works only for plain
470-
<py-config> with inline config: type="json" and src="..." are not
471-
supported by this logic, which should remain simple.
472-
"""
473-
cfg = self._parse_py_config(snippet)
474-
if cfg is None:
475-
# we don't have any <py-config>, let's add one
476-
py_config_maybe = f"""
477-
<py-config>
478-
execution_thread = "{execution_thread}"
479-
</py-config>
480-
"""
481-
else:
482-
cfg["execution_thread"] = execution_thread
483-
dumped_cfg = toml.dumps(cfg)
484-
new_py_config = f"""
485-
<py-config>
486-
{dumped_cfg}
487-
</py-config>
488-
"""
489-
snippet = re.sub(
490-
"<py-config>.*</py-config>", new_py_config, snippet, flags=re.DOTALL
491-
)
492-
# no need for extra config, it's already in the snippet
493-
py_config_maybe = ""
494-
#
495-
return snippet, py_config_maybe
481+
SCRIPT_TAG_REGEX = re.compile('(<script type="py"|<py-script)')
496482

497483
def _pyscript_format(self, snippet, *, execution_thread, extra_head=""):
498-
if execution_thread is None:
499-
py_config_maybe = ""
500-
else:
501-
snippet, py_config_maybe = self._inject_execution_thread_config(
502-
snippet, execution_thread
503-
)
484+
if execution_thread == "worker":
485+
# turn <script type="py"> into <script type="py" worker>, and
486+
# similarly for <py-script>
487+
snippet = self.SCRIPT_TAG_REGEX.sub(r"\1 worker", snippet)
504488

505489
doc = f"""
506490
<html>
@@ -510,10 +494,19 @@ def _pyscript_format(self, snippet, *, execution_thread, extra_head=""):
510494
type="module"
511495
src="{self.http_server_addr}/build/core.js"
512496
></script>
497+
<script type="module">
498+
addEventListener(
499+
'py:all-done',
500+
() => {{
501+
console.debug('---py:all-done---')
502+
}},
503+
{{ once: true }}
504+
);
505+
</script>
506+
513507
{extra_head}
514508
</head>
515509
<body>
516-
{py_config_maybe}
517510
{snippet}
518511
</body>
519512
</html>
@@ -578,7 +571,7 @@ def assert_banner_message(self, expected_message):
578571
Ensure that there is an alert banner on the page with the given message.
579572
Currently it only handles a single.
580573
"""
581-
banner = self.page.wait_for_selector(".alert-banner")
574+
banner = self.page.wait_for_selector(".py-error")
582575
banner_text = banner.inner_text()
583576

584577
if expected_message not in banner_text:

pyscript.core/tests/integration/test_00_support.py

Lines changed: 0 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -474,22 +474,3 @@ def test_404(self):
474474
assert [
475475
"Failed to load resource: the server responded with a status of 404 (Not Found)"
476476
] == self.console.all.lines
477-
478-
def test__pyscript_format_inject_execution_thread(self):
479-
"""
480-
This is slightly different than other tests: it doesn't use playwright, it
481-
just tests that our own internal helper works
482-
"""
483-
doc = self._pyscript_format("<b>Hello</b>", execution_thread="main")
484-
cfg = self._parse_py_config(doc)
485-
assert cfg == {"execution_thread": "main"}
486-
487-
def test__pyscript_format_modify_existing_py_config(self):
488-
src = """
489-
<py-config>
490-
hello = 42
491-
</py-config>
492-
"""
493-
doc = self._pyscript_format(src, execution_thread="main")
494-
cfg = self._parse_py_config(doc)
495-
assert cfg == {"execution_thread": "main", "hello": 42}

0 commit comments

Comments
 (0)
0