#!/usr/bin/env python3
"""
Release Test Runner
リリース前に実行すべき全ての検証テストを包括的に実行
"""
import sys
import os
import subprocess
import time
import asyncio
from pathlib import Path
from dotenv import load_dotenv
# .envファイルを読み込み
load_dotenv()
# プロジェクトルートをパスに追加
project_root = Path(__file__).parent
sys.path.insert(0, str(project_root))
# テストインポート
from tests.test_mcp_system_validation import MCPSystemValidationTest
class ReleaseTestRunner:
"""リリーステスト実行管理クラス"""
def __init__(self):
self.test_results = {}
self.total_duration = 0
self.skip_mcp_server_test = True # デフォルトでMCPサーバーテストをスキップ
def print_header(self):
"""ヘッダー出力"""
print("=" * 100)
print("🚀 FINVIZ MCP SERVER - RELEASE VALIDATION TESTS")
print("=" * 100)
print("本テストはリリース前の品質検証を実行します")
print("- 環境チェック")
print("- ユニットテスト実行")
print("- システム検証テスト実行")
if not self.skip_mcp_server_test:
print("- MCPサーバー起動テスト")
print("-" * 100)
def check_environment(self):
"""環境チェック"""
print("🔍 環境チェック実行中...")
checks = []
# Python バージョン確認
python_version = f"{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}"
if sys.version_info >= (3, 8):
print(f"✅ Python バージョン: {python_version}")
checks.append(True)
else:
print(f"❌ Python バージョン不適合: {python_version} (3.8以上必要)")
checks.append(False)
# 必要ファイル確認
required_files = ['src/server.py', 'pyproject.toml', 'requirements.txt']
for file_path in required_files:
if os.path.exists(file_path):
print(f"✅ 必要ファイル存在: {file_path}")
checks.append(True)
else:
print(f"❌ 必要ファイル不存在: {file_path}")
checks.append(False)
# 環境変数確認
finviz_key = os.getenv('FINVIZ_API_KEY')
if finviz_key:
print(f"✅ FINVIZ_API_KEY設定済み")
checks.append(True)
else:
print(f"⚠️ FINVIZ_API_KEY未設定(一部機能に制限あり)")
checks.append(True) # 警告だが、テスト続行可能
# 依存パッケージ確認
try:
import pandas, requests, bs4
print("✅ 依存パッケージ利用可能")
checks.append(True)
except ImportError as e:
print(f"❌ 依存パッケージ不足: {e}")
checks.append(False)
return all(checks)
def run_mcp_server_startup_test(self):
"""MCPサーバー起動テスト(オプション)"""
print("\n🔌 MCPサーバー起動テスト実行中...")
print("⚠️ 注意: RuntimeWarningは技術的警告で機能には影響しません")
try:
result = subprocess.run([
sys.executable, "-c",
"""
import sys
import subprocess
import time
import signal
# MCPサーバーを起動
proc = subprocess.Popen([sys.executable, '-m', 'mcp.server.stdio', 'src.server'],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
# 3秒待機
time.sleep(3)
# プロセスが実行中かチェック
if proc.poll() is None:
print('SUCCESS: MCPサーバーが正常に起動')
proc.terminate()
proc.wait()
exit(0)
else:
stdout, stderr = proc.communicate()
if stderr:
print(f'ERROR: {stderr.decode()}')
exit(1)
"""
], capture_output=True, text=True, timeout=10)
if result.returncode == 0:
print("✅ MCPサーバー起動テスト成功")
return True
else:
print(f"❌ MCPサーバー起動失敗: {result.stderr}")
return False
except subprocess.TimeoutExpired:
print("❌ MCPサーバー起動テストタイムアウト")
return False
except Exception as e:
print(f"❌ MCPサーバー起動テストエラー: {e}")
return False
def run_system_validation_tests(self):
"""システム検証テスト実行"""
print("\n🧪 システム検証テスト実行...")
start_time = time.time()
validator = MCPSystemValidationTest()
success = validator.run_all_tests()
duration = time.time() - start_time
self.test_results['system_validation'] = {
'success': success,
'duration': duration,
'details': validator.test_results
}
return success
def run_unit_tests(self):
"""ユニットテスト実行"""
print("\n🔬 ユニットテスト実行...")
# テストファイル一覧
unit_test_files = [
"tests/test_basic.py", # 基本機能ユニットテスト
# "tests/test_error_handling.py", # 開発中: 一時的に無効化
]
integration_test_files = [
# "tests/test_mcp_integration.py", # 開発中: 一時的に無効化
]
all_tests_passed = True
test_summary = {"passed": 0, "failed": 0, "total": 0}
# ユニットテスト実行
print(" 📋 基本ユニットテスト:")
for test_file in unit_test_files:
if os.path.exists(test_file):
try:
result = subprocess.run([
sys.executable, test_file
], capture_output=True, text=True, timeout=30)
if result.returncode == 0:
print(f" ✅ {test_file}")
test_summary["passed"] += 1
else:
print(f" ❌ {test_file}")
print(f" エラー: {result.stderr[:200]}...")
test_summary["failed"] += 1
all_tests_passed = False
test_summary["total"] += 1
except subprocess.TimeoutExpired:
print(f" ⏰ {test_file} (タイムアウト)")
test_summary["failed"] += 1
test_summary["total"] += 1
all_tests_passed = False
except Exception as e:
print(f" ❌ {test_file} (実行エラー: {e})")
test_summary["failed"] += 1
test_summary["total"] += 1
all_tests_passed = False
else:
print(f" ⚠️ {test_file} (ファイル不存在)")
# 統合テスト実行(pytest使用)
print(" 🔗 統合テスト:")
if integration_test_files:
for test_file in integration_test_files:
if os.path.exists(test_file):
try:
result = subprocess.run([
sys.executable, "-m", "pytest", test_file,
"-v", "--tb=short", "--timeout=60"
], capture_output=True, text=True, timeout=90)
if result.returncode == 0:
print(f" ✅ {test_file}")
test_summary["passed"] += 1
else:
print(f" ❌ {test_file}")
# より詳細なエラー出力
error_lines = result.stdout.split('\n')[-10:] # 最後の10行
for line in error_lines:
if line.strip():
print(f" {line}")
test_summary["failed"] += 1
all_tests_passed = False
test_summary["total"] += 1
except subprocess.TimeoutExpired:
print(f" ⏰ {test_file} (タイムアウト)")
test_summary["failed"] += 1
test_summary["total"] += 1
all_tests_passed = False
except Exception as e:
print(f" ❌ {test_file} (実行エラー: {e})")
test_summary["failed"] += 1
test_summary["total"] += 1
all_tests_passed = False
else:
print(f" ⚠️ {test_file} (ファイル不存在)")
else:
print(" ℹ️ 統合テスト: 現在開発中のため無効化(リリースには影響なし)")
# サマリー出力
print(f" 📊 ユニットテスト結果: {test_summary['passed']}/{test_summary['total']} 成功")
if all_tests_passed:
print("✅ ユニットテスト成功")
else:
print("❌ ユニットテスト失敗")
return all_tests_passed
def generate_release_report(self):
"""リリースレポート生成"""
print("\n" + "=" * 100)
print("📊 RELEASE VALIDATION REPORT")
print("=" * 100)
# 全体サマリー
all_tests_passed = all(
result.get('success', False)
for result in self.test_results.values()
)
total_tests = sum(
len(result.get('details', []))
for result in self.test_results.values()
)
total_passed = sum(
sum(1 for detail in result.get('details', []) if detail.success)
for result in self.test_results.values()
)
print(f"📈 総合結果:")
print(f" 全体判定: {'🟢 PASS' if all_tests_passed else '🔴 FAIL'}")
print(f" テスト成功率: {total_passed}/{total_tests} ({(total_passed/total_tests*100):.1f}%)" if total_tests > 0 else " テスト成功率: N/A")
print(f" 総実行時間: {self.total_duration:.2f}秒")
# カテゴリ別結果
print(f"\n📋 カテゴリ別結果:")
for category, result in self.test_results.items():
status = "✅" if result['success'] else "❌"
duration = result['duration']
print(f" {status} {category}: {duration:.2f}秒")
# 品質メトリクス(システム検証テストから)
if 'system_validation' in self.test_results:
system_details = self.test_results['system_validation']['details']
if system_details:
total_stocks = sum(detail.stocks_found for detail in system_details)
avg_quality = sum(detail.data_quality_score for detail in system_details) / len(system_details)
print(f"\n📊 品質メトリクス:")
print(f" 検出銘柄総数: {total_stocks}")
print(f" 平均品質スコア: {avg_quality:.1f}/100")
# リリース判定
print(f"\n🎯 リリース判定:")
if all_tests_passed:
print(" 🟢 リリース承認 - 全テスト成功")
print(" 本バージョンは本格運用可能です")
else:
failed_categories = [cat for cat, result in self.test_results.items() if not result['success']]
print(" 🔴 リリース保留 - 修正が必要です")
print(f" 失敗カテゴリ: {', '.join(failed_categories)}")
print(" 上記の失敗項目を修正後、再テストしてください")
print("=" * 100)
return all_tests_passed
def run_all_release_tests(self, include_mcp_server_test=False):
"""全リリーステストを実行"""
self.skip_mcp_server_test = not include_mcp_server_test
start_time = time.time()
self.print_header()
# 1. 環境チェック
if not self.check_environment():
print("❌ 環境チェック失敗 - テストを中止します")
return False
# 2. ユニットテスト(優先実行)
unit_test_start = time.time()
unit_success = self.run_unit_tests()
unit_duration = time.time() - unit_test_start
self.test_results['unit_tests'] = {
'success': unit_success,
'duration': unit_duration,
'details': []
}
# 3. システム検証テスト(メインテスト)
system_success = self.run_system_validation_tests()
# 4. MCPサーバー起動テスト(オプション)
if include_mcp_server_test:
server_test_start = time.time()
server_success = self.run_mcp_server_startup_test()
server_duration = time.time() - server_test_start
self.test_results['mcp_server_startup'] = {
'success': server_success,
'duration': server_duration,
'details': []
}
# 総実行時間
self.total_duration = time.time() - start_time
# 5. レポート生成
overall_success = self.generate_release_report()
return overall_success
# メイン実行
def main():
"""メイン実行関数"""
# コマンドライン引数でMCPサーバーテストを制御
include_mcp_test = "--include-mcp-test" in sys.argv
if include_mcp_test:
print("🔌 MCPサーバー起動テストを含めて実行します")
else:
print("⚠️ MCPサーバー起動テストはスキップします(--include-mcp-test で有効化)")
runner = ReleaseTestRunner()
success = runner.run_all_release_tests(include_mcp_server_test=include_mcp_test)
if success:
print("\n🎉 リリーステスト完了 - 全て成功!")
print("本バージョンはリリース可能です。")
return True
else:
print("\n⚠️ リリーステスト完了 - 修正が必要です")
print("失敗した項目を修正後、再度テストしてください。")
return False
if __name__ == "__main__":
try:
result = main()
sys.exit(0 if result else 1)
except KeyboardInterrupt:
print("\n\n⚠️ テストが中断されました")
sys.exit(2)
except Exception as e:
print(f"\n\n❌ 予期しないエラー: {e}")
sys.exit(3)