Skip to content

Commit f9ad5bf

Browse files
Python testing: Fix reporting on setup_class error (#35016)
* Python testing: Fix reporting on setup_class error Also add error text to make the error easier to find. * Restyled by isort * Fix lint * Change exemption to not carry chip_error reference chip_error is a ctypes struct with a const char* pointer internally. This cannot be pickled, so it's causing problems with the mobly framework. * Fix some tests using removed class member --------- Co-authored-by: Restyled.io <commits@restyled.io>
1 parent 1d4f684 commit f9ad5bf

File tree

5 files changed

+65
-14
lines changed

5 files changed

+65
-14
lines changed

src/controller/python/chip/exceptions/__init__.py

+5-9
Original file line numberDiff line numberDiff line change
@@ -39,21 +39,17 @@ class ChipStackException(Exception):
3939

4040

4141
class ChipStackError(ChipStackException):
42-
def __init__(self, chip_error: PyChipError, msg=None):
43-
self._chip_error = chip_error
44-
self.msg = msg if msg else "Chip Stack Error %d" % chip_error.code
42+
def __init__(self, code: int, msg=None):
43+
self.code = code
44+
self.msg = msg if msg else "Chip Stack Error %d" % self.code
4545

4646
@classmethod
4747
def from_chip_error(cls, chip_error: PyChipError) -> ChipStackError:
48-
return cls(chip_error, str(chip_error))
49-
50-
@property
51-
def chip_error(self) -> PyChipError | None:
52-
return self._chip_error
48+
return cls(chip_error.code, str(chip_error))
5349

5450
@property
5551
def err(self) -> int:
56-
return self._chip_error.code
52+
return self.code
5753

5854
def __str__(self):
5955
return self.msg

src/controller/python/chip/native/__init__.py

+5-1
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ class ErrorSDKPart(enum.IntEnum):
6969
class PyChipError(ctypes.Structure):
7070
''' The ChipError for Python library.
7171
72-
We are using the following struct for passing the infomations of CHIP_ERROR between C++ and Python:
72+
We are using the following struct for passing the information of CHIP_ERROR between C++ and Python:
7373
7474
```c
7575
struct PyChipError
@@ -88,6 +88,10 @@ def raise_on_error(self) -> None:
8888
if exception is not None: # Ensure exception is not None to avoid mypy error and only raise valid exceptions
8989
raise exception
9090

91+
@classmethod
92+
def from_code(cls, code):
93+
return cls(code=code, line=0, file=ctypes.c_void_p())
94+
9195
@property
9296
def is_success(self) -> bool:
9397
return self.code == 0

src/python_testing/TC_CADMIN_1_9.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
from chip import ChipDeviceCtrl
3232
from chip.ChipDeviceCtrl import CommissioningParameters
3333
from chip.exceptions import ChipStackError
34+
from chip.native import PyChipError
3435
from matter_testing_support import MatterBaseTest, TestStep, async_test_body, default_matter_test_main
3536
from mobly import asserts
3637

@@ -74,7 +75,7 @@ async def CommissionOnNetwork(
7475
await self.th2.CommissionOnNetwork(
7576
nodeId=self.dut_node_id, setupPinCode=setup_code,
7677
filterType=ChipDeviceCtrl.DiscoveryFilterType.LONG_DISCRIMINATOR, filter=self.discriminator)
77-
errcode = ctx.exception.chip_error
78+
errcode = PyChipError.from_code(ctx.exception.err)
7879
return errcode
7980

8081
async def CommissionAttempt(

src/python_testing/TC_CGEN_2_4.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
from chip import ChipDeviceCtrl
3939
from chip.ChipDeviceCtrl import CommissioningParameters
4040
from chip.exceptions import ChipStackError
41+
from chip.native import PyChipError
4142
from matter_testing_support import MatterBaseTest, async_test_body, default_matter_test_main
4243
from mobly import asserts
4344

@@ -78,7 +79,7 @@ async def CommissionToStageSendCompleteAndCleanup(
7879
await self.th2.CommissionOnNetwork(
7980
nodeId=self.dut_node_id, setupPinCode=params.setupPinCode,
8081
filterType=ChipDeviceCtrl.DiscoveryFilterType.LONG_DISCRIMINATOR, filter=self.discriminator)
81-
errcode = ctx.exception.chip_error
82+
errcode = PyChipError.from_code(ctx.exception.err)
8283
asserts.assert_true(errcode.sdk_part == expectedErrorPart, 'Unexpected error type returned from CommissioningComplete')
8384
asserts.assert_true(errcode.sdk_code == expectedErrCode, 'Unexpected error code returned from CommissioningComplete')
8485
revokeCmd = Clusters.AdministratorCommissioning.Commands.RevokeCommissioning()

src/python_testing/matter_testing_support.py

+51-2
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import random
2828
import re
2929
import sys
30+
import textwrap
3031
import time
3132
import typing
3233
import uuid
@@ -1123,12 +1124,60 @@ def on_fail(self, record):
11231124
self.failed = True
11241125
if self.runner_hook and not self.is_commissioning:
11251126
exception = record.termination_signal.exception
1126-
step_duration = (datetime.now(timezone.utc) - self.step_start_time) / timedelta(microseconds=1)
1127-
test_duration = (datetime.now(timezone.utc) - self.test_start_time) / timedelta(microseconds=1)
1127+
1128+
try:
1129+
step_duration = (datetime.now(timezone.utc) - self.step_start_time) / timedelta(microseconds=1)
1130+
except AttributeError:
1131+
# If we failed during setup, these may not be populated
1132+
step_duration = 0
1133+
try:
1134+
test_duration = (datetime.now(timezone.utc) - self.test_start_time) / timedelta(microseconds=1)
1135+
except AttributeError:
1136+
test_duration = 0
11281137
# TODO: I have no idea what logger, logs, request or received are. Hope None works because I have nothing to give
11291138
self.runner_hook.step_failure(logger=None, logs=None, duration=step_duration, request=None, received=None)
11301139
self.runner_hook.test_stop(exception=exception, duration=test_duration)
11311140

1141+
def extract_error_text() -> tuple[str, str]:
1142+
no_stack_trace = ("Stack Trace Unavailable", "")
1143+
if not record.termination_signal.stacktrace:
1144+
return no_stack_trace
1145+
trace = record.termination_signal.stacktrace.splitlines()
1146+
if not trace:
1147+
return no_stack_trace
1148+
1149+
if isinstance(exception, signals.TestError):
1150+
# Exception gets raised by the mobly framework, so the proximal error is one line back in the stack trace
1151+
assert_candidates = [idx for idx, line in enumerate(trace) if "asserts" in line and "asserts.py" not in line]
1152+
if not assert_candidates:
1153+
return "Unknown error, please see stack trace above", ""
1154+
assert_candidate_idx = assert_candidates[-1]
1155+
else:
1156+
# Normal assert is on the Last line
1157+
assert_candidate_idx = -1
1158+
probable_error = trace[assert_candidate_idx]
1159+
1160+
# Find the file marker immediately above the probable error
1161+
file_candidates = [idx for idx, line in enumerate(trace[:assert_candidate_idx]) if "File" in line]
1162+
if not file_candidates:
1163+
return probable_error, "Unknown file"
1164+
return probable_error.strip(), trace[file_candidates[-1]].strip()
1165+
1166+
probable_error, probable_file = extract_error_text()
1167+
logging.error(textwrap.dedent(f"""
1168+
1169+
******************************************************************
1170+
*
1171+
* Test {self.current_test_info.name} failed for the following reason:
1172+
* {exception}
1173+
*
1174+
* {probable_file}
1175+
* {probable_error}
1176+
*
1177+
*******************************************************************
1178+
1179+
"""))
1180+
11321181
def on_pass(self, record):
11331182
''' Called by Mobly on test pass
11341183

0 commit comments

Comments
 (0)