Skip to content

Commit

Permalink
Increase starter test coverage.
Browse files Browse the repository at this point in the history
  • Loading branch information
BenediktBurger committed Nov 20, 2024
1 parent 15094a1 commit 7667d7a
Show file tree
Hide file tree
Showing 6 changed files with 61 additions and 18 deletions.
21 changes: 7 additions & 14 deletions pyleco/management/starter.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
if __name__ != "__main__":
from ..utils.message_handler import MessageHandler
from ..utils.parser import parser, parse_command_line_parameters
else:
else: # pragma: no cover
from pyleco.utils.message_handler import MessageHandler
from pyleco.utils.parser import parser, parse_command_line_parameters

Expand Down Expand Up @@ -119,9 +119,7 @@ def __init__(
self.folder_name = "test_tasks"

log.info(f"Starter started with tasks in folder '{self.directory}'.")
if tasks is not None:
for task in tasks:
self.start_task(task)
self.start_tasks(tasks or ())

def register_rpc_methods(self) -> None:
super().register_rpc_methods()
Expand All @@ -140,21 +138,13 @@ def _listen_close(self, waiting_time: Optional[int] = None) -> None:
log.info("Starter stopped.")

def stop_all_tasks(self) -> None:
self.started_tasks = {}
keys = list(self.threads.keys())
for name in keys:
# set all stop signals
self.events[name].set()
for name in keys:
# wait for threads to have stopped
thread = self.threads[name]
thread.join(2)
if thread.is_alive():
log.warning(f"Task '{name}' did not stop in time!")
# TODO add possibility to stop thread otherwise.
try:
del self.threads[name]
except Exception as exc:
log.exception(f"Deleting task {name} failed", exc_info=exc)
self.wait_for_stopped_thread(name)

def heartbeat(self) -> None:
"""Check installed tasks at heartbeating."""
Expand Down Expand Up @@ -205,6 +195,9 @@ def stop_task(self, name: str) -> None:
return
log.info(f"Stopping task '{name}'.")
self.events[name].set()
self.wait_for_stopped_thread(name)

def wait_for_stopped_thread(self, name: str) -> None:
thread = self.threads[name]
thread.join(timeout=2)
if thread.is_alive():
Expand Down
3 changes: 3 additions & 0 deletions pyleco/management/test_tasks/failing_task.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# it shall fail.

raise ValueError("I fail for testing.")
3 changes: 3 additions & 0 deletions pyleco/management/test_tasks/no_task.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
"""
Task which can be imported, but not started as method `task` is missing.
"""
4 changes: 3 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,8 @@ omit = [
# Omit LECO definitions
"pyleco/errors.py",
"pyleco/core/leco_protocols.py",
# exclude import file
# omit import file
"pyleco/json_utils/rpc_server.py",
# omit files for testing only
"pyleco/management/test_tasks/*",
]
20 changes: 17 additions & 3 deletions tests/acceptance_tests/test_starter_live.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ def start_coordinator(namespace: str, port: int, coordinators=None, **kwargs):
def start_starter(event: threading.Event):
path = os.path.dirname(test_task.__file__)
starter = Starter(directory=path, port=PORT)
starter.heartbeat()
starter.listen(event)


Expand Down Expand Up @@ -82,9 +83,13 @@ def test_sign_in(director: StarterDirector):


def test_tasks_listing(director: StarterDirector):
assert director.list_tasks() == [{
"name": "test_task",
"tooltip": "Example scheme for an Actor for pymeasure instruments. 'test_task'\n"}]
assert director.list_tasks() == [
{
"name": "test_task",
"tooltip": "Example scheme for an Actor for pymeasure instruments. 'test_task'\n",
},
{"name": "failing_task", "tooltip": ""}
]


def test_start_task(director: StarterDirector):
Expand All @@ -100,3 +105,12 @@ def test_stop_task(director: StarterDirector):
status = Status(director.status_tasks("test_task").get("test_task", 0))
assert Status.STARTED not in status
assert Status.RUNNING not in status


def test_start_task_again(director: StarterDirector):
sleep(1)
director.start_tasks(["test_task", "failing_task", "no_task"])
status = Status(director.status_tasks("test_task")["test_task"])
assert Status.STARTED in status
assert Status.RUNNING in status
director.stop_tasks(["test_task", "no_task"])
28 changes: 28 additions & 0 deletions tests/management/test_starter.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,17 @@ def test_sanitize_tasks(tasks):
assert isinstance(t, str)


@pytest.mark.parametrize("tasks, invalid_task_name", (
(5, 5),
(("valid", 6), 6),
([["list"], "abc"], "['list']")),
)
def test_invalid_tasks(tasks, invalid_task_name, caplog):
assert sanitize_tasks(tasks) == ()
assert caplog.messages == [f"Invalid task name '{invalid_task_name}' received."]



def test_init(starter: Starter):
assert starter.started_tasks == {}
assert starter.threads == {}
Expand Down Expand Up @@ -220,3 +231,20 @@ def test_restart_tasks(starter: Starter):
starter.restart_tasks(["a", "b"])
assert starter.stop_task.call_args_list == [call("a"), call("b")]
assert starter.start_task.call_args_list == [call("a"), call("b")]


def test_stop_all_tasks(starter: Starter):
# arrange
starter.started_tasks["t1"] = Status.STARTED
starter.threads["t1"] = FakeThread(alive=True) # type: ignore
event = starter.events["t1"] = SimpleEvent() # type: ignore
# act
starter.stop_all_tasks()
assert "t1" not in starter.threads
assert "t1" not in starter.started_tasks
assert event.is_set() is True


def test_list_tasks_failing(starter: Starter):
starter.directory = "/abcdefghijklmno"
assert starter.list_tasks() == []

0 comments on commit 7667d7a

Please sign in to comment.