|
| 1 | +import inspect |
1 | 2 | import logging |
2 | 3 | from unittest.mock import patch |
3 | 4 |
|
4 | 5 | import numpy |
5 | 6 | import pytest |
6 | 7 | from p4p.nt import NTScalar |
7 | 8 |
|
| 9 | +import p4pillon.rules as rules_module |
8 | 10 | 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 |
10 | 21 | from p4pillon.utils import overwrite_unmarked |
11 | 22 |
|
| 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 | + |
12 | 34 |
|
13 | 35 | class TestTimestamp: |
14 | 36 | @pytest.mark.parametrize( |
@@ -521,3 +543,24 @@ def test_initialise_calc_rule(self): |
521 | 543 | assert len(rule._variables) == 1 and rule._variables[0] == "a:pv:name" |
522 | 544 | assert rule._server == "fakeServer" |
523 | 545 | 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