7a0da7932b
Backwards Compatibility / Verify Encryption Constants (push) Waiting to run
Backwards Compatibility / PyPI Version Compatibility (push) Waiting to run
Backwards Compatibility / Database Migration Tests (push) Waiting to run
CodeQL Advanced / Analyze (javascript-typescript) (push) Waiting to run
CodeQL Advanced / Analyze (python) (push) Waiting to run
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-form] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-metrics] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-workflow] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [settings-core] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [settings-pages] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [history-news] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [library] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [link-analytics] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [mobile] (push) Blocked by required conditions
Docker Tests (Consolidated) / detect-changes (push) Waiting to run
Docker Tests (Consolidated) / Build Test Image (push) Waiting to run
Docker Tests (Consolidated) / All Pytest Tests + Coverage (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [accessibility] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [api-crud] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-login] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-pages] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-register] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [chat-core] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [chat-lifecycle] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [error-benchmark] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) (push) Blocked by required conditions
Docker Tests (Consolidated) / Accessibility Tests (push) Blocked by required conditions
Docker Tests (Consolidated) / LLM Unit Tests (push) Blocked by required conditions
Docker Tests (Consolidated) / LLM Example Tests (push) Blocked by required conditions
Docker Tests (Consolidated) / Production Image Smoke Test (push) Blocked by required conditions
Docker Tests (Consolidated) / Infrastructure Tests (push) Blocked by required conditions
OSSF Scorecard / OSSF Security Scorecard Analysis (push) Waiting to run
OSV-Scanner (Scheduled) / scan-scheduled (push) Failing after 0s
Create Release / test-gate (push) Has been cancelled
Create Release / release-gate (push) Has been cancelled
Create Release / ci-gate (push) Has been cancelled
Create Release / version-check (push) Has been cancelled
Create Release / e2e-test-gate (push) Has been cancelled
Create Release / responsive-test-gate (push) Has been cancelled
Create Release / compat-test-gate (push) Has been cancelled
Create Release / compose-integration-gate (push) Has been cancelled
Create Release / vulture-gate (push) Has been cancelled
Create Release / build (push) Has been cancelled
Create Release / provenance (push) Has been cancelled
Create Release / prerelease-docker (push) Has been cancelled
Create Release / publish-docker (push) Has been cancelled
Create Release / create-release (push) Has been cancelled
Create Release / cleanup-changelog (push) Has been cancelled
Create Release / trigger-pypi (push) Has been cancelled
Create Release / monitor-pypi (push) Has been cancelled
Create Release / Clean up orphan prerelease tags and signatures (push) Has been cancelled
631 lines
23 KiB
Python
Executable File
631 lines
23 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""
|
|
Comprehensive Test Runner for Local Deep Research
|
|
|
|
This script orchestrates all test types and provides different test profiles
|
|
for various scenarios (CI/CD, development, full validation).
|
|
|
|
Usage:
|
|
python tests/run_all_tests.py [profile]
|
|
|
|
Profiles:
|
|
fast - Quick health checks and unit tests (< 30s)
|
|
standard - Standard development testing (< 5min)
|
|
full - Optimized full testing, no redundant runs (< 10min)
|
|
comprehensive - Legacy full testing, includes redundant runs (< 15min)
|
|
ci - CI/CD optimized tests
|
|
unit-only - Unit tests only, no server dependencies (< 2min)
|
|
"""
|
|
|
|
import argparse
|
|
import os
|
|
import subprocess
|
|
from subprocess import CalledProcessError
|
|
import sys
|
|
import time
|
|
from pathlib import Path
|
|
from typing import List, Tuple
|
|
from functools import cache
|
|
|
|
|
|
@cache
|
|
def find_exe(tool_name: str) -> Path:
|
|
"""
|
|
Finds a particular command-line tool.
|
|
|
|
Args:
|
|
tool_name: The name of the tool.
|
|
|
|
Returns:
|
|
The path to the tool.
|
|
|
|
"""
|
|
which_output = ""
|
|
try:
|
|
which_result = subprocess.run(
|
|
["/usr/bin/which", tool_name], check=True, capture_output=True
|
|
)
|
|
which_output = which_result.stdout.decode("utf8")
|
|
except CalledProcessError as err:
|
|
if err.returncode != 1: # pragma: no cover
|
|
# If it returns 1, it's probably because the executable does not
|
|
# exist. Otherwise, something weird happened.
|
|
raise err
|
|
pass
|
|
if not which_output:
|
|
raise OSError(f"Could not find '{tool_name}'. Is it installed?")
|
|
|
|
tool_path = Path(which_output.rstrip("\n"))
|
|
return tool_path
|
|
|
|
|
|
class TestRunner:
|
|
def __init__(self):
|
|
self.project_root = Path(__file__).parent.parent.absolute()
|
|
self.tests_dir = self.project_root / "tests"
|
|
self.results: List[Tuple[str, bool, float]] = []
|
|
self.server_process = None
|
|
|
|
def log(self, message: str, level: str = "INFO"):
|
|
"""Log a message with timestamp."""
|
|
timestamp = time.strftime("%H:%M:%S")
|
|
prefix = (
|
|
"✅" if level == "SUCCESS" else "❌" if level == "ERROR" else "📋"
|
|
)
|
|
print(f"[{timestamp}] {prefix} {message}")
|
|
|
|
def run_command(
|
|
self, cmd: List[str], name: str, cwd: str = None, timeout: int = 300
|
|
) -> bool:
|
|
"""Run a command and track its result."""
|
|
self.log(f"Running {name}...")
|
|
start_time = time.time()
|
|
|
|
try:
|
|
# Set environment
|
|
env = os.environ.copy()
|
|
env["PYTHONPATH"] = str(self.project_root)
|
|
|
|
result = subprocess.run(
|
|
cmd,
|
|
cwd=cwd or self.project_root,
|
|
env=env,
|
|
capture_output=True,
|
|
text=True,
|
|
timeout=timeout,
|
|
)
|
|
|
|
duration = time.time() - start_time
|
|
success = result.returncode == 0
|
|
|
|
if success:
|
|
self.log(
|
|
f"{name} completed successfully ({duration:.1f}s)",
|
|
"SUCCESS",
|
|
)
|
|
else:
|
|
self.log(f"{name} failed ({duration:.1f}s)", "ERROR")
|
|
print(f"\n{'=' * 60}")
|
|
print(f"DETAILED ERROR OUTPUT FOR: {name}")
|
|
print(f"{'=' * 60}")
|
|
|
|
# Print detailed output for failures with full context
|
|
if result.stdout:
|
|
print("STDOUT:")
|
|
lines = result.stdout.strip().split("\n")
|
|
|
|
# Show test summary/results
|
|
for line in lines[-15:]: # Check last 15 lines for summary
|
|
if any(
|
|
keyword in line.lower()
|
|
for keyword in [
|
|
"failed",
|
|
"passed",
|
|
"error",
|
|
"collected",
|
|
"warnings",
|
|
]
|
|
):
|
|
print(f" {line}")
|
|
|
|
# Show specific failed tests with more context
|
|
print("\nFAILED TESTS WITH CONTEXT:")
|
|
for i, line in enumerate(lines):
|
|
if "FAILED" in line and "::" in line:
|
|
print(f" {line.strip()}")
|
|
# Show next few lines for error details
|
|
for j in range(i + 1, min(i + 10, len(lines))):
|
|
if lines[j].strip() and not lines[j].startswith(
|
|
"===="
|
|
):
|
|
print(f" {lines[j].strip()}")
|
|
if (
|
|
"KeyError" in lines[j]
|
|
or "Exception" in lines[j]
|
|
):
|
|
break
|
|
|
|
# Show full stack traces for exceptions
|
|
print("\nFULL STACK TRACES:")
|
|
in_traceback = False
|
|
traceback_lines = []
|
|
for line in lines:
|
|
if "Traceback (most recent call last):" in line:
|
|
in_traceback = True
|
|
traceback_lines = [line]
|
|
elif in_traceback:
|
|
traceback_lines.append(line)
|
|
if line.strip() and not line.startswith(
|
|
(" ", "\t", "File ", " ")
|
|
):
|
|
# End of traceback
|
|
for tb_line in traceback_lines:
|
|
print(f" {tb_line}")
|
|
print()
|
|
in_traceback = False
|
|
traceback_lines = []
|
|
elif any(
|
|
error_type in line
|
|
for error_type in [
|
|
"KeyError:",
|
|
"ImportError:",
|
|
"ModuleNotFoundError:",
|
|
"AttributeError:",
|
|
"Exception:",
|
|
]
|
|
):
|
|
print(f" ERROR: {line.strip()}")
|
|
|
|
# Show import errors with context
|
|
print("\nIMPORT ERRORS:")
|
|
for i, line in enumerate(lines):
|
|
if (
|
|
"ImportError" in line
|
|
or "ModuleNotFoundError" in line
|
|
):
|
|
print(f" {line.strip()}")
|
|
# Show preceding line for context
|
|
if i > 0:
|
|
print(f" Context: {lines[i - 1].strip()}")
|
|
|
|
# Show KeyError details specifically
|
|
print("\nKEYERROR DETAILS:")
|
|
for i, line in enumerate(lines):
|
|
if "KeyError" in line:
|
|
print(f" {line.strip()}")
|
|
# Show surrounding context
|
|
for j in range(
|
|
max(0, i - 3), min(len(lines), i + 4)
|
|
):
|
|
if j != i:
|
|
print(
|
|
f" [{j - i:+d}] {lines[j].strip()}"
|
|
)
|
|
|
|
# Show pytest short test summary if available
|
|
print("\nPYTEST SHORT SUMMARY:")
|
|
in_summary = False
|
|
for line in lines:
|
|
if "short test summary info" in line.lower():
|
|
in_summary = True
|
|
elif in_summary:
|
|
if line.startswith("="):
|
|
break
|
|
print(f" {line.strip()}")
|
|
|
|
if result.stderr:
|
|
print("\nSTDERR:")
|
|
stderr_lines = result.stderr.strip().split("\n")
|
|
for line in stderr_lines[-10:]: # Last 10 lines
|
|
print(f" {line}")
|
|
|
|
print(f"{'=' * 60}\n")
|
|
|
|
self.results.append((name, success, duration))
|
|
return success
|
|
|
|
except subprocess.TimeoutExpired:
|
|
duration = time.time() - start_time
|
|
self.log(f"{name} timed out after {timeout}s", "ERROR")
|
|
self.results.append((name, False, duration))
|
|
return False
|
|
except Exception as e:
|
|
duration = time.time() - start_time
|
|
self.log(f"{name} failed with exception: {e}", "ERROR")
|
|
self.results.append((name, False, duration))
|
|
return False
|
|
|
|
def check_server_running(self) -> bool:
|
|
"""Check if the web server is running."""
|
|
try:
|
|
import requests
|
|
|
|
response = requests.get(
|
|
"http://127.0.0.1:5000/api/v1/health", timeout=5
|
|
)
|
|
return response.status_code == 200
|
|
except Exception:
|
|
return False
|
|
|
|
def start_server(self) -> bool:
|
|
"""Start the web server using pdm run ldr-web."""
|
|
if self.check_server_running():
|
|
self.log("Server already running at http://127.0.0.1:5000")
|
|
return True
|
|
|
|
self.log("Starting web server with 'pdm run ldr-web'...")
|
|
try:
|
|
import subprocess
|
|
import time
|
|
|
|
# Start server in background
|
|
self.server_process = subprocess.Popen(
|
|
["pdm", "run", "ldr-web"],
|
|
cwd=self.project_root,
|
|
stdout=subprocess.PIPE,
|
|
stderr=subprocess.PIPE,
|
|
text=True,
|
|
)
|
|
|
|
# Wait for server to start (max 45 seconds)
|
|
for i in range(45):
|
|
time.sleep(1) # allow: unmarked-sleep
|
|
if self.check_server_running():
|
|
self.log(
|
|
f"Server started successfully after {i + 1} seconds"
|
|
)
|
|
return True
|
|
if self.server_process.poll() is not None:
|
|
# Process died
|
|
stdout, stderr = self.server_process.communicate()
|
|
self.log(
|
|
f"Server failed to start. Exit code: {self.server_process.returncode}",
|
|
"ERROR",
|
|
)
|
|
if stderr:
|
|
print("STDERR:", stderr[-500:])
|
|
return False
|
|
|
|
self.log("Server startup timed out after 45 seconds", "ERROR")
|
|
# Try to get output for debugging
|
|
if self.server_process.poll() is None:
|
|
self.server_process.terminate()
|
|
try:
|
|
stdout, stderr = self.server_process.communicate(timeout=5)
|
|
if stderr:
|
|
print("STDERR after timeout:", stderr[-500:])
|
|
except Exception:
|
|
pass
|
|
return False
|
|
|
|
except Exception as e:
|
|
self.log(f"Failed to start server: {e}", "ERROR")
|
|
return False
|
|
|
|
def stop_server(self):
|
|
"""Stop the web server if we started it."""
|
|
if hasattr(self, "server_process") and self.server_process:
|
|
self.log("Stopping web server...")
|
|
self.server_process.terminate()
|
|
try:
|
|
self.server_process.wait(timeout=10)
|
|
except subprocess.TimeoutExpired:
|
|
self.server_process.kill()
|
|
self.server_process.wait()
|
|
|
|
def run_health_checks(self, start_server_if_needed: bool = True) -> bool:
|
|
"""Run health check tests."""
|
|
self.log("=== HEALTH CHECKS ===")
|
|
|
|
# Start server if needed for health checks
|
|
if start_server_if_needed and not self.check_server_running():
|
|
if not self.start_server():
|
|
self.log("⚠️ Could not start server for health checks", "ERROR")
|
|
return False
|
|
|
|
# Try Python version first
|
|
python_success = self.run_command(
|
|
[sys.executable, "tests/health_check/run_quick_health_check.py"],
|
|
"Health Check (Python)",
|
|
timeout=120,
|
|
)
|
|
|
|
# Try shell version as backup
|
|
if not python_success:
|
|
shell_script = (
|
|
self.tests_dir / "health_check" / "test_endpoints_health.sh"
|
|
)
|
|
if shell_script.exists():
|
|
self.run_command(
|
|
["bash", str(shell_script)],
|
|
"Health Check (Shell)",
|
|
timeout=120,
|
|
)
|
|
|
|
return python_success
|
|
|
|
def run_unit_tests(self) -> bool:
|
|
"""Run unit and feature tests."""
|
|
self.log("=== UNIT & FEATURE TESTS ===")
|
|
|
|
# Add extra debugging for CI environments
|
|
pytest_args = [
|
|
find_exe("pdm"),
|
|
"run",
|
|
"pytest",
|
|
"-v", # Verbose output
|
|
"tests/test_settings_manager.py",
|
|
"tests/test_google_pse.py",
|
|
"tests/test_wikipedia_url_security.py",
|
|
"tests/test_search_engines_enhanced.py",
|
|
"tests/test_utils.py",
|
|
"tests/rate_limiting/", # Rate limiting test suite
|
|
"tests/retriever_integration/", # LangChain retriever integration tests
|
|
"tests/feature_tests/",
|
|
"tests/fix_tests/",
|
|
"tests/infrastructure_tests/", # Infrastructure and architecture tests
|
|
"tests/content_fetcher/", # Content fetcher tests (URL classifier, HTML downloader)
|
|
# Critical stability tests - prevent breaking changes
|
|
"tests/database/test_encryption_constants.py", # Encryption params MUST NOT change
|
|
"tests/database/test_schema_stability.py", # Table names MUST NOT change
|
|
"tests/performance/database/test_backwards_compatibility.py", # DB compatibility tests
|
|
"--cov=src",
|
|
"--cov-report=term-missing",
|
|
]
|
|
|
|
# In CI, add even more debugging
|
|
if os.environ.get("CI") or os.environ.get("GITHUB_ACTIONS"):
|
|
pytest_args.extend(
|
|
[
|
|
"--tb=short", # Short traceback format
|
|
"--durations=10", # Show 10 slowest tests
|
|
]
|
|
)
|
|
self.log("Running with enhanced CI debugging options")
|
|
|
|
# Log environment info for debugging
|
|
self.log(
|
|
f"CI Environment detected: CI={os.environ.get('CI')}, GITHUB_ACTIONS={os.environ.get('GITHUB_ACTIONS')}"
|
|
)
|
|
self.log(f"Python path: {sys.executable}")
|
|
self.log(f"Working directory: {os.getcwd()}")
|
|
|
|
return self.run_command(
|
|
pytest_args,
|
|
"Unit & Feature Tests",
|
|
timeout=300,
|
|
)
|
|
|
|
def run_integration_tests(self) -> bool:
|
|
"""Run integration tests (external dependencies)."""
|
|
self.log("=== INTEGRATION TESTS ===")
|
|
|
|
return self.run_command(
|
|
[
|
|
sys.executable,
|
|
"-m",
|
|
"pytest",
|
|
"-v",
|
|
"tests/searxng/",
|
|
"-k",
|
|
"not slow",
|
|
],
|
|
"Integration Tests",
|
|
timeout=120,
|
|
)
|
|
|
|
def run_ui_tests(self, start_server_if_needed: bool = True) -> bool:
|
|
"""Run UI tests with Puppeteer."""
|
|
self.log("=== UI TESTS ===")
|
|
|
|
if start_server_if_needed and not self.check_server_running():
|
|
if not self.start_server():
|
|
self.log(
|
|
"⚠️ Could not start server - skipping UI tests", "ERROR"
|
|
)
|
|
return False
|
|
elif not self.check_server_running():
|
|
self.log(
|
|
"⚠️ Warning: Server not running at http://127.0.0.1:5000",
|
|
"ERROR",
|
|
)
|
|
self.log("Please start the server with: pdm run ldr-web")
|
|
return False
|
|
|
|
# Check for Node.js and run UI tests
|
|
try:
|
|
subprocess.run(
|
|
["node", "--version"], capture_output=True, check=True
|
|
)
|
|
except (subprocess.CalledProcessError, FileNotFoundError):
|
|
self.log("Node.js not found - skipping UI tests", "ERROR")
|
|
return False
|
|
|
|
# Check if we're in a headless environment (CI)
|
|
cmd = ["node", "run_all_tests.js"]
|
|
if os.environ.get("CI") or not os.environ.get("DISPLAY"):
|
|
# If no display is available, use xvfb-run if available
|
|
try:
|
|
subprocess.run(
|
|
["which", "xvfb-run"], capture_output=True, check=True
|
|
)
|
|
cmd = ["xvfb-run", "-a", "-s", "-screen 0 1920x1080x24"] + cmd
|
|
self.log(
|
|
"Running UI tests with xvfb-run for headless environment"
|
|
)
|
|
except (subprocess.CalledProcessError, FileNotFoundError):
|
|
self.log("Warning: No display available and xvfb-run not found")
|
|
# Set headless environment variable for Puppeteer
|
|
os.environ["PUPPETEER_HEADLESS"] = "true"
|
|
|
|
return self.run_command(
|
|
cmd,
|
|
"UI Tests (Puppeteer)",
|
|
cwd=self.tests_dir / "ui_tests",
|
|
timeout=300,
|
|
)
|
|
|
|
def run_all_pytest_tests(self) -> bool:
|
|
"""Run all pytest tests with coverage (unit, integration, feature tests)."""
|
|
self.log("=== ALL PYTEST TESTS ===")
|
|
|
|
# Run all tests with coverage
|
|
cmd = [
|
|
sys.executable,
|
|
"-m",
|
|
"pytest",
|
|
"--verbose",
|
|
"--color=yes",
|
|
"--cov=src",
|
|
"--cov-report=term",
|
|
"--cov-report=html:coverage_html",
|
|
"--cov-config=.coveragerc",
|
|
]
|
|
timeout = 600 # 10 minutes for all tests
|
|
|
|
if os.environ.get("CI"):
|
|
cmd.extend(
|
|
[
|
|
"--tb=line", # Show one line per failure for easier debugging
|
|
"--timeout=60", # Per-test timeout of 60 seconds
|
|
# Removed -x and --maxfail to show all test results
|
|
]
|
|
)
|
|
self.log(
|
|
"Running in CI mode with verbose output - showing all test results"
|
|
)
|
|
|
|
return self.run_command(cmd, "All Pytest Tests", timeout=timeout)
|
|
|
|
def print_summary(self):
|
|
"""Print test execution summary."""
|
|
self.log("=" * 60)
|
|
self.log("TEST EXECUTION SUMMARY")
|
|
self.log("=" * 60)
|
|
|
|
total_tests = len(self.results)
|
|
passed = sum(1 for _, success, _ in self.results if success)
|
|
failed = total_tests - passed
|
|
total_time = sum(duration for _, _, duration in self.results)
|
|
|
|
for name, success, duration in self.results:
|
|
status = "✅ PASS" if success else "❌ FAIL"
|
|
self.log(f"{status} {name} ({duration:.1f}s)")
|
|
|
|
self.log("-" * 60)
|
|
self.log(
|
|
f"📊 Total: {total_tests} | ✅ Passed: {passed} | ❌ Failed: {failed}"
|
|
)
|
|
self.log(f"⏱️ Total Time: {total_time:.1f}s")
|
|
self.log(f"📈 Success Rate: {(passed / total_tests * 100):.1f}%")
|
|
|
|
if failed == 0:
|
|
self.log("🎉 All tests passed!", "SUCCESS")
|
|
else:
|
|
self.log(f"⚠️ {failed} test(s) failed", "ERROR")
|
|
|
|
return failed == 0
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(
|
|
description="Run Local Deep Research tests"
|
|
)
|
|
parser.add_argument(
|
|
"profile",
|
|
nargs="?",
|
|
default="standard",
|
|
choices=[
|
|
"fast",
|
|
"standard",
|
|
"full",
|
|
"comprehensive",
|
|
"ci",
|
|
"unit-only",
|
|
],
|
|
help="Test profile to run",
|
|
)
|
|
parser.add_argument(
|
|
"--no-server-start",
|
|
action="store_true",
|
|
help="Skip automatic server startup - assume server is already running",
|
|
)
|
|
|
|
args = parser.parse_args()
|
|
runner = TestRunner()
|
|
|
|
runner.log(f"🚀 Starting {args.profile.upper()} test profile")
|
|
start_time = time.time()
|
|
|
|
success = True
|
|
|
|
try:
|
|
if args.profile == "fast":
|
|
# Quick tests for rapid feedback (< 30s)
|
|
success &= runner.run_health_checks(
|
|
start_server_if_needed=not args.no_server_start
|
|
)
|
|
success &= runner.run_unit_tests()
|
|
|
|
elif args.profile == "standard":
|
|
# Standard development testing (< 5min)
|
|
success &= runner.run_health_checks(
|
|
start_server_if_needed=not args.no_server_start
|
|
)
|
|
success &= runner.run_unit_tests()
|
|
success &= runner.run_ui_tests(
|
|
start_server_if_needed=not args.no_server_start
|
|
)
|
|
|
|
elif args.profile == "full":
|
|
# Optimized full testing - no duplicate test runs (< 10min)
|
|
success &= runner.run_health_checks(
|
|
start_server_if_needed=not args.no_server_start
|
|
)
|
|
# Run all pytest tests (includes unit, integration, feature tests)
|
|
success &= runner.run_all_pytest_tests()
|
|
# UI tests require special setup so run separately
|
|
success &= runner.run_ui_tests(
|
|
start_server_if_needed=not args.no_server_start
|
|
)
|
|
|
|
elif args.profile == "comprehensive":
|
|
# Legacy comprehensive testing - runs some tests multiple times (< 15min)
|
|
# Kept for backwards compatibility
|
|
success &= runner.run_health_checks(
|
|
start_server_if_needed=not args.no_server_start
|
|
)
|
|
success &= runner.run_all_pytest_tests()
|
|
success &= (
|
|
runner.run_integration_tests()
|
|
) # Redundant but kept for compatibility
|
|
success &= runner.run_ui_tests(
|
|
start_server_if_needed=not args.no_server_start
|
|
)
|
|
|
|
elif args.profile == "ci":
|
|
# CI/CD optimized tests
|
|
# Skip health checks in CI as they require a running server
|
|
# which is not available in the CI environment
|
|
success &= runner.run_unit_tests()
|
|
# Skip UI tests in CI unless explicitly configured
|
|
|
|
elif args.profile == "unit-only":
|
|
# Unit tests only - no server dependencies
|
|
success &= runner.run_unit_tests()
|
|
|
|
finally:
|
|
# Always cleanup server if we started it
|
|
runner.stop_server()
|
|
|
|
total_duration = time.time() - start_time
|
|
runner.log(f"🏁 Test suite completed in {total_duration:.1f}s")
|
|
|
|
overall_success = runner.print_summary()
|
|
|
|
sys.exit(0 if overall_success else 1)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|