mirror of
https://github.com/home-assistant/operating-system.git
synced 2025-07-12 15:46:30 +00:00
Improve handling of timeouts in tests (#2890)
* Improve handling of timeouts in tests Make timeout handling in tests more transparent. Added a custom shell driver that allows to define global timeout for commands in the config file, and replaced for/sleep constructs with infinite loops that will be eventually terminated by pytest-timeout plugin. Current timeouts taken from last runs on Github CI with some extra headroom. * test_supervisor_is_updated shouldn't be skipped if no update was needed * Allow more time for system startup * Allow even more time for system startup
This commit is contained in:
parent
c33fc03fd6
commit
2888ccf28e
@ -44,7 +44,7 @@ def shell_json(target, strategy) -> callable:
|
|||||||
strategy.transition("shell")
|
strategy.transition("shell")
|
||||||
shell = target.get_driver("ShellDriver")
|
shell = target.get_driver("ShellDriver")
|
||||||
|
|
||||||
def get_json_response(command, *, timeout=60) -> dict:
|
def get_json_response(command, *, timeout=None) -> dict:
|
||||||
return json.loads("\n".join(shell.run_check(command, timeout=timeout)))
|
return json.loads("\n".join(shell.run_check(command, timeout=timeout)))
|
||||||
|
|
||||||
return get_json_response
|
return get_json_response
|
||||||
|
2
tests/pytest.ini
Normal file
2
tests/pytest.ini
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
[pytest]
|
||||||
|
timeout_method = signal
|
@ -12,11 +12,12 @@ targets:
|
|||||||
nic: user,model=virtio-net-pci
|
nic: user,model=virtio-net-pci
|
||||||
disk: disk-image
|
disk: disk-image
|
||||||
bios: bios
|
bios: bios
|
||||||
- ShellDriver:
|
- CustomTimeoutShellDriver:
|
||||||
login_prompt: 'homeassistant login: '
|
login_prompt: 'homeassistant login: '
|
||||||
username: 'root'
|
username: 'root'
|
||||||
prompt: '# '
|
prompt: '# '
|
||||||
login_timeout: 300
|
login_timeout: 300
|
||||||
|
command_timeout: 300
|
||||||
- QEMUShellStrategy: {}
|
- QEMUShellStrategy: {}
|
||||||
|
|
||||||
tools:
|
tools:
|
||||||
|
@ -4,6 +4,7 @@ import os
|
|||||||
import attr
|
import attr
|
||||||
|
|
||||||
from labgrid import target_factory, step
|
from labgrid import target_factory, step
|
||||||
|
from labgrid.driver import ShellDriver
|
||||||
from labgrid.strategy import Strategy, StrategyError
|
from labgrid.strategy import Strategy, StrategyError
|
||||||
|
|
||||||
|
|
||||||
@ -13,6 +14,19 @@ class Status(enum.Enum):
|
|||||||
shell = 2
|
shell = 2
|
||||||
|
|
||||||
|
|
||||||
|
@target_factory.reg_driver
|
||||||
|
@attr.s(eq=False)
|
||||||
|
class CustomTimeoutShellDriver(ShellDriver):
|
||||||
|
"""ShellDriver with a config-customizable timeout for run and run_check."""
|
||||||
|
command_timeout = attr.ib(default=30, validator=attr.validators.instance_of(int))
|
||||||
|
|
||||||
|
def run(self, cmd: str, *, timeout=None, codec="utf-8", decodeerrors="strict"):
|
||||||
|
return super().run(cmd, timeout=timeout or self.command_timeout, codec=codec, decodeerrors=decodeerrors)
|
||||||
|
|
||||||
|
def run_check(self, cmd: str, *, timeout=None, codec="utf-8", decodeerrors="strict"):
|
||||||
|
return super().run_check(cmd, timeout=timeout or self.command_timeout, codec=codec, decodeerrors=decodeerrors)
|
||||||
|
|
||||||
|
|
||||||
@target_factory.reg_driver
|
@target_factory.reg_driver
|
||||||
@attr.s(eq=False)
|
@attr.s(eq=False)
|
||||||
class QEMUShellStrategy(Strategy):
|
class QEMUShellStrategy(Strategy):
|
||||||
@ -20,7 +34,7 @@ class QEMUShellStrategy(Strategy):
|
|||||||
|
|
||||||
bindings = {
|
bindings = {
|
||||||
"qemu": "QEMUDriver",
|
"qemu": "QEMUDriver",
|
||||||
"shell": "ShellDriver",
|
"shell": "CustomTimeoutShellDriver",
|
||||||
}
|
}
|
||||||
|
|
||||||
status = attr.ib(default=Status.unknown)
|
status = attr.ib(default=Status.unknown)
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
labgrid==23.0.3
|
labgrid==23.0.3
|
||||||
pytest==7.2.2
|
pytest==7.2.2
|
||||||
pytest-dependency==0.5.1
|
pytest-dependency==0.5.1
|
||||||
|
pytest-timeout==2.2.0
|
||||||
|
@ -1,10 +1,14 @@
|
|||||||
import logging
|
import logging
|
||||||
from time import sleep
|
from time import sleep
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.dependency()
|
||||||
|
@pytest.mark.timeout(600)
|
||||||
def test_init(shell):
|
def test_init(shell):
|
||||||
def check_container_running(container_name):
|
def check_container_running(container_name):
|
||||||
out = shell.run_check(
|
out = shell.run_check(
|
||||||
@ -13,35 +17,37 @@ def test_init(shell):
|
|||||||
return "running" in out
|
return "running" in out
|
||||||
|
|
||||||
# wait for important containers first
|
# wait for important containers first
|
||||||
for _ in range(20):
|
while True:
|
||||||
if check_container_running("homeassistant") and check_container_running("hassio_supervisor"):
|
if check_container_running("homeassistant") and check_container_running("hassio_supervisor"):
|
||||||
break
|
break
|
||||||
|
|
||||||
sleep(5)
|
sleep(1)
|
||||||
|
|
||||||
# wait for system ready
|
# wait for system ready
|
||||||
for _ in range(20):
|
while True:
|
||||||
output = "\n".join(shell.run_check("ha os info || true"))
|
output = "\n".join(shell.run_check("ha os info || true"))
|
||||||
if "System is not ready" not in output:
|
if "System is not ready" not in output:
|
||||||
break
|
break
|
||||||
|
|
||||||
sleep(5)
|
sleep(1)
|
||||||
|
|
||||||
output = shell.run_check("ha os info")
|
output = shell.run_check("ha os info")
|
||||||
_LOGGER.info("%s", "\n".join(output))
|
_LOGGER.info("%s", "\n".join(output))
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.dependency(depends=["test_init"])
|
||||||
def test_dmesg(shell):
|
def test_dmesg(shell):
|
||||||
output = shell.run_check("dmesg")
|
output = shell.run_check("dmesg")
|
||||||
_LOGGER.info("%s", "\n".join(output))
|
_LOGGER.info("%s", "\n".join(output))
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.dependency(depends=["test_init"])
|
||||||
def test_supervisor_logs(shell):
|
def test_supervisor_logs(shell):
|
||||||
output = shell.run_check("ha su logs")
|
output = shell.run_check("ha su logs")
|
||||||
_LOGGER.info("%s", "\n".join(output))
|
_LOGGER.info("%s", "\n".join(output))
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.dependency(depends=["test_init"])
|
||||||
def test_systemctl_status(shell):
|
def test_systemctl_status(shell):
|
||||||
output = shell.run_check(
|
output = shell.run_check("systemctl --no-pager -l status -a || true")
|
||||||
"systemctl --no-pager -l status -a || true", timeout=90
|
|
||||||
)
|
|
||||||
_LOGGER.info("%s", "\n".join(output))
|
_LOGGER.info("%s", "\n".join(output))
|
||||||
|
@ -16,30 +16,30 @@ def stash() -> dict:
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.dependency()
|
@pytest.mark.dependency()
|
||||||
|
@pytest.mark.timeout(600)
|
||||||
def test_start_supervisor(shell, shell_json):
|
def test_start_supervisor(shell, shell_json):
|
||||||
def check_container_running(container_name):
|
def check_container_running(container_name):
|
||||||
out = shell.run_check(f"docker container inspect -f '{{{{.State.Status}}}}' {container_name} || true")
|
out = shell.run_check(f"docker container inspect -f '{{{{.State.Status}}}}' {container_name} || true")
|
||||||
return "running" in out
|
return "running" in out
|
||||||
|
|
||||||
for _ in range(20):
|
while True:
|
||||||
if check_container_running("homeassistant") and check_container_running("hassio_supervisor"):
|
if check_container_running("homeassistant") and check_container_running("hassio_supervisor"):
|
||||||
break
|
break
|
||||||
|
|
||||||
sleep(5)
|
sleep(1)
|
||||||
|
|
||||||
supervisor_ip = "\n".join(
|
supervisor_ip = "\n".join(
|
||||||
shell.run_check("docker inspect --format='{{.NetworkSettings.IPAddress}}' hassio_supervisor")
|
shell.run_check("docker inspect --format='{{.NetworkSettings.IPAddress}}' hassio_supervisor")
|
||||||
)
|
)
|
||||||
|
|
||||||
for _ in range(20):
|
while True:
|
||||||
try:
|
try:
|
||||||
if shell_json(f"curl -sSL http://{supervisor_ip}/supervisor/ping").get("result") == "ok":
|
if shell_json(f"curl -sSL http://{supervisor_ip}/supervisor/ping").get("result") == "ok":
|
||||||
break
|
break
|
||||||
except ExecutionError:
|
except ExecutionError:
|
||||||
pass # avoid failure when the container is restarting
|
pass # avoid failure when the container is restarting
|
||||||
sleep(5)
|
|
||||||
else:
|
sleep(1)
|
||||||
raise AssertionError("Supervisor did not start in time")
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.dependency(depends=["test_start_supervisor"])
|
@pytest.mark.dependency(depends=["test_start_supervisor"])
|
||||||
@ -55,6 +55,7 @@ def test_check_supervisor(shell_json):
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.dependency(depends=["test_check_supervisor"])
|
@pytest.mark.dependency(depends=["test_check_supervisor"])
|
||||||
|
@pytest.mark.timeout(300)
|
||||||
def test_update_supervisor(shell_json):
|
def test_update_supervisor(shell_json):
|
||||||
supervisor_info = shell_json("ha supervisor info --no-progress --raw-json")
|
supervisor_info = shell_json("ha supervisor info --no-progress --raw-json")
|
||||||
supervisor_version = supervisor_info.get("data").get("version")
|
supervisor_version = supervisor_info.get("data").get("version")
|
||||||
@ -68,9 +69,9 @@ def test_update_supervisor(shell_json):
|
|||||||
else:
|
else:
|
||||||
assert result.get("result") == "ok", f"Supervisor update failed: {result}"
|
assert result.get("result") == "ok", f"Supervisor update failed: {result}"
|
||||||
|
|
||||||
for _ in range(40):
|
while True:
|
||||||
try:
|
try:
|
||||||
supervisor_info = shell_json("ha supervisor info --no-progress --raw-json", timeout=90)
|
supervisor_info = shell_json("ha supervisor info --no-progress --raw-json")
|
||||||
data = supervisor_info.get("data")
|
data = supervisor_info.get("data")
|
||||||
if data and data.get("version") == data.get("version_latest"):
|
if data and data.get("version") == data.get("version_latest"):
|
||||||
logger.info(
|
logger.info(
|
||||||
@ -82,14 +83,13 @@ def test_update_supervisor(shell_json):
|
|||||||
break
|
break
|
||||||
except ExecutionError:
|
except ExecutionError:
|
||||||
pass # avoid failure when the container is restarting
|
pass # avoid failure when the container is restarting
|
||||||
sleep(5)
|
|
||||||
else:
|
sleep(1)
|
||||||
raise AssertionError("Supervisor did not update in time")
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.dependency(depends=["test_update_supervisor"])
|
@pytest.mark.dependency(depends=["test_check_supervisor"])
|
||||||
def test_supervisor_is_updated(shell_json):
|
def test_supervisor_is_updated(shell_json):
|
||||||
supervisor_info = shell_json("ha supervisor info --no-progress --raw-json", timeout=90)
|
supervisor_info = shell_json("ha supervisor info --no-progress --raw-json")
|
||||||
data = supervisor_info.get("data")
|
data = supervisor_info.get("data")
|
||||||
assert data and data.get("version") == data.get("version_latest")
|
assert data and data.get("version") == data.get("version_latest")
|
||||||
|
|
||||||
@ -98,7 +98,7 @@ def test_supervisor_is_updated(shell_json):
|
|||||||
def test_addon_install(shell_json):
|
def test_addon_install(shell_json):
|
||||||
# install Core SSH add-on
|
# install Core SSH add-on
|
||||||
assert (
|
assert (
|
||||||
shell_json("ha addons install core_ssh --no-progress --raw-json", timeout=300).get("result") == "ok"
|
shell_json("ha addons install core_ssh --no-progress --raw-json").get("result") == "ok"
|
||||||
), "Core SSH add-on install failed"
|
), "Core SSH add-on install failed"
|
||||||
# check Core SSH add-on is installed
|
# check Core SSH add-on is installed
|
||||||
assert (
|
assert (
|
||||||
@ -153,6 +153,7 @@ def test_addon_uninstall(shell_json):
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.dependency(depends=["test_supervisor_is_updated"])
|
@pytest.mark.dependency(depends=["test_supervisor_is_updated"])
|
||||||
|
@pytest.mark.timeout(450)
|
||||||
def test_restart_supervisor(shell, shell_json):
|
def test_restart_supervisor(shell, shell_json):
|
||||||
result = shell_json("ha supervisor restart --no-progress --raw-json")
|
result = shell_json("ha supervisor restart --no-progress --raw-json")
|
||||||
assert result.get("result") == "ok", f"Supervisor restart failed: {result}"
|
assert result.get("result") == "ok", f"Supervisor restart failed: {result}"
|
||||||
@ -161,24 +162,20 @@ def test_restart_supervisor(shell, shell_json):
|
|||||||
shell.run_check("docker inspect --format='{{.NetworkSettings.IPAddress}}' hassio_supervisor")
|
shell.run_check("docker inspect --format='{{.NetworkSettings.IPAddress}}' hassio_supervisor")
|
||||||
)
|
)
|
||||||
|
|
||||||
for _ in range(100):
|
while True:
|
||||||
try:
|
try:
|
||||||
if shell_json(f"curl -sSL http://{supervisor_ip}/supervisor/ping").get("result") == "ok":
|
if shell_json(f"curl -sSL http://{supervisor_ip}/supervisor/ping").get("result") == "ok":
|
||||||
if shell_json("ha os info --no-progress --raw-json").get("result") == "ok":
|
if shell_json("ha os info --no-progress --raw-json").get("result") == "ok":
|
||||||
break
|
break
|
||||||
except ExecutionError:
|
except ExecutionError:
|
||||||
pass # avoid failure when the container is restarting
|
pass # avoid failure when the container is restarting
|
||||||
sleep(5)
|
|
||||||
else:
|
sleep(1)
|
||||||
raise AssertionError("Supervisor did not start in time")
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.dependency(depends=["test_create_backup"])
|
@pytest.mark.dependency(depends=["test_create_backup"])
|
||||||
def test_restore_backup(shell_json, stash):
|
def test_restore_backup(shell_json, stash):
|
||||||
result = shell_json(
|
result = shell_json(f"ha backups restore {stash.get('slug')} --addons core_ssh --no-progress --raw-json")
|
||||||
f"ha backups restore {stash.get('slug')} --addons core_ssh --no-progress --raw-json",
|
|
||||||
timeout=300,
|
|
||||||
)
|
|
||||||
assert result.get("result") == "ok", f"Backup restore failed: {result}"
|
assert result.get("result") == "ok", f"Backup restore failed: {result}"
|
||||||
logger.info("Backup restore result: %s", result)
|
logger.info("Backup restore result: %s", result)
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user