Trace return types
@@ -202,6 +202,41 @@ class ExecutionObserver:
"""
+class ReturnTypeObserver(ExecutionObserver):
+ """Observes the runtime types seen during execution."""
+
+ def __init__(self):
+ self._return_type_trace: dict[int, type] = {}
+
+ def before_test_case_execution(self, test_case: tc.TestCase):
+ self._return_type_trace.clear()
+
+ def after_test_case_execution(
+ self, test_case: tc.TestCase, result: ExecutionResult
+ ):
+ result.store_return_types(dict(self._return_type_trace))
+
+ def before_statement_execution(
+ self, statement: stmt.Statement, exec_ctx: ExecutionContext
+ ):
+ pass # not relevant
+
+ def after_statement_execution(
+ self,
+ statement: stmt.Statement,
+ exec_ctx: ExecutionContext,
+ exception: Exception | None = None,
+ ) -> None:
+ if (
+ exception is None
+ and (ret_val := statement.ret_val) is not None
+ and not ret_val.is_none_type()
+ ):
+ self._return_type_trace[statement.get_position()] = type(
+ exec_ctx.get_reference_value(ret_val)
+ )
+
+
class ExecutionResult:
"""Result of an execution."""
@@ -210,6 +245,7 @@ class ExecutionResult:
self._assertion_traces: dict[type, at.AssertionTrace] = {}
self._execution_trace: ExecutionTrace | None = None
self._timeout = timeout
+ self._return_type_trace: dict[int, type] = {}
@property
def timeout(self) -> bool:
@@ -220,6 +256,16 @@ class ExecutionResult:
"""
return self._timeout
+ @property
+ def return_type_trace(self) -> dict[int, type]:
+ """Provide the stored type trace.
+
+ Returns:
+ The observed return types per statement.
+ Not every statement index may have an entry.
+ """
+ return self._return_type_trace
+
@property
def exceptions(self) -> dict[int, Exception]:
"""Provide a map of statements indices that threw an exception.
@@ -295,6 +341,29 @@ class ExecutionResult:
return min(self._exceptions.keys())
return None
+ def store_return_types(self, return_types: dict[int, type]) -> None:
+ """Report that the statement with the given stmt_idx has type tp_.
+
+ Args:
+ return_types: The observed types for each statement index.
+ """
+ self._return_type_trace = return_types
+
+ def delete_statement_data(self, deleted_statements: set[int]) -> None:
+ """It may happen that the test case is modified after execution, for example,
+ by removing unused primitives. We have to update the execution result to reflect
+ this, otherwise the indexes maybe wrong.
+
+ Args:
+ deleted_statements: The indexes of the deleted statements
+ """
+ self._return_type_trace = ExecutionResult.shift_dict(
+ self._return_type_trace, deleted_statements
+ )
+ self._exceptions = ExecutionResult.shift_dict(
+ self._exceptions, deleted_statements
+ )
+
def __str__(self) -> str:
return (
f"ExecutionResult(exceptions: {self._exceptions}, "
