Source code for matrix_toolkit.anymatrix.testing.test_runner

"""
Test runner for anymatrix - runs property tests on all matrices

Similar to test_anymatrix_properties.m from MATLAB version.
"""

import time
from typing import Dict, List, Optional, Tuple
import warnings
from matrix_toolkit.anymatrix import AnyMatrix
from matrix_toolkit.anymatrix.testing.property_checker import check_matrix_properties


class AnyMatrixTestRunner:
    """Run comprehensive tests on anymatrix collection"""
    
    def __init__(self, anymatrix_instance: Optional[AnyMatrix] = None):
        """
        Initialize test runner
        
        Args:
            anymatrix_instance: AnyMatrix instance to test (creates new if None)
        """
        self.am = anymatrix_instance or AnyMatrix()
        self.results = {}
        self.test_sizes = [3, 5, 8, 10, 15, 20, 25, 30]
    
    def test_single_matrix(
        self, 
        matrix_id: str,
        sizes: Optional[List[int]] = None,
        verbose: bool = False
    ) -> Dict[str, bool]:
        """
        Test a single matrix with various sizes
        
        Args:
            matrix_id: Matrix identifier (e.g., 'core/beta')
            sizes: List of sizes to test (uses default if None)
            verbose: Print detailed results
        
        Returns:
            Dictionary of test results
        """
        sizes = sizes or self.test_sizes
        expected_properties = self.am.properties(matrix_id)
        
        all_results = {}
        
        for size in sizes:
            try:
                # Try to generate matrix with this size
                matrix = self.am.generate(matrix_id, size)
                
                # Check properties
                prop_results = check_matrix_properties(
                    matrix,
                    f"{matrix_id}(n={size})",
                    expected_properties,
                    verbose=verbose
                )
                
                all_results[size] = {
                    'success': True,
                    'properties': prop_results
                }
                
            except Exception as e:
                all_results[size] = {
                    'success': False,
                    'error': str(e)
                }
                if verbose:
                    print(f"  Error with size {size}: {e}")
        
        return all_results
    
    def test_matrix_no_args(
        self, 
        matrix_id: str,
        verbose: bool = False
    ) -> Dict[str, bool]:
        """
        Test matrix generation without arguments
        
        Args:
            matrix_id: Matrix identifier
            verbose: Print results
        
        Returns:
            Test results
        """
        expected_properties = self.am.properties(matrix_id)
        
        try:
            matrix = self.am.generate(matrix_id)
            
            if verbose:
                print(f"\nTesting {matrix_id} (no arguments)")
            
            results = check_matrix_properties(
                matrix,
                matrix_id,
                expected_properties,
                verbose=verbose
            )
            
            return {
                'success': True,
                'properties': results
            }
            
        except Exception as e:
            if verbose:
                print(f"Cannot generate {matrix_id} without arguments: {e}")
            return {
                'success': False,
                'error': str(e)
            }
    
    def run_all_tests(
        self,
        groups: Optional[List[str]] = None,
        verbose: bool = True,
        stop_on_error: bool = False
    ) -> Dict:
        """
        Run tests on all matrices in specified groups
        
        Args:
            groups: List of groups to test (all if None)
            verbose: Print progress
            stop_on_error: Stop on first error
        
        Returns:
            Comprehensive test results
        """
        groups = groups or self.am.groups()
        
        start_time = time.time()
        results = {}
        total_matrices = 0
        passed_matrices = 0
        failed_matrices = 0
        
        if verbose:
            print("\n" + "=" * 70)
            print("ANYMATRIX TEST SUITE")
            print("=" * 70)
        
        for group in groups:
            if verbose:
                print(f"\n{'Testing group: ' + group.upper():-^70}")
            
            group_results = {}
            matrices = self.am.list(group)
            
            for matrix_name in matrices:
                matrix_id = f"{group}/{matrix_name}"
                total_matrices += 1
                
                if verbose:
                    print(f"\n[{total_matrices}] Testing: {matrix_id}")
                
                try:
                    # Test without arguments first
                    no_args_result = self.test_matrix_no_args(matrix_id, verbose=verbose)
                    
                    # Test with various sizes
                    sized_results = self.test_single_matrix(matrix_id, verbose=verbose)
                    
                    # Combine results
                    group_results[matrix_name] = {
                        'no_args': no_args_result,
                        'sized': sized_results
                    }
                    
                    # Check if any test passed
                    any_success = no_args_result.get('success', False) or \
                                any(r.get('success', False) for r in sized_results.values())
                    
                    if any_success:
                        passed_matrices += 1
                        if verbose:
                            print(f"  ✓ {matrix_id} PASSED")
                    else:
                        failed_matrices += 1
                        if verbose:
                            print(f"  ✗ {matrix_id} FAILED")
                    
                except Exception as e:
                    failed_matrices += 1
                    group_results[matrix_name] = {
                        'error': str(e)
                    }
                    
                    if verbose:
                        print(f"  ✗ {matrix_id} ERROR: {e}")
                    
                    if stop_on_error:
                        raise
            
            results[group] = group_results
        
        elapsed_time = time.time() - start_time
        
        if verbose:
            print("\n" + "=" * 70)
            print("TEST SUMMARY")
            print("=" * 70)
            print(f"Total matrices tested: {total_matrices}")
            print(f"Passed: {passed_matrices} ({100*passed_matrices/total_matrices:.1f}%)")
            print(f"Failed: {failed_matrices} ({100*failed_matrices/total_matrices:.1f}%)")
            print(f"Time elapsed: {elapsed_time:.2f} seconds")
            print("=" * 70 + "\n")
        
        return {
            'results': results,
            'summary': {
                'total': total_matrices,
                'passed': passed_matrices,
                'failed': failed_matrices,
                'time': elapsed_time
            }
        }
    
    def generate_report(self, test_results: Dict, output_file: Optional[str] = None):
        """
        Generate a detailed test report
        
        Args:
            test_results: Results from run_all_tests()
            output_file: File to write report to (prints if None)
        """
        lines = []
        
        lines.append("=" * 70)
        lines.append("ANYMATRIX PROPERTY TEST REPORT")
        lines.append("=" * 70)
        lines.append("")
        
        summary = test_results['summary']
        lines.append(f"Total Matrices: {summary['total']}")
        lines.append(f"Passed: {summary['passed']}")
        lines.append(f"Failed: {summary['failed']}")
        lines.append(f"Time: {summary['time']:.2f} seconds")
        lines.append("")
        
        lines.append("=" * 70)
        lines.append("DETAILED RESULTS")
        lines.append("=" * 70)
        
        for group, matrices in test_results['results'].items():
            lines.append(f"\nGroup: {group.upper()}")
            lines.append("-" * 70)
            
            for matrix_name, result in matrices.items():
                matrix_id = f"{group}/{matrix_name}"
                
                if 'error' in result:
                    lines.append(f"  {matrix_id}: ERROR - {result['error']}")
                    continue
                
                # Check no-args test
                no_args = result.get('no_args', {})
                if no_args.get('success'):
                    lines.append(f"  {matrix_id} (no args): PASS")
                
                # Check sized tests
                sized = result.get('sized', {})
                passed_sizes = [s for s, r in sized.items() if r.get('success')]
                if passed_sizes:
                    lines.append(f"  {matrix_id} (sizes {passed_sizes}): PASS")
                
                # Show property failures
                if no_args.get('properties'):
                    failed_props = [p for p, v in no_args['properties'].items() if v is False]
                    if failed_props:
                        lines.append(f"    Failed properties: {', '.join(failed_props)}")
        
        report = '\n'.join(lines)
        
        if output_file:
            with open(output_file, 'w') as f:
                f.write(report)
            print(f"Report written to: {output_file}")
        else:
            print(report)
        
        return report


[docs] def run_all_tests( groups: Optional[List[str]] = None, verbose: bool = True, generate_report: bool = False, report_file: Optional[str] = None ) -> Dict: """ Convenience function to run all anymatrix tests Args: groups: Groups to test (all if None) verbose: Print progress generate_report: Generate detailed report report_file: File to save report Returns: Test results dictionary """ runner = AnyMatrixTestRunner() results = runner.run_all_tests(groups=groups, verbose=verbose) if generate_report: runner.generate_report(results, output_file=report_file) return results