Files
slic/tests/test_utils_opmsg.py
T
tligui_y cf2d49d202
Run CI Tests / test (push) Successful in 1m36s
Update tests/test_utils_opmsg.py
2025-08-09 01:01:31 +02:00

336 lines
12 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import time
import threading
import pytest
from morbidissimo import MorIOC
from slic.utils.opmsg import (
IDS, BEAMLINES, N_MSG_HISTORY,
OperationMessageStatus, OperationMessageEntry,
OperationMessage, OperationMessages, MachineStatus,
clean_name, IDS_INVERSE
)
def ioc():
"""
IOC de test pour:
- STATUS / STATUS-DATE
- OP-DATEi / OP-MSGi
- OP-MSG-tmp / OP-MSG-TMP
- CATEGORY / DOWNTIME
"""
def run_op_prefix(prefix: str):
with MorIOC(prefix) as mor:
mor.host(
STATUS={"type": "enum", "enums": ["OFFLINE", "PREPARATION", "REMOTE", "ATTENDED"]},
**{"STATUS-DATE": str},
**{f"OP-DATE{i}": str for i in range(N_MSG_HISTORY)},
**{f"OP-MSG{i}": str for i in range(N_MSG_HISTORY)},
**{"OP-MSG-tmp": str},
**{"OP-MSG-TMP": str},
)
mor.serve(
STATUS="OFFLINE",
**{"STATUS-DATE": "2024-01-01 00:00:00"},
**{f"OP-DATE{i}": f"2024-01-01 00:00:0{i}" for i in range(N_MSG_HISTORY)},
**{f"OP-MSG{i}": f"Initial message {i}" for i in range(N_MSG_HISTORY)},
**{"OP-MSG-tmp": ""},
**{"OP-MSG-TMP": ""},
)
while True:
mor.serve()
time.sleep(0.1)
def run_status_prefix(beamline: str):
# IMPORTANT: le code utilise SF-STATUS-{beamline}
with MorIOC(f"SF-STATUS-{beamline}") as mor:
mor.host(
CATEGORY={"type": "enum", "enums": ["USER", "MD", "SD", "ACCESS", "DOWN"]},
DOWNTIME=str,
)
mor.serve(CATEGORY="USER", DOWNTIME="00:00:00")
while True:
mor.serve()
time.sleep(0.1)
threads = []
# IOCs pour opmsg (un par ID)
for ID in IDS.values():
prefix = f"SF-OP:{ID}-MSG"
t = threading.Thread(target=run_op_prefix, args=(prefix,), daemon=True)
t.start()
threads.append(t)
# IOCs pour machinestatus
for bl in BEAMLINES:
t = threading.Thread(target=run_status_prefix, args=(bl,), daemon=True)
t.start()
threads.append(t)
return threads
@pytest.fixture(scope="module", autouse=True)
def run_all_iocs():
threads = ioc()
time.sleep(2.0) # laisser le temps aux serveurs de démarrer
yield
# -------- OperationMessageStatus --------
class TestOperationMessageStatus:
@pytest.fixture
def status(self):
s = OperationMessageStatus("SF-OP:CR-MSG")
assert s.pv_date.wait_for_connection(timeout=2.0), "PV STATUS-DATE not connected"
assert s.pv_status.wait_for_connection(timeout=2.0), "PV STATUS not connected"
return s
def test_initialization_and_enum(self, status):
assert status.pv_date.connected
assert status.pv_status.connected
allowed = status.get_allowed()
assert set(["OFFLINE", "PREPARATION", "REMOTE", "ATTENDED"]).issubset(set(allowed))
def test_properties_date_status(self, status):
status.pv_date.put("2025-08-08 10:00:00", wait=True)
status.pv_status.put("REMOTE", wait=True)
assert status.date == "2025-08-08 10:00:00"
assert status.status == "REMOTE"
def test_repr_uses_properties(self, status):
status.pv_date.put("2025-08-08 10:01:02", wait=True)
status.pv_status.put("ATTENDED", wait=True)
r = repr(status)
assert "2025-08-08 10:01:02 ATTENDED" in r
@pytest.mark.parametrize("method,expected", [
("set_offline", "OFFLINE"),
("set_preparation", "PREPARATION"),
("set_remote", "REMOTE"),
("set_attended", "ATTENDED"),
])
def test_set_methods_change_status(self, status, method, expected):
getattr(status, method)()
assert status.status == expected
def test_update_direct_valid(self, status):
status.update("REMOTE")
assert status.status == "REMOTE"
def test_update_invalid_raises_valueerror(self, status):
with pytest.raises(ValueError) as exc:
status.update("TOTALLY_INVALID_STATUS")
assert "not from allowed values" in str(exc.value)
'''
# -------- OperationMessageEntry --------
class TestOperationMessageEntry:
@pytest.fixture(params=range(N_MSG_HISTORY))
def entry(self, request):
idx = request.param
e = OperationMessageEntry("SF-OP:CR-MSG", idx)
assert e.pv_date.wait_for_connection(timeout=2.0), f"PV OP-DATE{idx} not connected"
assert e.pv_msg.wait_for_connection(timeout=2.0), f"PV OP-MSG{idx} not connected"
return e
def test_initialization(self, entry):
assert entry.pv_date.connected
assert entry.pv_msg.connected
def test_date_property(self, entry):
test_val = "2025-08-08 12:34:56"
entry.pv_date.put(test_val, wait=True)
assert entry.date == test_val
def test_msg_property(self, entry):
test_val = "Hello from test"
entry.pv_msg.put(test_val, wait=True)
assert entry.msg == test_val
def test_repr(self, entry):
date_val = "2025-08-08 13:00:00"
msg_val = "System maintenance"
entry.pv_date.put(date_val, wait=True)
entry.pv_msg.put(msg_val, wait=True)
# petite margede temps pour la lecture
time.sleep(0.05)
assert repr(entry) == f"{date_val} {msg_val}"
# -------- OperationMessage --------
class TestOperationMessage:
def test_init_with_name(self):
om = OperationMessage(name="Control Room")
assert om.name == "Control Room"
assert om.ID == "CR"
assert om.prefix == "SF-OP:CR-MSG"
assert om.pv_send.wait_for_connection(timeout=2.0)
assert om.status.pv_status.wait_for_connection(timeout=2.0)
assert len(om.entries) == N_MSG_HISTORY
for i, entry in enumerate(om.entries):
assert entry.pv_date.wait_for_connection(timeout=2.0), f"OP-DATE{i} not connected"
assert entry.pv_msg.wait_for_connection(timeout=2.0), f"OP-MSG{i} not connected"
def test_init_with_id(self):
om = OperationMessage(ID="cr") # case-insensitive
assert om.ID == "CR"
assert om.name == IDS_INVERSE["CR"]
assert om.prefix == "SF-OP:CR-MSG"
def test_init_error_when_missing_both(self):
with pytest.raises(ValueError):
OperationMessage()
def test_getitem_returns_entry(self):
om = OperationMessage(name="Control Room")
for i in range(N_MSG_HISTORY):
e = om[i]
assert isinstance(e, OperationMessageEntry)
def test_update_puts_to_tmp_channel(self):
om = OperationMessage(name="Control Room")
assert om.pv_send.wait_for_connection(timeout=2.0)
msg = "Beam down at 14:00"
om.update(msg)
# laisser lIOC cycler (serve() toutes 100 ms)
time.sleep(0.2)
got = om.pv_send.get(as_string=True)
if isinstance(got, bytes):
got = got.decode("utf-8", errors="ignore")
assert got == msg
def test_status_object_is_wired(self):
om = OperationMessage(name="Control Room")
om.status.update("REMOTE")
assert om.status.status == "REMOTE"
def test_repr_contains_header_and_entries(self):
om = OperationMessage(name="Control Room")
om.status.pv_status.put("ATTENDED", wait=True)
om.entries[0].pv_date.put("2025-08-08 12:00:00", wait=True)
om.entries[0].pv_msg.put("Slot A ok", wait=True)
r = repr(om)
assert "Control Room (CR)" in r
assert "ATTENDED" in r
assert "2025-08-08 12:00:00" in r
assert "Slot A ok" in r
# -------- OperationMessages --------
class TestOperationMessages:
def test_initialization_populates_entries_and_items(self):
oms = OperationMessages()
assert set(oms.entries.keys()) == set(IDS.keys())
for name, om in oms.entries.items():
assert isinstance(om, OperationMessage)
assert om.pv_send.wait_for_connection(timeout=2.0)
assert om.status.pv_status.wait_for_connection(timeout=2.0)
for i, entry in enumerate(om.entries):
assert isinstance(entry, OperationMessageEntry)
assert entry.pv_date.wait_for_connection(timeout=2.0), f"{om.prefix}:OP-DATE{i} not connected"
assert entry.pv_msg.wait_for_connection(timeout=2.0), f"{om.prefix}:OP-MSG{i} not connected"
for name in IDS.keys():
attr = clean_name(name)
assert hasattr(oms, attr), f"Missing attribute: {attr}"
assert getattr(oms, attr) is oms.entries[name]
def test_getitem_supports_cleaned_name_and_cleaned_id(self):
oms = OperationMessages()
name = "Control Room"
ID = IDS[name]
cleaned_name = "control_room"
cleaned_id = "cr"
om_by_name = oms[cleaned_name]
assert isinstance(om_by_name, OperationMessage)
assert om_by_name.ID == ID
om_by_id = oms[cleaned_id]
assert isinstance(om_by_id, OperationMessage)
assert om_by_id is om_by_name
om_by_original_name = oms["Control Room"]
assert om_by_original_name is om_by_name
with pytest.raises(KeyError):
_ = oms["does_not_exist"]
def test_iter_yields_operationmessage_instances(self):
oms = OperationMessages()
vals = list(iter(oms))
assert len(vals) == len(IDS)
assert all(isinstance(v, OperationMessage) for v in vals)
for om in vals:
# état initial injecté par lIOC de test
assert om.status.status == "OFFLINE"
assert om.status.date == "2024-01-01 00:00:00"
for i, entry in enumerate(om.entries):
assert entry.date == f"2024-01-01 00:00:0{i}"
assert entry.msg == f"Initial message {i}"
def test_key_completions_exposes_all_cleaned_keys(self):
oms = OperationMessages()
keys = set(oms._ipython_key_completions_())
expected_names = {clean_name(n) for n in IDS.keys()}
expected_ids = {clean_name(ID) for ID in IDS.values()}
assert expected_names.issubset(keys)
assert expected_ids.issubset(keys)
def test_repr_includes_each_submessage_repr(self):
oms = OperationMessages()
r = repr(oms)
assert "Control Room (CR)" in r
om_cr = oms["control_room"]
om_cr.entries[0].pv_date.put("2025-08-08 12:00:00", wait=True)
om_cr.entries[0].pv_msg.put("Test message 1", wait=True)
om_cr.entries[1].pv_date.put("2025-08-08 12:05:00", wait=True)
om_cr.entries[1].pv_msg.put("Test message 2", wait=True)
time.sleep(0.05)
r2 = repr(oms)
assert "2025-08-08 12:00:00" in r2
assert "Test message 1" in r2
assert "2025-08-08 12:05:00" in r2
assert "Test message 2" in r2
# -------- MachineStatus --------
class TestMachineStatus:
@pytest.mark.parametrize("beamline", BEAMLINES)
def test_init_and_connections(self, beamline):
ms = MachineStatus(beamline)
assert ms.beamline == beamline.upper()
assert ms.pv_category.wait_for_connection(timeout=2.0), f"{beamline}: CATEGORY not connected"
assert ms.pv_downtime.wait_for_connection(timeout=2.0), f"{beamline}: DOWNTIME not connected"
def test_init_invalid_beamline_raises(self):
with pytest.raises(ValueError) as exc:
MachineStatus("NOT_A_BEAMLINE")
assert 'beamline "NOT_A_BEAMLINE" must be from:' in str(exc.value)
@pytest.mark.parametrize("beamline", BEAMLINES)
def test_properties_category_and_downtime(self, beamline):
ms = MachineStatus(beamline)
ms.pv_category.put("MD", wait=True)
ms.pv_downtime.put("01:23:45", wait=True)
assert ms.category == "MD"
assert ms.downtime == "01:23:45"
ms.pv_category.put("SD", wait=True)
ms.pv_downtime.put("00:10:00", wait=True)
assert ms.category == "SD"
assert ms.downtime == "00:10:00"
@pytest.mark.parametrize("beamline", BEAMLINES)
def test_repr_contains_header_and_values(self, beamline):
ms = MachineStatus(beamline)
ms.pv_category.put("ACCESS", wait=True)
ms.pv_downtime.put("00:05:00", wait=True)
r = repr(ms)
assert beamline in r
assert "ACCESS" in r
assert "00:05:00" in r
'''