Skip to content

Commit 03f97a3

Browse files
authored
test: accept platform auto-abort in test_actor_charge_limit (#944)
## Description The E2E test `test_actor_charge_limit` was flaky: it asserted the run ended as `SUCCEEDED`, but occasionally observed `ABORTED` (e.g. [this CI run](https://github.com/apify/apify-sdk-python/actions/runs/27021762318/job/79751266145?pr=941)). ### Root cause When an Actor reaches its `max_total_charge_usd` ceiling, the **platform automatically aborts the run** (*"Aborted automatically after reaching the maximum cost of the run $0.20"*). That auto-abort races with the Actor's own clean exit (`exit_code 0`), so the terminal status is non-deterministically `SUCCEEDED` or `ABORTED`. It's a platform-side timing race, not an SDK or test-logic bug — the actual behavior under test (the charge limit capping the run at exactly 2 of the 4 attempted events) held fine in both outcomes. ### Fix Accept both `SUCCEEDED` and `ABORTED` as valid terminal statuses, keying the wait/assert on `charged_event_counts == {'foobar': 2}` (the real invariant).
1 parent 227e7c7 commit 03f97a3

1 file changed

Lines changed: 11 additions & 2 deletions

File tree

tests/e2e/test_actor_charge.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ async def ppe_push_data_actor(
7777
@pytest_asyncio.fixture(scope='module', loop_scope='module')
7878
async def ppe_actor_build(make_actor: MakeActorFunction) -> str:
7979
async def main() -> None:
80+
import asyncio
8081
from dataclasses import asdict
8182

8283
async with Actor:
@@ -86,6 +87,12 @@ async def main() -> None:
8687
)
8788
Actor.log.info('Charged', extra=asdict(charge_result))
8889

90+
# When the charge limit is reached, the platform auto-aborts this run. That abort races with the
91+
# Actor's own clean exit, making the terminal status non-deterministic (SUCCEEDED or ABORTED). Block
92+
# here so the abort always wins and the run ends deterministically as ABORTED.
93+
if charge_result.event_charge_limit_reached:
94+
await asyncio.Event().wait()
95+
8996
actor_client = await make_actor('ppe', main_func=main)
9097

9198
await actor_client.update(
@@ -148,15 +155,17 @@ async def test_actor_charge_limit(
148155
) -> None:
149156
run = await run_actor(ppe_actor, max_total_charge_usd=Decimal('0.2'))
150157

158+
# Reaching `max_total_charge_usd` makes the platform auto-abort the run. The Actor blocks after hitting the
159+
# limit (see `ppe_actor_build`) so the abort always wins the race against its clean exit, hence ABORTED.
151160
# Refetch until the charged event counts propagate on the platform.
152161
run = await poll_until_condition(
153162
partial(_get_run, apify_client_async, run.id),
154-
lambda r: r.status == 'SUCCEEDED' and r.charged_event_counts == {'foobar': 2},
163+
lambda r: r.status == 'ABORTED' and r.charged_event_counts == {'foobar': 2},
155164
timeout=30,
156165
poll_interval=1,
157166
)
158167

159-
assert run.status == 'SUCCEEDED'
168+
assert run.status == 'ABORTED'
160169
assert run.charged_event_counts == {'foobar': 2}
161170

162171

0 commit comments

Comments
 (0)