|
27 | 27 | import random
|
28 | 28 | import re
|
29 | 29 | import sys
|
| 30 | +import textwrap |
30 | 31 | import time
|
31 | 32 | import typing
|
32 | 33 | import uuid
|
@@ -1123,12 +1124,60 @@ def on_fail(self, record):
|
1123 | 1124 | self.failed = True
|
1124 | 1125 | if self.runner_hook and not self.is_commissioning:
|
1125 | 1126 | 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 |
1128 | 1137 | # TODO: I have no idea what logger, logs, request or received are. Hope None works because I have nothing to give
|
1129 | 1138 | self.runner_hook.step_failure(logger=None, logs=None, duration=step_duration, request=None, received=None)
|
1130 | 1139 | self.runner_hook.test_stop(exception=exception, duration=test_duration)
|
1131 | 1140 |
|
| 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 | + |
1132 | 1181 | def on_pass(self, record):
|
1133 | 1182 | ''' Called by Mobly on test pass
|
1134 | 1183 |
|
|
0 commit comments