chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 12:25:07 +08:00
commit a26e856398
1681 changed files with 296950 additions and 0 deletions
@@ -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?