131 lines
5.4 KiB
Python
131 lines
5.4 KiB
Python
"""Integration test for script.wait during on_boot (issue #12043).
|
|
|
|
This test verifies that script.wait works correctly when triggered from on_boot.
|
|
The issue was that ScriptWaitAction::setup() unconditionally disabled the loop,
|
|
even if play_complex() had already been called (from an on_boot trigger at the
|
|
same priority level) and enabled it.
|
|
|
|
The race condition occurs because:
|
|
1. on_boot's default priority is 600.0 (setup_priority::DATA)
|
|
2. ScriptWaitAction's default setup priority is also DATA (600.0)
|
|
3. When they have the same priority, if on_boot runs first and triggers a script,
|
|
ScriptWaitAction::play_complex() enables the loop
|
|
4. Then ScriptWaitAction::setup() runs and unconditionally disables the loop
|
|
5. The wait never completes because the loop is disabled
|
|
|
|
The fix adds a conditional check (like WaitUntilAction has) to only disable the
|
|
loop in setup() if num_running_ is 0.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import asyncio
|
|
import re
|
|
|
|
import pytest
|
|
|
|
from .types import APIClientConnectedFactory, RunCompiledFunction
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_script_wait_on_boot(
|
|
yaml_config: str,
|
|
run_compiled: RunCompiledFunction,
|
|
api_client_connected: APIClientConnectedFactory,
|
|
) -> None:
|
|
"""Test that script.wait works correctly when triggered from on_boot.
|
|
|
|
This reproduces issue #12043 where script.wait would hang forever when
|
|
triggered from on_boot due to a race condition in ScriptWaitAction::setup().
|
|
"""
|
|
test_complete = asyncio.Event()
|
|
|
|
# Track progress through the boot sequence
|
|
boot_started = False
|
|
first_script_started = False
|
|
first_script_completed = False
|
|
first_wait_returned = False
|
|
second_script_started = False
|
|
second_script_completed = False
|
|
all_completed = False
|
|
|
|
# Patterns for boot sequence logs
|
|
boot_start_pattern = re.compile(r"on_boot: Starting boot sequence")
|
|
show_start_pattern = re.compile(r"show_start_page: Starting")
|
|
show_complete_pattern = re.compile(r"show_start_page: Completed")
|
|
first_wait_pattern = re.compile(r"on_boot: First script completed")
|
|
flip_start_pattern = re.compile(r"flip_thru_pages: Starting")
|
|
flip_complete_pattern = re.compile(r"flip_thru_pages: Completed")
|
|
all_complete_pattern = re.compile(r"on_boot: All boot scripts completed")
|
|
|
|
def check_output(line: str) -> None:
|
|
"""Check log output for boot sequence progress."""
|
|
nonlocal boot_started, first_script_started, first_script_completed
|
|
nonlocal first_wait_returned, second_script_started, second_script_completed
|
|
nonlocal all_completed
|
|
|
|
if boot_start_pattern.search(line):
|
|
boot_started = True
|
|
elif show_start_pattern.search(line):
|
|
first_script_started = True
|
|
elif show_complete_pattern.search(line):
|
|
first_script_completed = True
|
|
elif first_wait_pattern.search(line):
|
|
first_wait_returned = True
|
|
elif flip_start_pattern.search(line):
|
|
second_script_started = True
|
|
elif flip_complete_pattern.search(line):
|
|
second_script_completed = True
|
|
elif all_complete_pattern.search(line):
|
|
all_completed = True
|
|
test_complete.set()
|
|
|
|
async with (
|
|
run_compiled(yaml_config, line_callback=check_output),
|
|
api_client_connected() as client,
|
|
):
|
|
# Verify device info
|
|
device_info = await client.device_info()
|
|
assert device_info is not None
|
|
assert device_info.name == "test-script-wait-on-boot"
|
|
|
|
# Wait for on_boot sequence to complete
|
|
# The boot sequence should complete automatically
|
|
# Timeout is generous to allow for delays in the scripts
|
|
try:
|
|
await asyncio.wait_for(test_complete.wait(), timeout=5.0)
|
|
except TimeoutError:
|
|
# Build a detailed error message showing where the boot sequence got stuck
|
|
progress = []
|
|
if boot_started:
|
|
progress.append("boot started")
|
|
if first_script_started:
|
|
progress.append("show_start_page started")
|
|
if first_script_completed:
|
|
progress.append("show_start_page completed")
|
|
if first_wait_returned:
|
|
progress.append("first script.wait returned")
|
|
if second_script_started:
|
|
progress.append("flip_thru_pages started")
|
|
if second_script_completed:
|
|
progress.append("flip_thru_pages completed")
|
|
|
|
if not first_wait_returned and first_script_completed:
|
|
pytest.fail(
|
|
f"Test timed out - script.wait hung after show_start_page completed! "
|
|
f"This is the issue #12043 bug. Progress: {', '.join(progress)}"
|
|
)
|
|
else:
|
|
pytest.fail(
|
|
f"Test timed out. Progress: {', '.join(progress) if progress else 'none'}"
|
|
)
|
|
|
|
# Verify the complete boot sequence executed in order
|
|
assert boot_started, "on_boot did not start"
|
|
assert first_script_started, "show_start_page did not start"
|
|
assert first_script_completed, "show_start_page did not complete"
|
|
assert first_wait_returned, "First script.wait did not return"
|
|
assert second_script_started, "flip_thru_pages did not start"
|
|
assert second_script_completed, "flip_thru_pages did not complete"
|
|
assert all_completed, "Boot sequence did not complete"
|