Source code for trigdroid.api.results

"""Test result classes for TrigDroid API."""

from typing import Optional, Dict, Any, List
from datetime import datetime
from dataclasses import dataclass, field

from ..core.enums import TestPhase, TestResult as TestResultEnum
from .config import TestConfiguration


[docs] @dataclass class TestResult: """Result of a TrigDroid test execution. Contains all information about test execution including success status, timing information, configuration used, and any errors encountered. Examples: # Successful test result = TestResult(success=True, phase=TestPhase.EXECUTION) # Failed test result = TestResult( success=False, phase=TestPhase.SETUP, error="Device connection failed", config=config ) """ success: bool phase: TestPhase error: Optional[str] = None config: Optional[TestConfiguration] = None start_time: datetime = field(default_factory=datetime.now) end_time: Optional[datetime] = None duration_seconds: Optional[float] = None # Test execution details device_info: Dict[str, Any] = field(default_factory=dict) tests_run: List[str] = field(default_factory=list) tests_passed: List[str] = field(default_factory=list) tests_failed: List[str] = field(default_factory=list) tests_skipped: List[str] = field(default_factory=list) # Frida-specific results frida_hooks_loaded: int = 0 frida_hooks_active: int = 0 frida_errors: List[str] = field(default_factory=list) # Sensor test results sensor_tests_executed: Dict[str, bool] = field(default_factory=dict) sensor_values_changed: Dict[str, int] = field(default_factory=dict) # Network test results network_state_changes: Dict[str, bool] = field(default_factory=dict) # Application lifecycle results app_started: bool = False app_crashed: bool = False app_background_time: float = 0.0 # Log files and outputs log_file_path: Optional[str] = None changelog_file_path: Optional[str] = None screenshot_paths: List[str] = field(default_factory=list)
[docs] def __post_init__(self): """Calculate derived fields after initialization.""" if self.end_time and self.start_time: self.duration_seconds = (self.end_time - self.start_time).total_seconds()
[docs] def mark_completed(self, success: Optional[bool] = None) -> None: """Mark the test as completed. Args: success: Optional override for success status """ self.end_time = datetime.now() if self.start_time: self.duration_seconds = (self.end_time - self.start_time).total_seconds() if success is not None: self.success = success
[docs] def add_test_result(self, test_name: str, passed: bool, error: Optional[str] = None) -> None: """Add result for a specific test. Args: test_name: Name of the test passed: Whether the test passed error: Optional error message """ self.tests_run.append(test_name) if passed: self.tests_passed.append(test_name) else: self.tests_failed.append(test_name) if error: if not self.error: self.error = error else: self.error += f"; {error}"
[docs] def skip_test(self, test_name: str, reason: str) -> None: """Mark a test as skipped. Args: test_name: Name of the test reason: Reason for skipping """ self.tests_skipped.append(test_name)
@property def total_tests(self) -> int: """Total number of tests run.""" return len(self.tests_run) @property def passed_tests(self) -> int: """Number of tests that passed.""" return len(self.tests_passed) @property def failed_tests(self) -> int: """Number of tests that failed.""" return len(self.tests_failed) @property def skipped_tests(self) -> int: """Number of tests that were skipped.""" return len(self.tests_skipped) @property def success_rate(self) -> float: """Success rate as a percentage.""" if self.total_tests == 0: return 0.0 return (self.passed_tests / self.total_tests) * 100 @property def result_enum(self) -> TestResultEnum: """Get the result as an enum value.""" if self.success: return TestResultEnum.SUCCESS elif self.total_tests == 0: return TestResultEnum.SKIPPED else: return TestResultEnum.FAILURE
[docs] def to_dict(self) -> Dict[str, Any]: """Convert result to dictionary. Returns: Dictionary representation of the result """ return { 'success': self.success, 'phase': self.phase.value, 'error': self.error, 'start_time': self.start_time.isoformat() if self.start_time else None, 'end_time': self.end_time.isoformat() if self.end_time else None, 'duration_seconds': self.duration_seconds, 'device_info': self.device_info, 'total_tests': self.total_tests, 'passed_tests': self.passed_tests, 'failed_tests': self.failed_tests, 'skipped_tests': self.skipped_tests, 'success_rate': self.success_rate, 'tests_run': self.tests_run, 'tests_passed': self.tests_passed, 'tests_failed': self.tests_failed, 'tests_skipped': self.tests_skipped, 'frida_hooks_loaded': self.frida_hooks_loaded, 'frida_hooks_active': self.frida_hooks_active, 'frida_errors': self.frida_errors, 'sensor_tests_executed': self.sensor_tests_executed, 'sensor_values_changed': self.sensor_values_changed, 'network_state_changes': self.network_state_changes, 'app_started': self.app_started, 'app_crashed': self.app_crashed, 'app_background_time': self.app_background_time, 'log_file_path': self.log_file_path, 'changelog_file_path': self.changelog_file_path, 'screenshot_paths': self.screenshot_paths, }
[docs] def summary(self) -> str: """Get a human-readable summary of the results. Returns: Summary string """ if self.success: status = "✓ PASSED" else: status = "✗ FAILED" duration_str = f"{self.duration_seconds:.1f}s" if self.duration_seconds else "unknown" summary_parts = [ f"{status} in {duration_str}", f"Tests: {self.passed_tests} passed, {self.failed_tests} failed, {self.skipped_tests} skipped" ] if self.error: summary_parts.append(f"Error: {self.error}") if self.frida_hooks_active > 0: summary_parts.append(f"Frida: {self.frida_hooks_active} hooks active") if self.app_background_time > 0: summary_parts.append(f"Background time: {self.app_background_time:.1f}s") return " | ".join(summary_parts)