From 1986f4fe77ffd5849736a6cffc48d7b2bafbd831 Mon Sep 17 00:00:00 2001 From: cecille Date: Thu, 15 Aug 2024 10:31:35 -0400 Subject: [PATCH 1/5] Python testing: Fix reporting on setup_class error Also add error text to make the error easier to find. --- src/python_testing/matter_testing_support.py | 53 +++++++++++++++++++- 1 file changed, 51 insertions(+), 2 deletions(-) diff --git a/src/python_testing/matter_testing_support.py b/src/python_testing/matter_testing_support.py index d44754f379d92e..d7d9372261fb44 100644 --- a/src/python_testing/matter_testing_support.py +++ b/src/python_testing/matter_testing_support.py @@ -29,6 +29,7 @@ import sys import time import typing +import textwrap import uuid from binascii import hexlify, unhexlify from dataclasses import asdict as dataclass_asdict @@ -1123,12 +1124,60 @@ def on_fail(self, record): self.failed = True if self.runner_hook and not self.is_commissioning: exception = record.termination_signal.exception - step_duration = (datetime.now(timezone.utc) - self.step_start_time) / timedelta(microseconds=1) - test_duration = (datetime.now(timezone.utc) - self.test_start_time) / timedelta(microseconds=1) + + try: + step_duration = (datetime.now(timezone.utc) - self.step_start_time) / timedelta(microseconds=1) + except AttributeError: + # If we failed during setup, these may not be populated + step_duration = 0 + try: + test_duration = (datetime.now(timezone.utc) - self.test_start_time) / timedelta(microseconds=1) + except AttributeError: + test_duration = 0 # TODO: I have no idea what logger, logs, request or received are. Hope None works because I have nothing to give self.runner_hook.step_failure(logger=None, logs=None, duration=step_duration, request=None, received=None) self.runner_hook.test_stop(exception=exception, duration=test_duration) + def extract_error_text() -> tuple[str, str]: + no_stack_trace = ("Stack Trace Unavailable", "") + if not record.termination_signal.stacktrace: + return no_stack_trace + trace = record.termination_signal.stacktrace.splitlines() + if not trace: + return no_stack_trace + + if isinstance(exception, signals.TestError): + # Exception gets raised by the mobly framework, so the proximal error is one line back in the stack trace + assert_candidates = [idx for idx, line in enumerate(trace) if "asserts" in line and not "asserts.py" in line] + if not assert_candidates: + return "Unknown error, please see stack trace above", "" + assert_candidate_idx = assert_candidates[-1] + else: + # Normal assert is on the Last line + assert_candidate_idx = -1 + probable_error = trace[assert_candidate_idx] + + # Find the file marker immediately above the probable error + file_candidates = [idx for idx, line in enumerate(trace[:assert_candidate_idx]) if "File" in line] + if not file_candidates: + return probable_error, "Unknown file" + return probable_error.strip(), trace[file_candidates[-1]].strip() + + probable_error, probable_file = extract_error_text() + logging.error(textwrap.dedent(f""" + + ****************************************************************** + * + * Test {self.current_test_info.name} failed for the following reason: + * {exception} + * + * {probable_file} + * {probable_error} + * + ******************************************************************* + + """)) + def on_pass(self, record): ''' Called by Mobly on test pass From a90a290ab12515c9787cabfaf751f24d1ad1a8b2 Mon Sep 17 00:00:00 2001 From: "Restyled.io" Date: Thu, 15 Aug 2024 15:03:20 +0000 Subject: [PATCH 2/5] Restyled by isort --- src/python_testing/matter_testing_support.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/python_testing/matter_testing_support.py b/src/python_testing/matter_testing_support.py index d7d9372261fb44..093850d11d450b 100644 --- a/src/python_testing/matter_testing_support.py +++ b/src/python_testing/matter_testing_support.py @@ -27,9 +27,9 @@ import random import re import sys +import textwrap import time import typing -import textwrap import uuid from binascii import hexlify, unhexlify from dataclasses import asdict as dataclass_asdict From b64a987664277877793d4001c2565e74132b1b02 Mon Sep 17 00:00:00 2001 From: cecille Date: Fri, 16 Aug 2024 10:34:11 -0400 Subject: [PATCH 3/5] Fix lint --- src/python_testing/matter_testing_support.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/python_testing/matter_testing_support.py b/src/python_testing/matter_testing_support.py index 093850d11d450b..eb8a6ba20d63b9 100644 --- a/src/python_testing/matter_testing_support.py +++ b/src/python_testing/matter_testing_support.py @@ -1148,7 +1148,7 @@ def extract_error_text() -> tuple[str, str]: if isinstance(exception, signals.TestError): # Exception gets raised by the mobly framework, so the proximal error is one line back in the stack trace - assert_candidates = [idx for idx, line in enumerate(trace) if "asserts" in line and not "asserts.py" in line] + assert_candidates = [idx for idx, line in enumerate(trace) if "asserts" in line and "asserts.py" not in line] if not assert_candidates: return "Unknown error, please see stack trace above", "" assert_candidate_idx = assert_candidates[-1] From 490f27b8c92c84e55e3e8d169d709ab21d6e9e74 Mon Sep 17 00:00:00 2001 From: cecille Date: Fri, 16 Aug 2024 11:46:28 -0400 Subject: [PATCH 4/5] 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. --- src/controller/python/chip/exceptions/__init__.py | 14 +++++--------- src/controller/python/chip/native/__init__.py | 2 +- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/src/controller/python/chip/exceptions/__init__.py b/src/controller/python/chip/exceptions/__init__.py index c7f692e9284d26..b293a68eac9434 100644 --- a/src/controller/python/chip/exceptions/__init__.py +++ b/src/controller/python/chip/exceptions/__init__.py @@ -39,21 +39,17 @@ class ChipStackException(Exception): class ChipStackError(ChipStackException): - def __init__(self, chip_error: PyChipError, msg=None): - self._chip_error = chip_error - self.msg = msg if msg else "Chip Stack Error %d" % chip_error.code + def __init__(self, code: int, msg=None): + self.code = code + self.msg = msg if msg else "Chip Stack Error %d" % self.code @classmethod def from_chip_error(cls, chip_error: PyChipError) -> ChipStackError: - return cls(chip_error, str(chip_error)) - - @property - def chip_error(self) -> PyChipError | None: - return self._chip_error + return cls(chip_error.code, str(chip_error)) @property def err(self) -> int: - return self._chip_error.code + return self.code def __str__(self): return self.msg diff --git a/src/controller/python/chip/native/__init__.py b/src/controller/python/chip/native/__init__.py index 518339017b587c..4c8b58f1277b78 100644 --- a/src/controller/python/chip/native/__init__.py +++ b/src/controller/python/chip/native/__init__.py @@ -69,7 +69,7 @@ class ErrorSDKPart(enum.IntEnum): class PyChipError(ctypes.Structure): ''' The ChipError for Python library. - We are using the following struct for passing the infomations of CHIP_ERROR between C++ and Python: + We are using the following struct for passing the information of CHIP_ERROR between C++ and Python: ```c struct PyChipError From bcdabaaff67be6b379329029c3873a6ac0f2d496 Mon Sep 17 00:00:00 2001 From: cecille Date: Fri, 16 Aug 2024 14:13:49 -0400 Subject: [PATCH 5/5] Fix some tests using removed class member --- src/controller/python/chip/native/__init__.py | 4 ++++ src/python_testing/TC_CADMIN_1_9.py | 3 ++- src/python_testing/TC_CGEN_2_4.py | 3 ++- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/controller/python/chip/native/__init__.py b/src/controller/python/chip/native/__init__.py index 4c8b58f1277b78..2528aeca32bd1b 100644 --- a/src/controller/python/chip/native/__init__.py +++ b/src/controller/python/chip/native/__init__.py @@ -88,6 +88,10 @@ def raise_on_error(self) -> None: if exception is not None: # Ensure exception is not None to avoid mypy error and only raise valid exceptions raise exception + @classmethod + def from_code(cls, code): + return cls(code=code, line=0, file=ctypes.c_void_p()) + @property def is_success(self) -> bool: return self.code == 0 diff --git a/src/python_testing/TC_CADMIN_1_9.py b/src/python_testing/TC_CADMIN_1_9.py index f37e3723ed1055..c3d67b9bf60ace 100644 --- a/src/python_testing/TC_CADMIN_1_9.py +++ b/src/python_testing/TC_CADMIN_1_9.py @@ -31,6 +31,7 @@ from chip import ChipDeviceCtrl from chip.ChipDeviceCtrl import CommissioningParameters from chip.exceptions import ChipStackError +from chip.native import PyChipError from matter_testing_support import MatterBaseTest, TestStep, async_test_body, default_matter_test_main from mobly import asserts @@ -74,7 +75,7 @@ async def CommissionOnNetwork( await self.th2.CommissionOnNetwork( nodeId=self.dut_node_id, setupPinCode=setup_code, filterType=ChipDeviceCtrl.DiscoveryFilterType.LONG_DISCRIMINATOR, filter=self.discriminator) - errcode = ctx.exception.chip_error + errcode = PyChipError.from_code(ctx.exception.err) return errcode async def CommissionAttempt( diff --git a/src/python_testing/TC_CGEN_2_4.py b/src/python_testing/TC_CGEN_2_4.py index 7d9d075dc16cb9..09fbc6d593a53e 100644 --- a/src/python_testing/TC_CGEN_2_4.py +++ b/src/python_testing/TC_CGEN_2_4.py @@ -38,6 +38,7 @@ from chip import ChipDeviceCtrl from chip.ChipDeviceCtrl import CommissioningParameters from chip.exceptions import ChipStackError +from chip.native import PyChipError from matter_testing_support import MatterBaseTest, async_test_body, default_matter_test_main from mobly import asserts @@ -78,7 +79,7 @@ async def CommissionToStageSendCompleteAndCleanup( await self.th2.CommissionOnNetwork( nodeId=self.dut_node_id, setupPinCode=params.setupPinCode, filterType=ChipDeviceCtrl.DiscoveryFilterType.LONG_DISCRIMINATOR, filter=self.discriminator) - errcode = ctx.exception.chip_error + errcode = PyChipError.from_code(ctx.exception.err) asserts.assert_true(errcode.sdk_part == expectedErrorPart, 'Unexpected error type returned from CommissioningComplete') asserts.assert_true(errcode.sdk_code == expectedErrCode, 'Unexpected error code returned from CommissioningComplete') revokeCmd = Clusters.AdministratorCommissioning.Commands.RevokeCommissioning()