Skip to content

Commit a5ffe26

Browse files
raballewclaude
andcommitted
test: add _cleanup_after_lease guard tests for _stop_requested flag
Verify that _cleanup_after_lease does not report AVAILABLE status when _stop_requested is True, both with skip_after_lease_hook set and with no hook executor configured. This is a regression test for the exporter state machine fix in PR #349 that prevents dying exporters from appearing available to the controller. Ref: #245 Generated-By: Forge/20260416_202053_681470_8c18858d_i245 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 01cbb7e commit a5ffe26

1 file changed

Lines changed: 53 additions & 0 deletions

File tree

python/packages/jumpstarter/jumpstarter/exporter/exporter_test.py

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -391,6 +391,59 @@ async def test_finally_sets_before_lease_hook_on_early_cancel(self):
391391
)
392392

393393

394+
class TestStopRequestedGuard:
395+
async def test_cleanup_does_not_report_available_when_stop_requested_with_skip(self):
396+
"""When _stop_requested is True and skip_after_lease_hook is True,
397+
_cleanup_after_lease must NOT report AVAILABLE. This prevents the
398+
controller from assigning new leases to a dying exporter (issue #245)."""
399+
lease_ctx = make_lease_context()
400+
lease_ctx.before_lease_hook.set()
401+
lease_ctx.skip_after_lease_hook = True
402+
403+
statuses = []
404+
405+
async def track_status(status, message=""):
406+
statuses.append(status)
407+
408+
exporter = make_exporter(lease_ctx)
409+
exporter._stop_requested = True
410+
exporter._report_status = AsyncMock(side_effect=track_status)
411+
412+
await exporter._cleanup_after_lease(lease_ctx)
413+
414+
available_statuses = [s for s in statuses if s == ExporterStatus.AVAILABLE]
415+
assert len(available_statuses) == 0, (
416+
f"AVAILABLE must NOT be reported when _stop_requested is True, "
417+
f"got statuses: {statuses}"
418+
)
419+
assert lease_ctx.after_lease_hook_done.is_set()
420+
421+
async def test_cleanup_does_not_report_available_when_stop_requested_no_hooks(self):
422+
"""When _stop_requested is True and no hook executor is configured,
423+
_cleanup_after_lease must NOT report AVAILABLE. Even without hooks,
424+
the _stop_requested guard prevents AVAILABLE during shutdown."""
425+
lease_ctx = make_lease_context()
426+
lease_ctx.before_lease_hook.set()
427+
428+
statuses = []
429+
430+
async def track_status(status, message=""):
431+
statuses.append(status)
432+
433+
exporter = make_exporter(lease_ctx, hook_executor=None)
434+
exporter._stop_requested = True
435+
exporter._report_status = AsyncMock(side_effect=track_status)
436+
437+
await exporter._cleanup_after_lease(lease_ctx)
438+
439+
available_statuses = [s for s in statuses if s == ExporterStatus.AVAILABLE]
440+
assert len(available_statuses) == 0, (
441+
f"AVAILABLE must NOT be reported when _stop_requested is True "
442+
f"(no hooks), got statuses: {statuses}"
443+
)
444+
assert lease_ctx.after_lease_hook_done.is_set()
445+
446+
394447
class TestIdempotentLeaseEnd:
395448
async def test_duplicate_cleanup_is_noop(self):
396449
"""Calling _cleanup_after_lease twice for the same LeaseContext

0 commit comments

Comments
 (0)