"""P4 acceptance test suite. Tests all P4 milestones: M1 (AI frontend API), M2 (task dispatch), M3 (batch scheduler + monitor), M4 (reports + visualization data), M5 (deployment config). Run: python scripts/test_p4_acceptance.py """ import json import os import sys import time from datetime import datetime # Add project root to path sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) PASS = 0 FAIL = 0 RESULTS = [] def test(name, condition, detail=""): global PASS, FAIL if condition: PASS += 1 RESULTS.append(("PASS", name, detail)) print(f" [PASS] {name}") else: FAIL += 1 RESULTS.append(("FAIL", name, detail)) print(f" [FAIL] {name} - {detail}") def test_m1_ai_api(): """P4-M1: AI API endpoints and frontend integration.""" print("\n[P4-M1] AI Frontend Integration") try: from web.backend.app.routers import ai, ai_plan, analysis, adaptive, search test("AI router modules importable", True) except Exception as e: test("AI router modules importable", False, str(e)) # Check frontend AI API file ai_api_path = os.path.join("web", "frontend", "src", "api", "ai.ts") test("Frontend AI API file exists", os.path.exists(ai_api_path)) # Check AI views ai_views = ["PlanGenerator.vue", "L0Prescreen.vue", "AdaptiveOptimize.vue", "ResultAnalysis.vue", "FidelityCalibration.vue", "ExperienceEnhance.vue"] for view in ai_views: path = os.path.join("web", "frontend", "src", "views", "ai", view) test(f"AI view {view} exists", os.path.exists(path)) # Check components comps = ["ConfidenceBadge.vue", "FeasibilityIndicator.vue"] for comp in comps: path = os.path.join("web", "frontend", "src", "components", "ai", comp) test(f"AI component {comp} exists", os.path.exists(path)) def test_m2_task_dispatch(): """P4-M2: Task management and dispatch.""" print("\n[P4-M2] Task Dispatch and Callback") try: from web.backend.app.models.task import Task test("Task model importable", True) except Exception as e: test("Task model importable", False, str(e)) try: from web.backend.app.services.task_manager import get_task_manager tm = get_task_manager() test("TaskManager service instantiable", tm is not None) except Exception as e: test("TaskManager service instantiable", False, str(e)) # Check task router router_path = os.path.join("web", "backend", "app", "routers", "tasks.py") test("Tasks router exists", os.path.exists(router_path)) # Check frontend task manager tm_path = os.path.join("web", "frontend", "src", "views", "TaskManager.vue") test("Frontend TaskManager exists", os.path.exists(tm_path)) # Check local executor exec_path = os.path.join("scripts", "task_executor.py") test("Local task executor exists", os.path.exists(exec_path)) def test_m3_batch_scheduler(): """P4-M3: Batch scheduler and monitoring.""" print("\n[P4-M3] Batch Scheduler and Monitoring") try: from web.backend.app.services.batch_scheduler import BatchScheduler, get_scheduler scheduler = get_scheduler() test("BatchScheduler instantiable", scheduler is not None) # Test add task task = scheduler.add_task("test-task-1", "Test Task", priority=5, parameters=[{"x": 1}, {"x": 2}]) test("Add task to scheduler", task["task_id"] == "test-task-1") # Test statistics stats = scheduler.get_statistics() test("Scheduler statistics has queued_count", "queued_count" in stats) test("Scheduler statistics has overall_progress", "overall_progress" in stats) # Cleanup scheduler.cancel_task("test-task-1") except Exception as e: test("BatchScheduler functional", False, str(e)) # Check monitor router monitor_path = os.path.join("web", "backend", "app", "routers", "monitor.py") test("Monitor router exists", os.path.exists(monitor_path)) # Check frontend monitor monitor_vue = os.path.join("web", "frontend", "src", "views", "MonitorDashboard.vue") test("Frontend MonitorDashboard exists", os.path.exists(monitor_vue)) # Check robust motorcad robust_path = os.path.join("scripts", "robust_motorcad.py") test("Robust MotorCAD core exists", os.path.exists(robust_path)) def test_m4_visualization_reports(): """P4-M4: Advanced visualization and reports.""" print("\n[P4-M4] Visualization and Reports") try: from web.backend.app.services.report_generator import ReportGenerator, get_report_generator rg = get_report_generator() test("ReportGenerator instantiable", rg is not None) # Test JSON report generation (fallback mode) task_data = {"task_id": "test-report", "task_name": "Test", "status": "completed", "plan_data": {"x": 1}, "result_metrics": {"efficiency": 90}} report_path = rg.generate_report(task_data) test("Report generated (JSON fallback)", os.path.exists(report_path)) if os.path.exists(report_path): os.remove(report_path) except Exception as e: test("ReportGenerator functional", False, str(e)) # Check reports router reports_path = os.path.join("web", "backend", "app", "routers", "reports.py") test("Reports router exists", os.path.exists(reports_path)) # Check frontend visualization viz_path = os.path.join("web", "frontend", "src", "views", "AdvancedVisualization.vue") test("Frontend AdvancedVisualization exists", os.path.exists(viz_path)) def test_m5_deployment(): """P4-M5: Deployment configuration.""" print("\n[P4-M5] Deployment and Packaging") files = { "Dockerfile": os.path.join("Dockerfile"), "docker-compose.yml": os.path.join("docker-compose.yml"), "nginx.conf": os.path.join("nginx.conf"), "deploy.ps1": os.path.join("deploy.ps1"), } for name, path in files.items(): test(f"Deployment file {name} exists", os.path.exists(path)) # Check deploy.ps1 is ASCII only deploy_path = files["deploy.ps1"] if os.path.exists(deploy_path): with open(deploy_path, "r", encoding="utf-8") as f: content = f.read() non_ascii = [c for c in content if ord(c) > 127] test("deploy.ps1 is ASCII-only", len(non_ascii) == 0, f"{len(non_ascii)} non-ASCII chars" if non_ascii else "") def test_router_registration(): """Verify all routers are registered in main.py.""" print("\n[Integration] Router Registration") main_path = os.path.join("web", "backend", "app", "main.py") if os.path.exists(main_path): with open(main_path, "r", encoding="utf-8") as f: content = f.read() expected_routers = ["tasks", "monitor", "reports"] for router in expected_routers: test(f"Router '{router}' imported in main.py", f"import {router}" in content or f", {router}" in content) test(f"Router '{router}' included in main.py", f"include_router({router}.router)" in content) else: test("main.py exists", False) def main(): print("=" * 60) print("PCB AFM Simulation System - P4 Acceptance Test") print(f"Date: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}") print("=" * 60) os.chdir(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) test_m1_ai_api() test_m2_task_dispatch() test_m3_batch_scheduler() test_m4_visualization_reports() test_m5_deployment() test_router_registration() print("\n" + "=" * 60) print(f"RESULTS: {PASS} passed, {FAIL} failed, {PASS + FAIL} total") print("=" * 60) if FAIL > 0: print("\nFailed tests:") for status, name, detail in RESULTS: if status == "FAIL": print(f" - {name}: {detail}") sys.exit(1) else: print("\nAll tests passed!") sys.exit(0) if __name__ == "__main__": main()