Skip to content

Commit 401c531

Browse files
committed
add test infrastructure
1 parent f57e4a7 commit 401c531

File tree

5 files changed

+217
-0
lines changed

5 files changed

+217
-0
lines changed
+64
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
name: setup-test-env
2+
description: "Setup test environment"
3+
4+
inputs:
5+
python-version:
6+
default: "3.10"
7+
description: "Python version to test."
8+
9+
runs:
10+
using: composite
11+
12+
steps:
13+
- name: Setup miniforge
14+
uses: conda-incubator/setup-miniconda@v3
15+
with:
16+
miniforge-version: latest
17+
activate-environment: bokeh-fastapi-test
18+
19+
- name: Display conda info
20+
shell: bash -el {0}
21+
run: conda info
22+
23+
- name: Checkout repository
24+
uses: actions/checkout@v4
25+
with:
26+
repository: bokeh/bokeh
27+
path: ./tests
28+
fetch-tags: true
29+
30+
# - name: Restore conda environment
31+
# id: cache
32+
# uses: actions/cache@v4
33+
# with:
34+
# path: ${{ env.CONDA }}/envs
35+
# key:
36+
# env-${{ runner.os }}-${{ runner.arch }}-${{ inputs.python-version
37+
# }}|${{ hashFiles('environment-dev.yml', 'pyproject.toml') }}
38+
# restore-keys: |
39+
# env-${{ runner.os }}-${{ runner.arch }}-${{ inputs.python-version }}
40+
41+
- name: Update conda environment if necessary
42+
# if: steps.cache.outputs.cache-hit != 'true'
43+
shell: bash
44+
run: conda env update --name bokeh-fastapi-test --file ./tests/bokeh/conda/environment-test-${{ inputs.python-version }}.yml
45+
46+
- name: Install bokeh-fastapi
47+
shell: bash -el {0}
48+
run: pip install .
49+
50+
- name: Display development environment
51+
shell: bash -el {0}
52+
run: conda list
53+
54+
- name: Checkout bokeh tests
55+
shell: bash -el {0}
56+
working-directory: ./tests/bokeh
57+
run: |
58+
BOKEH_VERSION=$(conda list --json | jq --raw-output '.[] | select(.name=="bokeh").version')
59+
git checkout "${BOKEH_VERSION}"
60+
61+
- name: Apply test patches
62+
shell: bash
63+
working-directory: ./tests
64+
run: ./setup.sh

.github/workflows/test.yml

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
name: test
2+
3+
on:
4+
pull_request:
5+
6+
jobs:
7+
detect-usage:
8+
runs-on: ubuntu-latest
9+
defaults:
10+
run:
11+
shell: bash -el {0}
12+
13+
steps:
14+
- name: Checkout repository
15+
uses: actions/checkout@v4
16+
17+
- name: Setup environment
18+
uses: ./.github/actions/setup-test-env
19+
20+
- name: Restore test cache
21+
id: cache
22+
uses: actions/cache@v4
23+
with:
24+
path: ./.bokeh_fastapi_cache
25+
key: test-${{ runner.os }}-${{ runner.arch }}|${{}}-${{}}
26+
restore-keys: test-${{ runner.os }}-${{ runner.arch }}
27+
28+
- name: Create test cache
29+
if: steps.cache.outputs.cache-hit != 'true'
30+
working-directory: ./tests/bokeh
31+
run: pytest || true

.gitignore

+3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
src/bokeh_fastapi/_version.py
22

3+
tests/bokeh
4+
.bokeh_fastapi_cache
5+
36
# Byte-compiled / optimized / DLL files
47
__pycache__/
58
*.py[cod]

tests/conftest_patch.py

+110
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
import contextlib
2+
import json
3+
import sys
4+
import unittest.mock
5+
from pathlib import Path
6+
7+
import bokeh
8+
import pytest
9+
from bokeh.server.tornado import BokehTornado
10+
from bokeh_fastapi import BokehFastAPI
11+
12+
13+
def cache_path() -> Path:
14+
folder = Path(__file__).parents[3] / ".bokeh_fastapi_cache"
15+
folder.mkdir(parents=True, exist_ok=True)
16+
name = f"python-{sys.version_info[0]}.{sys.version_info[1]}-bokeh-{bokeh.__version__}.json"
17+
return folder / name
18+
19+
20+
TESTS = []
21+
PATCHES = {}
22+
23+
24+
def update_required_patches(modules):
25+
global PATCHES
26+
for module in modules:
27+
if module in PATCHES:
28+
continue
29+
30+
for name, obj in module.__dict__.items():
31+
if isinstance(obj, type) and issubclass(obj, BokehTornado):
32+
PATCHES[module.__name__] = name
33+
break
34+
35+
36+
class BokehFastAPICompat(BokehFastAPI):
37+
def __init__(self, *args, **kwargs):
38+
kwargs["websocket_origins"] = kwargs.pop("extra_websocket_origins")
39+
kwargs.pop("absolute_url", None)
40+
kwargs.pop("index", None)
41+
kwargs.pop("websocket_max_message_size_bytes", None)
42+
kwargs.pop("extra_patterns", None)
43+
super().__init__(*args, **kwargs)
44+
45+
def initialize(self, *args, **kwargs):
46+
pass
47+
48+
def start(self, *args, **kwargs):
49+
pass
50+
51+
def __call__(self, *args, **kwargs):
52+
pass
53+
54+
55+
@pytest.hookimpl(wrapper=True)
56+
def pytest_collection_modifyitems(config, items):
57+
path = cache_path()
58+
if path.exists():
59+
with open(cache_path()) as file:
60+
cache = json.load(file)
61+
62+
tests = set(cache["tests"])
63+
select = []
64+
deselect = []
65+
for item in items:
66+
(select if item.nodeid in tests else deselect).append(item)
67+
items[:] = select
68+
config.hook.pytest_deselected(items=deselect)
69+
70+
for module_name, obj_name in cache["patches"].items():
71+
unittest.mock.patch(
72+
f"{module_name}.{obj_name}", new=BokehFastAPICompat
73+
).start()
74+
else:
75+
update_required_patches({item.module for item in items})
76+
77+
return (yield)
78+
79+
80+
def pytest_terminal_summary():
81+
path = cache_path()
82+
if not path.exists():
83+
with open(path, "w") as file:
84+
json.dump({"patches": PATCHES, "tests": TESTS}, file, indent=2)
85+
86+
87+
@pytest.fixture(autouse=True)
88+
def detect_bokeh_tornado_usage(request):
89+
update_required_patches(
90+
[
91+
module
92+
for name, module in sys.modules.items()
93+
if (name == "bokeh" or name.startswith("bokeh."))
94+
and name != "bokeh.server.tornado"
95+
]
96+
)
97+
98+
with contextlib.ExitStack() as stack:
99+
spies = [
100+
stack.enter_context(
101+
unittest.mock.patch(f"{module_name}.{obj_name}", wraps=BokehTornado)
102+
)
103+
for module_name, obj_name in PATCHES.items()
104+
]
105+
106+
yield
107+
108+
global TESTS
109+
if any(spy.called for spy in spies):
110+
TESTS.append(request.node.nodeid)

tests/setup.sh

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
TESTS_DIR='./bokeh/tests'
2+
CONFTEST="${TESTS_DIR}/conftest.py"
3+
BACKUP="${TESTS_DIR}/conftest.py.bak"
4+
5+
if [[ ! -f "${BACKUP}" ]]; then
6+
cp "${CONFTEST}" "${BACKUP}"
7+
fi
8+
9+
cat "${BACKUP}" './conftest_patch.py' > "${CONFTEST}"

0 commit comments

Comments
 (0)