- Add complete pytest testing framework with conftest.py and test files - Add performance monitoring and benchmarking capabilities - Add plugin system with ccache plugin example - Add comprehensive documentation (API, deployment, testing, etc.) - Add Docker API wrapper for service deployment - Add advanced configuration examples - Remove old wget package file - Update core modules with enhanced functionality
475 lines
14 KiB
Python
475 lines
14 KiB
Python
"""
|
|
Custom exceptions for deb-mock
|
|
|
|
This module provides a comprehensive exception hierarchy inspired by Mock's
|
|
exception handling system, adapted for Debian-based build environments.
|
|
"""
|
|
|
|
import functools
|
|
import sys
|
|
from typing import Any, Dict, List, Optional
|
|
|
|
|
|
class DebMockError(Exception):
|
|
"""
|
|
Base exception for all deb-mock errors.
|
|
|
|
This is the root exception class that all other deb-mock exceptions
|
|
inherit from. It provides common functionality for error reporting
|
|
and recovery suggestions.
|
|
"""
|
|
|
|
def __init__(
|
|
self,
|
|
message: str,
|
|
exit_code: int = 1,
|
|
context: Optional[Dict[str, Any]] = None,
|
|
suggestions: Optional[List[str]] = None,
|
|
):
|
|
"""
|
|
Initialize the exception with message and optional context.
|
|
|
|
Args:
|
|
message: Human-readable error message
|
|
exit_code: Suggested exit code for CLI applications
|
|
context: Additional context information for debugging
|
|
suggestions: List of suggested actions to resolve the error
|
|
"""
|
|
super().__init__(message)
|
|
self.message = message
|
|
self.exit_code = exit_code
|
|
self.context = context or {}
|
|
self.suggestions = suggestions or []
|
|
|
|
def __str__(self) -> str:
|
|
"""Return formatted error message with context and suggestions."""
|
|
lines = [f"Error: {self.message}"]
|
|
|
|
# Add context information if available
|
|
if self.context:
|
|
lines.append("\nContext:")
|
|
for key, value in self.context.items():
|
|
lines.append(f" {key}: {value}")
|
|
|
|
# Add suggestions if available
|
|
if self.suggestions:
|
|
lines.append("\nSuggestions:")
|
|
for i, suggestion in enumerate(self.suggestions, 1):
|
|
lines.append(f" {i}. {suggestion}")
|
|
|
|
return "\n".join(lines)
|
|
|
|
def print_error(self, file=sys.stderr) -> None:
|
|
"""Print formatted error message to specified file."""
|
|
print(str(self), file=file)
|
|
|
|
def get_exit_code(self) -> int:
|
|
"""Get the suggested exit code for this error."""
|
|
return self.exit_code
|
|
|
|
|
|
class ConfigurationError(DebMockError):
|
|
"""
|
|
Raised when there's an error in configuration.
|
|
|
|
This exception is raised when configuration files are invalid,
|
|
missing required options, or contain conflicting settings.
|
|
"""
|
|
|
|
def __init__(
|
|
self,
|
|
message: str,
|
|
config_file: Optional[str] = None,
|
|
config_section: Optional[str] = None,
|
|
):
|
|
context = {}
|
|
if config_file:
|
|
context["config_file"] = config_file
|
|
if config_section:
|
|
context["config_section"] = config_section
|
|
|
|
suggestions = [
|
|
"Check the configuration file syntax",
|
|
"Verify all required options are set",
|
|
"Ensure configuration values are valid for your system",
|
|
]
|
|
|
|
super().__init__(message, exit_code=2, context=context, suggestions=suggestions)
|
|
|
|
|
|
class ChrootError(DebMockError):
|
|
"""
|
|
Raised when there's an error with chroot operations.
|
|
|
|
This exception covers chroot creation, management, and cleanup errors.
|
|
"""
|
|
|
|
def __init__(
|
|
self,
|
|
message: str,
|
|
chroot_name: Optional[str] = None,
|
|
operation: Optional[str] = None,
|
|
chroot_path: Optional[str] = None,
|
|
):
|
|
context = {}
|
|
if chroot_name:
|
|
context["chroot_name"] = chroot_name
|
|
if operation:
|
|
context["operation"] = operation
|
|
if chroot_path:
|
|
context["chroot_path"] = chroot_path
|
|
|
|
suggestions = [
|
|
"Ensure you have sufficient disk space",
|
|
"Check that you have root privileges for chroot operations",
|
|
"Verify the chroot name is valid",
|
|
"Try cleaning up existing chroots with 'deb-mock clean-chroot'",
|
|
]
|
|
|
|
super().__init__(message, exit_code=3, context=context, suggestions=suggestions)
|
|
|
|
|
|
class SbuildError(DebMockError):
|
|
"""
|
|
Raised when there's an error with sbuild operations.
|
|
|
|
This exception covers sbuild execution, configuration, and result processing.
|
|
"""
|
|
|
|
def __init__(
|
|
self,
|
|
message: str,
|
|
sbuild_config: Optional[str] = None,
|
|
build_log: Optional[str] = None,
|
|
return_code: Optional[int] = None,
|
|
):
|
|
context = {}
|
|
if sbuild_config:
|
|
context["sbuild_config"] = sbuild_config
|
|
if build_log:
|
|
context["build_log"] = build_log
|
|
if return_code is not None:
|
|
context["return_code"] = return_code
|
|
|
|
suggestions = [
|
|
"Check the build log for detailed error information",
|
|
"Verify that sbuild is properly configured",
|
|
"Ensure all build dependencies are available",
|
|
"Try updating the chroot with 'deb-mock update-chroot'",
|
|
]
|
|
|
|
super().__init__(message, exit_code=4, context=context, suggestions=suggestions)
|
|
|
|
|
|
class BuildError(DebMockError):
|
|
"""
|
|
Raised when a build fails.
|
|
|
|
This exception is raised when package building fails due to
|
|
compilation errors, missing dependencies, or other build issues.
|
|
"""
|
|
|
|
def __init__(
|
|
self,
|
|
message: str,
|
|
source_package: Optional[str] = None,
|
|
build_log: Optional[str] = None,
|
|
artifacts: Optional[List[str]] = None,
|
|
):
|
|
context = {}
|
|
if source_package:
|
|
context["source_package"] = source_package
|
|
if build_log:
|
|
context["build_log"] = build_log
|
|
if artifacts:
|
|
context["artifacts"] = artifacts
|
|
|
|
suggestions = [
|
|
"Review the build log for specific error messages",
|
|
"Check that all build dependencies are installed",
|
|
"Verify the source package is valid and complete",
|
|
"Try building with verbose output: 'deb-mock --verbose build'",
|
|
]
|
|
|
|
super().__init__(message, exit_code=5, context=context, suggestions=suggestions)
|
|
|
|
|
|
class DependencyError(DebMockError):
|
|
"""
|
|
Raised when there are dependency issues.
|
|
|
|
This exception covers missing build dependencies, version conflicts,
|
|
and other dependency-related problems.
|
|
"""
|
|
|
|
def __init__(
|
|
self,
|
|
message: str,
|
|
missing_packages: Optional[List[str]] = None,
|
|
conflicting_packages: Optional[List[str]] = None,
|
|
):
|
|
context = {}
|
|
if missing_packages:
|
|
context["missing_packages"] = missing_packages
|
|
if conflicting_packages:
|
|
context["conflicting_packages"] = conflicting_packages
|
|
|
|
suggestions = [
|
|
"Install missing build dependencies",
|
|
"Resolve package conflicts by updating or removing conflicting packages",
|
|
"Check that your chroot has access to the required repositories",
|
|
"Try updating the chroot: 'deb-mock update-chroot'",
|
|
]
|
|
|
|
super().__init__(message, exit_code=6, context=context, suggestions=suggestions)
|
|
|
|
|
|
class MetadataError(DebMockError):
|
|
"""
|
|
Raised when there's an error with metadata handling.
|
|
|
|
This exception covers metadata capture, storage, and retrieval errors.
|
|
"""
|
|
|
|
def __init__(
|
|
self,
|
|
message: str,
|
|
metadata_file: Optional[str] = None,
|
|
operation: Optional[str] = None,
|
|
):
|
|
context = {}
|
|
if metadata_file:
|
|
context["metadata_file"] = metadata_file
|
|
if operation:
|
|
context["operation"] = operation
|
|
|
|
suggestions = [
|
|
"Check that the metadata directory is writable",
|
|
"Verify that the metadata file format is valid",
|
|
"Ensure sufficient disk space for metadata storage",
|
|
]
|
|
|
|
super().__init__(message, exit_code=7, context=context, suggestions=suggestions)
|
|
|
|
|
|
class CacheError(DebMockError):
|
|
"""
|
|
Raised when there's an error with cache operations.
|
|
|
|
This exception covers root cache, package cache, and ccache errors.
|
|
"""
|
|
|
|
def __init__(
|
|
self,
|
|
message: str,
|
|
cache_type: Optional[str] = None,
|
|
cache_path: Optional[str] = None,
|
|
operation: Optional[str] = None,
|
|
):
|
|
context = {}
|
|
if cache_type:
|
|
context["cache_type"] = cache_type
|
|
if cache_path:
|
|
context["cache_path"] = cache_path
|
|
if operation:
|
|
context["operation"] = operation
|
|
|
|
suggestions = [
|
|
"Check that cache directories are writable",
|
|
"Ensure sufficient disk space for cache operations",
|
|
"Try cleaning up old caches: 'deb-mock cleanup-caches'",
|
|
"Verify cache configuration settings",
|
|
]
|
|
|
|
super().__init__(message, exit_code=8, context=context, suggestions=suggestions)
|
|
|
|
|
|
class PluginError(DebMockError):
|
|
"""
|
|
Raised when there's an error with plugin operations.
|
|
|
|
This exception covers plugin loading, configuration, and execution errors.
|
|
"""
|
|
|
|
def __init__(
|
|
self,
|
|
message: str,
|
|
plugin_name: Optional[str] = None,
|
|
plugin_config: Optional[Dict[str, Any]] = None,
|
|
):
|
|
context = {}
|
|
if plugin_name:
|
|
context["plugin_name"] = plugin_name
|
|
if plugin_config:
|
|
context["plugin_config"] = plugin_config
|
|
|
|
suggestions = [
|
|
"Check that the plugin is properly installed",
|
|
"Verify plugin configuration is valid",
|
|
"Ensure plugin dependencies are satisfied",
|
|
"Try disabling the plugin if it's causing issues",
|
|
]
|
|
|
|
super().__init__(message, exit_code=9, context=context, suggestions=suggestions)
|
|
|
|
|
|
class NetworkError(DebMockError):
|
|
"""
|
|
Raised when there are network-related errors.
|
|
|
|
This exception covers repository access, package downloads, and
|
|
other network operations.
|
|
"""
|
|
|
|
def __init__(
|
|
self,
|
|
message: str,
|
|
url: Optional[str] = None,
|
|
proxy: Optional[str] = None,
|
|
timeout: Optional[int] = None,
|
|
):
|
|
context = {}
|
|
if url:
|
|
context["url"] = url
|
|
if proxy:
|
|
context["proxy"] = proxy
|
|
if timeout:
|
|
context["timeout"] = timeout
|
|
|
|
suggestions = [
|
|
"Check your internet connection",
|
|
"Verify repository URLs are accessible",
|
|
"Configure proxy settings if behind a firewall",
|
|
"Try using a different mirror or repository",
|
|
]
|
|
|
|
super().__init__(message, exit_code=10, context=context, suggestions=suggestions)
|
|
|
|
|
|
class PermissionError(DebMockError):
|
|
"""
|
|
Raised when there are permission-related errors.
|
|
|
|
This exception covers insufficient privileges for chroot operations,
|
|
file access, and other permission issues.
|
|
"""
|
|
|
|
def __init__(
|
|
self,
|
|
message: str,
|
|
operation: Optional[str] = None,
|
|
path: Optional[str] = None,
|
|
required_privileges: Optional[str] = None,
|
|
):
|
|
context = {}
|
|
if operation:
|
|
context["operation"] = operation
|
|
if path:
|
|
context["path"] = path
|
|
if required_privileges:
|
|
context["required_privileges"] = required_privileges
|
|
|
|
suggestions = [
|
|
"Run the command with appropriate privileges (sudo)",
|
|
"Check file and directory permissions",
|
|
"Verify your user is in the required groups",
|
|
"Ensure the target paths are writable",
|
|
]
|
|
|
|
super().__init__(message, exit_code=11, context=context, suggestions=suggestions)
|
|
|
|
|
|
class ValidationError(DebMockError):
|
|
"""
|
|
Raised when input validation fails.
|
|
|
|
This exception covers validation of source packages, configuration,
|
|
and other input data.
|
|
"""
|
|
|
|
def __init__(
|
|
self,
|
|
message: str,
|
|
field: Optional[str] = None,
|
|
value: Optional[str] = None,
|
|
expected_format: Optional[str] = None,
|
|
):
|
|
context = {}
|
|
if field:
|
|
context["field"] = field
|
|
if value:
|
|
context["value"] = value
|
|
if expected_format:
|
|
context["expected_format"] = expected_format
|
|
|
|
suggestions = [
|
|
"Check the input format and syntax",
|
|
"Verify that required fields are provided",
|
|
"Ensure values are within acceptable ranges",
|
|
"Review the documentation for correct usage",
|
|
]
|
|
|
|
super().__init__(message, exit_code=12, context=context, suggestions=suggestions)
|
|
|
|
|
|
class UIDManagerError(DebMockError):
|
|
"""Raised when UID/GID management operations fail"""
|
|
|
|
def __init__(self, message, chroot_name=None, operation=None):
|
|
super().__init__(message)
|
|
self.chroot_name = chroot_name
|
|
self.operation = operation
|
|
|
|
def get_exit_code(self):
|
|
return 20 # UID management error
|
|
|
|
|
|
class PerformanceError(Exception):
|
|
"""Raised when performance monitoring or optimization fails"""
|
|
pass
|
|
|
|
|
|
# Convenience functions for common error patterns
|
|
def handle_exception(func):
|
|
"""
|
|
Decorator to handle exceptions and provide consistent error reporting.
|
|
|
|
This decorator catches DebMockError exceptions and provides
|
|
formatted error output with suggestions for resolution.
|
|
"""
|
|
|
|
@functools.wraps(func)
|
|
def wrapper(*args, **kwargs):
|
|
try:
|
|
return func(*args, **kwargs)
|
|
except DebMockError as e:
|
|
e.print_error()
|
|
sys.exit(e.get_exit_code())
|
|
except Exception as e:
|
|
# Convert unexpected exceptions to DebMockError
|
|
error = DebMockError(
|
|
f"Unexpected error: {str(e)}",
|
|
context={"exception_type": type(e).__name__},
|
|
suggestions=[
|
|
"This may be a bug in deb-mock",
|
|
"Check the logs for more details",
|
|
"Report the issue with full error context",
|
|
],
|
|
)
|
|
error.print_error()
|
|
sys.exit(1)
|
|
|
|
return wrapper
|
|
|
|
|
|
def format_error_context(**kwargs) -> Dict[str, Any]:
|
|
"""
|
|
Helper function to format error context information.
|
|
|
|
Args:
|
|
**kwargs: Key-value pairs for context information
|
|
|
|
Returns:
|
|
Formatted context dictionary
|
|
"""
|
|
return {k: v for k, v in kwargs.items() if v is not None}
|