initial commit
This commit is contained in:
109
libraries/FastLED/ci/util/test_exceptions.py
Normal file
109
libraries/FastLED/ci/util/test_exceptions.py
Normal file
@@ -0,0 +1,109 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Custom exceptions for test failures that need to bubble up to callers."""
|
||||
|
||||
import subprocess
|
||||
from dataclasses import dataclass
|
||||
from typing import List, Optional
|
||||
|
||||
|
||||
@dataclass
|
||||
class TestFailureInfo:
|
||||
"""Information about a single test failure"""
|
||||
|
||||
test_name: str
|
||||
command: str
|
||||
return_code: int
|
||||
output: str
|
||||
error_type: str = "test_failure"
|
||||
|
||||
|
||||
class FastLEDTestException(Exception):
|
||||
"""Base exception for FastLED test failures"""
|
||||
|
||||
def __init__(self, message: str, failures: Optional[List[TestFailureInfo]] = None):
|
||||
super().__init__(message)
|
||||
self.failures = failures or []
|
||||
self.message = message
|
||||
|
||||
def add_failure(self, failure: TestFailureInfo) -> None:
|
||||
"""Add a test failure to this exception"""
|
||||
self.failures.append(failure)
|
||||
|
||||
def has_failures(self) -> bool:
|
||||
"""Check if this exception contains any failures"""
|
||||
return len(self.failures) > 0
|
||||
|
||||
def get_failure_summary(self) -> str:
|
||||
"""Get a summary of all failures"""
|
||||
if not self.failures:
|
||||
return self.message
|
||||
|
||||
summary = [self.message]
|
||||
summary.append(f"\nFailed tests ({len(self.failures)}):")
|
||||
for failure in self.failures:
|
||||
summary.append(
|
||||
f" - {failure.test_name}: {failure.error_type} (exit code {failure.return_code})"
|
||||
)
|
||||
|
||||
return "\n".join(summary)
|
||||
|
||||
def get_detailed_failure_info(self) -> str:
|
||||
"""Get detailed information about all failures"""
|
||||
if not self.failures:
|
||||
return self.message
|
||||
|
||||
details = [self.message]
|
||||
details.append(f"\n{'=' * 50}")
|
||||
details.append("DETAILED FAILURE INFORMATION")
|
||||
details.append(f"{'=' * 50}")
|
||||
|
||||
for i, failure in enumerate(self.failures, 1):
|
||||
cmd_str: str = (
|
||||
subprocess.list2cmdline(failure.command)
|
||||
if isinstance(failure.command, list)
|
||||
else failure.command
|
||||
)
|
||||
assert isinstance(cmd_str, str)
|
||||
details.append(f"\n{i}. {failure.test_name}")
|
||||
details.append(f" Command: {cmd_str}")
|
||||
details.append(f" Error Type: {failure.error_type}")
|
||||
details.append(f" Exit Code: {failure.return_code}")
|
||||
details.append(f" Output:")
|
||||
# Indent the output
|
||||
for line in failure.output.split("\n"):
|
||||
details.append(f" {line}")
|
||||
|
||||
return "\n".join(details)
|
||||
|
||||
|
||||
class CompilationFailedException(FastLEDTestException):
|
||||
"""Exception for compilation failures"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
message: str = "Compilation failed",
|
||||
failures: Optional[List[TestFailureInfo]] = None,
|
||||
):
|
||||
super().__init__(message, failures)
|
||||
|
||||
|
||||
class TestExecutionFailedException(FastLEDTestException):
|
||||
"""Exception for test execution failures"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
message: str = "Test execution failed",
|
||||
failures: Optional[List[TestFailureInfo]] = None,
|
||||
):
|
||||
super().__init__(message, failures)
|
||||
|
||||
|
||||
class TestTimeoutException(FastLEDTestException):
|
||||
"""Exception for test timeouts"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
message: str = "Test execution timed out",
|
||||
failures: Optional[List[TestFailureInfo]] = None,
|
||||
):
|
||||
super().__init__(message, failures)
|
||||
Reference in New Issue
Block a user