Skip to content

Commit

Permalink
test: improve coverage
Browse files Browse the repository at this point in the history
  • Loading branch information
jourdain committed Jan 5, 2025
1 parent 8936c06 commit eba8290
Show file tree
Hide file tree
Showing 10 changed files with 507 additions and 9 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/test_and_release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ jobs:
pip install -r tests/requirements.txt
# Run the tests with coverage so we get a coverage report too
pip install coverage
coverage run --source . -m pytest .
coverage run --omit "*/tests/*" --source . -m pytest .
# Print the coverage report
coverage report -m
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
# OS files
.DS_Store

# test file
trame_net.log

# Editor directories and files
.idea
.vscode
Expand Down
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ dev = [
"pre-commit",
"ruff",
"pytest",
"pytest-asyncio",
]

[build-system]
Expand Down
93 changes: 93 additions & 0 deletions tests/test_async.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import asyncio
import pytest

import multiprocessing
import time
from concurrent.futures import ProcessPoolExecutor

from trame.app import get_server, asynchronous


@pytest.mark.asyncio
async def test_thread_state_sync():
running_states = []
value_changes = []

MULTI_PROCESS_MANAGER = multiprocessing.Manager()
SPAWN = multiprocessing.get_context("spawn")
PROCESS_EXECUTOR = ProcessPoolExecutor(1, mp_context=SPAWN)

loop = asyncio.get_event_loop()
queue = MULTI_PROCESS_MANAGER.Queue()

server = get_server("test_thread_state_sync")
server.state.running = False
server.state.a = 0

@server.state.change("running")
def on_running_change(running, **_):
running_states.append(running)

@server.state.change("a")
def on_a_change(a, **_):
value_changes.append(a)

server.start(exec_mode="task", port=0)
assert await server.ready

def exec_in_thread(queue):
with asynchronous.StateQueue(queue) as state:
assert state.queue is queue

state.running = True

state.update(
{
"b": 10,
"c": 20,
}
)

for i in range(10):
time.sleep(0.1)
state.a = i
assert state.a == i
assert state["a"] == i

state.running = False

asynchronous.decorate_task(
loop.run_in_executor(
PROCESS_EXECUTOR,
exec_in_thread(queue),
)
)
asynchronous.create_state_queue_monitor_task(server, queue)

previous_size = len(value_changes)
while len(value_changes) < 10:
await asyncio.sleep(0.15)
assert len(value_changes) > previous_size
previous_size = len(value_changes)

assert running_states == [False, True, False]

assert server.state.b == 10
assert server.state.c == 20

await server.stop()


@pytest.mark.asyncio
async def test_task_decorator():
bg_update = "idle"

@asynchronous.task
async def run_something():
nonlocal bg_update
bg_update = "ok"

run_something()
assert bg_update == "idle"
await asyncio.sleep(0.1)
assert bg_update == "ok"
54 changes: 54 additions & 0 deletions tests/test_client.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import asyncio
import pytest

from trame.app import get_server, get_client, asynchronous


@pytest.mark.asyncio
async def test_client_connection():
server = get_server("test_client_connection")
server.start(exec_mode="task", port=0)
assert await server.ready
assert server.running

url = f"ws://localhost:{server.port}/ws"
client = get_client(url)
asynchronous.create_task(client.connect(secret="wslink-secret"))
await asyncio.sleep(0.1)

# should be a noop
await client.connect()
assert client.connected == 2

@client.change("a")
def on_change(a, **_):
assert a == 2

@server.trigger("add")
def server_method(*args):
result = 0
for v in args:
result += v
return result

with server.state as state:
state.a = 2

await server.network_completion
await asyncio.sleep(0.1) # wait for client network

assert server.state.a == client.state.a

with client.state as state:
state.b = {"a": 1, "b": 2, "_filter": ["b"]}

await asyncio.sleep(0.1) # wait for client network
assert server.state.b == {"a": 1, "_filter": ["b"]}

assert await client.call_trigger("add", [1, 2, 3]) == 6
assert await client.call_trigger("add") == 0

await asyncio.sleep(0.1)
await client.diconnect()
await asyncio.sleep(0.5)
await server.stop()
94 changes: 93 additions & 1 deletion tests/test_controller.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import pytest
import logging
import asyncio

from trame_server.controller import FunctionNotImplementedError

Expand Down Expand Up @@ -27,7 +28,98 @@ def fn_2(x):
b_name = controller.trigger_name(fn_2)
a_name_next = controller.trigger_name(fn_1)

fn_1_r = controller.trigger_fn(a_name)
fn_2_r = controller.trigger_fn(b_name)

assert a_name != b_name
assert a_name == a_name_next
assert a_name == "trigger__1"
assert b_name == "trigger__2"
assert fn_1 is fn_1_r
assert fn_2 is fn_2_r


def test_composition(controller):
def fn():
return 1

@controller.add("func_attr")
def fn_1():
return 1.5

assert controller.func_attr() == [1.5]

@controller.add("func_attr", clear=True)
def fn_2():
return 2

@controller.once("func_attr")
def fn_3():
return 3

# get
f_attr = controller.func
f_item = controller["func"]

assert f_attr is f_item

# set
controller.func_attr = fn
controller["func_item"] = fn

assert controller.func_attr() == [1, 2, 3]
assert controller.func_attr() == [1, 2]
assert controller.func_attr() == [1, 2]

# invalid set
with pytest.raises(Exception):
controller.trigger = fn


@pytest.mark.asyncio
async def test_tasks(controller):
@controller.add("async_fn")
def sync_fn_add():
return 1

@controller.add_task("async_fn", clear=True)
async def async_fn():
asyncio.sleep(0.01)
return 2

@controller.add("async_fn")
def sync_fn_add_2():
return 4

@controller.set("async_fn")
def set_fn():
return 5

result = controller.async_fn()
assert len(result) == 3
assert result[0] == 5
assert result[1] == 4
assert await result[2] == 2

result = controller.async_fn()
assert len(result) == 3

with pytest.raises(KeyError):
controller.async_fn.remove(async_fn)
controller.async_fn.remove_task(async_fn)

result = controller.async_fn()
assert len(result) == 2

controller.async_fn.discard(async_fn) # no error if missing
controller.async_fn.discard(sync_fn_add_2)

assert controller.async_fn() == 5

@controller.set("async_fn", clear=True)
def set_fn_2():
return 10

assert controller.async_fn() == 10
assert controller.async_fn.exists()
controller.async_fn.clear()
assert not controller.async_fn.exists()
16 changes: 16 additions & 0 deletions tests/test_namespace.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
from trame.app import get_server
from trame.ui.html import DivLayout
from trame.widgets import html


def test_namespace_template():
server = get_server("test_namespace_template")
child_server = server.create_child_server(prefix="child_")
child_server.state.a = 10

layout = DivLayout(child_server)
with layout:
html.Div("{{ a }}")

assert layout.html == "<div >\n<div >\n{{ child_a }}\n</div>\n</div>"
assert child_server.translator("a") == "child_a"
Loading

0 comments on commit eba8290

Please sign in to comment.