Enhance check_python_file_by_test_suite function with robust error handling and logging. Added validation for file existence, module loading, and function execution. Improved resource cleanup and working directory management to ensure stability and reliability during test execution.
This commit is contained in:
@@ -208,27 +208,151 @@ def is_extension_installed(actual: str, rules: Dict, **options):
|
||||
|
||||
|
||||
def check_python_file_by_test_suite(actual_files, test_file, **options) -> float:
|
||||
"""Check the python file by running the test suite in the given test file."""
|
||||
|
||||
"""Check the python file by running the test suite in the given test file.
|
||||
|
||||
This function is now more robust and handles various error conditions:
|
||||
- File existence validation
|
||||
- Module loading errors
|
||||
- Function execution errors
|
||||
- Proper resource cleanup
|
||||
- Working directory management
|
||||
"""
|
||||
import os
|
||||
import uuid
|
||||
import logging
|
||||
from pathlib import Path
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
test_function_name = options.get('test_function_name', 'test')
|
||||
# Create a unique module name, it can be arbitrary but must be unique in the current runtime environment
|
||||
module_name = 'dynamic_module'
|
||||
|
||||
# Load the module from the given file path
|
||||
spec = importlib.util.spec_from_file_location(module_name, test_file)
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
sys.modules[module_name] = module # Add the loaded module to sys.modules
|
||||
spec.loader.exec_module(module) # Execute the module to make its content available
|
||||
|
||||
# Retrieve the function by name from the loaded module and execute it
|
||||
test_function = getattr(module, test_function_name)
|
||||
try:
|
||||
if test_function():
|
||||
return 1.0
|
||||
else:
|
||||
return 0.0
|
||||
except Exception as e:
|
||||
|
||||
# Validate inputs
|
||||
if not test_file:
|
||||
logger.error("test_file is None or empty")
|
||||
return 0.0
|
||||
|
||||
# Convert to absolute path and check existence
|
||||
test_file_path = Path(test_file).resolve()
|
||||
if not test_file_path.exists():
|
||||
logger.error(f"Test file does not exist: {test_file_path}")
|
||||
return 0.0
|
||||
|
||||
if not test_file_path.is_file():
|
||||
logger.error(f"Test file path is not a file: {test_file_path}")
|
||||
return 0.0
|
||||
|
||||
# Create unique module name to avoid conflicts
|
||||
module_name = f'dynamic_test_module_{uuid.uuid4().hex[:8]}'
|
||||
|
||||
# Store original working directory and sys.path
|
||||
original_cwd = os.getcwd()
|
||||
original_sys_path = sys.path.copy()
|
||||
|
||||
try:
|
||||
# Change to the directory containing the test file
|
||||
test_dir = test_file_path.parent
|
||||
os.chdir(test_dir)
|
||||
logger.debug(f"Changed working directory to: {test_dir}")
|
||||
|
||||
# Add test directory to Python path if not already present
|
||||
if str(test_dir) not in sys.path:
|
||||
sys.path.insert(0, str(test_dir))
|
||||
logger.debug(f"Added {test_dir} to sys.path")
|
||||
|
||||
# Try to load the module
|
||||
try:
|
||||
spec = importlib.util.spec_from_file_location(module_name, test_file_path)
|
||||
if spec is None:
|
||||
logger.error(f"Could not create module spec for {test_file_path}")
|
||||
return 0.0
|
||||
|
||||
if spec.loader is None:
|
||||
logger.error(f"Module spec has no loader for {test_file_path}")
|
||||
return 0.0
|
||||
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
if module is None:
|
||||
logger.error(f"Could not create module from spec for {test_file_path}")
|
||||
return 0.0
|
||||
|
||||
# Add to sys.modules temporarily
|
||||
sys.modules[module_name] = module
|
||||
|
||||
# Execute the module
|
||||
spec.loader.exec_module(module)
|
||||
logger.debug(f"Successfully loaded test module: {module_name}")
|
||||
|
||||
except SyntaxError as e:
|
||||
logger.error(f"Syntax error in test file: {e}")
|
||||
return 0.0
|
||||
except ImportError as e:
|
||||
logger.error(f"Import error loading test file: {e}")
|
||||
return 0.0
|
||||
except Exception as e:
|
||||
logger.error(f"Error loading test module: {e}")
|
||||
return 0.0
|
||||
|
||||
# Try to get the test function
|
||||
try:
|
||||
if not hasattr(module, test_function_name):
|
||||
logger.error(f"Test function '{test_function_name}' not found in {test_file_path}")
|
||||
return 0.0
|
||||
|
||||
test_function = getattr(module, test_function_name)
|
||||
|
||||
if not callable(test_function):
|
||||
logger.error(f"'{test_function_name}' is not callable in {test_file_path}")
|
||||
return 0.0
|
||||
|
||||
logger.debug(f"Found test function: {test_function_name}")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting test function: {e}")
|
||||
return 0.0
|
||||
|
||||
# Execute the test function
|
||||
try:
|
||||
result = test_function()
|
||||
logger.debug(f"Test function returned: {result} (type: {type(result)})")
|
||||
|
||||
# Handle different return types
|
||||
if isinstance(result, bool):
|
||||
return 1.0 if result else 0.0
|
||||
elif isinstance(result, (int, float)):
|
||||
# Normalize to 0.0-1.0 range
|
||||
normalized = max(0.0, min(1.0, float(result)))
|
||||
if normalized != result:
|
||||
logger.warning(f"Test result {result} normalized to {normalized}")
|
||||
return normalized
|
||||
else:
|
||||
# For any other type, treat as True if truthy
|
||||
bool_result = bool(result)
|
||||
logger.warning(f"Test returned non-boolean/numeric value {result}, treating as {bool_result}")
|
||||
return 1.0 if bool_result else 0.0
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error executing test function: {e}")
|
||||
return 0.0
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Unexpected error in check_python_file_by_test_suite: {e}")
|
||||
return 0.0
|
||||
|
||||
finally:
|
||||
# Cleanup: remove the module from sys.modules
|
||||
if module_name in sys.modules:
|
||||
del sys.modules[module_name]
|
||||
logger.debug(f"Cleaned up module: {module_name}")
|
||||
|
||||
# Restore original working directory
|
||||
try:
|
||||
os.chdir(original_cwd)
|
||||
logger.debug(f"Restored working directory to: {original_cwd}")
|
||||
except Exception as e:
|
||||
logger.warning(f"Could not restore working directory: {e}")
|
||||
|
||||
# Restore original sys.path
|
||||
sys.path[:] = original_sys_path
|
||||
logger.debug("Restored sys.path")
|
||||
|
||||
|
||||
def check_python_file_by_gold_file(actual_files, gold_file: str, **options) -> float:
|
||||
|
||||
Reference in New Issue
Block a user