Skip to content

Commit 1cd6d70

Browse files
committed
create/update custom benchmark and scenarios for smoketest, remove benchmark retrieval smoketest
1 parent bf54da8 commit 1cd6d70

2 files changed

Lines changed: 246 additions & 177 deletions

File tree

tests/smoketests/sdk/test_async_benchmark.py

Lines changed: 125 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -1,91 +1,126 @@
11
"""Asynchronous SDK smoke tests for AsyncBenchmark operations.
22
33
These tests validate the AsyncBenchmark class against the real API.
4-
Until AsyncBenchmarkOps is available (PR3), we use the raw async API client
5-
to find or create benchmarks for testing.
4+
We create a dedicated smoketest benchmark and scenarios with consistent names
5+
so that resources are reused across test runs (since there's no delete endpoint).
66
"""
77

88
from __future__ import annotations
99

10+
from typing import List, Tuple
11+
1012
import pytest
1113

12-
from runloop_api_client.sdk import AsyncBenchmark, AsyncRunloopSDK, AsyncScenarioRun, AsyncBenchmarkRun
14+
from runloop_api_client import AsyncRunloopSDK
15+
from runloop_api_client.sdk import AsyncScenario, AsyncBenchmark, AsyncScenarioRun, AsyncBenchmarkRun
1316

1417
pytestmark = [pytest.mark.smoketest]
1518

1619
TWO_MINUTE_TIMEOUT = 120
1720

18-
19-
class TestAsyncBenchmarkRetrieval:
20-
"""Test AsyncBenchmark retrieval operations."""
21-
22-
@pytest.mark.timeout(TWO_MINUTE_TIMEOUT)
23-
async def test_benchmark_from_existing(self, async_sdk_client: AsyncRunloopSDK) -> None:
24-
"""Test creating AsyncBenchmark from existing benchmark.
25-
26-
This test:
27-
1. Lists benchmarks via raw async API
28-
2. Creates an AsyncBenchmark wrapper
29-
3. Validates get_info returns correct data
30-
"""
31-
# List existing benchmarks via raw API
32-
benchmarks_page = await async_sdk_client.api.benchmarks.list_public(limit=1)
33-
benchmarks = benchmarks_page.benchmarks
34-
35-
if not benchmarks:
36-
raise Exception("No benchmarks available to test")
37-
38-
benchmark_data = benchmarks[0]
39-
40-
# Create AsyncBenchmark wrapper
41-
# TODO: use AsyncBenchmarkOps to create a benchmark once implemented
42-
benchmark = AsyncBenchmark(
43-
client=async_sdk_client.api,
44-
benchmark_id=benchmark_data.id,
45-
)
46-
47-
assert benchmark.id == benchmark_data.id
48-
49-
# Test get_info
50-
info = await benchmark.get_info()
51-
assert info.id == benchmark_data.id
52-
assert info.name == benchmark_data.name
21+
# Consistent names for smoketest resources
22+
SMOKETEST_BENCHMARK_NAME = "sdk-smoketest-benchmark"
23+
SMOKETEST_SCENARIO_1_NAME = "sdk-smoketest-scenario-1"
24+
SMOKETEST_SCENARIO_2_NAME = "sdk-smoketest-scenario-2"
25+
26+
27+
async def get_or_create_scenario(
28+
async_sdk_client: AsyncRunloopSDK,
29+
name: str,
30+
problem_statement: str,
31+
) -> AsyncScenario:
32+
"""Get an existing scenario by name or create a new one."""
33+
# Check if scenario already exists
34+
scenarios = await async_sdk_client.scenario.list(name=name, limit=1)
35+
for scenario in scenarios:
36+
# Return the first matching scenario
37+
return scenario
38+
39+
# Create a new scenario using the SDK builder
40+
return await (
41+
async_sdk_client.scenario.builder(name)
42+
.with_problem_statement(problem_statement)
43+
.add_shell_command_scorer("pass-scorer", command="exit 0")
44+
.push()
45+
)
46+
47+
48+
async def get_or_create_benchmark(
49+
async_sdk_client: AsyncRunloopSDK,
50+
name: str,
51+
scenario_ids: List[str],
52+
) -> AsyncBenchmark:
53+
"""Get an existing benchmark by name or create a new one."""
54+
# Check if benchmark already exists
55+
benchmarks_page = await async_sdk_client.api.benchmarks.list(name=name, limit=1)
56+
for benchmark in benchmarks_page.benchmarks:
57+
# Return the first matching benchmark
58+
return AsyncBenchmark(async_sdk_client.api, benchmark.id)
59+
60+
# Create a new benchmark
61+
return AsyncBenchmark(
62+
async_sdk_client.api,
63+
(
64+
await async_sdk_client.api.benchmarks.create(
65+
name=name,
66+
scenario_ids=scenario_ids,
67+
description="Smoketest benchmark for SDK testing",
68+
)
69+
).id,
70+
)
71+
72+
73+
@pytest.fixture(scope="module")
74+
async def smoketest_benchmark(
75+
async_sdk_client: AsyncRunloopSDK,
76+
) -> Tuple[AsyncBenchmark, List[str]]:
77+
"""Create or retrieve the smoketest benchmark and scenario IDs."""
78+
# Create or get scenarios
79+
scenario_1 = await get_or_create_scenario(
80+
async_sdk_client,
81+
SMOKETEST_SCENARIO_1_NAME,
82+
"Smoketest scenario 1 - basic validation",
83+
)
84+
scenario_2 = await get_or_create_scenario(
85+
async_sdk_client,
86+
SMOKETEST_SCENARIO_2_NAME,
87+
"Smoketest scenario 2 - basic validation",
88+
)
89+
90+
scenario_ids = [scenario_1.id, scenario_2.id]
91+
92+
# Create or get benchmark
93+
benchmark = await get_or_create_benchmark(
94+
async_sdk_client,
95+
SMOKETEST_BENCHMARK_NAME,
96+
scenario_ids,
97+
)
98+
99+
return benchmark, scenario_ids
53100

54101

55102
class TestAsyncBenchmarkRun:
56103
"""Test AsyncBenchmark run operations."""
57104

58105
@pytest.mark.timeout(TWO_MINUTE_TIMEOUT)
59-
async def test_benchmark_run_and_cancel(self, async_sdk_client: AsyncRunloopSDK) -> None:
106+
async def test_benchmark_run_and_cancel(
107+
self,
108+
async_sdk_client: AsyncRunloopSDK,
109+
smoketest_benchmark: Tuple[AsyncBenchmark, List[str]],
110+
) -> None:
60111
"""Test starting and canceling a benchmark run.
61112
62113
This test:
63-
1. Finds an existing benchmark
114+
1. Uses the smoketest benchmark fixture
64115
2. Starts a new benchmark run via the AsyncBenchmark class
65116
3. Validates the run object
66117
4. Cancels the run
67118
"""
68-
# Find an existing benchmark via raw API
69-
benchmarks_page = await async_sdk_client.api.benchmarks.list_public(limit=1)
70-
benchmarks = benchmarks_page.benchmarks
71-
72-
if not benchmarks:
73-
raise Exception("No benchmarks available to test")
74-
75-
benchmark_data = benchmarks[0]
76-
if not benchmark_data.scenario_ids:
77-
raise Exception("No scenarios available to test")
78-
79-
# Create AsyncBenchmark wrapper
80-
benchmark = AsyncBenchmark(
81-
client=async_sdk_client.api,
82-
benchmark_id=benchmark_data.id,
83-
)
119+
benchmark, scenario_ids = smoketest_benchmark
84120

85121
# Start a run
86122
run = await benchmark.start_run(run_name="sdk-smoketest-async-benchmark-run")
87-
scenario = async_sdk_client.scenario.from_id(benchmark_data.scenario_ids[0])
88-
scenario_run = None
123+
scenario_runs: List[AsyncScenarioRun] = []
89124

90125
try:
91126
assert isinstance(run, AsyncBenchmarkRun)
@@ -95,61 +130,60 @@ async def test_benchmark_run_and_cancel(self, async_sdk_client: AsyncRunloopSDK)
95130
# Get run info
96131
info = await run.get_info()
97132
assert info.id == run.id
98-
assert info.state in ["running", "completed", "canceled"]
99-
100-
# Start a scenario run
101-
scenario_run = await scenario.run(
102-
benchmark_run_id=run.id, run_name="sdk-smoketest-async-benchmark-run-scenario"
103-
)
104-
scenario_runs = await run.list_scenario_runs()
105-
assert isinstance(scenario_runs, list)
106-
assert len(scenario_runs) == 1
107-
assert isinstance(scenario_runs[0], AsyncScenarioRun)
108-
assert scenario_runs[0].id == scenario_run.id
109-
assert scenario_runs[0].devbox_id == scenario_run.devbox_id
133+
assert info.state == "running"
134+
135+
# Run the scenarios
136+
for scenario_id in scenario_ids:
137+
scenario = async_sdk_client.scenario.from_id(scenario_id)
138+
scenario_runs.append(
139+
await scenario.run_async(
140+
benchmark_run_id=run.id, run_name="sdk-smoketest-async-benchmark-run-scenario"
141+
)
142+
)
143+
144+
benchmark_scenario_runs = await run.list_scenario_runs()
145+
assert isinstance(benchmark_scenario_runs, list)
146+
assert len(benchmark_scenario_runs) == len(scenario_runs)
147+
for scenario_run in benchmark_scenario_runs:
148+
assert isinstance(scenario_run, AsyncScenarioRun)
149+
assert any(
150+
scenario_run.id == scenario_run.id and scenario_run.devbox_id == scenario_run.devbox_id
151+
for scenario_run in scenario_runs
152+
)
110153

111154
# Cancel the scenario run
112-
scenario_result = await scenario_run.cancel()
113-
assert scenario_result.state in ["canceled", "completed"]
155+
for scenario_run in scenario_runs:
156+
scenario_result = await scenario_run.cancel()
157+
assert scenario_result.state in ["canceled", "completed"]
114158

115159
# Cancel the benchmark run
116160
result = await run.cancel()
117161
assert result.state in ["canceled", "completed"]
118162

119163
except Exception:
120164
# Ensure cleanup on any error
121-
if scenario_run:
122-
await async_sdk_client.api.scenarios.runs.cancel(scenario_run.id)
123-
await async_sdk_client.api.benchmarks.runs.cancel(run.id)
165+
for scenario_run in scenario_runs:
166+
await scenario_run.cancel()
167+
await run.cancel()
124168
raise
125169

126170

127171
class TestAsyncBenchmarkListRuns:
128172
"""Test AsyncBenchmark list_runs operations."""
129173

130174
@pytest.mark.timeout(TWO_MINUTE_TIMEOUT)
131-
async def test_list_runs(self, async_sdk_client: AsyncRunloopSDK) -> None:
175+
async def test_list_runs(
176+
self,
177+
smoketest_benchmark: Tuple[AsyncBenchmark, List[str]],
178+
) -> None:
132179
"""Test listing benchmark runs.
133180
134181
This test:
135-
1. Finds an existing benchmark
182+
1. Uses the smoketest benchmark fixture
136183
2. Lists its runs
137184
3. Validates returned objects are AsyncBenchmarkRun instances
138185
"""
139-
# Find an existing benchmark via raw API
140-
benchmarks_page = await async_sdk_client.api.benchmarks.list_public(limit=1)
141-
benchmarks = benchmarks_page.benchmarks
142-
143-
if not benchmarks:
144-
raise Exception("No benchmarks available to test")
145-
146-
benchmark_data = benchmarks[0]
147-
148-
# Create AsyncBenchmark wrapper
149-
benchmark = AsyncBenchmark(
150-
client=async_sdk_client.api,
151-
benchmark_id=benchmark_data.id,
152-
)
186+
benchmark, _ = smoketest_benchmark
153187

154188
runs = await benchmark.list_runs()
155189
assert isinstance(runs, list)

0 commit comments

Comments
 (0)