Skip to content

Commit 167bb95

Browse files
committed
further refine test tooling
adds ServerMountPoint object
1 parent d815398 commit 167bb95

File tree

5 files changed

+93
-121
lines changed

5 files changed

+93
-121
lines changed

idom/testing.py

Lines changed: 61 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,15 @@
11
from urllib.parse import urlunparse, urlencode
2-
from contextlib import contextmanager, AbstractContextManager
3-
from idom.core.element import ElementConstructor
2+
from contextlib import contextmanager
43
from typing import (
54
Callable,
6-
NamedTuple,
75
Tuple,
86
Iterator,
97
Type,
108
Optional,
11-
Union,
129
Any,
1310
Dict,
11+
Generic,
12+
TypeVar,
1413
)
1514

1615
from selenium.webdriver.remote.webdriver import WebDriver
@@ -24,26 +23,13 @@
2423

2524

2625
__all__ = [
27-
"server_base_url",
2826
"find_available_port",
2927
"create_simple_selenium_web_driver",
30-
"create_mount_and_server",
31-
"MountAndServer",
28+
"ServerMountPoint",
3229
]
3330

3431

35-
MountType = Union[Callable[[ElementConstructor], None], Any]
36-
MountContext = Callable[[], "AbstractContextManager[MountType]"]
37-
AnyAbstractRenderServer = AbstractRenderServer[Any, Any]
38-
39-
40-
def server_base_url(host: str, port: int, path: str = "", query: Optional[Any] = None):
41-
return urlunparse(["http", f"{host}:{port}", path, "", urlencode(query or ()), ""])
42-
43-
44-
class MountAndServer(NamedTuple):
45-
mount: MountContext
46-
server: AnyAbstractRenderServer
32+
AnyRenderServer = AbstractRenderServer[Any, Any]
4733

4834

4935
def create_simple_selenium_web_driver(
@@ -62,49 +48,68 @@ def create_simple_selenium_web_driver(
6248
return driver
6349

6450

65-
def create_mount_and_server(
66-
server_type: Type[AnyAbstractRenderServer] = PerClientStateServer,
67-
host: str = "127.0.0.1",
68-
port: Optional[int] = None,
69-
server_config: Optional[Any] = None,
70-
run_kwargs: Optional[Dict[str, Any]] = None,
71-
mount_and_server_constructor: Callable[..., Any] = hotswap_server,
72-
app: Optional[Any] = None,
73-
**other_options: Any,
74-
) -> MountAndServer:
75-
mount, server = mount_and_server_constructor(
76-
(
77-
server_type
78-
if issubclass(server_type, _RenderServerWithLastError)
79-
else type(
80-
server_type.__name__,
81-
(_RenderServerWithLastError, server_type),
82-
{"last_server_error_for_idom_testing": Ref(None)},
83-
)
84-
),
85-
host,
86-
port or find_available_port(host),
87-
server_config,
88-
run_kwargs,
89-
app,
90-
**other_options,
91-
)
92-
93-
assert isinstance(server, _RenderServerWithLastError)
51+
_Mount = TypeVar("_Mount")
52+
_Server = TypeVar("_Server", bound=AnyRenderServer)
53+
54+
55+
class ServerMountPoint(Generic[_Mount, _Server]):
56+
57+
__slots__ = "server", "host", "port", "_mount"
58+
59+
def __init__(
60+
self,
61+
server_type: Type[_Server] = PerClientStateServer,
62+
host: str = "127.0.0.1",
63+
port: Optional[int] = None,
64+
server_config: Optional[Any] = None,
65+
run_kwargs: Optional[Dict[str, Any]] = None,
66+
mount_and_server_constructor: "Callable[..., Tuple[_Mount, _Server]]" = hotswap_server,
67+
app: Optional[Any] = None,
68+
**other_options: Any,
69+
):
70+
self.host = host
71+
self.port = port or find_available_port(host)
72+
self._mount, self.server = mount_and_server_constructor(
73+
(
74+
server_type
75+
if issubclass(server_type, _RenderServerWithLastError)
76+
else type(
77+
server_type.__name__,
78+
(_RenderServerWithLastError, server_type),
79+
{"last_server_error_for_idom_testing": Ref(None)},
80+
)
81+
),
82+
self.host,
83+
self.port,
84+
server_config,
85+
run_kwargs,
86+
app,
87+
**other_options,
88+
)
89+
90+
def url(self, path: str = "", query: Optional[Any] = None) -> str:
91+
return urlunparse(
92+
[
93+
"http",
94+
f"{self.host}:{self.port}",
95+
path,
96+
"",
97+
urlencode(query or ()),
98+
"",
99+
]
100+
)
94101

95102
@contextmanager
96-
def mount_context() -> Iterator[MountType]:
97-
server.last_server_error_for_idom_testing.current = None
103+
def open_mount_function(self) -> Iterator[_Mount]:
104+
self.server.last_server_error_for_idom_testing.current = None
98105
try:
99-
yield mount
106+
yield self._mount
100107
finally:
101-
if server.last_server_error_for_idom_testing.current is not None:
102-
raise server.last_server_error_for_idom_testing.current
103-
104-
return MountAndServer(mount_context, server)
108+
if self.server.last_server_error_for_idom_testing.current is not None:
109+
raise self.server.last_server_error_for_idom_testing.current
105110

106111

107-
class _RenderServerWithLastError(AnyAbstractRenderServer):
112+
class _RenderServerWithLastError(AnyRenderServer):
108113
"""A server that updates the ``last_server_error`` fixture"""
109114

110115
last_server_error_for_idom_testing: Ref[Optional[Exception]]

tests/conftest.py

Lines changed: 16 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import logging
22
import inspect
3-
from typing import Callable, Any, Tuple, Iterator, Iterable
3+
from typing import Any, Iterator, Iterable
44

55
from loguru import logger
66
import pytest
@@ -14,13 +14,7 @@
1414

1515
import idom
1616
from idom.client import manage as manage_client
17-
from idom.server.prefab import AbstractRenderServer
18-
from idom.server.utils import find_available_port
19-
from idom.testing import (
20-
server_base_url,
21-
create_mount_and_server,
22-
create_simple_selenium_web_driver,
23-
)
17+
from idom.testing import ServerMountPoint, create_simple_selenium_web_driver
2418

2519

2620
def pytest_collection_modifyitems(items: Iterable[Any]) -> None:
@@ -46,13 +40,13 @@ def pytest_addoption(parser: Parser) -> None:
4640

4741

4842
@pytest.fixture
49-
def display(driver, host, port, mount):
43+
def display(driver, server_mount_point, mount):
5044
display_id = idom.Ref(0)
5145

5246
def mount_and_display(element_constructor, query=None, check_mount=True):
5347
element_id = f"display-{display_id.set_current(display_id.current + 1)}"
5448
mount(lambda: idom.html.div({"id": element_id}, element_constructor()))
55-
driver.get(server_base_url(host, port, query=query))
49+
driver.get(server_mount_point.url(query=query))
5650
if check_mount:
5751
driver.find_element_by_id(element_id)
5852
return element_id
@@ -61,38 +55,30 @@ def mount_and_display(element_constructor, query=None, check_mount=True):
6155

6256

6357
@pytest.fixture
64-
def driver_get(driver, host, port):
65-
return lambda query=None: driver.get(server_base_url(host, port, query=query))
66-
67-
68-
@pytest.fixture(scope="module")
69-
def last_server_error(server):
70-
return server.last_server_error_for_idom_testing
58+
def driver_get(driver, server_mount_point):
59+
return lambda query=None: driver.get(server_mount_point.url(query=query))
7160

7261

7362
@pytest.fixture
74-
def mount(mount_and_server):
75-
with mount_and_server.mount() as mount:
63+
def mount(server_mount_point):
64+
with server_mount_point.open_mount_function() as mount:
7665
yield mount
7766

7867

79-
@pytest.fixture(scope="module")
80-
def server(mount_and_server):
81-
"""An IDOM server"""
82-
server = mount_and_server.server
83-
yield server
84-
server.stop()
68+
@pytest.fixture
69+
def last_server_error(server_mount_point):
70+
return server_mount_point.server.last_server_error_for_idom_testing
8571

8672

8773
@pytest.fixture(scope="module")
88-
def mount_and_server(
89-
host: str, port: int
90-
) -> Tuple[Callable[..., None], AbstractRenderServer]:
74+
def server_mount_point():
9175
"""An IDOM layout mount function and server as a tuple
9276
9377
The ``mount`` and ``server`` fixtures use this.
9478
"""
95-
return create_mount_and_server(host=host, port=port, server_config={"cors": True})
79+
mount_point = ServerMountPoint(server_config={"cors": True})
80+
yield mount_point
81+
mount_point.server.stop()
9682

9783

9884
@pytest.fixture(scope="module")
@@ -101,7 +87,7 @@ def driver_wait(driver):
10187

10288

10389
@pytest.fixture(scope="module")
104-
def driver(create_driver: Callable[[], Chrome]) -> Chrome:
90+
def driver(create_driver) -> Chrome:
10591
"""A Selenium web driver"""
10692
return create_driver()
10793

@@ -124,23 +110,6 @@ def create():
124110
d.quit()
125111

126112

127-
@pytest.fixture(scope="module")
128-
def server_url(host, port):
129-
return server_base_url(host, port)
130-
131-
132-
@pytest.fixture(scope="session")
133-
def host() -> str:
134-
"""The hostname for the IDOM server setup by ``mount_and_server``"""
135-
return "127.0.0.1"
136-
137-
138-
@pytest.fixture(scope="module")
139-
def port(host: str) -> int:
140-
"""The port for the IDOM server setup by ``mount_and_server``"""
141-
return find_available_port(host)
142-
143-
144113
@pytest.fixture
145114
def client_implementation():
146115
original = idom.client.current

tests/test_server/test_sanic/test_base.py

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,22 +5,20 @@
55

66
import idom
77
from idom.server.sanic import PerClientStateServer
8-
from idom.testing import create_mount_and_server
8+
from idom.testing import ServerMountPoint
99

1010

1111
@pytest.fixture(scope="module")
12-
def mount_and_server(host, port):
13-
return create_mount_and_server(
12+
def server_mount_point():
13+
return ServerMountPoint(
1414
PerClientStateServer,
15-
host,
16-
port,
1715
# test that we can use a custom app instance
1816
app=Sanic(),
1917
)
2018

2119

22-
def test_serve_has_loop_attribute(server):
23-
assert isinstance(server.loop, asyncio.AbstractEventLoop)
20+
def test_serve_has_loop_attribute(server_mount_point):
21+
assert isinstance(server_mount_point.server.loop, asyncio.AbstractEventLoop)
2422

2523

2624
def test_no_application_until_running():

tests/test_server/test_sanic/test_multiview_server.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,19 @@
33
import idom
44
from idom.server.sanic import PerClientStateServer
55
from idom.server.prefab import multiview_server
6-
from idom.testing import create_mount_and_server
6+
from idom.testing import ServerMountPoint
77

88
from tests.driver_utils import no_such_element
99

1010

1111
@pytest.fixture(scope="module")
12-
def mount_and_server(host, port):
13-
return create_mount_and_server(
14-
PerClientStateServer, host, port, mount_and_server_constructor=multiview_server
12+
def server_mount_point():
13+
return ServerMountPoint(
14+
PerClientStateServer, mount_and_server_constructor=multiview_server
1515
)
1616

1717

18-
def test_multiview_server(driver_get, driver, mount, server):
18+
def test_multiview_server(driver_get, driver, mount):
1919
manual_id = mount["manually_set_id"](lambda: idom.html.h1({"id": "e1"}, ["e1"]))
2020
auto_view_id = mount(lambda: idom.html.h1({"id": "e2"}, ["e2"]))
2121

tests/test_server/test_sanic/test_shared_state_client.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,19 +6,19 @@
66

77
import idom
88
from idom.server.sanic import SharedClientStateServer
9-
from idom.testing import create_mount_and_server
9+
from idom.testing import ServerMountPoint
1010

1111

1212
@pytest.fixture(scope="module")
13-
def mount_and_server(host, port):
13+
def server_mount_point():
1414
"""An IDOM layout mount function and server as a tuple
1515
1616
The ``mount`` and ``server`` fixtures use this.
1717
"""
18-
return create_mount_and_server(SharedClientStateServer, host, port, sync_views=True)
18+
return ServerMountPoint(SharedClientStateServer, sync_views=True)
1919

2020

21-
def test_shared_client_state(create_driver, mount, server_url):
21+
def test_shared_client_state(create_driver, mount, server_mount_point):
2222
driver_1 = create_driver()
2323
driver_2 = create_driver()
2424
was_garbage_collected = Event()
@@ -45,8 +45,8 @@ def Counter(count):
4545

4646
mount(IncrCounter)
4747

48-
driver_1.get(server_url)
49-
driver_2.get(server_url)
48+
driver_1.get(server_mount_point.url())
49+
driver_2.get(server_mount_point.url())
5050

5151
client_1_button = driver_1.find_element_by_id("incr-button")
5252
client_2_button = driver_2.find_element_by_id("incr-button")

0 commit comments

Comments
 (0)