Skip to content

Commit 3e6657c

Browse files
authored
[Python] Eliminate ZCLReadAttribute/ZCLSend (#33428)
* Convert TestLevelControlCluster to asyncio Remove ZCLReadAttribute and ZCLSend API use from the level control test TestLevelControlCluster and convert to asyncio. * Convert TestReadBasicAttributes to asyncio Remove ZCLReadAttribute API use from basic information cluster test and convert to use asyncio. * Use SendCommand directly in send_zcl_command Avoid using ZCLSend API instead use SendCommand directly in the send_zcl_command helper function. * Convert TestFailsafe to use asyncio/SendCommand Remove ZCLSend API usage and call SendCommand directly. Also convert the test to a test using asyncio. * Convert TestOnOffCluster to use asyncio/SendCommand Remove ZCLSend API usage and call SendCommand directly. Also convert the test to a test using asyncio. * Drop TestResult helper class The class is no longer required. Test results are tested directly. * Fix send_zcl_command argument formatting * Catch exception more specifically * Fix TestWriteBasicAttributes for all cases It seems TestWriteBasicAttributes did not correctly write the attributes. The broad exception handling seems to have hidden this issue even. Make sure the attributes with the correct value get written, and check for unexpected and expected IM errors in the per-attribute results specifically. * Fix TestFailsafe by catching correct exception * Drop unused import
1 parent 219e198 commit 3e6657c

File tree

7 files changed

+134
-152
lines changed

7 files changed

+134
-152
lines changed

src/controller/python/test/test_scripts/base.py

+82-107
Original file line numberDiff line numberDiff line change
@@ -178,29 +178,6 @@ def run(self):
178178
TestFail("Timeout", doCrash=True)
179179

180180

181-
class TestResult:
182-
def __init__(self, operationName, result):
183-
self.operationName = operationName
184-
self.result = result
185-
186-
def assertStatusEqual(self, expected):
187-
if self.result is None:
188-
raise Exception(f"{self.operationName}: no result got")
189-
if self.result.status != expected:
190-
raise Exception(
191-
f"{self.operationName}: expected status {expected}, got {self.result.status}")
192-
return self
193-
194-
def assertValueEqual(self, expected):
195-
self.assertStatusEqual(0)
196-
if self.result is None:
197-
raise Exception(f"{self.operationName}: no result got")
198-
if self.result.value != expected:
199-
raise Exception(
200-
f"{self.operationName}: expected value {expected}, got {self.result.value}")
201-
return self
202-
203-
204181
class BaseTestHelper:
205182
def __init__(self, nodeid: int, paaTrustStorePath: str, testCommissioner: bool = False,
206183
keypair: p256keypair.P256Keypair = None):
@@ -368,15 +345,16 @@ def TestOnNetworkCommissioning(self, discriminator: int, setuppin: int, nodeid:
368345
def TestUsedTestCommissioner(self):
369346
return self.devCtrl.GetTestCommissionerUsed()
370347

371-
def TestFailsafe(self, nodeid: int):
348+
async def TestFailsafe(self, nodeid: int):
372349
self.logger.info("Testing arm failsafe")
373350

374351
self.logger.info("Setting failsafe on CASE connection")
375-
err, resp = self.devCtrl.ZCLSend("GeneralCommissioning", "ArmFailSafe", nodeid,
376-
0, 0, dict(expiryLengthSeconds=60, breadcrumb=1), blocking=True)
377-
if err != 0:
352+
try:
353+
resp = await self.devCtrl.SendCommand(nodeid, 0,
354+
Clusters.GeneralCommissioning.Commands.ArmFailSafe(expiryLengthSeconds=60, breadcrumb=1))
355+
except IM.InteractionModelError as ex:
378356
self.logger.error(
379-
"Failed to send arm failsafe command error is {} with im response{}".format(err, resp))
357+
"Failed to send arm failsafe command error is {}".format(ex.status))
380358
return False
381359

382360
if resp.errorCode is not Clusters.GeneralCommissioning.Enums.CommissioningErrorEnum.kOk:
@@ -387,17 +365,17 @@ def TestFailsafe(self, nodeid: int):
387365
self.logger.info(
388366
"Attempting to open basic commissioning window - this should fail since the failsafe is armed")
389367
try:
390-
asyncio.run(self.devCtrl.SendCommand(
368+
await self.devCtrl.SendCommand(
391369
nodeid,
392370
0,
393371
Clusters.AdministratorCommissioning.Commands.OpenBasicCommissioningWindow(180),
394372
timedRequestTimeoutMs=10000
395-
))
373+
)
396374
# we actually want the exception here because we want to see a failure, so return False here
397375
self.logger.error(
398376
'Incorrectly succeeded in opening basic commissioning window')
399377
return False
400-
except Exception:
378+
except IM.InteractionModelError:
401379
pass
402380

403381
# TODO:
@@ -413,51 +391,52 @@ def TestFailsafe(self, nodeid: int):
413391
self.logger.info(
414392
"Attempting to open enhanced commissioning window - this should fail since the failsafe is armed")
415393
try:
416-
asyncio.run(self.devCtrl.SendCommand(
394+
await self.devCtrl.SendCommand(
417395
nodeid, 0, Clusters.AdministratorCommissioning.Commands.OpenCommissioningWindow(
418396
commissioningTimeout=180,
419397
PAKEPasscodeVerifier=verifier,
420398
discriminator=discriminator,
421399
iterations=iterations,
422-
salt=salt), timedRequestTimeoutMs=10000))
400+
salt=salt), timedRequestTimeoutMs=10000)
423401

424402
# we actually want the exception here because we want to see a failure, so return False here
425403
self.logger.error(
426404
'Incorrectly succeeded in opening enhanced commissioning window')
427405
return False
428-
except Exception:
406+
except IM.InteractionModelError:
429407
pass
430408

431409
self.logger.info("Disarming failsafe on CASE connection")
432-
err, resp = self.devCtrl.ZCLSend("GeneralCommissioning", "ArmFailSafe", nodeid,
433-
0, 0, dict(expiryLengthSeconds=0, breadcrumb=1), blocking=True)
434-
if err != 0:
410+
try:
411+
resp = await self.devCtrl.SendCommand(nodeid, 0,
412+
Clusters.GeneralCommissioning.Commands.ArmFailSafe(expiryLengthSeconds=0, breadcrumb=1))
413+
except IM.InteractionModelError as ex:
435414
self.logger.error(
436-
"Failed to send arm failsafe command error is {} with im response{}".format(err, resp))
415+
"Failed to send arm failsafe command error is {}".format(ex.status))
437416
return False
438417

439418
self.logger.info(
440419
"Opening Commissioning Window - this should succeed since the failsafe was just disarmed")
441420
try:
442-
asyncio.run(
443-
self.devCtrl.SendCommand(
444-
nodeid,
445-
0,
446-
Clusters.AdministratorCommissioning.Commands.OpenBasicCommissioningWindow(180),
447-
timedRequestTimeoutMs=10000
448-
))
421+
await self.devCtrl.SendCommand(
422+
nodeid,
423+
0,
424+
Clusters.AdministratorCommissioning.Commands.OpenBasicCommissioningWindow(180),
425+
timedRequestTimeoutMs=10000
426+
)
449427
except Exception:
450428
self.logger.error(
451429
'Failed to open commissioning window after disarming failsafe')
452430
return False
453431

454432
self.logger.info(
455433
"Attempting to arm failsafe over CASE - this should fail since the commissioning window is open")
456-
err, resp = self.devCtrl.ZCLSend("GeneralCommissioning", "ArmFailSafe", nodeid,
457-
0, 0, dict(expiryLengthSeconds=60, breadcrumb=1), blocking=True)
458-
if err != 0:
434+
try:
435+
resp = await self.devCtrl.SendCommand(nodeid, 0,
436+
Clusters.GeneralCommissioning.Commands.ArmFailSafe(expiryLengthSeconds=60, breadcrumb=1))
437+
except IM.InteractionModelError as ex:
459438
self.logger.error(
460-
"Failed to send arm failsafe command error is {} with im response{}".format(err, resp))
439+
"Failed to send arm failsafe command error is {}".format(ex.status))
461440
return False
462441
if resp.errorCode is Clusters.GeneralCommissioning.Enums.CommissioningErrorEnum.kBusyWithOtherAdmin:
463442
return True
@@ -1095,50 +1074,48 @@ def SetNetworkCommissioningParameters(self, dataset: str):
10951074
self.devCtrl.SetThreadOperationalDataset(bytes.fromhex(dataset))
10961075
return True
10971076

1098-
def TestOnOffCluster(self, nodeid: int, endpoint: int, group: int):
1077+
async def TestOnOffCluster(self, nodeid: int, endpoint: int):
10991078
self.logger.info(
11001079
"Sending On/Off commands to device {} endpoint {}".format(nodeid, endpoint))
1101-
err, resp = self.devCtrl.ZCLSend("OnOff", "On", nodeid,
1102-
endpoint, group, {}, blocking=True)
1103-
if err != 0:
1080+
1081+
try:
1082+
await self.devCtrl.SendCommand(nodeid, endpoint,
1083+
Clusters.OnOff.Commands.On())
1084+
except IM.InteractionModelError as ex:
11041085
self.logger.error(
1105-
"failed to send OnOff.On: error is {} with im response{}".format(err, resp))
1086+
"failed to send OnOff.On: error is {}".format(ex.status))
11061087
return False
1107-
err, resp = self.devCtrl.ZCLSend("OnOff", "Off", nodeid,
1108-
endpoint, group, {}, blocking=True)
1109-
if err != 0:
1088+
1089+
try:
1090+
await self.devCtrl.SendCommand(nodeid, endpoint,
1091+
Clusters.OnOff.Commands.Off())
1092+
except IM.InteractionModelError as ex:
11101093
self.logger.error(
1111-
"failed to send OnOff.Off: error is {} with im response {}".format(err, resp))
1094+
"failed to send OnOff.Off: error is {}".format(ex.status))
11121095
return False
11131096
return True
11141097

1115-
def TestLevelControlCluster(self, nodeid: int, endpoint: int, group: int):
1098+
async def TestLevelControlCluster(self, nodeid: int, endpoint: int):
11161099
self.logger.info(
11171100
f"Sending MoveToLevel command to device {nodeid} endpoint {endpoint}")
1118-
try:
1119-
commonArgs = dict(transitionTime=0, optionsMask=1, optionsOverride=1)
11201101

1102+
commonArgs = dict(transitionTime=0, optionsMask=1, optionsOverride=1)
1103+
1104+
async def _moveClusterLevel(setLevel):
1105+
await self.devCtrl.SendCommand(nodeid,
1106+
endpoint,
1107+
Clusters.LevelControl.Commands.MoveToLevel(**commonArgs, level=setLevel))
1108+
res = await self.devCtrl.ReadAttribute(nodeid, [(endpoint, Clusters.LevelControl.Attributes.CurrentLevel)])
1109+
readVal = res[endpoint][Clusters.LevelControl][Clusters.LevelControl.Attributes.CurrentLevel]
1110+
if readVal != setLevel:
1111+
raise Exception(f"Read attribute LevelControl.CurrentLevel: expected value {setLevel}, got {readVal}")
1112+
1113+
try:
11211114
# Move to 1
1122-
self.devCtrl.ZCLSend("LevelControl", "MoveToLevel", nodeid,
1123-
endpoint, group, dict(**commonArgs, level=1), blocking=True)
1124-
res = self.devCtrl.ZCLReadAttribute(cluster="LevelControl",
1125-
attribute="CurrentLevel",
1126-
nodeid=nodeid,
1127-
endpoint=endpoint,
1128-
groupid=group)
1129-
TestResult("Read attribute LevelControl.CurrentLevel",
1130-
res).assertValueEqual(1)
1115+
await _moveClusterLevel(1)
11311116

11321117
# Move to 254
1133-
self.devCtrl.ZCLSend("LevelControl", "MoveToLevel", nodeid,
1134-
endpoint, group, dict(**commonArgs, level=254), blocking=True)
1135-
res = self.devCtrl.ZCLReadAttribute(cluster="LevelControl",
1136-
attribute="CurrentLevel",
1137-
nodeid=nodeid,
1138-
endpoint=endpoint,
1139-
groupid=group)
1140-
TestResult("Read attribute LevelControl.CurrentLevel",
1141-
res).assertValueEqual(254)
1118+
await _moveClusterLevel(254)
11421119

11431120
return True
11441121
except Exception as ex:
@@ -1171,29 +1148,27 @@ def TestResolve(self, nodeid):
11711148
self.logger.exception("Failed to resolve. {}".format(ex))
11721149
return False
11731150

1174-
def TestReadBasicAttributes(self, nodeid: int, endpoint: int, group: int):
1151+
async def TestReadBasicAttributes(self, nodeid: int, endpoint: int):
1152+
attrs = Clusters.BasicInformation.Attributes
11751153
basic_cluster_attrs = {
1176-
"VendorName": "TEST_VENDOR",
1177-
"VendorID": 0xFFF1,
1178-
"ProductName": "TEST_PRODUCT",
1179-
"ProductID": 0x8001,
1180-
"NodeLabel": "Test",
1181-
"Location": "XX",
1182-
"HardwareVersion": 0,
1183-
"HardwareVersionString": "TEST_VERSION",
1184-
"SoftwareVersion": 1,
1185-
"SoftwareVersionString": "1.0",
1154+
attrs.VendorName: "TEST_VENDOR",
1155+
attrs.VendorID: 0xFFF1,
1156+
attrs.ProductName: "TEST_PRODUCT",
1157+
attrs.ProductID: 0x8001,
1158+
attrs.NodeLabel: "Test",
1159+
attrs.Location: "XX",
1160+
attrs.HardwareVersion: 0,
1161+
attrs.HardwareVersionString: "TEST_VERSION",
1162+
attrs.SoftwareVersion: 1,
1163+
attrs.SoftwareVersionString: "1.0",
11861164
}
11871165
failed_zcl = {}
11881166
for basic_attr, expected_value in basic_cluster_attrs.items():
11891167
try:
1190-
res = self.devCtrl.ZCLReadAttribute(cluster="BasicInformation",
1191-
attribute=basic_attr,
1192-
nodeid=nodeid,
1193-
endpoint=endpoint,
1194-
groupid=group)
1195-
TestResult(f"Read attribute {basic_attr}", res).assertValueEqual(
1196-
expected_value)
1168+
res = await self.devCtrl.ReadAttribute(nodeid, [(endpoint, basic_attr)])
1169+
readVal = res[endpoint][Clusters.BasicInformation][basic_attr]
1170+
if readVal != expected_value:
1171+
raise Exception(f"Read attribute: expected value {expected_value}, got {readVal}")
11971172
except Exception as ex:
11981173
failed_zcl[basic_attr] = str(ex)
11991174
if failed_zcl:
@@ -1217,16 +1192,16 @@ class AttributeWriteRequest:
12171192
failed_attribute_write = []
12181193
for req in requests:
12191194
try:
1220-
try:
1221-
await self.devCtrl.WriteAttribute(nodeid, [(endpoint, req.attribute, 0)])
1222-
if req.expected_status != IM.Status.Success:
1223-
raise AssertionError(
1224-
f"Write attribute {req.attribute.__qualname__} expects failure but got success response")
1225-
except Exception as ex:
1226-
if req.expected_status != IM.Status.Success:
1227-
continue
1228-
else:
1229-
raise ex
1195+
# Errors tested here is in the per-attribute result list (type AttributeStatus)
1196+
write_res = await self.devCtrl.WriteAttribute(nodeid, [(endpoint, req.attribute(req.value))])
1197+
status = write_res[0].Status
1198+
if req.expected_status != status:
1199+
raise AssertionError(
1200+
f"Write attribute {req.attribute.__qualname__} expects {req.expected_status} but got {status}")
1201+
1202+
# Only execute read tests where write is successful.
1203+
if req.expected_status != IM.Status.Success:
1204+
continue
12301205

12311206
res = await self.devCtrl.ReadAttribute(nodeid, [(endpoint, req.attribute)])
12321207
val = res[endpoint][req.cluster][req.attribute]

src/controller/python/test/test_scripts/commissioning_failure_test.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

2020
# Commissioning test.
2121

22+
import asyncio
2223
import os
2324
import sys
2425
from optparse import OptionParser
@@ -121,9 +122,8 @@ def main():
121122
FailIfNot(test.TestCommissionFailure(1, 0), "Failed to commission device")
122123

123124
logger.info("Testing on off cluster")
124-
FailIfNot(test.TestOnOffCluster(nodeid=1,
125-
endpoint=LIGHTING_ENDPOINT_ID,
126-
group=GROUP_ID), "Failed to test on off cluster")
125+
FailIfNot(asyncio.run(test.TestOnOffCluster(nodeid=1,
126+
endpoint=LIGHTING_ENDPOINT_ID)), "Failed to test on off cluster")
127127

128128
timeoutTicker.stop()
129129

src/controller/python/test/test_scripts/commissioning_test.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

2020
# Commissioning test.
2121

22+
import asyncio
2223
import os
2324
import sys
2425
from optparse import OptionParser
@@ -146,9 +147,8 @@ def main():
146147
TestFail("Must provide device address or setup payload to commissioning the device")
147148

148149
logger.info("Testing on off cluster")
149-
FailIfNot(test.TestOnOffCluster(nodeid=options.nodeid,
150-
endpoint=LIGHTING_ENDPOINT_ID,
151-
group=GROUP_ID), "Failed to test on off cluster")
150+
FailIfNot(asyncio.run(test.TestOnOffCluster(nodeid=options.nodeid,
151+
endpoint=LIGHTING_ENDPOINT_ID)), "Failed to test on off cluster")
152152

153153
FailIfNot(test.TestUsedTestCommissioner(),
154154
"Test commissioner check failed")

src/controller/python/test/test_scripts/failsafe_tests.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

2020
# Commissioning test.
2121

22+
import asyncio
2223
import os
2324
import sys
2425
from optparse import OptionParser
@@ -99,7 +100,7 @@ def main():
99100
nodeid=1),
100101
"Failed to finish key exchange")
101102

102-
FailIfNot(test.TestFailsafe(nodeid=1), "Failed failsafe test")
103+
FailIfNot(asyncio.run(test.TestFailsafe(nodeid=1)), "Failed failsafe test")
103104

104105
timeoutTicker.stop()
105106

0 commit comments

Comments
 (0)