#!/usr/bin/env python3 """ Adds StellaOps.TestKit ProjectReference to test projects that use TestCategories but are missing the reference. """ import os import re import sys from pathlib import Path def get_relative_path_to_testkit(csproj_path: Path) -> str: """Calculate relative path from csproj to TestKit project.""" # TestKit is at src/__Libraries/StellaOps.TestKit/StellaOps.TestKit.csproj csproj_dir = csproj_path.parent src_root = None # Walk up to find src directory current = csproj_dir depth = 0 while current.name != 'src' and depth < 10: current = current.parent depth += 1 if current.name == 'src': src_root = current else: return None # Calculate relative path from csproj to src/__Libraries/StellaOps.TestKit rel_path = os.path.relpath( src_root / '__Libraries' / 'StellaOps.TestKit' / 'StellaOps.TestKit.csproj', csproj_dir ) # Normalize to forward slashes for XML return rel_path.replace('\\', '/') def project_uses_testkit(csproj_dir: Path) -> bool: """Check if any .cs file in the project directory uses TestCategories.""" for cs_file in csproj_dir.rglob('*.cs'): if '/obj/' in str(cs_file) or '/bin/' in str(cs_file): continue try: content = cs_file.read_text(encoding='utf-8-sig', errors='ignore') if 'TestCategories.' in content: return True except: pass return False def project_has_testkit_reference(content: str) -> bool: """Check if csproj already references TestKit.""" return 'StellaOps.TestKit' in content def add_testkit_reference(csproj_path: Path, dry_run: bool = False) -> bool: """Add TestKit reference to csproj if needed.""" try: content = csproj_path.read_text(encoding='utf-8') except Exception as e: print(f" Error reading {csproj_path}: {e}", file=sys.stderr) return False if project_has_testkit_reference(content): return False if not project_uses_testkit(csproj_path.parent): return False rel_path = get_relative_path_to_testkit(csproj_path) if not rel_path: print(f" Could not determine path to TestKit from {csproj_path}", file=sys.stderr) return False # Find a good place to insert the reference - look for existing ProjectReference if ' that contains ProjectReference pattern = r'( ]+/>\s*\n)( )' replacement = f'\\1 \n\\2' fixed = re.sub(pattern, replacement, content, count=1) else: # No ProjectReference, add a new ItemGroup before pattern = r'()' new_item_group = f''' \\1''' fixed = re.sub(pattern, new_item_group, content) if fixed == content: print(f" Could not find insertion point in {csproj_path}", file=sys.stderr) return False if not dry_run: csproj_path.write_text(fixed, encoding='utf-8') return True def main(): import argparse parser = argparse.ArgumentParser(description='Add TestKit reference to test projects') parser.add_argument('--path', default='src', help='Path to scan') parser.add_argument('--dry-run', action='store_true', help='Show what would be fixed') args = parser.parse_args() root = Path(args.path) fixed_count = 0 # Find all test project files for csproj in root.rglob('*.Tests.csproj'): if add_testkit_reference(csproj, dry_run=args.dry_run): print(f"{'Would add' if args.dry_run else 'Added'} TestKit reference to: {csproj}") fixed_count += 1 # Also check *UnitTests, *SmokeTests, etc. for pattern in ['*UnitTests.csproj', '*IntegrationTests.csproj', '*SmokeTests.csproj', '*FixtureTests.csproj']: for csproj in root.rglob(pattern): if add_testkit_reference(csproj, dry_run=args.dry_run): print(f"{'Would add' if args.dry_run else 'Added'} TestKit reference to: {csproj}") fixed_count += 1 print(f"\nAdded TestKit reference to: {fixed_count} projects") if __name__ == '__main__': main()