mirror of https://github.com/pyodide/pyodide.git
MAINT Switch to pytest-pyodide (#2893)
Co-authored-by: ryanking13 <def6488@gmail.com>
This commit is contained in:
parent
5d72fd427e
commit
c85e00806b
|
@ -25,7 +25,7 @@ jobs:
|
|||
name: Test docs
|
||||
command: |
|
||||
mkdir test-results
|
||||
pip install ./pyodide-test-runner
|
||||
pip install pytest-pyodide
|
||||
npm install -g node-fetch@2
|
||||
pytest docs/sphinx_pyodide/tests --junitxml=test-results/junit.xml
|
||||
- store_test_results:
|
||||
|
@ -243,7 +243,7 @@ jobs:
|
|||
command: |
|
||||
make npm-link
|
||||
mkdir test-results
|
||||
pip install ./pyodide-test-runner
|
||||
pip install pytest-pyodide
|
||||
npm install -g node-fetch@2
|
||||
if [ -z "<< parameters.cache-dir >>" ]; then
|
||||
export CACHE_DIR=".test_cache/.pytest_cache_$(echo $RANDOM | md5sum | head -c 10)"
|
||||
|
@ -269,22 +269,10 @@ jobs:
|
|||
name: stack-size
|
||||
command: |
|
||||
make npm-link
|
||||
pip install ./pyodide-test-runner
|
||||
pip install pytest-pyodide
|
||||
npm install -g node-fetch@2
|
||||
pytest -s benchmark/stack_usage.py | sed -n 's/## //pg'
|
||||
|
||||
test-test-runner:
|
||||
<<: *defaults
|
||||
resource_class: medium+
|
||||
steps:
|
||||
- attach_workspace:
|
||||
at: .
|
||||
- run:
|
||||
name: test
|
||||
command: |
|
||||
npm install -g node-fetch@2
|
||||
cd pyodide-test-runner && pytest -v .
|
||||
|
||||
test-js:
|
||||
<<: *defaults
|
||||
resource_class: small
|
||||
|
@ -322,7 +310,7 @@ jobs:
|
|||
- run:
|
||||
name: benchmark
|
||||
command: |
|
||||
pip install ./pyodide-test-runner
|
||||
pip install pytest-pyodide
|
||||
npm install -g node-fetch@2
|
||||
python benchmark/benchmark.py all --output dist/benchmarks.json
|
||||
|
||||
|
@ -516,7 +504,7 @@ workflows:
|
|||
|
||||
- test-main:
|
||||
name: test-core-chrome
|
||||
test-params: -k "chrome and not webworker" src packages/micropip packages/fpcast-test packages/sharedlib-test-py/ packages/cpp-exceptions-test/
|
||||
test-params: --runtime chrome -k "not webworker" src packages/micropip packages/fpcast-test packages/sharedlib-test-py/ packages/cpp-exceptions-test/
|
||||
requires:
|
||||
- build-core
|
||||
filters:
|
||||
|
@ -525,7 +513,7 @@ workflows:
|
|||
|
||||
- test-main:
|
||||
name: test-core-firefox
|
||||
test-params: -k "firefox and not webworker" src packages/micropip packages/fpcast-test packages/sharedlib-test-py/ packages/cpp-exceptions-test/
|
||||
test-params: --runtime firefox -k "not webworker" src packages/micropip packages/fpcast-test packages/sharedlib-test-py/ packages/cpp-exceptions-test/
|
||||
requires:
|
||||
- build-core
|
||||
filters:
|
||||
|
@ -534,7 +522,7 @@ workflows:
|
|||
|
||||
- test-main:
|
||||
name: test-core-node
|
||||
test-params: -k node src packages/micropip packages/fpcast-test packages/sharedlib-test-py/ packages/cpp-exceptions-test/
|
||||
test-params: --runtime node src packages/micropip packages/fpcast-test packages/sharedlib-test-py/ packages/cpp-exceptions-test/
|
||||
requires:
|
||||
- build-core
|
||||
filters:
|
||||
|
@ -543,7 +531,7 @@ workflows:
|
|||
|
||||
- test-main:
|
||||
name: test-core-chrome-webworker
|
||||
test-params: -k chrome src/tests/test_webworker.py
|
||||
test-params: --runtime chrome src/tests/test_webworker.py
|
||||
requires:
|
||||
- test-core-chrome
|
||||
filters:
|
||||
|
@ -552,7 +540,7 @@ workflows:
|
|||
|
||||
- test-main:
|
||||
name: test-core-firefox-webworker
|
||||
test-params: -k firefox src/tests/test_webworker.py
|
||||
test-params: --runtime firefox src/tests/test_webworker.py
|
||||
requires:
|
||||
- test-core-firefox
|
||||
filters:
|
||||
|
@ -561,7 +549,7 @@ workflows:
|
|||
|
||||
- test-main:
|
||||
name: test-packages-chrome-no-numpy-dependents
|
||||
test-params: -k chrome packages/test* packages/*/test*
|
||||
test-params: --runtime chrome packages/test* packages/*/test*
|
||||
cache-dir: .pytest_cache_chrome
|
||||
requires:
|
||||
- build-packages-no-numpy-dependents
|
||||
|
@ -576,7 +564,7 @@ workflows:
|
|||
|
||||
- test-main:
|
||||
name: test-packages-chrome
|
||||
test-params: -k chrome packages/test* packages/*/test* --skip-passed
|
||||
test-params: --runtime chrome packages/test* packages/*/test* --skip-passed
|
||||
cache-dir: .pytest_cache_chrome
|
||||
requires:
|
||||
- test-packages-chrome-no-numpy-dependents
|
||||
|
@ -587,7 +575,7 @@ workflows:
|
|||
|
||||
- test-main:
|
||||
name: test-packages-firefox-no-numpy-dependents
|
||||
test-params: -k firefox packages/test* packages/*/test*
|
||||
test-params: --runtime firefox packages/test* packages/*/test*
|
||||
cache-dir: .pytest_cache_firefox
|
||||
requires:
|
||||
- build-packages-no-numpy-dependents
|
||||
|
@ -602,7 +590,7 @@ workflows:
|
|||
|
||||
- test-main:
|
||||
name: test-packages-firefox
|
||||
test-params: -k firefox packages/test* packages/*/test* --skip-passed
|
||||
test-params: --runtime firefox packages/test* packages/*/test* --skip-passed
|
||||
cache-dir: .pytest_cache_firefox
|
||||
requires:
|
||||
- test-packages-firefox-no-numpy-dependents
|
||||
|
@ -613,7 +601,7 @@ workflows:
|
|||
|
||||
- test-main:
|
||||
name: test-packages-node-no-numpy-dependents
|
||||
test-params: -k node packages/test* packages/*/test*
|
||||
test-params: --runtime node packages/test* packages/*/test*
|
||||
cache-dir: .pytest_cache_node
|
||||
requires:
|
||||
- build-packages-no-numpy-dependents
|
||||
|
@ -628,7 +616,7 @@ workflows:
|
|||
|
||||
- test-main:
|
||||
name: test-packages-node
|
||||
test-params: -k node packages/test* packages/*/test* --skip-passed
|
||||
test-params: --runtime node packages/test* packages/*/test* --skip-passed
|
||||
cache-dir: .pytest_cache_node
|
||||
requires:
|
||||
- test-packages-node-no-numpy-dependents
|
||||
|
@ -637,13 +625,6 @@ workflows:
|
|||
tags:
|
||||
only: /.*/
|
||||
|
||||
- test-test-runner:
|
||||
requires:
|
||||
- build-core
|
||||
filters:
|
||||
tags:
|
||||
only: /.*/
|
||||
|
||||
- test-js:
|
||||
requires:
|
||||
- build-core
|
||||
|
|
|
@ -33,8 +33,7 @@ jobs:
|
|||
shell: bash -l {0}
|
||||
run: |
|
||||
mkdir test-results
|
||||
python3 -m pip install ./pyodide-test-runner
|
||||
python3 -m pip install -e ./pyodide-build
|
||||
cd pyodide-build && python3 -m pip install -e ".[test]" && cd ..
|
||||
python3 -m pip install pytest-cov hypothesis pytz
|
||||
- name: Run tests
|
||||
shell: bash -l {0}
|
||||
|
@ -42,7 +41,7 @@ jobs:
|
|||
PYODIDE_ROOT=. pytest \
|
||||
--junitxml=test-results/junit.xml \
|
||||
--verbose \
|
||||
-k 'not (chrome or firefox or node)' \
|
||||
--runtime host \
|
||||
--cov=pyodide_build --cov=pyodide \
|
||||
src pyodide-build packages/micropip/
|
||||
- uses: codecov/codecov-action@v3
|
||||
|
@ -157,7 +156,7 @@ jobs:
|
|||
shell: bash -l {0}
|
||||
run: |
|
||||
pip install -r requirements.txt
|
||||
pip install ./pyodide-test-runner
|
||||
cd pyodide-build && pip install -e ".[test]" && cd ..
|
||||
# FIXME: playwright 1.23.0 has unknown performance issue on firefox
|
||||
pip install "playwright<1.23.0" && python -m playwright install
|
||||
|
||||
|
@ -170,7 +169,8 @@ jobs:
|
|||
ls -lh
|
||||
ls -lh dist/
|
||||
tools/pytest_wrapper.py src packages/micropip/ \
|
||||
-v -k "${BROWSER}" \
|
||||
-v \
|
||||
--runtime "${BROWSER}" \
|
||||
--runner "${RUNNER}" \
|
||||
--durations 50 \
|
||||
--junitxml=test-results/core_test.xml
|
||||
|
@ -184,7 +184,9 @@ jobs:
|
|||
ls -lh
|
||||
ls -lh dist/
|
||||
tools/pytest_wrapper.py packages/test* packages/*/test* \
|
||||
-v -k "numpy and not joblib and ${BROWSER}" \
|
||||
-v \
|
||||
-k "numpy and not joblib" \
|
||||
--runtime "${BROWSER}" \
|
||||
--runner "${RUNNER}" \
|
||||
--durations 50 \
|
||||
--junitxml=test-results/packages_test.xml
|
||||
|
|
|
@ -8,7 +8,7 @@ from time import time
|
|||
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
|
||||
|
||||
from pyodide_test_runner import ( # noqa: E402
|
||||
from pytest_pyodide import ( # noqa: E402
|
||||
SeleniumChromeWrapper,
|
||||
SeleniumFirefoxWrapper,
|
||||
spawn_web_server,
|
||||
|
|
10
conftest.py
10
conftest.py
|
@ -12,15 +12,15 @@ DIST_PATH = ROOT_PATH / "dist"
|
|||
sys.path.append(str(ROOT_PATH / "pyodide-build"))
|
||||
sys.path.append(str(ROOT_PATH / "src" / "py"))
|
||||
|
||||
import pyodide_test_runner.browser
|
||||
from pyodide_test_runner.utils import maybe_skip_test
|
||||
from pyodide_test_runner.utils import package_is_built as _package_is_built
|
||||
from pyodide_test_runner.utils import parse_xfail_browsers
|
||||
import pytest_pyodide.browser
|
||||
from pytest_pyodide.utils import maybe_skip_test
|
||||
from pytest_pyodide.utils import package_is_built as _package_is_built
|
||||
from pytest_pyodide.utils import parse_xfail_browsers
|
||||
|
||||
# There are a bunch of global objects that occasionally enter the hiwire cache
|
||||
# but never leave. The refcount checks get angry about them if they aren't preloaded.
|
||||
# We need to go through and touch them all once to keep everything okay.
|
||||
pyodide_test_runner.browser.INITIALIZE_SCRIPT = """
|
||||
pytest_pyodide.browser.INITIALIZE_SCRIPT = """
|
||||
pyodide.globals.get;
|
||||
pyodide._api.pyodide_code.eval_code;
|
||||
pyodide._api.pyodide_code.eval_code_async;
|
||||
|
|
|
@ -147,7 +147,7 @@ The tests should go in one or more files like
|
|||
`test_<package-name>.py`. The tests should look like:
|
||||
|
||||
```py
|
||||
from pyodide_test_runner import run_in_pyodide
|
||||
from pytest_pyodide import run_in_pyodide
|
||||
|
||||
@run_in_pyodide(packages=["<package-name>"])
|
||||
def test_mytestname(selenium):
|
||||
|
@ -160,7 +160,7 @@ If you want to run your package's full pytest test suite and your package
|
|||
vendors tests you can do it like:
|
||||
|
||||
```py
|
||||
from pyodide_test_runner import run_in_pyodide
|
||||
from pytest_pyodide import run_in_pyodide
|
||||
|
||||
@run_in_pyodide(packages=["<package-name>-tests", "pytest"])
|
||||
def test_mytestname(selenium):
|
||||
|
|
|
@ -60,7 +60,6 @@ flexible.
|
|||
There are 5 test locations that are collected by pytest:
|
||||
|
||||
- `src/tests/`: general Pyodide tests and tests running the CPython test suite
|
||||
- `pyodide-test-runner/pyodide_test_runner/tests/`: Tests for the testing
|
||||
system.
|
||||
- `pyodide-build/pyodide_build/tests/`: tests related to Pyodide build system
|
||||
(do not require selenium to run)
|
||||
|
@ -163,10 +162,10 @@ commits, you will either have to pull in the remote changes or force push.
|
|||
|
||||
Many tests simply involve running a chunk of code in Pyodide and ensuring it
|
||||
doesn't error. In this case, one can use the `run_in_pyodide` decorate from
|
||||
`pyodide_test_runner.decorator`, e.g.
|
||||
`pytest_pyodide.decorator`, e.g.
|
||||
|
||||
```python
|
||||
from pyodide_test_runner import run_in_pyodide
|
||||
from pytest_pyodide import run_in_pyodide
|
||||
|
||||
@run_in_pyodide
|
||||
def test_add(selenium):
|
||||
|
@ -178,7 +177,7 @@ decorator can also be called with a `packages` argument to load packages before
|
|||
running the test. For example:
|
||||
|
||||
```python
|
||||
from pyodide_test_runner import run_in_pyodide
|
||||
from pytest_pyodide import run_in_pyodide
|
||||
|
||||
@run_in_pyodide(packages = ["regex"])
|
||||
def test_regex(selenium_standalone):
|
||||
|
@ -201,7 +200,7 @@ innermost decorator. Any decorators inside of `@run_in_pyodide` will be have no
|
|||
effect on the behavior of the test.
|
||||
|
||||
```python
|
||||
from pyodide_test_runner import run_in_pyodide
|
||||
from pytest_pyodide import run_in_pyodide
|
||||
|
||||
@pytest.mark.parametrize("x", [1, 2, 3])
|
||||
@run_in_pyodide(packages = ["regex"])
|
||||
|
|
|
@ -225,7 +225,8 @@ substitutions:
|
|||
{pr}`2510`, {pr}`2541`
|
||||
|
||||
- {{ Breaking }} `pyodide_build.testing` is removed. `run_in_pyodide`
|
||||
decorator can now be accessed through `pyodide_test_runner`.
|
||||
decorator can now be accessed through
|
||||
[`pytest-runner`](https://github.com/pyodide/pytest-pyodide) package.
|
||||
{pr}`2418`
|
||||
|
||||
## Version 0.20.0
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
from pyodide_test_runner import run_in_pyodide
|
||||
from pytest_pyodide import run_in_pyodide
|
||||
|
||||
|
||||
@run_in_pyodide(packages=["Jinja2"])
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
from pyodide_test_runner import run_in_pyodide
|
||||
from pytest_pyodide import run_in_pyodide
|
||||
|
||||
|
||||
@run_in_pyodide(
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
from pyodide_test_runner import run_in_pyodide
|
||||
from pytest_pyodide import run_in_pyodide
|
||||
|
||||
|
||||
@run_in_pyodide(packages=["RobotRaconteur", "numpy"])
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import pytest
|
||||
from pyodide_test_runner import run_in_pyodide
|
||||
from pytest_pyodide import run_in_pyodide
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
from pyodide_test_runner import run_in_pyodide
|
||||
from pytest_pyodide import run_in_pyodide
|
||||
|
||||
|
||||
@run_in_pyodide(packages=["bitarray-tests"])
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
from pyodide_test_runner import run_in_pyodide
|
||||
from pytest_pyodide import run_in_pyodide
|
||||
|
||||
|
||||
@run_in_pyodide(packages=["boost-histogram"])
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
from pyodide_test_runner import run_in_pyodide
|
||||
from pytest_pyodide import run_in_pyodide
|
||||
|
||||
|
||||
@run_in_pyodide(packages=["cffi"])
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import pytest
|
||||
from pyodide_test_runner import run_in_pyodide
|
||||
from pytest_pyodide import run_in_pyodide
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
from pyodide_test_runner import run_in_pyodide
|
||||
from pytest_pyodide import run_in_pyodide
|
||||
|
||||
|
||||
@run_in_pyodide(packages=["cloudpickle"])
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
from hypothesis import HealthCheck, given, settings
|
||||
from hypothesis.strategies import binary, integers
|
||||
from pyodide_test_runner import run_in_pyodide
|
||||
from pyodide_test_runner.fixture import selenium_context_manager
|
||||
from pytest_pyodide import run_in_pyodide
|
||||
from pytest_pyodide.fixture import selenium_context_manager
|
||||
|
||||
|
||||
@run_in_pyodide(packages=["cryptography"])
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
from pyodide_test_runner import run_in_pyodide
|
||||
from pytest_pyodide import run_in_pyodide
|
||||
|
||||
|
||||
@run_in_pyodide(packages=["fpcast-test"])
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
from functools import reduce
|
||||
|
||||
import pytest
|
||||
from pyodide_test_runner import run_in_pyodide
|
||||
from pytest_pyodide import run_in_pyodide
|
||||
|
||||
# Need to skip_refcount_check because we use matplotlib
|
||||
DECORATORS = [
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
from pyodide_test_runner import run_in_pyodide
|
||||
from pytest_pyodide import run_in_pyodide
|
||||
|
||||
|
||||
@run_in_pyodide(packages=["gmpy2"])
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
from pyodide_test_runner import run_in_pyodide
|
||||
from pytest_pyodide import run_in_pyodide
|
||||
|
||||
|
||||
@run_in_pyodide(packages=["numpy", "imageio"])
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
from pyodide_test_runner import run_in_pyodide
|
||||
from pytest_pyodide import run_in_pyodide
|
||||
|
||||
|
||||
@run_in_pyodide(packages=["jedi"])
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
from pyodide_test_runner import run_in_pyodide
|
||||
from pytest_pyodide import run_in_pyodide
|
||||
|
||||
|
||||
@run_in_pyodide(packages=["lazy-object-proxy"])
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
from pyodide_test_runner import run_in_pyodide
|
||||
from pytest_pyodide import run_in_pyodide
|
||||
|
||||
|
||||
@run_in_pyodide(packages=["lxml"])
|
||||
|
|
|
@ -6,7 +6,7 @@ from pathlib import Path
|
|||
from tempfile import TemporaryDirectory
|
||||
|
||||
import pytest
|
||||
from pyodide_test_runner import run_in_pyodide, spawn_web_server
|
||||
from pytest_pyodide import run_in_pyodide, spawn_web_server
|
||||
|
||||
sys.path.append(str(Path(__file__).resolve().parent / "src"))
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
from pyodide_test_runner import run_in_pyodide
|
||||
from pytest_pyodide import run_in_pyodide
|
||||
|
||||
|
||||
@run_in_pyodide(packages=["msgpack"])
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
from pyodide_test_runner import run_in_pyodide
|
||||
from pytest_pyodide import run_in_pyodide
|
||||
|
||||
|
||||
@run_in_pyodide(
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
from pyodide_test_runner import run_in_pyodide
|
||||
from pytest_pyodide import run_in_pyodide
|
||||
|
||||
|
||||
@run_in_pyodide(packages=["networkx"])
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
from pyodide_test_runner import run_in_pyodide
|
||||
from pytest_pyodide import run_in_pyodide
|
||||
|
||||
|
||||
@run_in_pyodide(
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import pytest
|
||||
from pyodide_test_runner import run_in_pyodide
|
||||
from pytest_pyodide import run_in_pyodide
|
||||
|
||||
|
||||
@pytest.mark.xfail_browsers(
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import pytest
|
||||
from pyodide_test_runner import run_in_pyodide
|
||||
from pytest_pyodide import run_in_pyodide
|
||||
|
||||
|
||||
def test_numpy(selenium):
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import base64
|
||||
import pathlib
|
||||
|
||||
from pyodide_test_runner import run_in_pyodide
|
||||
from pytest_pyodide import run_in_pyodide
|
||||
|
||||
REFERENCE_IMAGES_PATH = pathlib.Path(__file__).parent / "reference-images"
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
from pyodide_test_runner import run_in_pyodide
|
||||
from pytest_pyodide import run_in_pyodide
|
||||
|
||||
|
||||
@run_in_pyodide(packages=["pyclipper"])
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
from pyodide_test_runner import run_in_pyodide
|
||||
from pytest_pyodide import run_in_pyodide
|
||||
|
||||
|
||||
@run_in_pyodide(packages=["pyproj"])
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import pytest
|
||||
from pyodide_test_runner import run_in_pyodide
|
||||
from pytest_pyodide import run_in_pyodide
|
||||
|
||||
solvers = [
|
||||
"cadical",
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
from pyodide_test_runner import run_in_pyodide
|
||||
from pytest_pyodide import run_in_pyodide
|
||||
|
||||
|
||||
@run_in_pyodide(packages=["python_solvespace"])
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import pytest
|
||||
from pyodide_test_runner import run_in_pyodide
|
||||
from pytest_pyodide import run_in_pyodide
|
||||
|
||||
|
||||
@pytest.mark.driver_timeout(30)
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
from pyodide_test_runner import run_in_pyodide
|
||||
from pytest_pyodide import run_in_pyodide
|
||||
|
||||
|
||||
@run_in_pyodide(packages=["pyyaml"])
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
from pyodide_test_runner import run_in_pyodide
|
||||
from pytest_pyodide import run_in_pyodide
|
||||
|
||||
|
||||
@run_in_pyodide(
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
from pyodide_test_runner import run_in_pyodide
|
||||
from pytest_pyodide import run_in_pyodide
|
||||
|
||||
|
||||
@run_in_pyodide(packages=["regex"])
|
||||
|
|
|
@ -3,7 +3,7 @@ from collections.abc import Callable
|
|||
from typing import Any
|
||||
|
||||
import pytest
|
||||
from pyodide_test_runner import run_in_pyodide
|
||||
from pytest_pyodide import run_in_pyodide
|
||||
|
||||
if "CI" in os.environ:
|
||||
xfail_browsers: Callable[[Any], Any] = pytest.mark.xfail_browsers(
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import pytest
|
||||
from pyodide_test_runner.fixture import selenium_context_manager
|
||||
from pytest_pyodide.fixture import selenium_context_manager
|
||||
|
||||
|
||||
@pytest.mark.driver_timeout(40)
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import pytest
|
||||
from pyodide_test_runner import run_in_pyodide
|
||||
from pytest_pyodide import run_in_pyodide
|
||||
|
||||
|
||||
@pytest.mark.driver_timeout(40)
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
from pyodide_test_runner import run_in_pyodide
|
||||
from pytest_pyodide import run_in_pyodide
|
||||
|
||||
|
||||
@run_in_pyodide(packages=["shapely"])
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
from pyodide_test_runner import run_in_pyodide
|
||||
from pytest_pyodide import run_in_pyodide
|
||||
|
||||
|
||||
@run_in_pyodide(packages=["sharedlib-test-py"])
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import pytest
|
||||
from pyodide_test_runner import run_in_pyodide
|
||||
from pytest_pyodide import run_in_pyodide
|
||||
|
||||
|
||||
@pytest.mark.driver_timeout(40)
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
from pyodide_test_runner import run_in_pyodide
|
||||
from pytest_pyodide import run_in_pyodide
|
||||
|
||||
|
||||
@run_in_pyodide(packages=["sqlalchemy"])
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
from pyodide_test_runner import run_in_pyodide
|
||||
from pytest_pyodide import run_in_pyodide
|
||||
|
||||
|
||||
@run_in_pyodide(packages=["test", "ssl"], pytest_assert_rewrites=False)
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
from pyodide_test_runner import run_in_pyodide
|
||||
from pytest_pyodide import run_in_pyodide
|
||||
|
||||
|
||||
@run_in_pyodide(packages=["sympy"])
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
from pyodide_test_runner.decorator import run_in_pyodide
|
||||
from pytest_pyodide.decorator import run_in_pyodide
|
||||
|
||||
|
||||
@run_in_pyodide(packages=["termcolor"])
|
||||
|
|
|
@ -2,7 +2,7 @@ import functools
|
|||
import os
|
||||
|
||||
import pytest
|
||||
from pyodide_test_runner import SeleniumWrapper
|
||||
from pytest_pyodide import SeleniumWrapper
|
||||
|
||||
from conftest import ROOT_PATH, package_is_built
|
||||
from pyodide_build.io import parse_package_config
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
from pyodide_test_runner import run_in_pyodide
|
||||
from pytest_pyodide import run_in_pyodide
|
||||
|
||||
|
||||
@run_in_pyodide(
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
from pyodide_test_runner import run_in_pyodide
|
||||
from pytest_pyodide import run_in_pyodide
|
||||
|
||||
|
||||
@run_in_pyodide(packages=["wrapt"])
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
from pyodide_test_runner import run_in_pyodide
|
||||
from pytest_pyodide import run_in_pyodide
|
||||
|
||||
|
||||
@run_in_pyodide(packages=["xarray"])
|
||||
|
|
|
@ -3,7 +3,7 @@ import base64
|
|||
import pathlib
|
||||
|
||||
import pytest
|
||||
from pyodide_test_runner import run_in_pyodide
|
||||
from pytest_pyodide import run_in_pyodide
|
||||
|
||||
DEMO_PATH = pathlib.Path(__file__).parent / "test_data"
|
||||
DATA_TRAIN = base64.b64encode((DEMO_PATH / "dermatology.data").read_bytes())
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
from pyodide_test_runner import run_in_pyodide
|
||||
from pytest_pyodide import run_in_pyodide
|
||||
|
||||
|
||||
@run_in_pyodide(packages=["numpy", "numcodecs", "zarr"])
|
||||
|
|
|
@ -180,6 +180,7 @@ def test_environment_var_substitution(monkeypatch):
|
|||
)
|
||||
|
||||
|
||||
@pytest.mark.xfail(reason="FIXME: emcc is not available during test")
|
||||
def test_exports_node(tmp_path):
|
||||
template = """
|
||||
int l();
|
||||
|
|
|
@ -35,6 +35,7 @@ console_scripts =
|
|||
[options.extras_require]
|
||||
test =
|
||||
pytest
|
||||
pytest-pyodide
|
||||
|
||||
[options.packages.find]
|
||||
where = .
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
# pyodide-test-runner
|
|
@ -1,4 +0,0 @@
|
|||
pytest_plugins = [
|
||||
"pyodide_test_runner.hook",
|
||||
"pyodide_test_runner.fixture",
|
||||
]
|
|
@ -1,29 +0,0 @@
|
|||
from .browser import (
|
||||
BrowserWrapper,
|
||||
NodeWrapper,
|
||||
PlaywrightChromeWrapper,
|
||||
PlaywrightFirefoxWrapper,
|
||||
PlaywrightWrapper,
|
||||
SeleniumChromeWrapper,
|
||||
SeleniumFirefoxWrapper,
|
||||
SeleniumWrapper,
|
||||
)
|
||||
from .decorator import run_in_pyodide
|
||||
from .fixture import * # noqa: F403, F401
|
||||
from .server import spawn_web_server
|
||||
from .utils import parse_driver_timeout, set_webdriver_script_timeout
|
||||
|
||||
__all__ = [
|
||||
"BrowserWrapper",
|
||||
"SeleniumWrapper",
|
||||
"PlaywrightWrapper",
|
||||
"SeleniumFirefoxWrapper",
|
||||
"SeleniumChromeWrapper",
|
||||
"PlaywrightChromeWrapper",
|
||||
"PlaywrightFirefoxWrapper",
|
||||
"NodeWrapper",
|
||||
"set_webdriver_script_timeout",
|
||||
"parse_driver_timeout",
|
||||
"run_in_pyodide",
|
||||
"spawn_web_server",
|
||||
]
|
|
@ -1,546 +0,0 @@
|
|||
import json
|
||||
import textwrap
|
||||
from pathlib import Path
|
||||
|
||||
import pexpect
|
||||
|
||||
TEST_SETUP_CODE = """
|
||||
Error.stackTraceLimit = Infinity;
|
||||
|
||||
// Fix globalThis is messed up in firefox see facebook/react#16606.
|
||||
// Replace it with window.
|
||||
globalThis.globalThis = globalThis.window || globalThis;
|
||||
|
||||
globalThis.sleep = function (s) {
|
||||
return new Promise((resolve) => setTimeout(resolve, s));
|
||||
};
|
||||
|
||||
globalThis.assert = function (cb, message = "") {
|
||||
if (message !== "") {
|
||||
message = "\\n" + message;
|
||||
}
|
||||
if (cb() !== true) {
|
||||
throw new Error(
|
||||
`Assertion failed: ${cb.toString().slice(6)}${message}`
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
globalThis.assertAsync = async function (cb, message = "") {
|
||||
if (message !== "") {
|
||||
message = "\\n" + message;
|
||||
}
|
||||
if ((await cb()) !== true) {
|
||||
throw new Error(
|
||||
`Assertion failed: ${cb.toString().slice(12)}${message}`
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
function checkError(err, errname, pattern, pat_str, thiscallstr) {
|
||||
if (typeof pattern === "string") {
|
||||
pattern = new RegExp(pattern);
|
||||
}
|
||||
if (!err) {
|
||||
throw new Error(`${thiscallstr} failed, no error thrown`);
|
||||
}
|
||||
if (err.constructor.name !== errname) {
|
||||
throw new Error(
|
||||
`${thiscallstr} failed, expected error ` +
|
||||
`of type '${errname}' got type '${err.constructor.name}'`
|
||||
);
|
||||
}
|
||||
if (!pattern.test(err.message)) {
|
||||
throw new Error(
|
||||
`${thiscallstr} failed, expected error ` +
|
||||
`message to match pattern ${pat_str} got:\n${err.message}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
globalThis.assertThrows = function (cb, errname, pattern) {
|
||||
let pat_str = typeof pattern === "string" ? `"${pattern}"` : `${pattern}`;
|
||||
let thiscallstr = `assertThrows(${cb.toString()}, "${errname}", ${pat_str})`;
|
||||
let err = undefined;
|
||||
try {
|
||||
cb();
|
||||
} catch (e) {
|
||||
err = e;
|
||||
}
|
||||
checkError(err, errname, pattern, pat_str, thiscallstr);
|
||||
};
|
||||
|
||||
globalThis.assertThrowsAsync = async function (cb, errname, pattern) {
|
||||
let pat_str = typeof pattern === "string" ? `"${pattern}"` : `${pattern}`;
|
||||
let thiscallstr = `assertThrowsAsync(${cb.toString()}, "${errname}", ${pat_str})`;
|
||||
let err = undefined;
|
||||
try {
|
||||
await cb();
|
||||
} catch (e) {
|
||||
err = e;
|
||||
}
|
||||
checkError(err, errname, pattern, pat_str, thiscallstr);
|
||||
};
|
||||
""".strip()
|
||||
|
||||
|
||||
class JavascriptException(Exception):
|
||||
def __init__(self, msg, stack):
|
||||
self.msg = msg
|
||||
self.stack = stack
|
||||
# In chrome the stack contains the message
|
||||
if self.stack and self.stack.startswith(self.msg):
|
||||
self.msg = ""
|
||||
|
||||
def __str__(self):
|
||||
return "\n\n".join(x for x in [self.msg, self.stack] if x)
|
||||
|
||||
|
||||
class BrowserWrapper:
|
||||
browser = ""
|
||||
JavascriptException = JavascriptException
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
server_port,
|
||||
server_hostname="127.0.0.1",
|
||||
server_log=None,
|
||||
load_pyodide=True,
|
||||
script_timeout=20,
|
||||
script_type="classic",
|
||||
dist_dir=None,
|
||||
*args,
|
||||
**kwargs,
|
||||
):
|
||||
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() # type: ignore[attr-defined]
|
||||
self.set_script_timeout(script_timeout)
|
||||
self.script_timeout = script_timeout
|
||||
self.prepare_driver()
|
||||
self.javascript_setup()
|
||||
if load_pyodide:
|
||||
self.load_pyodide()
|
||||
self.initialize_global_hiwire_objects()
|
||||
self.save_state()
|
||||
self.restore_state()
|
||||
|
||||
def get_driver(self):
|
||||
raise NotImplementedError()
|
||||
|
||||
def goto(self, page):
|
||||
raise NotImplementedError()
|
||||
|
||||
def set_script_timeout(self, timeout):
|
||||
raise NotImplementedError()
|
||||
|
||||
def quit(self):
|
||||
raise NotImplementedError()
|
||||
|
||||
def refresh(self):
|
||||
raise NotImplementedError()
|
||||
|
||||
def run_js_inner(self, code, check_code):
|
||||
raise NotImplementedError()
|
||||
|
||||
def prepare_driver(self):
|
||||
if self.script_type == "classic":
|
||||
self.goto(f"{self.base_url}/test.html")
|
||||
elif self.script_type == "module":
|
||||
self.goto(f"{self.base_url}/module_test.html")
|
||||
else:
|
||||
raise Exception("Unknown script type to load!")
|
||||
|
||||
def javascript_setup(self):
|
||||
self.run_js(
|
||||
TEST_SETUP_CODE,
|
||||
pyodide_checks=False,
|
||||
)
|
||||
|
||||
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
|
||||
"""
|
||||
)
|
||||
|
||||
def initialize_global_hiwire_objects(self):
|
||||
"""
|
||||
There are a bunch of global objects that occasionally enter the hiwire cache
|
||||
but never leave. The refcount checks get angry about them if they aren't preloaded.
|
||||
We need to go through and touch them all once to keep everything okay.
|
||||
"""
|
||||
self.run_js(
|
||||
"""
|
||||
pyodide.globals.get;
|
||||
pyodide._api.pyodide_code.eval_code;
|
||||
pyodide._api.pyodide_code.eval_code_async;
|
||||
pyodide._api.pyodide_code.find_imports;
|
||||
pyodide._api.pyodide_ffi.register_js_module;
|
||||
pyodide._api.pyodide_ffi.unregister_js_module;
|
||||
pyodide._api.importlib.invalidate_caches;
|
||||
pyodide._api.package_loader.unpack_buffer;
|
||||
pyodide._api.package_loader.get_dynlibs;
|
||||
pyodide._api.package_loader.sub_resource_hash;
|
||||
pyodide.runPython("");
|
||||
pyodide.pyimport("pyodide.ffi.wrappers").destroy();
|
||||
"""
|
||||
)
|
||||
|
||||
@property
|
||||
def pyodide_loaded(self):
|
||||
return self.run_js("return !!(self.pyodide && self.pyodide.runPython);")
|
||||
|
||||
@property
|
||||
def logs(self):
|
||||
logs = self.run_js("return self.logs;", pyodide_checks=False)
|
||||
if logs is not None:
|
||||
return "\n".join(str(x) for x in logs)
|
||||
return ""
|
||||
|
||||
def clean_logs(self):
|
||||
self.run_js("self.logs = []", pyodide_checks=False)
|
||||
|
||||
def run(self, code):
|
||||
return self.run_js(
|
||||
f"""
|
||||
let result = pyodide.runPython({code!r});
|
||||
if(result && result.toJs){{
|
||||
let converted_result = result.toJs();
|
||||
if(pyodide.isPyProxy(converted_result)){{
|
||||
converted_result = undefined;
|
||||
}}
|
||||
result.destroy();
|
||||
return converted_result;
|
||||
}}
|
||||
return result;
|
||||
"""
|
||||
)
|
||||
|
||||
def run_async(self, code):
|
||||
return self.run_js(
|
||||
f"""
|
||||
await pyodide.loadPackagesFromImports({code!r})
|
||||
let result = await pyodide.runPythonAsync({code!r});
|
||||
if(result && result.toJs){{
|
||||
let converted_result = result.toJs();
|
||||
if(pyodide.isPyProxy(converted_result)){{
|
||||
converted_result = undefined;
|
||||
}}
|
||||
result.destroy();
|
||||
return converted_result;
|
||||
}}
|
||||
return result;
|
||||
"""
|
||||
)
|
||||
|
||||
def run_js(self, code, pyodide_checks=True):
|
||||
"""Run JavaScript code and check for pyodide errors"""
|
||||
if isinstance(code, str) and code.startswith("\n"):
|
||||
# we have a multiline string, fix indentation
|
||||
code = textwrap.dedent(code)
|
||||
|
||||
if pyodide_checks:
|
||||
check_code = """
|
||||
if(globalThis.pyodide && pyodide._module && pyodide._module._PyErr_Occurred()){
|
||||
try {
|
||||
pyodide._module._pythonexc2js();
|
||||
} catch(e){
|
||||
console.error(`Python exited with error flag set! Error was:\n${e.message}`);
|
||||
// Don't put original error message in new one: we want
|
||||
// "pytest.raises(xxx, match=msg)" to fail
|
||||
throw new Error(`Python exited with error flag set!`);
|
||||
}
|
||||
}
|
||||
"""
|
||||
else:
|
||||
check_code = ""
|
||||
return self.run_js_inner(code, check_code)
|
||||
|
||||
def get_num_hiwire_keys(self):
|
||||
return self.run_js("return pyodide._module.hiwire.num_keys();")
|
||||
|
||||
@property
|
||||
def force_test_fail(self) -> bool:
|
||||
return self.run_js("return !!pyodide._api.fail_test;")
|
||||
|
||||
def clear_force_test_fail(self):
|
||||
self.run_js("pyodide._api.fail_test = false;")
|
||||
|
||||
def save_state(self):
|
||||
self.run_js("self.__savedState = pyodide._api.saveState();")
|
||||
|
||||
def restore_state(self):
|
||||
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")
|
||||
|
||||
def enable_pyproxy_tracing(self):
|
||||
self.run_js("pyodide._module.enable_pyproxy_allocation_tracing()")
|
||||
|
||||
def disable_pyproxy_tracing(self):
|
||||
self.run_js("pyodide._module.disable_pyproxy_allocation_tracing()")
|
||||
|
||||
def run_webworker(self, code):
|
||||
if isinstance(code, str) and code.startswith("\n"):
|
||||
# we have a multiline string, fix indentation
|
||||
code = textwrap.dedent(code)
|
||||
|
||||
worker_file = (
|
||||
"webworker_dev.js"
|
||||
if self.script_type == "classic"
|
||||
else "module_webworker_dev.js"
|
||||
)
|
||||
|
||||
return self.run_js(
|
||||
"""
|
||||
let worker = new Worker('{}', {{ type: '{}' }});
|
||||
let res = new Promise((res, rej) => {{
|
||||
worker.onerror = e => rej(e);
|
||||
worker.onmessage = e => {{
|
||||
if (e.data.results) {{
|
||||
res(e.data.results);
|
||||
}} else {{
|
||||
rej(e.data.error);
|
||||
}}
|
||||
}};
|
||||
worker.postMessage({{ python: {!r} }});
|
||||
}});
|
||||
return await res
|
||||
""".format(
|
||||
f"http://{self.server_hostname}:{self.server_port}/{worker_file}",
|
||||
self.script_type,
|
||||
code,
|
||||
),
|
||||
pyodide_checks=False,
|
||||
)
|
||||
|
||||
def load_package(self, packages):
|
||||
self.run_js(f"await pyodide.loadPackage({packages!r})")
|
||||
|
||||
|
||||
class SeleniumWrapper(BrowserWrapper):
|
||||
def goto(self, page):
|
||||
self.driver.get(page)
|
||||
|
||||
def set_script_timeout(self, timeout):
|
||||
self.driver.set_script_timeout(timeout)
|
||||
|
||||
def quit(self):
|
||||
self.driver.quit()
|
||||
|
||||
def refresh(self):
|
||||
self.driver.refresh()
|
||||
self.javascript_setup()
|
||||
|
||||
def run_js_inner(self, code, check_code):
|
||||
wrapper = """
|
||||
let cb = arguments[arguments.length - 1];
|
||||
let run = async () => { %s }
|
||||
(async () => {
|
||||
try {
|
||||
let result = await run();
|
||||
%s
|
||||
cb([0, result]);
|
||||
} catch (e) {
|
||||
cb([1, e.toString(), e.stack, e.message]);
|
||||
}
|
||||
})()
|
||||
"""
|
||||
retval = self.driver.execute_async_script(wrapper % (code, check_code))
|
||||
if retval[0] == 0:
|
||||
return retval[1]
|
||||
else:
|
||||
print("JavascriptException message: ", retval[3])
|
||||
raise JavascriptException(retval[1], retval[2])
|
||||
|
||||
@property
|
||||
def urls(self):
|
||||
for handle in self.driver.window_handles:
|
||||
self.driver.switch_to.window(handle)
|
||||
yield self.driver.current_url
|
||||
|
||||
|
||||
class PlaywrightWrapper(BrowserWrapper):
|
||||
def __init__(self, browsers, *args, **kwargs):
|
||||
self.browsers = browsers
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def goto(self, page):
|
||||
self.driver.goto(page)
|
||||
|
||||
def get_driver(self):
|
||||
return self.browsers[self.browser].new_page()
|
||||
|
||||
def set_script_timeout(self, timeout):
|
||||
# playwright uses milliseconds for timeout
|
||||
self.driver.set_default_timeout(timeout * 1000)
|
||||
|
||||
def quit(self):
|
||||
self.driver.close()
|
||||
|
||||
def refresh(self):
|
||||
self.driver.reload()
|
||||
self.javascript_setup()
|
||||
|
||||
def run_js_inner(self, code, check_code):
|
||||
# playwright `evaluate` waits until primise to resolve,
|
||||
# so we don't need to use a callback like selenium.
|
||||
wrapper = """
|
||||
let run = async () => { %s }
|
||||
(async () => {
|
||||
try {
|
||||
let result = await run();
|
||||
%s
|
||||
return [0, result];
|
||||
} catch (e) {
|
||||
return [1, e.toString(), e.stack];
|
||||
}
|
||||
})()
|
||||
"""
|
||||
retval = self.driver.evaluate(wrapper % (code, check_code))
|
||||
if retval[0] == 0:
|
||||
return retval[1]
|
||||
else:
|
||||
raise JavascriptException(retval[1], retval[2])
|
||||
|
||||
|
||||
class SeleniumFirefoxWrapper(SeleniumWrapper):
|
||||
|
||||
browser = "firefox"
|
||||
|
||||
def get_driver(self):
|
||||
from selenium.webdriver import Firefox
|
||||
from selenium.webdriver.firefox.options import Options
|
||||
|
||||
options = Options()
|
||||
options.add_argument("--headless")
|
||||
|
||||
return Firefox(executable_path="geckodriver", options=options)
|
||||
|
||||
|
||||
class SeleniumChromeWrapper(SeleniumWrapper):
|
||||
|
||||
browser = "chrome"
|
||||
|
||||
def get_driver(self):
|
||||
from selenium.webdriver import Chrome
|
||||
from selenium.webdriver.chrome.options import Options
|
||||
|
||||
options = Options()
|
||||
options.add_argument("--headless")
|
||||
options.add_argument("--no-sandbox")
|
||||
options.add_argument("--js-flags=--expose-gc")
|
||||
return Chrome(options=options)
|
||||
|
||||
def collect_garbage(self):
|
||||
self.driver.execute_cdp_cmd("HeapProfiler.collectGarbage", {})
|
||||
|
||||
|
||||
class PlaywrightChromeWrapper(PlaywrightWrapper):
|
||||
browser = "chrome"
|
||||
|
||||
def collect_garbage(self):
|
||||
client = self.driver.context.new_cdp_session(self.driver)
|
||||
client.send("HeapProfiler.collectGarbage")
|
||||
|
||||
|
||||
class PlaywrightFirefoxWrapper(PlaywrightWrapper):
|
||||
browser = "firefox"
|
||||
|
||||
|
||||
class NodeWrapper(BrowserWrapper):
|
||||
browser = "node"
|
||||
|
||||
def init_node(self):
|
||||
curdir = Path(__file__).parent
|
||||
self.p = pexpect.spawn("/bin/bash", timeout=60)
|
||||
self.p.setecho(False)
|
||||
self.p.delaybeforesend = None
|
||||
# disable canonical input processing mode to allow sending longer lines
|
||||
# See: https://pexpect.readthedocs.io/en/stable/api/pexpect.html#pexpect.spawn.send
|
||||
self.p.sendline("stty -icanon")
|
||||
self.p.sendline(
|
||||
f"node --expose-gc --experimental-wasm-bigint {curdir}/node_test_driver.js {self.base_url} {self.dist_dir}",
|
||||
)
|
||||
|
||||
try:
|
||||
self.p.expect_exact("READY!!")
|
||||
except pexpect.exceptions.EOF:
|
||||
raise JavascriptException("", self.p.before.decode())
|
||||
|
||||
def get_driver(self):
|
||||
self._logs = []
|
||||
self.init_node()
|
||||
|
||||
class NodeDriver:
|
||||
def __getattr__(self, x):
|
||||
raise NotImplementedError()
|
||||
|
||||
return NodeDriver()
|
||||
|
||||
def prepare_driver(self):
|
||||
pass
|
||||
|
||||
def set_script_timeout(self, timeout):
|
||||
self._timeout = timeout
|
||||
|
||||
def quit(self):
|
||||
self.p.sendeof()
|
||||
|
||||
def refresh(self):
|
||||
self.quit()
|
||||
self.init_node()
|
||||
self.javascript_setup()
|
||||
|
||||
def collect_garbage(self):
|
||||
self.run_js("gc()")
|
||||
|
||||
@property
|
||||
def logs(self):
|
||||
return "\n".join(self._logs)
|
||||
|
||||
def clean_logs(self):
|
||||
self._logs = []
|
||||
|
||||
def run_js_inner(self, code, check_code):
|
||||
check_code = ""
|
||||
wrapped = """
|
||||
let result = await (async () => {{ {} }})();
|
||||
{}
|
||||
return result;
|
||||
""".format(
|
||||
code,
|
||||
check_code,
|
||||
)
|
||||
from uuid import uuid4
|
||||
|
||||
cmd_id = str(uuid4())
|
||||
self.p.sendline(cmd_id)
|
||||
self.p.sendline(wrapped)
|
||||
self.p.sendline(cmd_id)
|
||||
self.p.expect_exact(f"{cmd_id}:UUID\r\n", timeout=self._timeout)
|
||||
self.p.expect_exact(f"{cmd_id}:UUID\r\n")
|
||||
if self.p.before:
|
||||
self._logs.append(self.p.before.decode()[:-2].replace("\r", ""))
|
||||
self.p.expect("[01]\r\n")
|
||||
success = int(self.p.match[0].decode()[0]) == 0
|
||||
self.p.expect_exact(f"\r\n{cmd_id}:UUID\r\n")
|
||||
if success:
|
||||
return json.loads(self.p.before.decode().replace("undefined", "null"))
|
||||
else:
|
||||
raise JavascriptException("", self.p.before.decode())
|
|
@ -1,300 +0,0 @@
|
|||
import ast
|
||||
import pickle
|
||||
import sys
|
||||
from base64 import b64decode, b64encode
|
||||
from collections.abc import Callable, Collection
|
||||
from copy import deepcopy
|
||||
from typing import Any
|
||||
|
||||
import pytest
|
||||
|
||||
from .hook import ORIGINAL_MODULE_ASTS, REWRITTEN_MODULE_ASTS
|
||||
from .utils import package_is_built as _package_is_built
|
||||
|
||||
|
||||
def package_is_built(package_name):
|
||||
return _package_is_built(package_name, pytest.pyodide_dist_dir)
|
||||
|
||||
|
||||
class SeleniumType:
|
||||
JavascriptException: type
|
||||
browser: str
|
||||
|
||||
def load_package(self, *args, **kwargs):
|
||||
...
|
||||
|
||||
def run_async(self, code: str):
|
||||
...
|
||||
|
||||
|
||||
def _encode(obj: Any) -> str:
|
||||
"""
|
||||
Pickle and base 64 encode obj so we can send it to Pyodide using string
|
||||
templating.
|
||||
"""
|
||||
return b64encode(pickle.dumps(obj)).decode()
|
||||
|
||||
|
||||
def _create_outer_test_function(
|
||||
run_test: Callable,
|
||||
node: ast.stmt,
|
||||
) -> Callable:
|
||||
"""
|
||||
Create the top level item: it will be called by pytest and it calls
|
||||
run_test.
|
||||
|
||||
If the original function looked like:
|
||||
|
||||
@outer_decorators
|
||||
@run_in_pyodide
|
||||
@inner_decorators
|
||||
<async?> def func(<selenium_arg_name>, arg1, arg2, arg3):
|
||||
# do stuff
|
||||
|
||||
This wrapper looks like:
|
||||
|
||||
def <func_name>(<selenium_arg_name>, arg1, arg2, arg3):
|
||||
run_test(<selenium_arg_name>, (arg1, arg2, arg3))
|
||||
|
||||
Any inner_decorators get ignored. Any outer_decorators get applied by
|
||||
the Python interpreter via the normal mechanism
|
||||
"""
|
||||
node_args = deepcopy(node.args)
|
||||
if not node_args.args:
|
||||
raise ValueError(
|
||||
f"Function {node.name} should take at least one argument whose name should start with 'selenium'"
|
||||
)
|
||||
|
||||
selenium_arg_name = node_args.args[0].arg
|
||||
if not selenium_arg_name.startswith("selenium"):
|
||||
raise ValueError(
|
||||
f"Function {node.name}'s first argument name '{selenium_arg_name}' should start with 'selenium'"
|
||||
)
|
||||
|
||||
new_node = ast.FunctionDef(
|
||||
name=node.name, args=node_args, body=[], lineno=1, decorator_list=[]
|
||||
)
|
||||
|
||||
run_test_id = "run-test-not-valid-identifier"
|
||||
|
||||
# Make onwards call with two args:
|
||||
# 1. <selenium_arg_name>
|
||||
# 2. all other arguments in a tuple
|
||||
func_body = ast.parse("return run_test(selenium_arg_name, (arg1, arg2, ...))").body
|
||||
onwards_call = func_body[0].value
|
||||
onwards_call.func = ast.Name(id=run_test_id, ctx=ast.Load())
|
||||
onwards_call.args[0].id = selenium_arg_name # Set variable name
|
||||
onwards_call.args[1].elts = [ # Set tuple elements
|
||||
ast.Name(id=arg.arg, ctx=ast.Load()) for arg in node_args.args[1:]
|
||||
]
|
||||
|
||||
# Add extra <selenium_arg_name> argument
|
||||
new_node.body = func_body
|
||||
|
||||
# Make a best effort to show something that isn't total nonsense in the
|
||||
# traceback for the generated function when there is an error.
|
||||
# This will show:
|
||||
# > run_test(selenium_arg_name, (arg1, arg2, ...))
|
||||
# in the traceback.
|
||||
def fake_body_for_traceback(arg1, arg2, selenium_arg_name):
|
||||
run_test(selenium_arg_name, (arg1, arg2, ...))
|
||||
|
||||
# Adjust line numbers to point into our fake function
|
||||
lineno = fake_body_for_traceback.__code__.co_firstlineno
|
||||
ast.increment_lineno(new_node, lineno)
|
||||
|
||||
mod = ast.Module([new_node], type_ignores=[])
|
||||
ast.fix_missing_locations(mod)
|
||||
co = compile(mod, __file__, "exec")
|
||||
|
||||
# Need to give our code access to the actual "run_test" object which it
|
||||
# invokes.
|
||||
globs = {run_test_id: run_test}
|
||||
exec(co, globs)
|
||||
|
||||
return globs[node.name]
|
||||
|
||||
|
||||
class run_in_pyodide:
|
||||
def __new__(cls, function: Callable | None = None, /, **kwargs):
|
||||
if function:
|
||||
# Probably we were used like:
|
||||
#
|
||||
# @run_in_pyodide
|
||||
# def f():
|
||||
# pass
|
||||
return run_in_pyodide(**kwargs)(function)
|
||||
else:
|
||||
# Just do normal __new__ behavior
|
||||
return object.__new__(cls)
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
packages: Collection[str] = (),
|
||||
pytest_assert_rewrites: bool = True,
|
||||
*,
|
||||
_force_assert_rewrites: bool = False,
|
||||
):
|
||||
"""
|
||||
This decorator can be called in two ways --- with arguments and without
|
||||
arguments. If it is called without arguments, then the `function` argument
|
||||
catches the function the decorator is applied to. Otherwise, standalone and
|
||||
packages are the actual arguments to the decorator.
|
||||
|
||||
See docs/testing.md for details on how to use this.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
packages : List[str]
|
||||
List of packages to load before running the test
|
||||
|
||||
pytest_assert_rewrites : bool, default = True
|
||||
If True, use pytest assertion rewrites. This gives better error messages
|
||||
when an assertion fails, but requires us to load pytest.
|
||||
"""
|
||||
|
||||
self._pkgs = list(packages)
|
||||
self._pytest_not_built = False
|
||||
if (
|
||||
pytest_assert_rewrites
|
||||
and not package_is_built("pytest")
|
||||
and not _force_assert_rewrites
|
||||
):
|
||||
pytest_assert_rewrites = False
|
||||
self._pytest_not_built = True
|
||||
|
||||
if pytest_assert_rewrites:
|
||||
self._pkgs.append("pytest")
|
||||
|
||||
self._module_asts_dict = (
|
||||
REWRITTEN_MODULE_ASTS if pytest_assert_rewrites else ORIGINAL_MODULE_ASTS
|
||||
)
|
||||
|
||||
if package_is_built("tblib"):
|
||||
self._pkgs.append("tblib")
|
||||
|
||||
self._pytest_assert_rewrites = pytest_assert_rewrites
|
||||
|
||||
def _code_template(self, args: tuple) -> str:
|
||||
"""
|
||||
Unpickle function ast and its arguments, compile and call function, and
|
||||
if the function is async await the result. Last, if there was an
|
||||
exception, pickle it and send it back.
|
||||
"""
|
||||
return f"""
|
||||
async def __tmp():
|
||||
from base64 import b64encode, b64decode
|
||||
import pickle
|
||||
mod = pickle.loads(b64decode({_encode(self._mod)!r}))
|
||||
args = pickle.loads(b64decode({_encode(args)!r}))
|
||||
co = compile(mod, {self._module_filename!r}, "exec")
|
||||
d = {{}}
|
||||
exec(co, d)
|
||||
def encode(x):
|
||||
return b64encode(pickle.dumps(x)).decode()
|
||||
try:
|
||||
result = d[{self._func_name!r}](None, *args)
|
||||
if {self._async_func}:
|
||||
result = await result
|
||||
return [0, encode(result)]
|
||||
except BaseException as e:
|
||||
try:
|
||||
from tblib import pickling_support
|
||||
pickling_support.install()
|
||||
except ImportError:
|
||||
pass
|
||||
return [1, encode(e)]
|
||||
|
||||
try:
|
||||
result = await __tmp()
|
||||
finally:
|
||||
del __tmp
|
||||
result
|
||||
"""
|
||||
|
||||
def _run_test(self, selenium: SeleniumType, args: tuple):
|
||||
"""The main test runner, called from the AST generated in
|
||||
_create_outer_test_function."""
|
||||
code = self._code_template(args)
|
||||
if self._pkgs:
|
||||
selenium.load_package(self._pkgs)
|
||||
|
||||
r = selenium.run_async(code)
|
||||
[status, result] = r
|
||||
|
||||
result = pickle.loads(b64decode(result))
|
||||
if status:
|
||||
raise result
|
||||
else:
|
||||
return result
|
||||
|
||||
def _generate_pyodide_ast(
|
||||
self, module_ast: ast.Module, funcname: str, func_line_no: int
|
||||
) -> tuple[ast.Module, bool, ast.expr]:
|
||||
"""Generates appropriate AST for the test to run in Pyodide.
|
||||
|
||||
The test ast includes mypy magic imports and the test function definition.
|
||||
This will be pickled and sent to Pyodide.
|
||||
"""
|
||||
nodes: list[ast.stmt] = []
|
||||
it = iter(module_ast.body)
|
||||
while True:
|
||||
try:
|
||||
node = next(it)
|
||||
except StopIteration:
|
||||
raise Exception(
|
||||
f"Didn't find function {funcname} (line {func_line_no}) in module."
|
||||
) from None
|
||||
# We need to include the magic imports that pytest inserts
|
||||
if (
|
||||
isinstance(node, ast.Import)
|
||||
and node.names[0].asname
|
||||
and node.names[0].asname.startswith("@")
|
||||
):
|
||||
nodes.append(node)
|
||||
|
||||
if (
|
||||
node.end_lineno
|
||||
and node.end_lineno > func_line_no
|
||||
and node.lineno < func_line_no
|
||||
):
|
||||
it = iter(node.body)
|
||||
continue
|
||||
|
||||
# We also want the function definition for the current test
|
||||
if not isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
|
||||
continue
|
||||
|
||||
if node.lineno < func_line_no:
|
||||
continue
|
||||
|
||||
if node.name != funcname:
|
||||
raise RuntimeError(
|
||||
f"Internal run_in_pyodide error: looking for function '{funcname}' but found '{node.name}'"
|
||||
)
|
||||
|
||||
self._async_func = isinstance(node, ast.AsyncFunctionDef)
|
||||
node.decorator_list = []
|
||||
nodes.append(node)
|
||||
break
|
||||
|
||||
self._mod = ast.Module(nodes, type_ignores=[])
|
||||
ast.fix_missing_locations(self._mod)
|
||||
|
||||
self._node = node
|
||||
|
||||
def __call__(self, f: Callable) -> Callable:
|
||||
func_name = f.__name__
|
||||
module_filename = sys.modules[f.__module__].__file__ or ""
|
||||
module_ast = self._module_asts_dict[module_filename]
|
||||
|
||||
func_line_no = f.__code__.co_firstlineno
|
||||
|
||||
# _code_template needs this info.
|
||||
self._generate_pyodide_ast(module_ast, func_name, func_line_no)
|
||||
self._func_name = func_name
|
||||
self._module_filename = module_filename
|
||||
|
||||
wrapper = _create_outer_test_function(self._run_test, self._node)
|
||||
|
||||
return wrapper
|
|
@ -1,239 +0,0 @@
|
|||
import contextlib
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from .browser import (
|
||||
BrowserWrapper,
|
||||
NodeWrapper,
|
||||
PlaywrightChromeWrapper,
|
||||
PlaywrightFirefoxWrapper,
|
||||
SeleniumChromeWrapper,
|
||||
SeleniumFirefoxWrapper,
|
||||
)
|
||||
from .server import spawn_web_server
|
||||
from .utils import parse_driver_timeout, set_webdriver_script_timeout
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def playwright_browsers(request):
|
||||
if request.config.option.runner.lower() != "playwright":
|
||||
yield {}
|
||||
else:
|
||||
# import playwright here to allow running tests without playwright installation
|
||||
try:
|
||||
from playwright.sync_api import sync_playwright
|
||||
except ImportError:
|
||||
pytest.exit(
|
||||
"playwright not installed. try `pip install playwright && python -m playwright install`",
|
||||
returncode=1,
|
||||
)
|
||||
|
||||
with sync_playwright() as p:
|
||||
try:
|
||||
chromium = p.chromium.launch(
|
||||
args=[
|
||||
"--js-flags=--expose-gc",
|
||||
],
|
||||
)
|
||||
firefox = p.firefox.launch()
|
||||
# webkit = p.webkit.launch()
|
||||
except Exception as e:
|
||||
pytest.exit(f"playwright failed to launch\n{e}", returncode=1)
|
||||
try:
|
||||
yield {
|
||||
"chrome": chromium,
|
||||
"firefox": firefox,
|
||||
# "webkit": webkit,
|
||||
}
|
||||
finally:
|
||||
chromium.close()
|
||||
firefox.close()
|
||||
# webkit.close()
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def selenium_common(
|
||||
request, web_server_main, load_pyodide=True, script_type="classic", browsers=None
|
||||
):
|
||||
"""Returns an initialized selenium object.
|
||||
|
||||
If `_should_skip_test` indicate that the test will be skipped,
|
||||
return None, as initializing Pyodide for selenium is expensive
|
||||
"""
|
||||
|
||||
server_hostname, server_port, server_log = web_server_main
|
||||
runner_type = request.config.option.runner.lower()
|
||||
cls: type[BrowserWrapper]
|
||||
|
||||
browser_set = {
|
||||
("selenium", "firefox"): SeleniumFirefoxWrapper,
|
||||
("selenium", "chrome"): SeleniumChromeWrapper,
|
||||
("selenium", "node"): NodeWrapper,
|
||||
("playwright", "firefox"): PlaywrightFirefoxWrapper,
|
||||
("playwright", "chrome"): PlaywrightChromeWrapper,
|
||||
("playwright", "node"): NodeWrapper,
|
||||
}
|
||||
|
||||
cls = browser_set.get((runner_type, request.param))
|
||||
if cls is None:
|
||||
raise AssertionError(
|
||||
f"Unknown runner or browser: {runner_type} / {request.param}"
|
||||
)
|
||||
|
||||
dist_dir = Path(os.getcwd(), request.config.getoption("--dist-dir"))
|
||||
runner = cls(
|
||||
server_port=server_port,
|
||||
server_hostname=server_hostname,
|
||||
server_log=server_log,
|
||||
load_pyodide=load_pyodide,
|
||||
browsers=browsers,
|
||||
script_type=script_type,
|
||||
dist_dir=dist_dir,
|
||||
)
|
||||
try:
|
||||
yield runner
|
||||
finally:
|
||||
runner.quit()
|
||||
|
||||
|
||||
@pytest.fixture(params=["firefox", "chrome", "node"], scope="function")
|
||||
def selenium_standalone(request, web_server_main, playwright_browsers):
|
||||
with selenium_common(
|
||||
request, web_server_main, browsers=playwright_browsers
|
||||
) as selenium:
|
||||
with set_webdriver_script_timeout(
|
||||
selenium, script_timeout=parse_driver_timeout(request.node)
|
||||
):
|
||||
try:
|
||||
yield selenium
|
||||
finally:
|
||||
print(selenium.logs)
|
||||
|
||||
|
||||
@pytest.fixture(params=["firefox", "chrome", "node"], scope="module")
|
||||
def selenium_esm(request, web_server_main, playwright_browsers):
|
||||
with selenium_common(
|
||||
request,
|
||||
web_server_main,
|
||||
load_pyodide=True,
|
||||
browsers=playwright_browsers,
|
||||
script_type="module",
|
||||
) as selenium:
|
||||
with set_webdriver_script_timeout(
|
||||
selenium, script_timeout=parse_driver_timeout(request.node)
|
||||
):
|
||||
try:
|
||||
yield selenium
|
||||
finally:
|
||||
print(selenium.logs)
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def selenium_standalone_noload_common(
|
||||
request, web_server_main, playwright_browsers, script_type="classic"
|
||||
):
|
||||
with selenium_common(
|
||||
request,
|
||||
web_server_main,
|
||||
load_pyodide=False,
|
||||
browsers=playwright_browsers,
|
||||
script_type=script_type,
|
||||
) as selenium:
|
||||
with set_webdriver_script_timeout(
|
||||
selenium, script_timeout=parse_driver_timeout(request.node)
|
||||
):
|
||||
try:
|
||||
yield selenium
|
||||
finally:
|
||||
print(selenium.logs)
|
||||
|
||||
|
||||
@pytest.fixture(params=["firefox", "chrome"], scope="function")
|
||||
def selenium_webworker_standalone(
|
||||
request, web_server_main, playwright_browsers, script_type
|
||||
):
|
||||
# Avoid loading the fixture if the test is going to be skipped
|
||||
if request.param == "firefox" and script_type == "module":
|
||||
pytest.skip("firefox does not support module type web worker")
|
||||
|
||||
with selenium_standalone_noload_common(
|
||||
request, web_server_main, playwright_browsers, script_type=script_type
|
||||
) as selenium:
|
||||
yield selenium
|
||||
|
||||
|
||||
@pytest.fixture(params=["firefox", "chrome", "node"], scope="function")
|
||||
def selenium_standalone_noload(request, web_server_main, playwright_browsers):
|
||||
"""Only difference between this and selenium_webworker_standalone is that
|
||||
this also tests on node."""
|
||||
|
||||
with selenium_standalone_noload_common(
|
||||
request, web_server_main, playwright_browsers
|
||||
) as selenium:
|
||||
yield selenium
|
||||
|
||||
|
||||
# selenium instance cached at the module level
|
||||
@pytest.fixture(params=["firefox", "chrome", "node"], scope="module")
|
||||
def selenium_module_scope(request, web_server_main, playwright_browsers):
|
||||
with selenium_common(
|
||||
request, web_server_main, browsers=playwright_browsers
|
||||
) as selenium:
|
||||
yield selenium
|
||||
|
||||
|
||||
# Hypothesis is unhappy with function scope fixtures. Instead, use the
|
||||
# module scope fixture `selenium_module_scope` and use:
|
||||
# `with selenium_context_manager(selenium_module_scope) as selenium`
|
||||
@contextlib.contextmanager
|
||||
def selenium_context_manager(selenium_module_scope):
|
||||
try:
|
||||
selenium_module_scope.clean_logs()
|
||||
yield selenium_module_scope
|
||||
finally:
|
||||
print(selenium_module_scope.logs)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def selenium(request, selenium_module_scope):
|
||||
with selenium_context_manager(selenium_module_scope) as selenium:
|
||||
with set_webdriver_script_timeout(
|
||||
selenium, script_timeout=parse_driver_timeout(request.node)
|
||||
):
|
||||
yield selenium
|
||||
|
||||
|
||||
@pytest.fixture(params=["firefox", "chrome"], scope="function")
|
||||
def console_html_fixture(request, web_server_main, playwright_browsers):
|
||||
with selenium_common(
|
||||
request, web_server_main, load_pyodide=False, browsers=playwright_browsers
|
||||
) as selenium:
|
||||
selenium.goto(
|
||||
f"http://{selenium.server_hostname}:{selenium.server_port}/console.html"
|
||||
)
|
||||
selenium.javascript_setup()
|
||||
try:
|
||||
yield selenium
|
||||
finally:
|
||||
print(selenium.logs)
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def web_server_main(request):
|
||||
"""Web server that serves files in the dist directory"""
|
||||
with spawn_web_server(request.config.option.dist_dir) as output:
|
||||
yield output
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def web_server_secondary(request):
|
||||
"""Secondary web server that serves files dist directory"""
|
||||
with spawn_web_server(request.config.option.dist_dir) as output:
|
||||
yield output
|
||||
|
||||
|
||||
@pytest.fixture(params=["classic", "module"], scope="module")
|
||||
def script_type(request):
|
||||
return request.param
|
|
@ -1,91 +0,0 @@
|
|||
import ast
|
||||
import sys
|
||||
from copy import deepcopy
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
import pytest
|
||||
from _pytest.assertion.rewrite import AssertionRewritingHook, rewrite_asserts
|
||||
from _pytest.python import (
|
||||
pytest_pycollect_makemodule as orig_pytest_pycollect_makemodule,
|
||||
)
|
||||
|
||||
|
||||
def pytest_configure(config):
|
||||
|
||||
config.addinivalue_line(
|
||||
"markers",
|
||||
"skip_refcount_check: Don't run refcount checks",
|
||||
)
|
||||
|
||||
config.addinivalue_line(
|
||||
"markers",
|
||||
"skip_pyproxy_check: Don't run pyproxy allocation checks",
|
||||
)
|
||||
|
||||
config.addinivalue_line(
|
||||
"markers",
|
||||
"driver_timeout: Set script timeout in WebDriver",
|
||||
)
|
||||
|
||||
config.addinivalue_line(
|
||||
"markers",
|
||||
"xfail_browsers: xfail a test in specific browsers",
|
||||
)
|
||||
|
||||
pytest.pyodide_dist_dir = config.getoption("--dist-dir")
|
||||
|
||||
|
||||
@pytest.hookimpl(tryfirst=True)
|
||||
def pytest_addoption(parser):
|
||||
group = parser.getgroup("general")
|
||||
group.addoption(
|
||||
"--dist-dir",
|
||||
action="store",
|
||||
default="pyodide",
|
||||
help="Path to the pyodide dist directory",
|
||||
type=Path,
|
||||
)
|
||||
group.addoption(
|
||||
"--runner",
|
||||
default="selenium",
|
||||
choices=["selenium", "playwright"],
|
||||
help="Select testing frameworks, selenium or playwright (default: %(default)s)",
|
||||
)
|
||||
|
||||
|
||||
# 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] = {}
|
||||
|
||||
|
||||
def pytest_pycollect_makemodule(module_path: Path, path: Any, parent: Any) -> None:
|
||||
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)
|
|
@ -1,60 +0,0 @@
|
|||
import pickle
|
||||
from zoneinfo import ZoneInfo
|
||||
|
||||
from hypothesis import HealthCheck, settings, strategies
|
||||
|
||||
|
||||
def is_picklable(x):
|
||||
try:
|
||||
pickle.dumps(x)
|
||||
return True
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
||||
def is_equal_to_self(x):
|
||||
try:
|
||||
return x == x
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
||||
try:
|
||||
from exceptiongroup import ExceptionGroup
|
||||
except ImportError:
|
||||
|
||||
class ExceptionGroup:
|
||||
pass
|
||||
|
||||
|
||||
# Generate an object of any type
|
||||
any_strategy = (
|
||||
strategies.from_type(type)
|
||||
.flatmap(strategies.from_type)
|
||||
.filter(lambda x: not isinstance(x, ZoneInfo))
|
||||
.filter(is_picklable)
|
||||
.filter(lambda x: not isinstance(x, ExceptionGroup))
|
||||
)
|
||||
|
||||
any_equal_to_self_strategy = any_strategy.filter(is_equal_to_self)
|
||||
|
||||
std_hypothesis_settings = settings(
|
||||
deadline=2000,
|
||||
suppress_health_check=[HealthCheck.function_scoped_fixture],
|
||||
)
|
||||
|
||||
|
||||
def is_picklable(x):
|
||||
try:
|
||||
pickle.dumps(x)
|
||||
return True
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
||||
strategy = (
|
||||
strategies.from_type(type)
|
||||
.flatmap(strategies.from_type)
|
||||
.filter(lambda x: not isinstance(x, ZoneInfo))
|
||||
.filter(is_picklable)
|
||||
)
|
|
@ -1,84 +0,0 @@
|
|||
const vm = require("vm");
|
||||
const readline = require("readline");
|
||||
const path = require("path");
|
||||
const util = require("util");
|
||||
const node_fetch = require("node-fetch");
|
||||
|
||||
let baseUrl = process.argv[2];
|
||||
let distDir = process.argv[3];
|
||||
|
||||
let { loadPyodide } = require(`${distDir}/pyodide`);
|
||||
process.chdir(distDir);
|
||||
|
||||
// node requires full paths.
|
||||
function fetch(path) {
|
||||
return node_fetch(new URL(path, baseUrl).toString());
|
||||
}
|
||||
|
||||
const context = {
|
||||
loadPyodide,
|
||||
path,
|
||||
process,
|
||||
require,
|
||||
fetch,
|
||||
setTimeout,
|
||||
TextDecoder: util.TextDecoder,
|
||||
TextEncoder: util.TextEncoder,
|
||||
URL,
|
||||
clearInterval,
|
||||
clearTimeout,
|
||||
setInterval,
|
||||
setTimeout,
|
||||
};
|
||||
vm.createContext(context);
|
||||
vm.runInContext("globalThis.self = globalThis;", context);
|
||||
|
||||
// Get rid of all colors in output of console.log, they mess us up.
|
||||
for (let key of Object.keys(util.inspect.styles)) {
|
||||
util.inspect.styles[key] = undefined;
|
||||
}
|
||||
|
||||
const rl = readline.createInterface({
|
||||
input: process.stdin,
|
||||
output: process.stdout,
|
||||
terminal: false,
|
||||
});
|
||||
|
||||
let cur_code = "";
|
||||
let cur_uuid;
|
||||
rl.on("line", async function (line) {
|
||||
if (!cur_uuid) {
|
||||
cur_uuid = line;
|
||||
return;
|
||||
}
|
||||
if (line !== cur_uuid) {
|
||||
cur_code += line + "\n";
|
||||
} else {
|
||||
evalCode(cur_uuid, cur_code, context);
|
||||
cur_code = "";
|
||||
cur_uuid = undefined;
|
||||
}
|
||||
});
|
||||
|
||||
async function evalCode(uuid, code, eval_context) {
|
||||
let p = new Promise((resolve, reject) => {
|
||||
eval_context.___outer_resolve = resolve;
|
||||
eval_context.___outer_reject = reject;
|
||||
});
|
||||
let wrapped_code = `
|
||||
(async function(){
|
||||
${code}
|
||||
})().then(___outer_resolve).catch(___outer_reject);
|
||||
`;
|
||||
let delim = uuid + ":UUID";
|
||||
console.log(delim);
|
||||
try {
|
||||
vm.runInContext(wrapped_code, eval_context);
|
||||
let result = JSON.stringify(await p);
|
||||
console.log(`${delim}\n0\n${result}\n${delim}`);
|
||||
} catch (e) {
|
||||
console.log(`${delim}\n1\n${e.stack}\n${delim}`);
|
||||
}
|
||||
}
|
||||
console.log("READY!!");
|
||||
// evalCode("xxx", "let pyodide = await loadPyodide(); pyodide.runPython(`print([x*x+1 for x in range(10)])`);", context);
|
|
@ -1,82 +0,0 @@
|
|||
import contextlib
|
||||
import http.server
|
||||
import multiprocessing
|
||||
import os
|
||||
import pathlib
|
||||
import queue
|
||||
import shutil
|
||||
import socketserver
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def spawn_web_server(dist_dir):
|
||||
|
||||
tmp_dir = tempfile.mkdtemp()
|
||||
log_path = pathlib.Path(tmp_dir) / "http-server.log"
|
||||
q: multiprocessing.Queue[str] = multiprocessing.Queue()
|
||||
p = multiprocessing.Process(target=run_web_server, args=(q, log_path, dist_dir))
|
||||
|
||||
try:
|
||||
p.start()
|
||||
port = q.get()
|
||||
hostname = "127.0.0.1"
|
||||
|
||||
print(
|
||||
f"Spawning webserver at http://{hostname}:{port} "
|
||||
f"(see logs in {log_path})"
|
||||
)
|
||||
yield hostname, port, log_path
|
||||
finally:
|
||||
q.put("TERMINATE")
|
||||
p.join()
|
||||
shutil.rmtree(tmp_dir)
|
||||
|
||||
|
||||
def run_web_server(q, log_filepath, dist_dir):
|
||||
"""Start the HTTP web server
|
||||
|
||||
Parameters
|
||||
----------
|
||||
q : Queue
|
||||
communication queue
|
||||
log_path : pathlib.Path
|
||||
path to the file where to store the logs
|
||||
"""
|
||||
|
||||
os.chdir(dist_dir)
|
||||
|
||||
log_fh = log_filepath.open("w", buffering=1)
|
||||
sys.stdout = log_fh
|
||||
sys.stderr = log_fh
|
||||
|
||||
class Handler(http.server.SimpleHTTPRequestHandler):
|
||||
def log_message(self, format_, *args):
|
||||
print(
|
||||
"[%s] source: %s:%s - %s"
|
||||
% (self.log_date_time_string(), *self.client_address, format_ % args)
|
||||
)
|
||||
|
||||
def end_headers(self):
|
||||
# Enable Cross-Origin Resource Sharing (CORS)
|
||||
self.send_header("Access-Control-Allow-Origin", "*")
|
||||
super().end_headers()
|
||||
|
||||
with socketserver.TCPServer(("", 0), Handler) as httpd:
|
||||
host, port = httpd.server_address
|
||||
print(f"Starting webserver at http://{host}:{port}")
|
||||
httpd.server_name = "test-server" # type: ignore[attr-defined]
|
||||
httpd.server_port = port # type: ignore[attr-defined]
|
||||
q.put(port)
|
||||
|
||||
def service_actions():
|
||||
try:
|
||||
if q.get(False) == "TERMINATE":
|
||||
print("Stopping server...")
|
||||
sys.exit(0)
|
||||
except queue.Empty:
|
||||
pass
|
||||
|
||||
httpd.service_actions = service_actions # type: ignore[assignment]
|
||||
httpd.serve_forever()
|
|
@ -1,198 +0,0 @@
|
|||
import pytest
|
||||
from hypothesis import given, settings
|
||||
from pyodide_test_runner.decorator import run_in_pyodide
|
||||
from pyodide_test_runner.hypothesis import any_strategy, std_hypothesis_settings
|
||||
from pyodide_test_runner.utils import parse_driver_timeout
|
||||
|
||||
|
||||
@run_in_pyodide
|
||||
def example_func(selenium):
|
||||
pass
|
||||
|
||||
|
||||
@run_in_pyodide(_force_assert_rewrites=True)
|
||||
def test_selenium1(selenium):
|
||||
import pytest
|
||||
|
||||
with pytest.raises(AssertionError, match="assert 6 == 7"):
|
||||
x = 6
|
||||
y = 7
|
||||
assert x == y
|
||||
|
||||
|
||||
run_in_pyodide_alias = run_in_pyodide(_force_assert_rewrites=True)
|
||||
|
||||
|
||||
@run_in_pyodide_alias
|
||||
def test_selenium2(selenium):
|
||||
import pytest
|
||||
|
||||
x = 6
|
||||
y = 7
|
||||
with pytest.raises(AssertionError, match="assert 6 == 7"):
|
||||
assert x == y
|
||||
|
||||
|
||||
@run_in_pyodide(_force_assert_rewrites=True)
|
||||
async def test_selenium3(selenium):
|
||||
from asyncio import sleep
|
||||
|
||||
import pytest
|
||||
|
||||
await sleep(0.01)
|
||||
x = 6
|
||||
await sleep(0.01)
|
||||
y = 7
|
||||
with pytest.raises(AssertionError, match="assert 6 == 7"):
|
||||
assert x == y
|
||||
|
||||
|
||||
def test_inner_function_closure_error(selenium):
|
||||
x = 6
|
||||
|
||||
@run_in_pyodide
|
||||
def inner_function(selenium):
|
||||
assert x == 6
|
||||
return 7
|
||||
|
||||
with pytest.raises(NameError, match="'x' is not defined"):
|
||||
inner_function(selenium)
|
||||
|
||||
|
||||
def test_inner_function(selenium):
|
||||
@run_in_pyodide
|
||||
def inner_function(selenium, x):
|
||||
assert x == 6
|
||||
return 7
|
||||
|
||||
assert inner_function(selenium, 6) == 7
|
||||
|
||||
|
||||
def complicated_decorator(attr_name: str):
|
||||
def inner_func(value):
|
||||
def dec(func):
|
||||
def wrapper(*args, **kwargs):
|
||||
wrapper.dec_info.append((attr_name, value))
|
||||
return func(*args, **kwargs)
|
||||
|
||||
wrapper.dec_info = getattr(func, "dec_info", [])
|
||||
wrapper.__name__ = func.__name__
|
||||
return wrapper
|
||||
|
||||
return dec
|
||||
|
||||
return inner_func
|
||||
|
||||
|
||||
d1 = complicated_decorator("testdec1")
|
||||
d2 = complicated_decorator("testdec2")
|
||||
|
||||
|
||||
@d1("a")
|
||||
@d2("b")
|
||||
@d1("c")
|
||||
@run_in_pyodide
|
||||
def example_decorator_func(selenium):
|
||||
pass
|
||||
|
||||
|
||||
def test_selenium4(selenium_standalone):
|
||||
example_decorator_func(selenium_standalone)
|
||||
assert example_decorator_func.dec_info[-3:] == [
|
||||
("testdec1", "a"),
|
||||
("testdec2", "b"),
|
||||
("testdec1", "c"),
|
||||
]
|
||||
|
||||
|
||||
def test_local_fail_load_package(selenium_standalone):
|
||||
selenium = selenium_standalone
|
||||
|
||||
def _load_package_error(*args, **kwargs):
|
||||
raise OSError("STOP!")
|
||||
|
||||
selenium.load_package = _load_package_error
|
||||
|
||||
exc = None
|
||||
try:
|
||||
example_func(selenium)
|
||||
except OSError:
|
||||
exc = pytest.ExceptionInfo.from_current()
|
||||
|
||||
assert exc
|
||||
try:
|
||||
exc.getrepr()
|
||||
except IndexError as e:
|
||||
import traceback
|
||||
|
||||
traceback.print_exception(e)
|
||||
raise Exception(
|
||||
"run_in_pyodide decorator badly messed up the line numbers."
|
||||
" This could crash pytest. Printed the traceback to stdout."
|
||||
)
|
||||
|
||||
|
||||
@run_in_pyodide
|
||||
def test_trivial1(selenium):
|
||||
x = 7
|
||||
assert x == 7
|
||||
|
||||
|
||||
@run_in_pyodide()
|
||||
def test_trivial2(selenium):
|
||||
x = 7
|
||||
assert x == 7
|
||||
|
||||
|
||||
@run_in_pyodide(pytest_assert_rewrites=False)
|
||||
def test_trivial3(selenium):
|
||||
x = 7
|
||||
assert x == 7
|
||||
|
||||
|
||||
@pytest.mark.parametrize("jinja2", ["jINja2", "Jinja2"])
|
||||
@run_in_pyodide
|
||||
def test_parametrize(selenium, jinja2):
|
||||
try:
|
||||
assert jinja2.lower() == "jinja2"
|
||||
except Exception as e:
|
||||
print(e)
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="Nope!")
|
||||
@run_in_pyodide(pytest_assert_rewrites=False)
|
||||
def test_skip(selenium):
|
||||
x = 6
|
||||
assert x == 7
|
||||
|
||||
|
||||
@run_in_pyodide
|
||||
async def test_run_in_pyodide_async(selenium):
|
||||
from asyncio import sleep
|
||||
|
||||
x = 6
|
||||
await sleep(0.01)
|
||||
assert x == 6
|
||||
|
||||
|
||||
@pytest.mark.skip_refcount_check
|
||||
@pytest.mark.skip_pyproxy_check
|
||||
@given(obj=any_strategy)
|
||||
@settings(
|
||||
std_hypothesis_settings,
|
||||
max_examples=25,
|
||||
)
|
||||
@run_in_pyodide
|
||||
def test_hypothesis(selenium_standalone, obj):
|
||||
from pyodide import to_js
|
||||
|
||||
to_js(obj)
|
||||
|
||||
|
||||
run_in_pyodide_inner = run_in_pyodide()
|
||||
run_in_pyodide_alias2 = pytest.mark.driver_timeout(40)(run_in_pyodide_inner)
|
||||
|
||||
|
||||
@run_in_pyodide_alias2
|
||||
def test_run_in_pyodide_alias(request):
|
||||
assert parse_driver_timeout(request.node) == 40
|
|
@ -1,66 +0,0 @@
|
|||
def test_assert(selenium):
|
||||
selenium.run_js(
|
||||
r"""
|
||||
let shouldPass;
|
||||
shouldPass = true;
|
||||
assert(() => shouldPass, "blah");
|
||||
shouldPass = false;
|
||||
let threw = false;
|
||||
try {
|
||||
assert(() => shouldPass, "blah");
|
||||
} catch(e){
|
||||
threw = true;
|
||||
if(e.message !== `Assertion failed: shouldPass\nblah`){
|
||||
throw new Error(`Unexpected message:\n${e.message}`);
|
||||
}
|
||||
}
|
||||
if(!threw){
|
||||
throw new Error("Didn't throw!");
|
||||
}
|
||||
"""
|
||||
)
|
||||
|
||||
|
||||
def test_assert_throws(selenium):
|
||||
selenium.run_js(
|
||||
r"""
|
||||
let shouldPass;
|
||||
let threw;
|
||||
assertThrows(() => { throw new TypeError("aaabbbccc") }, "TypeError", "bbc");
|
||||
assertThrows(() => { throw new TypeError("aaabbbccc") }, "TypeError", /.{3}.{3}.{3}/);
|
||||
threw = false;
|
||||
try {
|
||||
assertThrows(() => 0, "TypeError", /.*/);
|
||||
} catch(e) {
|
||||
threw = true;
|
||||
assert(() => e.message == `assertThrows(() => 0, "TypeError", /.*/) failed, no error thrown`, e.message);
|
||||
}
|
||||
assert(() => threw);
|
||||
threw = false;
|
||||
try {
|
||||
assertThrows(() => { throw new ReferenceError("blah"); }, "TypeError", /.*/);
|
||||
} catch(e) {
|
||||
threw = true;
|
||||
assert(() => e.message.endsWith("expected error of type 'TypeError' got type 'ReferenceError'"));
|
||||
}
|
||||
assert(() => threw);
|
||||
threw = false;
|
||||
try {
|
||||
assertThrows(() => { throw new TypeError("blah"); }, "TypeError", "abcd");
|
||||
} catch(e) {
|
||||
threw = true;
|
||||
console.log(`!!${e.message}!!`);
|
||||
assert(() => e.message.endsWith(`expected error message to match pattern "abcd" got:\nblah`));
|
||||
}
|
||||
assert(() => threw);
|
||||
threw = false;
|
||||
try {
|
||||
assertThrows(() => { throw new TypeError("blah"); }, "TypeError", /a..d/);
|
||||
} catch(e) {
|
||||
threw = true;
|
||||
console.log(`!!${e.message}!!`);
|
||||
assert(() => e.message.endsWith(`expected error message to match pattern /a..d/ got:\nblah`));
|
||||
}
|
||||
assert(() => threw);
|
||||
"""
|
||||
)
|
|
@ -1,7 +0,0 @@
|
|||
import pathlib
|
||||
|
||||
|
||||
def test_web_server_secondary(selenium, web_server_secondary):
|
||||
host, port, logs = web_server_secondary
|
||||
assert pathlib.Path(logs).exists()
|
||||
assert selenium.server_port != port
|
|
@ -1,102 +0,0 @@
|
|||
import contextlib
|
||||
import functools
|
||||
import json
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def set_webdriver_script_timeout(selenium, script_timeout: float | None):
|
||||
"""Set selenium script timeout
|
||||
|
||||
Parameters
|
||||
----------
|
||||
selenum : SeleniumWrapper
|
||||
a SeleniumWrapper wrapper instance
|
||||
script_timeout : int | float
|
||||
value of the timeout in seconds
|
||||
"""
|
||||
if script_timeout is not None:
|
||||
selenium.set_script_timeout(script_timeout)
|
||||
yield
|
||||
# revert to the initial value
|
||||
if script_timeout is not None:
|
||||
selenium.set_script_timeout(selenium.script_timeout)
|
||||
|
||||
|
||||
def parse_driver_timeout(node) -> float | None:
|
||||
"""Parse driver timeout value from pytest request object"""
|
||||
mark = node.get_closest_marker("driver_timeout")
|
||||
if mark is None:
|
||||
return None
|
||||
else:
|
||||
return mark.args[0]
|
||||
|
||||
|
||||
def parse_xfail_browsers(node) -> dict[str, str]:
|
||||
mark = node.get_closest_marker("xfail_browsers")
|
||||
if mark is None:
|
||||
return {}
|
||||
return mark.kwargs
|
||||
|
||||
|
||||
def maybe_skip_test(item, dist_dir, delayed=False):
|
||||
"""If necessary skip test at the fixture level, to avoid
|
||||
|
||||
loading the selenium_standalone fixture which takes a long time.
|
||||
"""
|
||||
browsers = "|".join(["firefox", "chrome", "node"])
|
||||
|
||||
skip_msg = None
|
||||
# Testing a package. Skip the test if the package is not built.
|
||||
match = re.match(
|
||||
r".*/packages/(?P<name>[\w\-]+)/test_[\w\-]+\.py", str(item.parent.fspath)
|
||||
)
|
||||
if match:
|
||||
package_name = match.group("name")
|
||||
if not package_is_built(package_name, dist_dir) and re.match(
|
||||
rf"test_[\w\-]+\[({browsers})[^\]]*\]", item.name
|
||||
):
|
||||
skip_msg = f"package '{package_name}' is not built."
|
||||
|
||||
# Common package import test. Skip it if the package is not built.
|
||||
if (
|
||||
skip_msg is None
|
||||
and str(item.fspath).endswith("test_packages_common.py")
|
||||
and item.name.startswith("test_import")
|
||||
):
|
||||
match = re.match(rf"test_import\[({browsers})-(?P<name>[\w-]+)\]", item.name)
|
||||
if match:
|
||||
package_name = match.group("name")
|
||||
if not package_is_built(package_name, dist_dir):
|
||||
# If the test is going to be skipped remove the
|
||||
# selenium_standalone as it takes a long time to initialize
|
||||
skip_msg = f"package '{package_name}' is not built."
|
||||
else:
|
||||
raise AssertionError(
|
||||
f"Couldn't parse package name from {item.name}. This should not happen!"
|
||||
)
|
||||
|
||||
# TODO: also use this hook to skip doctests we cannot run (or run them
|
||||
# inside the selenium wrapper)
|
||||
|
||||
if skip_msg is not None:
|
||||
if delayed:
|
||||
item.add_marker(pytest.mark.skip(reason=skip_msg))
|
||||
else:
|
||||
pytest.skip(skip_msg)
|
||||
|
||||
|
||||
@functools.cache
|
||||
def built_packages(dist_dir: Path) -> list[str]:
|
||||
"""Returns the list of built package names from repodata.json"""
|
||||
repodata_path = dist_dir / "repodata.json"
|
||||
if not repodata_path.exists():
|
||||
return []
|
||||
return list(json.loads(repodata_path.read_text())["packages"].keys())
|
||||
|
||||
|
||||
def package_is_built(package_name: str, dist_dir: Path) -> bool:
|
||||
return package_name.lower() in built_packages(dist_dir)
|
|
@ -1,4 +0,0 @@
|
|||
[build-system]
|
||||
requires = ["setuptools>=42", "wheel"]
|
||||
|
||||
build-backend = "setuptools.build_meta"
|
|
@ -1,49 +0,0 @@
|
|||
[metadata]
|
||||
name = pyodide-test-runner
|
||||
version = 0.21.0.dev0
|
||||
author = Pyodide developers
|
||||
description = "Pytest plugin for testing Pyodide and third-party applications that use Pyodide"
|
||||
long_description = file: README.md
|
||||
long_description_content_type = text/markdown
|
||||
url = https://github.com/pyodide/pyodide
|
||||
project_urls =
|
||||
Bug Tracker = https://github.com/pyodide/pyodide/issues
|
||||
Documentation = https://pyodide.org/en/stable/
|
||||
classifiers =
|
||||
Programming Language :: Python :: 3
|
||||
License :: OSI Approved :: Mozilla Public License 2.0 (MPL 2.0)
|
||||
Operating System :: OS Independent
|
||||
Framework :: Pytest
|
||||
|
||||
[options]
|
||||
package_dir =
|
||||
= .
|
||||
packages = find:
|
||||
python_requires = >=3.10
|
||||
include_package_data = True
|
||||
install_requires =
|
||||
pexpect
|
||||
pytest
|
||||
pytest-asyncio
|
||||
selenium
|
||||
tblib
|
||||
|
||||
# This is required to add node driver code to the package.
|
||||
[options.package_data]
|
||||
pyodide_test_runner = *.js
|
||||
|
||||
# pytest will look up `pytest11` entrypoints to find plugins
|
||||
# See: https://docs.pytest.org/en/7.1.x/how-to/writing_plugins.html#making-your-plugin-installable-by-others
|
||||
[options.entry_points]
|
||||
pytest11 =
|
||||
pyodide_test_runner = pyodide_test_runner.fixture
|
||||
pyodide_test_runner_hook = pyodide_test_runner.hook
|
||||
|
||||
[options.packages.find]
|
||||
where = .
|
||||
|
||||
[tool:pytest]
|
||||
asyncio_mode = strict
|
||||
addopts =
|
||||
--tb=short
|
||||
--dist-dir=../dist
|
|
@ -7,7 +7,6 @@ norecursedirs =
|
|||
addopts =
|
||||
--doctest-modules
|
||||
--ignore="packages/matplotlib/src"
|
||||
--ignore="pyodide-test-runner"
|
||||
--tb=short
|
||||
--dist-dir=dist
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
from pyodide_test_runner import run_in_pyodide
|
||||
from pytest_pyodide import run_in_pyodide
|
||||
|
||||
|
||||
@run_in_pyodide
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
from pyodide_test_runner import run_in_pyodide
|
||||
from pytest_pyodide import run_in_pyodide
|
||||
|
||||
|
||||
@run_in_pyodide
|
||||
|
|
|
@ -3,7 +3,7 @@ import sys
|
|||
import time
|
||||
|
||||
import pytest
|
||||
from pyodide_test_runner import run_in_pyodide
|
||||
from pytest_pyodide import run_in_pyodide
|
||||
|
||||
from pyodide import console
|
||||
from pyodide.code import CodeRunner # noqa: E402
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# See also test_typeconversions, and test_python.
|
||||
import pytest
|
||||
from pyodide_test_runner import run_in_pyodide
|
||||
from pytest_pyodide import run_in_pyodide
|
||||
|
||||
|
||||
def test_jsproxy_dir(selenium):
|
||||
|
|
|
@ -4,7 +4,7 @@ from textwrap import dedent
|
|||
from typing import Any
|
||||
|
||||
import pytest
|
||||
from pyodide_test_runner import run_in_pyodide
|
||||
from pytest_pyodide import run_in_pyodide
|
||||
|
||||
from pyodide.code import CodeRunner, eval_code, find_imports, should_quiet # noqa: E402
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import pytest
|
||||
from pyodide_test_runner import run_in_pyodide
|
||||
from pytest_pyodide import run_in_pyodide
|
||||
|
||||
|
||||
@pytest.mark.xfail_browsers(node="XMLHttpRequest is not available in node")
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
from pyodide_test_runner import run_in_pyodide
|
||||
from pytest_pyodide import run_in_pyodide
|
||||
|
||||
|
||||
@run_in_pyodide
|
||||
|
|
|
@ -4,9 +4,9 @@ from typing import Any
|
|||
import pytest
|
||||
from hypothesis import example, given, settings, strategies
|
||||
from hypothesis.strategies import text
|
||||
from pyodide_test_runner import run_in_pyodide
|
||||
from pyodide_test_runner.fixture import selenium_context_manager
|
||||
from pyodide_test_runner.hypothesis import (
|
||||
from pytest_pyodide import run_in_pyodide
|
||||
from pytest_pyodide.fixture import selenium_context_manager
|
||||
from pytest_pyodide.hypothesis import (
|
||||
any_equal_to_self_strategy,
|
||||
any_strategy,
|
||||
std_hypothesis_settings,
|
||||
|
|
|
@ -64,11 +64,6 @@ PYTHON_TARGETS = [
|
|||
build_version_pattern(r"version\s*=\s*{{{python_version}}}"),
|
||||
prerelease=False,
|
||||
),
|
||||
Target(
|
||||
ROOT / "pyodide-test-runner/setup.cfg",
|
||||
build_version_pattern("version = {python_version}"),
|
||||
prerelease=False,
|
||||
),
|
||||
]
|
||||
|
||||
JS_TARGETS = [
|
||||
|
|
Loading…
Reference in New Issue