Skip to content

Commit 224082c

Browse files
committed
Parameterize smoketests to run both sync and async tests
1 parent e60ad46 commit 224082c

File tree

9 files changed

+242
-68
lines changed

9 files changed

+242
-68
lines changed

src/runloop_api_client/resources/devboxes/devboxes.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@
66
from typing_extensions import Literal
77

88
import httpx
9-
from uuid_utils import uuid7
9+
10+
# uuid_utils is not typed
11+
from uuid_utils import uuid7 # type: ignore
1012

1113
from .lsp import (
1214
LspResource,
@@ -809,7 +811,7 @@ def execute_and_await_completion(
809811
return the result within the initial request's timeout. If the execution is not yet
810812
complete, it switches to using wait_for_command to minimize latency while waiting.
811813
"""
812-
command_id = str(uuid7())
814+
command_id = str(uuid7()) # type: ignore
813815
execution = self.execute(
814816
devbox_id,
815817
command=command,
@@ -2251,7 +2253,7 @@ async def execute_and_await_completion(
22512253
complete, it switches to using wait_for_command to minimize latency while waiting.
22522254
"""
22532255

2254-
command_id = str(uuid7())
2256+
command_id = str(uuid7()) # type: ignore
22552257
execution = await self.execute(
22562258
devbox_id,
22572259
command=command,

tests/smoketests/README.md

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# Smoke tests
22

3-
End-to-end smoke tests run against the real API to validate critical flows (devboxes, snapshots, blueprints, executions/log tailing, scenarios/benchmarks).
3+
End-to-end smoke tests run against the real API to validate critical flows (devboxes, snapshots, blueprints, executions/log tailing, scenarios/benchmarks). Theses smoketests run both the
4+
async and sync clients.
45

56
- Local run (requires `RUNLOOP_API_KEY`):
67

@@ -13,13 +14,13 @@ export RUNLOOP_API_KEY=... # required
1314
uv pip install -r requirements-dev.lock
1415

1516
# Run all tests
16-
RUN_SMOKETESTS=1 uv run pytest -q -vv tests/smoketests
17+
RUN_SMOKETESTS=1 uv run pytest -q -vv -m smoketest tests/smoketests
1718

1819
# Run a single file
19-
RUN_SMOKETESTS=1 uv run pytest -q -vv tests/smoketests/test_devboxes.py
20+
RUN_SMOKETESTS=1 uv run pytest -q -vv -m smoketest tests/smoketests/test_devboxes.py
2021

2122
# Run a single test by name
22-
RUN_SMOKETESTS=1 uv run pytest -q -k "test_create_and_await_running_timeout" tests/smoketests/test_devboxes.py
23+
RUN_SMOKETESTS=1 uv run pytest -q -k -m smoketest "test_create_and_await_running_timeout" tests/smoketests/test_devboxes.py
2324
```
2425

2526
- GitHub Actions: add repo secret `RUNLOOP_SMOKETEST_DEV_API_KEY` and `RUNLOOP_SMOKETEST_PROD_API_KEY`. The workflow `.github/workflows/smoketests.yml` supports an input `environment` (dev|prod) and runs these tests in CI.

tests/smoketests/conftest.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
from __future__ import annotations
2+
3+
from typing import Iterator
4+
5+
import pytest
6+
from _pytest.fixtures import FixtureRequest # pyright: ignore[reportPrivateImportUsage]
7+
8+
from runloop_api_client import Runloop
9+
10+
from .utils import make_client, make_async_client_adapter
11+
12+
"""
13+
This file is used to create a client fixture for the tests.
14+
It makes it possible to run the tests with both sync and async clients.
15+
"""
16+
17+
18+
@pytest.fixture(scope="module", params=["sync", "async"], ids=["sync-client", "async-client"])
19+
def client(request: FixtureRequest) -> Iterator[Runloop]:
20+
if request.param == "sync":
21+
c: Runloop = make_client()
22+
try:
23+
yield c
24+
finally:
25+
try:
26+
# Runloop supports close()
27+
c.close()
28+
except Exception:
29+
pass
30+
else:
31+
c: Runloop = make_async_client_adapter()
32+
try:
33+
yield c
34+
finally:
35+
try:
36+
c.close()
37+
except Exception:
38+
pass

tests/smoketests/test_blueprints.py

Lines changed: 18 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,26 @@
1+
from __future__ import annotations
2+
3+
from typing import Iterator
4+
15
import pytest
26

7+
from runloop_api_client import Runloop
38
from runloop_api_client.lib.polling import PollingConfig
49

5-
from .utils import make_client, unique_name
10+
from .utils import unique_name
611

712
pytestmark = [pytest.mark.smoketest]
813

914

10-
client = make_client()
15+
@pytest.fixture(autouse=True, scope="module")
16+
def _cleanup(client: Runloop) -> Iterator[None]: # pyright: ignore[reportUnusedFunction]
17+
yield
18+
global _blueprint_id
19+
if _blueprint_id:
20+
try:
21+
client.blueprints.delete(_blueprint_id)
22+
except Exception:
23+
pass
1124

1225

1326
"""
@@ -18,17 +31,8 @@
1831
_blueprint_name = unique_name("bp")
1932

2033

21-
def teardown_module() -> None:
22-
global _blueprint_id
23-
if _blueprint_id:
24-
try:
25-
client.blueprints.delete(_blueprint_id)
26-
except Exception:
27-
pass
28-
29-
3034
@pytest.mark.timeout(30)
31-
def test_create_blueprint_and_await_build() -> None:
35+
def test_create_blueprint_and_await_build(client: Runloop) -> None:
3236
global _blueprint_id
3337
created = client.blueprints.create_and_await_build_complete(
3438
name=_blueprint_name,
@@ -39,7 +43,7 @@ def test_create_blueprint_and_await_build() -> None:
3943

4044

4145
@pytest.mark.timeout(30)
42-
def test_start_devbox_from_base_blueprint_by_id() -> None:
46+
def test_start_devbox_from_base_blueprint_by_id(client: Runloop) -> None:
4347
assert _blueprint_id
4448
devbox = client.devboxes.create_and_await_running(
4549
blueprint_id=_blueprint_id,
@@ -51,7 +55,7 @@ def test_start_devbox_from_base_blueprint_by_id() -> None:
5155

5256

5357
@pytest.mark.timeout(30)
54-
def test_start_devbox_from_base_blueprint_by_name() -> None:
58+
def test_start_devbox_from_base_blueprint_by_name(client: Runloop) -> None:
5559
devbox = client.devboxes.create_and_await_running(
5660
blueprint_name=_blueprint_name,
5761
polling_config=PollingConfig(max_attempts=120, interval_seconds=5.0, timeout_seconds=20 * 60),

tests/smoketests/test_devboxes.py

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,27 @@
1+
from __future__ import annotations
2+
3+
from typing import Iterator
4+
15
import pytest
26

7+
from runloop_api_client import Runloop
38
from runloop_api_client.lib.polling import PollingConfig, PollingTimeout
49

5-
from .utils import make_client, unique_name
10+
from .utils import unique_name
611

712
pytestmark = [pytest.mark.smoketest]
813

914

10-
client = make_client()
15+
@pytest.fixture(autouse=True, scope="module")
16+
def _cleanup(client: Runloop) -> Iterator[None]: # pyright: ignore[reportUnusedFunction]
17+
yield
18+
global _devbox_id
19+
if _devbox_id:
20+
try:
21+
client.devboxes.shutdown(_devbox_id)
22+
except Exception:
23+
pass
24+
1125

1226
"""
1327
Tests are run sequentially and can be dependent on each other.
@@ -17,14 +31,14 @@
1731

1832

1933
@pytest.mark.timeout(30)
20-
def test_create_devbox() -> None:
34+
def test_create_devbox(client: Runloop) -> None:
2135
created = client.devboxes.create(name=unique_name("smoke-devbox"))
2236
assert created.id
2337
client.devboxes.shutdown(created.id)
2438

2539

2640
@pytest.mark.timeout(30)
27-
def test_await_running_create_and_await_running() -> None:
41+
def test_await_running_create_and_await_running(client: Runloop) -> None:
2842
global _devbox_id
2943
created = client.devboxes.create_and_await_running(
3044
name=unique_name("smoketest-devbox2"),
@@ -34,27 +48,27 @@ def test_await_running_create_and_await_running() -> None:
3448
_devbox_id = created.id
3549

3650

37-
def test_list_devboxes() -> None:
51+
def test_list_devboxes(client: Runloop) -> None:
3852
page = client.devboxes.list(limit=10)
3953
assert isinstance(page.devboxes, list)
4054
assert len(page.devboxes) > 0
4155

4256

43-
def test_retrieve_devbox() -> None:
57+
def test_retrieve_devbox(client: Runloop) -> None:
4458
assert _devbox_id
4559
view = client.devboxes.retrieve(_devbox_id)
4660
assert view.id == _devbox_id
4761

4862

49-
def test_shutdown_devbox() -> None:
63+
def test_shutdown_devbox(client: Runloop) -> None:
5064
assert _devbox_id
5165
view = client.devboxes.shutdown(_devbox_id)
5266
assert view.id == _devbox_id
5367
assert view.status == "shutdown"
5468

5569

5670
@pytest.mark.timeout(90)
57-
def test_create_and_await_running_long_set_up() -> None:
71+
def test_create_and_await_running_long_set_up(client: Runloop) -> None:
5872
created = client.devboxes.create_and_await_running(
5973
name=unique_name("smoketest-devbox-await-running-long-set-up"),
6074
launch_parameters={"launch_commands": ["sleep 70"]},
@@ -65,7 +79,7 @@ def test_create_and_await_running_long_set_up() -> None:
6579

6680

6781
@pytest.mark.timeout(30)
68-
def test_create_and_await_running_timeout() -> None:
82+
def test_create_and_await_running_timeout(client: Runloop) -> None:
6983
with pytest.raises(PollingTimeout):
7084
client.devboxes.create_and_await_running(
7185
name=unique_name("smoketest-devbox-await-running-timeout"),

tests/smoketests/test_executions.py

Lines changed: 20 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,26 @@
1+
from __future__ import annotations
2+
3+
from typing import Iterator
4+
15
import pytest
26

7+
from runloop_api_client import Runloop
38
from runloop_api_client.lib.polling import PollingConfig
49

5-
from .utils import make_client, unique_name
10+
from .utils import unique_name
611

712
pytestmark = [pytest.mark.smoketest]
813

914

10-
client = make_client()
15+
@pytest.fixture(autouse=True, scope="module")
16+
def _cleanup(client: Runloop) -> Iterator[None]: # pyright: ignore[reportUnusedFunction]
17+
yield
18+
global _devbox_id
19+
if _devbox_id:
20+
try:
21+
client.devboxes.shutdown(_devbox_id)
22+
except Exception:
23+
pass
1124

1225

1326
"""
@@ -18,17 +31,8 @@
1831
_exec_id = None
1932

2033

21-
@pytest.fixture(scope="session")
22-
def some_function_name():
23-
# setup
24-
yield
25-
# teardown
26-
if _devbox_id:
27-
client.devboxes.shutdown(_devbox_id)
28-
29-
3034
@pytest.mark.timeout(30)
31-
def test_launch_devbox() -> None:
35+
def test_launch_devbox(client: Runloop) -> None:
3236
global _devbox_id
3337
created = client.devboxes.create_and_await_running(
3438
name=unique_name("exec-devbox"),
@@ -38,7 +42,7 @@ def test_launch_devbox() -> None:
3842

3943

4044
@pytest.mark.timeout(30)
41-
def test_execute_async_and_await_completion() -> None:
45+
def test_execute_async_and_await_completion(client: Runloop) -> None:
4246
assert _devbox_id
4347
global _exec_id
4448
started = client.devboxes.executions.execute_async(_devbox_id, command="echo hello && sleep 1")
@@ -52,7 +56,7 @@ def test_execute_async_and_await_completion() -> None:
5256

5357

5458
@pytest.mark.timeout(30)
55-
def test_tail_stdout_logs() -> None:
59+
def test_tail_stdout_logs(client: Runloop) -> None:
5660
assert _devbox_id and _exec_id
5761
stream = client.devboxes.executions.stream_stdout_updates(execution_id=_exec_id, devbox_id=_devbox_id)
5862
received = ""
@@ -64,7 +68,7 @@ def test_tail_stdout_logs() -> None:
6468

6569

6670
@pytest.mark.timeout(30)
67-
def test_execute_and_await_completion() -> None:
71+
def test_execute_and_await_completion(client: Runloop) -> None:
6872
assert _devbox_id
6973
completed = client.devboxes.execute_and_await_completion(
7074
_devbox_id,
@@ -75,7 +79,7 @@ def test_execute_and_await_completion() -> None:
7579

7680

7781
@pytest.mark.timeout(90)
78-
def test_execute_and_await_completion_long_running() -> None:
82+
def test_execute_and_await_completion_long_running(client: Runloop) -> None:
7983
assert _devbox_id
8084
completed = client.devboxes.execute_and_await_completion(
8185
_devbox_id,

0 commit comments

Comments
 (0)