Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Python testing: Fix reporting on setup_class error #35016

Merged
merged 5 commits into from
Aug 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 5 additions & 9 deletions src/controller/python/chip/exceptions/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 5 additions & 1 deletion src/controller/python/chip/native/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
3 changes: 2 additions & 1 deletion src/python_testing/TC_CADMIN_1_9.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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(
Expand Down
3 changes: 2 additions & 1 deletion src/python_testing/TC_CGEN_2_4.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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()
Expand Down
53 changes: 51 additions & 2 deletions src/python_testing/matter_testing_support.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import random
import re
import sys
import textwrap
import time
import typing
import uuid
Expand Down Expand Up @@ -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 "asserts.py" not 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

Expand Down
Loading