Compare commits
3 Commits
346d0a95d8
...
7a8e6f383a
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7a8e6f383a | ||
|
|
b673bda1f2 | ||
|
|
6c3ea5084b |
@@ -1,20 +1,21 @@
|
||||
child:
|
||||
child_app:
|
||||
module: child
|
||||
class: Child
|
||||
dependencies:
|
||||
- parent_app
|
||||
|
||||
sibling:
|
||||
sibling_app:
|
||||
module: sibling
|
||||
class: Sibling
|
||||
dependencies:
|
||||
- parent_app
|
||||
|
||||
parent:
|
||||
parent_app:
|
||||
module: parent
|
||||
class: Parent
|
||||
dependencies:
|
||||
- child
|
||||
- sibling
|
||||
- grand-parent_app
|
||||
|
||||
grand-parent:
|
||||
grand-parent_app:
|
||||
module: grand_parent
|
||||
class: GrandParent
|
||||
dependencies:
|
||||
- parent
|
||||
@@ -1,5 +1,6 @@
|
||||
from appdaemon.adapi import ADAPI
|
||||
|
||||
|
||||
class Child(ADAPI):
|
||||
def initialize(self):
|
||||
self.log(f"{self.__class__.__name__} Initialized")
|
||||
self.log(f'{self.__class__.__name__} Initialized')
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
from appdaemon.adapi import ADAPI
|
||||
|
||||
|
||||
class GrandParent(ADAPI):
|
||||
def initialize(self):
|
||||
self.log(f"{self.__class__.__name__} Initialized")
|
||||
self.log(f'{self.__class__.__name__} Initialized')
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
from appdaemon.adapi import ADAPI
|
||||
|
||||
|
||||
class Parent(ADAPI):
|
||||
def initialize(self):
|
||||
self.log(f"{self.__class__.__name__} Initialized")
|
||||
self.log(f'{self.__class__.__name__} Initialized')
|
||||
|
||||
@@ -2,6 +2,7 @@ from appdaemon.adapi import ADAPI
|
||||
|
||||
from child import Child
|
||||
|
||||
|
||||
class Sibling(Child):
|
||||
def initialize(self):
|
||||
self.log(f"{self.__class__.__name__} Initialized")
|
||||
self.log(f'{self.__class__.__name__} Initialized')
|
||||
|
||||
@@ -3,4 +3,4 @@ from appdaemon.adapi import ADAPI
|
||||
|
||||
class HelloWorld(ADAPI):
|
||||
def initialize(self):
|
||||
self.log(f"{self.__class__.__name__} Initialized")
|
||||
self.log(f'{self.__class__.__name__} Initialized')
|
||||
|
||||
@@ -3,4 +3,4 @@ from appdaemon.adapi import ADAPI
|
||||
|
||||
class SimpleApp(ADAPI):
|
||||
def initialize(self):
|
||||
self.log(f"{self.__class__.__name__} Initialized")
|
||||
self.log(f'{self.__class__.__name__} Initialized')
|
||||
|
||||
@@ -20,6 +20,9 @@ build-backend = "hatchling.build"
|
||||
managed = true
|
||||
dev-dependencies = []
|
||||
|
||||
[tool.ruff.format]
|
||||
quote-style = "single"
|
||||
|
||||
[tool.hatch.metadata]
|
||||
allow-direct-references = true
|
||||
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
def hello() -> str:
|
||||
return "Hello from ad-test!"
|
||||
return 'Hello from ad-test!'
|
||||
|
||||
@@ -38,7 +38,7 @@ def base_config() -> AppDaemonConfig:
|
||||
time_zone='America/Chicago',
|
||||
config_dir=CONFIG_DIR,
|
||||
config_file='appdaemon.yaml',
|
||||
module_debug={'_app_management': 'DEBUG'}
|
||||
module_debug={'_app_management': 'DEBUG'},
|
||||
)
|
||||
|
||||
|
||||
@@ -48,6 +48,10 @@ def ad(base_config: AppDaemonConfig):
|
||||
|
||||
ad = AppDaemon(Logging(), loop, base_config)
|
||||
|
||||
import sys
|
||||
|
||||
sys.path.insert(0, (CONFIG_DIR / 'apps/food-repo/src').as_posix())
|
||||
|
||||
for cfg in ad.logging.config.values():
|
||||
logger = logging.getLogger(cfg['name'])
|
||||
logger.propagate = True
|
||||
|
||||
@@ -3,16 +3,14 @@ import logging
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
import food.menu
|
||||
import pytest
|
||||
from appdaemon.appdaemon import AppDaemon
|
||||
from git import Repo
|
||||
|
||||
from . import utils
|
||||
from .utils import (
|
||||
count_error_lines,
|
||||
get_app_orders,
|
||||
get_load_order,
|
||||
get_loaded_apps,
|
||||
reset_file,
|
||||
)
|
||||
|
||||
@@ -20,6 +18,8 @@ INDENT = ' ' * 4
|
||||
|
||||
|
||||
def test_file(ad: AppDaemon, caplog: pytest.LogCaptureFixture, config_repo: Repo):
|
||||
import food.menu
|
||||
|
||||
module_file = Path(food.menu.__file__)
|
||||
|
||||
file_content = module_file.read_text()
|
||||
@@ -40,7 +40,11 @@ def test_file(ad: AppDaemon, caplog: pytest.LogCaptureFixture, config_repo: Repo
|
||||
reset_file(config_repo, module_file)
|
||||
|
||||
|
||||
def test_file_with_error(ad: AppDaemon, caplog: pytest.LogCaptureFixture, config_repo: Repo):
|
||||
def test_file_with_error(
|
||||
ad: AppDaemon, caplog: pytest.LogCaptureFixture, config_repo: Repo
|
||||
):
|
||||
import food.menu
|
||||
|
||||
module_file = Path(food.menu.__file__)
|
||||
|
||||
file_content = module_file.read_text().splitlines()
|
||||
@@ -61,23 +65,38 @@ def test_file_with_error(ad: AppDaemon, caplog: pytest.LogCaptureFixture, config
|
||||
reset_file(config_repo, module_file)
|
||||
|
||||
|
||||
def test_modification_child(ad: AppDaemon, caplog: pytest.LogCaptureFixture, config_repo: Repo):
|
||||
def test_modification_child(
|
||||
ad: AppDaemon, caplog: pytest.LogCaptureFixture, config_repo: Repo
|
||||
):
|
||||
assert isinstance(ad, AppDaemon)
|
||||
ad.loop.run_until_complete(asyncio.sleep(2.5))
|
||||
|
||||
module_file = Path(__file__).parents[2] / 'conf/apps/family/child.py'
|
||||
file_content = module_file.read_text()
|
||||
file_content += (INDENT * 2) + "self.log(f'Modified {self.__class__.__name__}')\n"
|
||||
module_file.write_text(file_content)
|
||||
utils.inject_into_file(module_file, 2, 'raise ImportError')
|
||||
|
||||
try:
|
||||
with caplog.at_level(logging.DEBUG):
|
||||
ad.loop.run_until_complete(asyncio.sleep(1.0))
|
||||
|
||||
assert count_error_lines(caplog) == 0
|
||||
assert get_loaded_apps(caplog) == {'child'}
|
||||
assert get_app_orders(caplog, 'stop') == ['child', 'parent', 'grand-parent']
|
||||
assert get_app_orders(caplog, 'start') == ['child', 'parent', 'grand-parent']
|
||||
assert "Error importing 'child'" in caplog.text
|
||||
|
||||
load_order = utils.get_load_order(caplog)
|
||||
assert load_order == ['child', 'sibling'] # sibling is a variant of child
|
||||
|
||||
stopping = get_app_orders(caplog, 'stop', ordered=False)
|
||||
assert stopping is not None, 'Failed to get app stop order'
|
||||
assert stopping == {'child_app', 'sibling_app'}
|
||||
|
||||
# start_order = get_app_orders(caplog, 'start')
|
||||
# assert start_order is None
|
||||
# assert start_order == ['child', 'parent', 'grand-parent'], f'Wrong app start order: {start_order}'
|
||||
|
||||
finally:
|
||||
reset_file(config_repo, module_file)
|
||||
ad.loop.run_until_complete(asyncio.sleep(1.0))
|
||||
|
||||
start_order = get_app_orders(caplog, 'start', ordered=False)
|
||||
assert start_order == {
|
||||
'child_app',
|
||||
'sibling_app',
|
||||
}, f'Wrong app start order: {start_order}'
|
||||
|
||||
@@ -5,7 +5,7 @@ from typing import List
|
||||
import pytest
|
||||
from appdaemon.appdaemon import AppDaemon
|
||||
|
||||
from .utils import get_load_order, count_error_lines
|
||||
from .utils import count_error_lines, get_load_order
|
||||
|
||||
|
||||
def validate_app_dependencies(ad: AppDaemon):
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import asyncio
|
||||
import re
|
||||
from pathlib import Path
|
||||
from typing import List
|
||||
from typing import Generator, List, Literal
|
||||
|
||||
import pytest
|
||||
from appdaemon.appdaemon import AppDaemon
|
||||
@@ -13,23 +13,47 @@ async def delayed_stop(ad: AppDaemon, delay: float):
|
||||
ad.stop()
|
||||
|
||||
|
||||
def get_load_order(caplog: pytest.LogCaptureFixture) -> List[str]:
|
||||
for record in caplog.records:
|
||||
if record.name == 'AppDaemon._app_management':
|
||||
if 'Determined module load order' in record.msg:
|
||||
return record.args[0]
|
||||
def get_load_order(
|
||||
caplog: pytest.LogCaptureFixture, ordered: bool = True
|
||||
) -> Generator[str, None, None]:
|
||||
records = (
|
||||
record.args[0]
|
||||
for record in get_logger_records(caplog, 'AppDaemon._app_management')
|
||||
if 'Determined module load order' in record.msg
|
||||
)
|
||||
try:
|
||||
result = list(records)[-1]
|
||||
if not ordered:
|
||||
result = set(result)
|
||||
return result
|
||||
except Exception:
|
||||
return
|
||||
|
||||
|
||||
def get_logger_records(caplog: pytest.LogCaptureFixture, name: str):
|
||||
def get_logger_records(caplog: pytest.LogCaptureFixture, logger_name: str):
|
||||
for record in caplog.records:
|
||||
if record.name == name:
|
||||
if record.name == logger_name:
|
||||
yield record
|
||||
|
||||
|
||||
def get_app_orders(caplog: pytest.LogCaptureFixture, phase: str) -> List[str]:
|
||||
for record in get_logger_records(caplog, 'AppDaemon._app_management'):
|
||||
if re.search(f'App {phase} order', record.msg):
|
||||
return record.args[0]
|
||||
def get_app_orders(
|
||||
caplog: pytest.LogCaptureFixture,
|
||||
phase: Literal['start', 'stop'],
|
||||
ordered: bool = True,
|
||||
) -> list[str]:
|
||||
"""Extracts the app start/stop order from the captured log lines"""
|
||||
records = (
|
||||
record.args[0]
|
||||
for record in get_logger_records(caplog, 'AppDaemon._app_management')
|
||||
if re.search(f'App {phase} order', record.msg)
|
||||
)
|
||||
try:
|
||||
result = list(records)[-1]
|
||||
if not ordered:
|
||||
result = set(result)
|
||||
return result
|
||||
except Exception:
|
||||
return
|
||||
|
||||
|
||||
def get_loaded_apps(caplog: pytest.LogCaptureFixture):
|
||||
@@ -39,7 +63,9 @@ def get_loaded_apps(caplog: pytest.LogCaptureFixture):
|
||||
|
||||
|
||||
def count_error_lines(caplog: pytest.LogCaptureFixture) -> int:
|
||||
error_log_lines = [msg for name, lvl, msg in caplog.record_tuples if name.startswith('Error')]
|
||||
error_log_lines = [
|
||||
msg for name, lvl, msg in caplog.record_tuples if name.startswith('Error')
|
||||
]
|
||||
return len(error_log_lines)
|
||||
|
||||
|
||||
@@ -47,3 +73,11 @@ def reset_file(repo: Repo, changed: Path):
|
||||
if not changed.is_absolute():
|
||||
changed = Path(repo.working_tree_dir) / changed
|
||||
repo.git.checkout('HEAD', '--', changed)
|
||||
|
||||
|
||||
def inject_into_file(file: Path, pos: int, line: str):
|
||||
content = file.read_text()
|
||||
lines = content.splitlines()
|
||||
lines.insert(pos, line)
|
||||
new_content = '\n'.join(lines)
|
||||
file.write_text(new_content)
|
||||
|
||||
Reference in New Issue
Block a user