# This file is part of Pynguin.
#
# SPDX-FileCopyrightText: 2019–2026 Pynguin Contributors
#
# SPDX-License-Identifier: MIT
#
"""Contains the result representation of a test execution."""
from __future__ import annotations
import dataclasses
from typing import TYPE_CHECKING, TypeVar
import pynguin.assertion.assertion_trace as at
from pynguin.instrumentation.tracer import ExecutionTrace
if TYPE_CHECKING:
import pynguin.utils.typetracing as tt
from pynguin.analyses.typesystem import ProperType
from pynguin.testcase.collection_tracker import CollectionTrace
T = TypeVar("T")
[docs]
@dataclasses.dataclass
class ExecutionResult:
"""Result of an execution."""
timeout: bool = False
exceptions: dict[int, BaseException] = dataclasses.field(default_factory=dict, init=False)
assertion_trace: at.AssertionTrace = dataclasses.field(
default_factory=at.AssertionTrace, init=False
)
assertion_verification_trace: at.AssertionVerificationTrace = dataclasses.field(
default_factory=at.AssertionVerificationTrace, init=False
)
execution_trace: ExecutionTrace = dataclasses.field(default_factory=ExecutionTrace, init=False)
# Observation of return types.
raw_return_types: dict[int, type] = dataclasses.field(default_factory=dict, init=False)
raw_return_type_generic_args: dict[int, tuple[type, ...]] = dataclasses.field(
default_factory=dict, init=False
)
# Observed return types converted to proper types.
proper_return_type_trace: dict[int, ProperType] = dataclasses.field(
default_factory=dict, init=False
)
proxy_knowledge: dict[tuple[int, str], tt.UsageTraceNode] = dataclasses.field(
default_factory=dict, init=False
)
collection_trace: dict[int, CollectionTrace] = dataclasses.field(
default_factory=dict, init=False
)
num_executed_statements: int = dataclasses.field(default=0, init=False)
[docs]
def has_test_exceptions(self) -> bool:
"""Returns true if any exceptions were thrown during the execution.
Returns:
Whether the test has exceptions
"""
return bool(self.exceptions)
[docs]
def report_new_thrown_exception(self, stmt_idx: int, ex: BaseException) -> None:
"""Report an exception that was thrown during execution.
Args:
stmt_idx: the index of the statement, that caused the exception
ex: the exception
"""
self.exceptions[stmt_idx] = ex
[docs]
def get_first_position_of_thrown_exception(self) -> int | None:
"""Provide the index of the first thrown exception or None.
Returns:
The index of the first thrown exception, if any
"""
if self.has_test_exceptions():
return min(self.exceptions.keys())
return None
[docs]
def delete_statement_data(self, deleted_statements: set[int]) -> None:
"""Delete statements at given indices.
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.raw_return_types = ExecutionResult.shift_dict(
self.raw_return_types, deleted_statements
)
self.raw_return_type_generic_args = ExecutionResult.shift_dict(
self.raw_return_type_generic_args, deleted_statements
)
self.proper_return_type_trace = ExecutionResult.shift_dict(
self.proper_return_type_trace, deleted_statements
)
self.exceptions = ExecutionResult.shift_dict(self.exceptions, deleted_statements)
self.collection_trace = ExecutionResult.shift_dict(
self.collection_trace, deleted_statements
)
[docs]
@staticmethod
def shift_dict(to_shift: dict[int, T], deleted_indexes: set[int]) -> dict[int, T]:
"""Shifts the entries in the given dictionary.
Compute the entries' new positions after the given statements were deleted.
Args:
to_shift: The dict to shift
deleted_indexes: A set of deleted statement indexes.
Returns:
The shifted dict
"""
# Count how many statements were deleted up to a given point
shifts = {}
delta = 0
for idx in range(max(to_shift.keys(), default=0) + 1):
if idx in deleted_indexes:
delta += 1
shifts[idx] = delta
# Shift all indexes accordingly
shifted = {}
for stmt_idx, value in to_shift.items():
if stmt_idx not in deleted_indexes:
shifted[stmt_idx - shifts[stmt_idx]] = value
return shifted
def __str__(self) -> str:
return f"ExecutionResult(exceptions: {self.exceptions}, trace: {self.execution_trace})"
def __repr__(self) -> str:
return str(self)