Skip to content

Commit c1c5a6c

Browse files
committed
Fix TimestampRule using nttype instead of nttypes, add regression tests for all Rules derived from BaseRule
1 parent 1f243c9 commit c1c5a6c

2 files changed

Lines changed: 45 additions & 2 deletions

File tree

p4pillon/rules/timestamp_rule.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ class TimestampRule(BaseRule):
1818
"""Set current timestamp unless provided with an alternative value"""
1919

2020
name = "timestamp"
21-
nttype = [SupportedNTTypes.ALL]
21+
nttypes = [SupportedNTTypes.ALL]
2222
fields = ["timeStamp"]
2323

2424
# @property

tests/unit/test_rules.py

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,36 @@
1+
import inspect
12
import logging
23
from unittest.mock import patch
34

45
import numpy
56
import pytest
67
from p4p.nt import NTScalar
78

9+
import p4pillon.rules as rules_module
810
from p4pillon.definitions import AlarmSeverity
9-
from p4pillon.rules import CalcRule, ControlRule, RulesFlow, ScalarToArrayWrapperRule, TimestampRule, ValueAlarmRule
11+
from p4pillon.rules import (
12+
BaseRule,
13+
CalcRule,
14+
ControlRule,
15+
RulesFlow,
16+
ScalarToArrayWrapperRule,
17+
TimestampRule,
18+
ValueAlarmRule,
19+
)
20+
from p4pillon.rules.rules import SupportedNTTypes
1021
from p4pillon.utils import overwrite_unmarked
1122

23+
# Concrete Rules exported from p4pillon.rules -- excludes BaseRule itself and
24+
# ScalarToArrayWrapperRule, whose name/nttypes are properties derived from the
25+
# rule it wraps rather than fixed class attributes.
26+
CONCRETE_RULE_CLASSES = [
27+
getattr(rules_module, class_name)
28+
for class_name in rules_module.__all__
29+
if inspect.isclass(getattr(rules_module, class_name))
30+
and issubclass(getattr(rules_module, class_name), BaseRule)
31+
and getattr(rules_module, class_name) not in (BaseRule, ScalarToArrayWrapperRule)
32+
]
33+
1234

1335
class TestTimestamp:
1436
@pytest.mark.parametrize(
@@ -521,3 +543,24 @@ def test_initialise_calc_rule(self):
521543
assert len(rule._variables) == 1 and rule._variables[0] == "a:pv:name"
522544
assert rule._server == "fakeServer"
523545
assert rule._pv_name == "this:pv:name"
546+
547+
548+
class TestRuleClassAttributes:
549+
"""`name` and `nttypes` are used by SharedNT/CompositeHandler for rule
550+
introspection (see BaseRule's docstring). A Rule that leaves either unset
551+
-- e.g. through a typo like `nttype` instead of `nttypes` -- silently
552+
becomes invisible to that machinery instead of raising an error."""
553+
554+
@pytest.mark.parametrize("rule_cls", CONCRETE_RULE_CLASSES, ids=lambda cls: cls.__name__)
555+
def test_name_is_set(self, rule_cls):
556+
assert isinstance(rule_cls.name, str) and rule_cls.name != ""
557+
558+
@pytest.mark.parametrize("rule_cls", CONCRETE_RULE_CLASSES, ids=lambda cls: cls.__name__)
559+
def test_nttypes_is_set(self, rule_cls):
560+
# None/[] both mean "applies to all types" (see BaseRule.nttypes docstring
561+
# and sharednt.py's `if supported_nttypes:` check) -- either is valid, but
562+
# whatever is set must only contain real SupportedNTTypes members.
563+
if rule_cls.nttypes is None:
564+
return
565+
assert isinstance(rule_cls.nttypes, list)
566+
assert all(isinstance(nttype, SupportedNTTypes) for nttype in rule_cls.nttypes)

0 commit comments

Comments
 (0)