diff --git a/.github/workflows/build-unstable.yml b/.github/workflows/build-unstable.yml index 7eec186e..73b0ba35 100644 --- a/.github/workflows/build-unstable.yml +++ b/.github/workflows/build-unstable.yml @@ -67,6 +67,9 @@ jobs: - name: Integration Tests run: make test-integration-parallel + - name: Examples Tests + run: make test-examples + - uses: actions/upload-artifact@v3 with: name: pyscript diff --git a/docs/changelog.md b/docs/changelog.md index ad19ac63..267563cb 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -7,6 +7,7 @@ Features -------- +- The default version of Pyodide is now `0.23.2`. See the [Pyodide Changelog](https://pyodide.org/en/stable/project/changelog.html#version-0-23-2) for a detailed list of changes. - Added the `@when` decorator for attaching Python functions as event handlers - The `py-mount` attribute on HTML elements has been deprecated, and will be removed in a future release. diff --git a/docs/development/developing.md b/docs/development/developing.md index 30a9dbcb..d8913e7a 100644 --- a/docs/development/developing.md +++ b/docs/development/developing.md @@ -146,8 +146,8 @@ $ pytest test_01_basic.py -k test_pyscript_hello -s [ 0.00 page.goto ] pyscript_hello.html [ 0.01 request ] 200 - fake_server - http://fake_server/pyscript_hello.html ... -[ 0.17 console.info ] [py-loader] Downloading pyodide-0.22.1... -[ 0.18 request ] 200 - CACHED - https://cdn.jsdelivr.net/pyodide/v0.22.1/full/pyodide.js +[ 0.17 console.info ] [py-loader] Downloading pyodide-x.y.z... +[ 0.18 request ] 200 - CACHED - https://cdn.jsdelivr.net/pyodide/vx.y.z/full/pyodide.js ... [ 3.59 console.info ] [pyscript/main] PyScript page fully initialized [ 3.60 console.log ] hello pyscript @@ -217,25 +217,6 @@ If you want to temporarily disable the cache, the easiest thing is to use If you want to clear the cache, you can use the special option `--clear-http-cache`: -``` -$ pytest --clear-http-cache -... --------------------- SmartRouter HTTP cache -------------------- -Requests found in the cache: - https://raw.githubusercontent.com/pyscript/pyscript/main/README.md - https://cdn.jsdelivr.net/pyodide/v0.22.1/full/repodata.json - https://cdn.jsdelivr.net/pyodide/v0.22.1/full/pyodide.asm.js - https://cdn.jsdelivr.net/pyodide/v0.22.1/full/micropip-0.1-py3-none-any.whl - https://cdn.jsdelivr.net/pyodide/v0.22.1/full/pyodide.asm.data - https://cdn.jsdelivr.net/pyodide/v0.22.1/full/pyodide.js - https://cdn.jsdelivr.net/pyodide/v0.22.1/full/pyodide.asm.wasm - https://cdn.jsdelivr.net/pyodide/v0.22.1/full/pyodide_py.tar - https://cdn.jsdelivr.net/pyodide/v0.22.1/full/pyparsing-3.0.9-py3-none-any.whl - https://cdn.jsdelivr.net/pyodide/v0.22.1/full/distutils.tar - https://cdn.jsdelivr.net/pyodide/v0.22.1/full/packaging-21.3-py3-none-any.whl -Cache cleared -``` - **NOTE**: this works only if you are inside `tests/integration`, or if you explicitly specify `tests/integration` from the command line. This is due to how `pytest` decides to search for and load the various `conftest.py`. diff --git a/docs/tutorials/py-config-interpreter.md b/docs/tutorials/py-config-interpreter.md index bfe06de3..9eea0e70 100644 --- a/docs/tutorials/py-config-interpreter.md +++ b/docs/tutorials/py-config-interpreter.md @@ -25,7 +25,7 @@ To get started, let's create a new `index.html` file and import `pyscript.js`. ``` -We are using the pyodide CDN to setup our interpreter, but you can also download the files from [the pyodide GitHub release](https://github.com/pyodide/pyodide/releases/tag/0.22.0a3), unzip them and use the `pyodide.js` file as your interpreter. +We are using the pyodide CDN to setup our interpreter, but you can also download the files from [the pyodide GitHub releases](https://github.com/pyodide/pyodide/releases/), unzip them and use the `pyodide.js` file as your interpreter. ## Setting the interpreter @@ -47,8 +47,8 @@ To set the interpreter, you can use the `interpreter` configuration in the `py-c [[interpreters]] - src = "https://cdn.jsdelivr.net/pyodide/v0.22.0a3/full/pyodide.js" - name = "pyodide-0.22.0a3" + src = "https://cdn.jsdelivr.net/pyodide/v0.23.0/full/pyodide.js" + name = "pyodide-0.23.0" lang = "python" @@ -75,8 +75,8 @@ To confirm that the interpreter is set correctly, you can open the DevTools and [[interpreters]] - src = "https://cdn.jsdelivr.net/pyodide/v0.22.0a3/full/pyodide.js" - name = "pyodide-0.22.0a3" + src = "https://cdn.jsdelivr.net/pyodide/v0.23.0/full/pyodide.js" + name = "pyodide-0.23.0" lang = "python" diff --git a/pyscriptjs/Makefile b/pyscriptjs/Makefile index 432e2f19..cb4a7193 100644 --- a/pyscriptjs/Makefile +++ b/pyscriptjs/Makefile @@ -86,20 +86,26 @@ run-examples: setup build examples make dev test: - make examples make test-ts make test-py make test-integration-parallel + make test-examples +# run all integration tests *including examples* sequentially test-integration: - make examples mkdir -p test_results $(PYTEST_EXE) -vv $(ARGS) tests/integration/ --log-cli-level=warning --junitxml=test_results/integration.xml +# run all integration tests *except examples* in parallel (examples use too much memory) test-integration-parallel: + mkdir -p test_results + $(PYTEST_EXE) --numprocesses auto -vv $(ARGS) tests/integration/ --log-cli-level=warning --junitxml=test_results/integration.xml -k 'not zz_examples' + +# run integration tests on only examples sequentially (to avoid running out of memory) +test-examples: make examples mkdir -p test_results - $(PYTEST_EXE) --numprocesses auto -vv $(ARGS) tests/integration/ --log-cli-level=warning --junitxml=test_results/integration.xml + $(PYTEST_EXE) -vv $(ARGS) tests/integration/ --log-cli-level=warning --junitxml=test_results/integration.xml -k 'zz_examples' test-py: @echo "Tests from $(src_dir)" diff --git a/pyscriptjs/environment.yml b/pyscriptjs/environment.yml index 4d9928da..c8bfad30 100644 --- a/pyscriptjs/environment.yml +++ b/pyscriptjs/environment.yml @@ -21,5 +21,5 @@ dependencies: - pytest-xdist==3.3.0 # We need Pyodide and micropip so we can import them in our Python # unit tests - - pyodide_py==0.22 + - pyodide_py==0.23.2 - micropip==0.2.2 diff --git a/pyscriptjs/package-lock.json b/pyscriptjs/package-lock.json index 5b64f4de..f7941de7 100644 --- a/pyscriptjs/package-lock.json +++ b/pyscriptjs/package-lock.json @@ -32,7 +32,7 @@ "jest": "29.1.2", "jest-environment-jsdom": "29.1.2", "prettier": "2.7.1", - "pyodide": "0.22.1", + "pyodide": "0.23.2", "synclink": "0.2.4", "ts-jest": "29.0.3", "typescript": "5.0.4" @@ -5176,9 +5176,9 @@ ] }, "node_modules/pyodide": { - "version": "0.22.1", - "resolved": "https://registry.npmjs.org/pyodide/-/pyodide-0.22.1.tgz", - "integrity": "sha512-6+PkFLTC+kcBKtFQxYBxR44J5IBxLm8UGkobLgZv1SxzV9qOU2rb0YYf0qDtlnfDiN/IQd2uckf+D8Zwe88Mqg==", + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/pyodide/-/pyodide-0.23.2.tgz", + "integrity": "sha512-GK4YDZVgzfAXK/7X0IiCI+142k0Ah/HwYTzDHtG8zC47dflWYuPozeFbOngShuL1M9Un5sCmHFqiH3boxJv0pQ==", "dev": true, "dependencies": { "base-64": "^1.0.0", @@ -9878,9 +9878,9 @@ "dev": true }, "pyodide": { - "version": "0.22.1", - "resolved": "https://registry.npmjs.org/pyodide/-/pyodide-0.22.1.tgz", - "integrity": "sha512-6+PkFLTC+kcBKtFQxYBxR44J5IBxLm8UGkobLgZv1SxzV9qOU2rb0YYf0qDtlnfDiN/IQd2uckf+D8Zwe88Mqg==", + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/pyodide/-/pyodide-0.23.2.tgz", + "integrity": "sha512-GK4YDZVgzfAXK/7X0IiCI+142k0Ah/HwYTzDHtG8zC47dflWYuPozeFbOngShuL1M9Un5sCmHFqiH3boxJv0pQ==", "dev": true, "requires": { "base-64": "^1.0.0", diff --git a/pyscriptjs/package.json b/pyscriptjs/package.json index f56295bd..2ce695aa 100644 --- a/pyscriptjs/package.json +++ b/pyscriptjs/package.json @@ -34,7 +34,7 @@ "jest": "29.1.2", "jest-environment-jsdom": "29.1.2", "prettier": "2.7.1", - "pyodide": "0.22.1", + "pyodide": "0.23.2", "synclink": "0.2.4", "ts-jest": "29.0.3", "typescript": "5.0.4" diff --git a/pyscriptjs/src/pyconfig.ts b/pyscriptjs/src/pyconfig.ts index 82dcadc4..c45f2672 100644 --- a/pyscriptjs/src/pyconfig.ts +++ b/pyscriptjs/src/pyconfig.ts @@ -54,8 +54,8 @@ export const defaultConfig: AppConfig = { type: 'app', interpreters: [ { - src: 'https://cdn.jsdelivr.net/pyodide/v0.22.1/full/pyodide.js', - name: 'pyodide-0.22.1', + src: 'https://cdn.jsdelivr.net/pyodide/v0.23.2/full/pyodide.js', + name: 'pyodide-0.23.2', lang: 'python', }, ], diff --git a/pyscriptjs/src/remote_interpreter.ts b/pyscriptjs/src/remote_interpreter.ts index a245aaaa..1585f9eb 100644 --- a/pyscriptjs/src/remote_interpreter.ts +++ b/pyscriptjs/src/remote_interpreter.ts @@ -72,7 +72,7 @@ export class RemoteInterpreter extends Object { // TODO: Remove this once `runtimes` is removed! interpreter: InterpreterInterface & ProxyMarked; - constructor(src = 'https://cdn.jsdelivr.net/pyodide/v0.22.1/full/pyodide.js') { + constructor(src = 'https://cdn.jsdelivr.net/pyodide/v0.23.2/full/pyodide.js') { super(); this.src = src; } @@ -91,10 +91,9 @@ export class RemoteInterpreter extends Object { * * This is because, if it's used, loadPyodide * behaves mischievously i.e. it tries to load - * `pyodide.asm.js` and `pyodide_py.tar` but - * with paths that are wrong such as: + * additional files but with paths that are wrong such as: * - * http://127.0.0.1:8080/build/pyodide_py.tar + * http://127.0.0.1:8080/build/... * which results in a 404 since `build` doesn't * contain these files and is clearly the wrong * path. @@ -171,13 +170,12 @@ export class RemoteInterpreter extends Object { * */ async loadPackage(names: string | string[]): Promise { logger.info(`pyodide.loadPackage: ${names.toString()}`); - // the new way in v0.22.1 is to pass it as a dict of options i.e. - // { messageCallback: logger.info.bind(logger), errorCallback: logger.info.bind(logger) } - // but one of our tests tries to use a locally downloaded older version of pyodide - // for which the signature of `loadPackage` accepts the above params as args i.e. - // the call uses `logger.info.bind(logger), logger.info.bind(logger)`. + // The signature of `loadPackage` changed in Pyodide 0.22; while we generally + // don't support older versions of Pyodide in any given release of PyScript, this + // significant change is useful in some testing scenarios (for now) const messageCallback = logger.info.bind(logger) as typeof logger.info; - if (this.interpreter.version.startsWith('0.22')) { + // Comparing version as number to avoid issues with lexicographic comparison + if (Number(this.interpreter.version.split('.')[1]) >= 22) { await this.interface.loadPackage(names, { messageCallback, errorCallback: messageCallback, diff --git a/pyscriptjs/tests/integration/support.py b/pyscriptjs/tests/integration/support.py index 2a6ed1fe..e207dbbc 100644 --- a/pyscriptjs/tests/integration/support.py +++ b/pyscriptjs/tests/integration/support.py @@ -493,7 +493,13 @@ class PyScriptTest: return doc def pyscript_run( - self, snippet, *, extra_head="", wait_for_pyscript=True, timeout=None + self, + snippet, + *, + extra_head="", + wait_for_pyscript=True, + timeout=None, + check_js_errors=True, ): """ Main entry point for pyscript tests. @@ -517,7 +523,7 @@ class PyScriptTest: self.writefile(filename, doc) self.goto(filename) if wait_for_pyscript: - self.wait_for_pyscript(timeout=timeout) + self.wait_for_pyscript(timeout=timeout, check_js_errors=check_js_errors) def iter_locator(self, loc): """ @@ -630,7 +636,7 @@ TEST_ITERATIONS = math.ceil( ) # 120 iters of 1/4 second -def wait_for_render(page, selector, pattern): +def wait_for_render(page, selector, pattern, timeout_seconds: int | None = None): """ Assert that rendering inserts data into the page as expected: search the DOM from within the timing loop for a string that is not present in the @@ -639,7 +645,12 @@ def wait_for_render(page, selector, pattern): re_sub_content = re.compile(pattern) py_rendered = False # Flag to be set to True when condition met - for _ in range(TEST_ITERATIONS): + if timeout_seconds: + check_iterations = math.ceil(timeout_seconds / TEST_TIME_INCREMENT) + else: + check_iterations = TEST_ITERATIONS + + for _ in range(check_iterations): content = page.inner_html(selector) if re_sub_content.search(content): py_rendered = True diff --git a/pyscriptjs/tests/integration/test_01_basic.py b/pyscriptjs/tests/integration/test_01_basic.py index 16d628d0..1170adc7 100644 --- a/pyscriptjs/tests/integration/test_01_basic.py +++ b/pyscriptjs/tests/integration/test_01_basic.py @@ -2,7 +2,7 @@ import re import pytest -from .support import PageErrors, PyScriptTest, skip_worker +from .support import PyScriptTest, skip_worker class TestBasic(PyScriptTest): @@ -246,22 +246,23 @@ class TestBasic(PyScriptTest): assert self.console.log.lines[-1] == "hello from foo" def test_py_script_src_not_found(self): - with pytest.raises(PageErrors) as exc: - self.pyscript_run( - """ - - """ - ) + self.pyscript_run( + """ + + """, + check_js_errors=False, + ) assert "Failed to load resource" in self.console.error.lines[0] - error_msgs = str(exc.value) expected_msg = "(PY0404): Fetching from URL foo.py failed with error 404" - assert expected_msg in error_msgs + assert any((expected_msg in line) for line in self.console.js_error.lines) assert self.assert_banner_message(expected_msg) pyscript_tag = self.page.locator("py-script") assert pyscript_tag.inner_html() == "" + self.check_js_errors(expected_msg) + def test_js_version(self): self.pyscript_run( """ diff --git a/pyscriptjs/tests/integration/test_py_config.py b/pyscriptjs/tests/integration/test_py_config.py index 26c40a5d..a43e55ba 100644 --- a/pyscriptjs/tests/integration/test_py_config.py +++ b/pyscriptjs/tests/integration/test_py_config.py @@ -80,9 +80,9 @@ class TestConfig(PyScriptTest): ) assert self.console.log.lines[-1] == "config name: app with external config" - # The default pyodide version is 0.22.1 as of writing - # this test which is newer than the one we are loading below - # (after downloading locally) -- which is 0.22.0 + # The default pyodide version is newer than + # the one we are loading below (after downloading locally) + # which is 0.22.0 # The test checks if loading a different interpreter is possible # and that too from a locally downloaded file without needing @@ -234,8 +234,8 @@ class TestConfig(PyScriptTest): { "interpreters": [ { - "src": "https://cdn.jsdelivr.net/pyodide/v0.22.1/full/pyodide.js", - "name": "pyodide-0.22.1", + "src": "https://cdn.jsdelivr.net/pyodide/v0.23.2/full/pyodide.js", + "name": "pyodide-0.23.2", "lang": "python" }, { diff --git a/pyscriptjs/tests/integration/test_zz_examples.py b/pyscriptjs/tests/integration/test_zz_examples.py index a30c2b44..a2dc3335 100644 --- a/pyscriptjs/tests/integration/test_zz_examples.py +++ b/pyscriptjs/tests/integration/test_zz_examples.py @@ -276,18 +276,20 @@ class TestExamples(PyScriptTest): self.check_tutor_generated_code() def test_panel_kmeans(self): - # XXX improve this test + # XXX improve this test>>>>>>> main self.goto("examples/panel_kmeans.html") - self.wait_for_pyscript(timeout=90 * 1000) + self.wait_for_pyscript(timeout=120 * 1000) assert self.page.title() == "Pyscript/Panel KMeans Demo" - wait_for_render(self.page, "*", "") + wait_for_render( + self.page, "*", "", timeout_seconds=60 * 2 + ) self.assert_no_banners() self.check_tutor_generated_code() def test_panel_stream(self): # XXX improve this test self.goto("examples/panel_stream.html") - self.wait_for_pyscript(timeout=90 * 1000) + self.wait_for_pyscript(timeout=3 * 60 * 1000) assert self.page.title() == "PyScript/Panel Streaming Demo" wait_for_render(self.page, "*", "") self.assert_no_banners() @@ -316,7 +318,7 @@ class TestExamples(PyScriptTest): def test_repl2(self): self.goto("examples/repl2.html") - self.wait_for_pyscript() + self.wait_for_pyscript(timeout=1.5 * 60 * 1000) assert self.page.title() == "Custom REPL Example" wait_for_render(self.page, "*", "") # confirm we can import utils and run one command diff --git a/pyscriptjs/tests/integration/test_zzz_docs_snippets.py b/pyscriptjs/tests/integration/test_zzz_docs_snippets.py index a3060857..aaabfd0b 100644 --- a/pyscriptjs/tests/integration/test_zzz_docs_snippets.py +++ b/pyscriptjs/tests/integration/test_zzz_docs_snippets.py @@ -130,12 +130,13 @@ class TestDocsSnippets(PyScriptTest): self.assert_no_banners() def test_tutorials_py_config_interpreter(self): + """Load a previous version of Pyodide""" self.pyscript_run( """ [[interpreters]] - src = "https://cdn.jsdelivr.net/pyodide/v0.22.0a3/full/pyodide.js" - name = "pyodide-0.22.0a3" + src = "https://cdn.jsdelivr.net/pyodide/v0.23.0/full/pyodide.js" + name = "pyodide-0.23.0" lang = "python" @@ -146,7 +147,7 @@ class TestDocsSnippets(PyScriptTest): ) py_terminal = self.page.wait_for_selector("py-terminal") - assert "0.22.0a3" in py_terminal.inner_text() + assert "0.23.0" in py_terminal.inner_text() self.assert_no_banners() @skip_worker("FIXME: display()")