Skip to content

Commit 5f1e187

Browse files
authored
fix(tests): poll for logs in smoke tests to handle CloudWatch ingestion latency (#774)
1 parent f835622 commit 5f1e187

3 files changed

Lines changed: 47 additions & 46 deletions

File tree

tests/smoketests/sdk/test_async_devbox.py

Lines changed: 23 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
from runloop_api_client.sdk import AsyncDevbox, AsyncRunloopSDK
1212
from tests.smoketests.utils import unique_name
1313
from runloop_api_client.lib.polling import PollingConfig
14+
from runloop_api_client.lib.polling_async import async_poll_until
1415

1516
pytestmark = [pytest.mark.smoketest, pytest.mark.asyncio]
1617

@@ -1042,18 +1043,20 @@ class TestAsyncDevboxLogs:
10421043

10431044
@pytest.mark.timeout(THIRTY_SECOND_TIMEOUT)
10441045
async def test_logs_basic(self, shared_devbox: AsyncDevbox) -> None:
1045-
"""Test retrieving devbox logs returns valid response structure."""
1046+
"""Test retrieving unfiltered devbox logs."""
10461047
test_message = "async basic log test message"
10471048
result = await shared_devbox.cmd.exec(f'echo "{test_message}"')
10481049
assert result.exit_code == 0
10491050

1050-
logs = await shared_devbox.logs()
1051-
1052-
assert logs is not None
1053-
assert hasattr(logs, "logs")
1054-
assert isinstance(logs.logs, list)
1055-
log_content = " ".join(str(log) for log in logs.logs)
1056-
assert test_message in log_content
1051+
# Log ingestion is async — logs may not be queryable immediately after
1052+
# command execution. Poll every 1s with a 10s timeout (well within the
1053+
# 30s test timeout) to accommodate variable ingestion latency.
1054+
logs = await async_poll_until(
1055+
retriever=lambda: shared_devbox.logs(),
1056+
is_terminal=lambda l: any(test_message in (log.message or "") for log in l.logs),
1057+
config=PollingConfig(timeout_seconds=10, interval_seconds=1),
1058+
)
1059+
assert any(test_message in (log.message or "") for log in logs.logs)
10571060

10581061
@pytest.mark.timeout(THIRTY_SECOND_TIMEOUT)
10591062
async def test_logs_with_execution_filter(self, shared_devbox: AsyncDevbox) -> None:
@@ -1062,13 +1065,12 @@ async def test_logs_with_execution_filter(self, shared_devbox: AsyncDevbox) -> N
10621065
result = await shared_devbox.cmd.exec(f'echo "{test_message}"')
10631066
assert result.exit_code == 0
10641067

1065-
logs = await shared_devbox.logs(execution_id=result.execution_id)
1066-
1067-
assert logs is not None
1068-
assert hasattr(logs, "logs")
1069-
assert isinstance(logs.logs, list)
1070-
log_content = " ".join(str(log) for log in logs.logs)
1071-
assert test_message in log_content
1068+
logs = await async_poll_until(
1069+
retriever=lambda: shared_devbox.logs(execution_id=result.execution_id),
1070+
is_terminal=lambda l: any(test_message in (log.message or "") for log in l.logs),
1071+
config=PollingConfig(timeout_seconds=10, interval_seconds=1),
1072+
)
1073+
assert any(test_message in (log.message or "") for log in logs.logs)
10721074

10731075
@pytest.mark.timeout(THIRTY_SECOND_TIMEOUT)
10741076
async def test_logs_with_shell_name_filter(self, shared_devbox: AsyncDevbox) -> None:
@@ -1080,10 +1082,9 @@ async def test_logs_with_shell_name_filter(self, shared_devbox: AsyncDevbox) ->
10801082
result = await shell.exec(f'echo "{test_message}"')
10811083
assert result.exit_code == 0
10821084

1083-
logs = await shared_devbox.logs(shell_name=shell_name)
1084-
1085-
assert logs is not None
1086-
assert hasattr(logs, "logs")
1087-
assert isinstance(logs.logs, list)
1088-
log_content = " ".join(str(log) for log in logs.logs)
1089-
assert test_message in log_content
1085+
logs = await async_poll_until(
1086+
retriever=lambda: shared_devbox.logs(shell_name=shell_name),
1087+
is_terminal=lambda l: any(test_message in (log.message or "") for log in l.logs),
1088+
config=PollingConfig(timeout_seconds=10, interval_seconds=1),
1089+
)
1090+
assert any(test_message in (log.message or "") for log in logs.logs)

tests/smoketests/sdk/test_devbox.py

Lines changed: 23 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010

1111
from runloop_api_client.sdk import Devbox, RunloopSDK
1212
from tests.smoketests.utils import unique_name
13-
from runloop_api_client.lib.polling import PollingConfig
13+
from runloop_api_client.lib.polling import PollingConfig, poll_until
1414

1515
pytestmark = [pytest.mark.smoketest]
1616

@@ -1028,18 +1028,20 @@ class TestDevboxLogs:
10281028

10291029
@pytest.mark.timeout(THIRTY_SECOND_TIMEOUT)
10301030
def test_logs_basic(self, shared_devbox: Devbox) -> None:
1031-
"""Test retrieving devbox logs returns valid response structure."""
1031+
"""Test retrieving unfiltered devbox logs."""
10321032
test_message = "basic log test message"
10331033
result = shared_devbox.cmd.exec(f'echo "{test_message}"')
10341034
assert result.exit_code == 0
10351035

1036-
logs = shared_devbox.logs()
1037-
1038-
assert logs is not None
1039-
assert hasattr(logs, "logs")
1040-
assert isinstance(logs.logs, list)
1041-
log_content = " ".join(str(log) for log in logs.logs)
1042-
assert test_message in log_content
1036+
# Log ingestion is async — logs may not be queryable immediately after
1037+
# command execution. Poll every 1s with a 10s timeout (well within the
1038+
# 30s test timeout) to accommodate variable ingestion latency.
1039+
logs = poll_until(
1040+
retriever=lambda: shared_devbox.logs(),
1041+
is_terminal=lambda l: any(test_message in (log.message or "") for log in l.logs),
1042+
config=PollingConfig(timeout_seconds=10, interval_seconds=1),
1043+
)
1044+
assert any(test_message in (log.message or "") for log in logs.logs)
10431045

10441046
@pytest.mark.timeout(THIRTY_SECOND_TIMEOUT)
10451047
def test_logs_with_execution_filter(self, shared_devbox: Devbox) -> None:
@@ -1048,13 +1050,12 @@ def test_logs_with_execution_filter(self, shared_devbox: Devbox) -> None:
10481050
result = shared_devbox.cmd.exec(f'echo "{test_message}"')
10491051
assert result.exit_code == 0
10501052

1051-
logs = shared_devbox.logs(execution_id=result.execution_id)
1052-
1053-
assert logs is not None
1054-
assert hasattr(logs, "logs")
1055-
assert isinstance(logs.logs, list)
1056-
log_content = " ".join(str(log) for log in logs.logs)
1057-
assert test_message in log_content
1053+
logs = poll_until(
1054+
retriever=lambda: shared_devbox.logs(execution_id=result.execution_id),
1055+
is_terminal=lambda l: any(test_message in (log.message or "") for log in l.logs),
1056+
config=PollingConfig(timeout_seconds=10, interval_seconds=1),
1057+
)
1058+
assert any(test_message in (log.message or "") for log in logs.logs)
10581059

10591060
@pytest.mark.timeout(THIRTY_SECOND_TIMEOUT)
10601061
def test_logs_with_shell_name_filter(self, shared_devbox: Devbox) -> None:
@@ -1066,10 +1067,9 @@ def test_logs_with_shell_name_filter(self, shared_devbox: Devbox) -> None:
10661067
result = shell.exec(f'echo "{test_message}"')
10671068
assert result.exit_code == 0
10681069

1069-
logs = shared_devbox.logs(shell_name=shell_name)
1070-
1071-
assert logs is not None
1072-
assert hasattr(logs, "logs")
1073-
assert isinstance(logs.logs, list)
1074-
log_content = " ".join(str(log) for log in logs.logs)
1075-
assert test_message in log_content
1070+
logs = poll_until(
1071+
retriever=lambda: shared_devbox.logs(shell_name=shell_name),
1072+
is_terminal=lambda l: any(test_message in (log.message or "") for log in l.logs),
1073+
config=PollingConfig(timeout_seconds=10, interval_seconds=1),
1074+
)
1075+
assert any(test_message in (log.message or "") for log in logs.logs)

uv.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)