chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,43 @@
|
||||
# pyodide-tests
|
||||
|
||||
Smoke and integration tests for the perspective-python Pyodide wheel. The tests
|
||||
are specced in `pytest` and executed with `playwright`, using the
|
||||
`pytest-pyodide` package.
|
||||
|
||||
These tests require that a Pyodide wheel has been built to rust/target/wheels
|
||||
|
||||
## test setup
|
||||
|
||||
Create a virtual environment. Install perspective-python requirements and
|
||||
special pyodide-only requirements:
|
||||
|
||||
```
|
||||
pip install -r rust/perspective-python/requirements.txt
|
||||
pip install -r rust/perspective-python/requirements-pyodide.txt
|
||||
```
|
||||
|
||||
## running tests
|
||||
|
||||
Run setup, select `perspective-pyodide` target:
|
||||
|
||||
```
|
||||
pnpm -w run setup
|
||||
```
|
||||
|
||||
Build the pyodide wheel:
|
||||
|
||||
```
|
||||
PSP_BUILD_WHEEL=1 pnpm -w run build
|
||||
```
|
||||
|
||||
Then run tests:
|
||||
|
||||
```
|
||||
pnpm -w test
|
||||
```
|
||||
|
||||
If you are prompted to install playwright browsers, run this in your venv:
|
||||
|
||||
```
|
||||
python -m playwright install
|
||||
```
|
||||
@@ -0,0 +1,11 @@
|
||||
# ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
||||
# ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
|
||||
# ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
|
||||
# ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
|
||||
# ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
|
||||
# ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
|
||||
# ┃ Copyright (c) 2017, the Perspective Authors. ┃
|
||||
# ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
|
||||
# ┃ This file is part of the Perspective library, distributed under the terms ┃
|
||||
# ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
|
||||
# ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
||||
@@ -0,0 +1,23 @@
|
||||
# ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
||||
# ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
|
||||
# ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
|
||||
# ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
|
||||
# ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
|
||||
# ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
|
||||
# ┃ Copyright (c) 2017, the Perspective Authors. ┃
|
||||
# ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
|
||||
# ┃ This file is part of the Perspective library, distributed under the terms ┃
|
||||
# ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
|
||||
# ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
||||
|
||||
|
||||
# This is where to change pytest_pyodide's config if necessary.
|
||||
# See: https://github.com/pyodide/pyodide/blob/4b1c22ba27f62fba8ae2e13fbaf0e23a32b580ad/conftest.py#L44-L48
|
||||
|
||||
|
||||
def pytest_addoption(parser):
|
||||
parser.addoption(
|
||||
"--perspective-emscripten-wheel",
|
||||
action="store",
|
||||
help="path to emscripten wheel",
|
||||
)
|
||||
@@ -0,0 +1,221 @@
|
||||
# ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
||||
# ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
|
||||
# ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
|
||||
# ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
|
||||
# ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
|
||||
# ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
|
||||
# ┃ Copyright (c) 2017, the Perspective Authors. ┃
|
||||
# ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
|
||||
# ┃ This file is part of the Perspective library, distributed under the terms ┃
|
||||
# ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
|
||||
# ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
||||
|
||||
# As of Feb 2025, the regular Python test suite doesn't work in pytest-pyodide
|
||||
# --run-in-pyodide, for several reasons.
|
||||
# One: https://github.com/pyodide/pytest-pyodide/issues/81
|
||||
#
|
||||
|
||||
# These pytest-pyodide tests are still quirky. Be wary of trying to split into
|
||||
# several files: relative imports will break, things seem to work best contained in
|
||||
# one python module.
|
||||
|
||||
from pytest_pyodide import run_in_pyodide, spawn_web_server
|
||||
import pytest
|
||||
|
||||
|
||||
# __________
|
||||
# < FIXTURES >
|
||||
# ----------
|
||||
# \ ^__^
|
||||
# \ (oo)\_______
|
||||
# (__)\ )\/\
|
||||
# ||----w |
|
||||
# || ||
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def psp_wheel_path(pytestconfig):
|
||||
wheel = pytestconfig.getoption("--perspective-emscripten-wheel")
|
||||
if wheel is None:
|
||||
raise RuntimeError(
|
||||
"pytest option --perspective-emscripten-wheel=<WHEEL> is required but wasn't specified"
|
||||
)
|
||||
return wheel
|
||||
|
||||
|
||||
# Based on micropip's test server fixture:
|
||||
# https://github.com/pyodide/micropip/blob/eb8c4497d742e515d24d532db2b9cc014328265b/tests/conftest.py#L64-L87
|
||||
# Run web server serving the directory containing the perspective wheel,
|
||||
# yielding the wheel's URL for the fixture value
|
||||
# FIXME: Try using https://github.com/pyodide/pytest-pyodide/tree/main?tab=readme-ov-file#copying-files-to-pyodide
|
||||
@pytest.fixture()
|
||||
def psp_wheel_url(psp_wheel_path):
|
||||
from pathlib import Path
|
||||
|
||||
wheel = Path(psp_wheel_path)
|
||||
wheels_dir = wheel.parent
|
||||
with spawn_web_server(wheels_dir) as server:
|
||||
host, port, _ = server
|
||||
wheel_url = f"http://{host}:{port}/{wheel.name}"
|
||||
yield wheel_url
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
@run_in_pyodide(packages=["micropip"])
|
||||
async def psp_installed(selenium, psp_wheel_url):
|
||||
"""Installs perspective wheel from rust/target/wheels dir using micropip"""
|
||||
# Autoused, so every test has perspective installed without them explicitly listing it as a fixture
|
||||
import micropip
|
||||
|
||||
await micropip.install(psp_wheel_url)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
@run_in_pyodide
|
||||
def server(selenium):
|
||||
from perspective import AsyncServer
|
||||
from pytest_pyodide.decorator import PyodideHandle
|
||||
|
||||
s = AsyncServer()
|
||||
return PyodideHandle(s)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
@run_in_pyodide
|
||||
def client(selenium, psp_installed, server):
|
||||
from perspective import AsyncClient
|
||||
from pytest_pyodide.decorator import PyodideHandle
|
||||
|
||||
async def send_request(msg):
|
||||
await sess.handle_request(msg)
|
||||
|
||||
async def send_response(msg):
|
||||
await client.handle_response(msg)
|
||||
|
||||
sess = server.new_session(send_response)
|
||||
client = AsyncClient(send_request)
|
||||
return PyodideHandle(client)
|
||||
|
||||
|
||||
# _________
|
||||
# < PYTESTS >
|
||||
# ---------
|
||||
# \ ^__^
|
||||
# \ (oo)\_______
|
||||
# (__)\ )\/\
|
||||
# ||----w |
|
||||
# || ||
|
||||
|
||||
|
||||
@run_in_pyodide
|
||||
async def test_parsing_bad_csv_raises_exception(selenium):
|
||||
import pytest
|
||||
import perspective
|
||||
|
||||
server = perspective.Server()
|
||||
client = server.new_local_client()
|
||||
with pytest.raises(perspective.PerspectiveError) as exc_info:
|
||||
client.table("a,b,c\n1,2")
|
||||
assert exc_info.match("CSV parse error")
|
||||
|
||||
|
||||
@run_in_pyodide
|
||||
async def test_parsing_good_csv(selenium):
|
||||
import perspective
|
||||
|
||||
server = perspective.Server()
|
||||
client = server.new_local_client()
|
||||
abc123 = client.table("a,b,c\n1,2,3\n")
|
||||
assert abc123.columns() == ["a", "b", "c"]
|
||||
|
||||
|
||||
# Test that perspective can load pyarrow Table values
|
||||
@run_in_pyodide
|
||||
async def test_perspective_and_pyarrow_together_at_last(selenium):
|
||||
import micropip
|
||||
|
||||
await micropip.install("pyarrow")
|
||||
|
||||
import perspective
|
||||
import pyarrow as pa
|
||||
|
||||
n_legs = pa.array([2, 2, 4, 4, 5, 100])
|
||||
animals = pa.array(
|
||||
["Flamingo", "Parrot", "Dog", "Horse", "Brittle stars", "Centipede"]
|
||||
)
|
||||
names = ["n_legs", "animals"]
|
||||
table = pa.Table.from_arrays([n_legs, animals], names=names)
|
||||
|
||||
server = perspective.Server()
|
||||
client = server.new_local_client()
|
||||
|
||||
animals = client.table(table, name="animals")
|
||||
assert animals.columns() == ["n_legs", "animals"]
|
||||
|
||||
|
||||
# Test that perspective can load pandas DataFrame values
|
||||
@run_in_pyodide
|
||||
async def test_pandas_dataframes(selenium):
|
||||
import micropip
|
||||
|
||||
await micropip.install(["pyarrow", "pandas"])
|
||||
|
||||
import perspective
|
||||
import pandas as pd
|
||||
|
||||
server = perspective.Server()
|
||||
client = server.new_local_client()
|
||||
df = pd.DataFrame({"a": [1, 2, 3], "b": [4, 5, 6]})
|
||||
ab = client.table(df, name="ab")
|
||||
assert sorted(ab.columns()) == ["a", "b", "index"]
|
||||
|
||||
|
||||
@run_in_pyodide
|
||||
async def test_async_client_kitchen_sink(selenium, client):
|
||||
"""run various things on the async client"""
|
||||
|
||||
from unittest.mock import Mock
|
||||
from perspective import PerspectiveError
|
||||
import pytest
|
||||
|
||||
table = await client.table({"a": [0]}, name="my-cool-data", limit=100)
|
||||
view = await table.view()
|
||||
# synchronous methods
|
||||
assert table.get_index() is None
|
||||
assert table.get_name() == "my-cool-data"
|
||||
limit = table.get_limit()
|
||||
assert limit == 100
|
||||
|
||||
# view update callbacks
|
||||
view_updated = Mock()
|
||||
await view.on_update(view_updated)
|
||||
for i in range(1, limit):
|
||||
await table.update([{"a": i}])
|
||||
await table.size() # force update to process
|
||||
assert (await table.size()) == limit
|
||||
assert view_updated.call_count == limit - 1
|
||||
rex = await view.to_records()
|
||||
assert rex == [{"a": i} for i in range(limit)]
|
||||
|
||||
# view/table delete callbacks
|
||||
view_deleted = Mock()
|
||||
table_deleted = Mock()
|
||||
await table.on_delete(table_deleted)
|
||||
await view.on_delete(view_deleted)
|
||||
with pytest.raises(PerspectiveError) as excinfo:
|
||||
await table.delete()
|
||||
assert excinfo.match(r"Cannot delete table with views")
|
||||
await view.delete()
|
||||
view_deleted.assert_called_once()
|
||||
await table.delete()
|
||||
table_deleted.assert_called_once()
|
||||
|
||||
# Cleaning up the client fixture's PyodideHandle causes pytest to
|
||||
# spew some nasty errors on exit. Seems like playwright's shutdown and
|
||||
# deletion of the PyodideHandle are racing, and PyodideHandle's finalizer
|
||||
# throws trying to execute code in the playwright browser which has already
|
||||
# stopped its event loop.
|
||||
# TODO: File bug on pytest-pyodide
|
||||
#
|
||||
# Exception ignored in: <function PyodideHandle.__del__ at 0x7b9df0c7a710>
|
||||
# playwright._impl._errors.Error: Event loop is closed! Is Playwright already stopped?
|
||||
Reference in New Issue
Block a user