|
|
|
|
|
""" |
|
|
Sistema de Testes Massivos para AgentGraph |
|
|
Interface HTML isolada para testes de consistência e performance |
|
|
""" |
|
|
import os |
|
|
import sys |
|
|
import asyncio |
|
|
import logging |
|
|
from flask import Flask, render_template, request, jsonify, send_file |
|
|
from datetime import datetime |
|
|
import json |
|
|
|
|
|
|
|
|
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) |
|
|
|
|
|
from testes.test_runner import MassiveTestRunner |
|
|
from testes.test_validator import TestValidator |
|
|
from testes.report_generator import ReportGenerator |
|
|
from utils.config import AVAILABLE_MODELS |
|
|
|
|
|
|
|
|
app = Flask(__name__, |
|
|
template_folder='templates', |
|
|
static_folder='static') |
|
|
|
|
|
|
|
|
logging.basicConfig( |
|
|
level=logging.INFO, |
|
|
format='%(asctime)s - %(levelname)s - %(message)s' |
|
|
) |
|
|
|
|
|
|
|
|
test_runner = None |
|
|
current_test_session = None |
|
|
|
|
|
@app.route('/') |
|
|
def index(): |
|
|
"""Página principal do sistema de testes""" |
|
|
return render_template('index.html', |
|
|
available_models=AVAILABLE_MODELS) |
|
|
|
|
|
@app.route('/api/models') |
|
|
def get_models(): |
|
|
"""Retorna modelos disponíveis""" |
|
|
return jsonify({ |
|
|
'success': True, |
|
|
'models': AVAILABLE_MODELS |
|
|
}) |
|
|
|
|
|
@app.route('/api/create_test_session', methods=['POST']) |
|
|
def create_test_session(): |
|
|
"""Cria nova sessão de teste""" |
|
|
global current_test_session |
|
|
|
|
|
try: |
|
|
data = request.json |
|
|
question = data.get('question', '').strip() |
|
|
|
|
|
if not question: |
|
|
return jsonify({ |
|
|
'success': False, |
|
|
'error': 'Pergunta é obrigatória' |
|
|
}) |
|
|
|
|
|
|
|
|
current_test_session = { |
|
|
'id': datetime.now().strftime('%Y%m%d_%H%M%S'), |
|
|
'question': question, |
|
|
'groups': [], |
|
|
'created_at': datetime.now().isoformat(), |
|
|
'status': 'created' |
|
|
} |
|
|
|
|
|
logging.info(f"Nova sessão de teste criada: {current_test_session['id']}") |
|
|
|
|
|
return jsonify({ |
|
|
'success': True, |
|
|
'session_id': current_test_session['id'], |
|
|
'message': 'Sessão de teste criada com sucesso' |
|
|
}) |
|
|
|
|
|
except Exception as e: |
|
|
logging.error(f"Erro ao criar sessão de teste: {e}") |
|
|
return jsonify({ |
|
|
'success': False, |
|
|
'error': str(e) |
|
|
}) |
|
|
|
|
|
@app.route('/api/add_test_group', methods=['POST']) |
|
|
def add_test_group(): |
|
|
"""Adiciona grupo de teste à sessão atual""" |
|
|
global current_test_session |
|
|
|
|
|
try: |
|
|
if not current_test_session: |
|
|
return jsonify({ |
|
|
'success': False, |
|
|
'error': 'Nenhuma sessão de teste ativa' |
|
|
}) |
|
|
|
|
|
data = request.json |
|
|
|
|
|
|
|
|
required_fields = ['sql_model', 'iterations'] |
|
|
for field in required_fields: |
|
|
if field not in data: |
|
|
return jsonify({ |
|
|
'success': False, |
|
|
'error': f'Campo obrigatório: {field}' |
|
|
}) |
|
|
|
|
|
sql_model = data['sql_model'] |
|
|
iterations = int(data['iterations']) |
|
|
processing_enabled = data.get('processing_enabled', False) |
|
|
processing_model = data.get('processing_model', None) |
|
|
|
|
|
|
|
|
if sql_model not in AVAILABLE_MODELS.values(): |
|
|
return jsonify({ |
|
|
'success': False, |
|
|
'error': 'Modelo SQL inválido' |
|
|
}) |
|
|
|
|
|
if iterations < 1 or iterations > 100: |
|
|
return jsonify({ |
|
|
'success': False, |
|
|
'error': 'Número de iterações deve ser entre 1 e 100' |
|
|
}) |
|
|
|
|
|
if processing_enabled and processing_model not in AVAILABLE_MODELS.values(): |
|
|
return jsonify({ |
|
|
'success': False, |
|
|
'error': 'Modelo de processamento inválido' |
|
|
}) |
|
|
|
|
|
|
|
|
group = { |
|
|
'id': len(current_test_session['groups']) + 1, |
|
|
'sql_model': sql_model, |
|
|
'sql_model_name': next(k for k, v in AVAILABLE_MODELS.items() if v == sql_model), |
|
|
'processing_enabled': processing_enabled, |
|
|
'processing_model': processing_model, |
|
|
'processing_model_name': next((k for k, v in AVAILABLE_MODELS.items() if v == processing_model), None) if processing_model else None, |
|
|
'iterations': iterations, |
|
|
'created_at': datetime.now().isoformat() |
|
|
} |
|
|
|
|
|
current_test_session['groups'].append(group) |
|
|
|
|
|
logging.info(f"Grupo adicionado à sessão {current_test_session['id']}: {group}") |
|
|
|
|
|
return jsonify({ |
|
|
'success': True, |
|
|
'group': group, |
|
|
'total_groups': len(current_test_session['groups']), |
|
|
'message': 'Grupo adicionado com sucesso' |
|
|
}) |
|
|
|
|
|
except Exception as e: |
|
|
logging.error(f"Erro ao adicionar grupo: {e}") |
|
|
return jsonify({ |
|
|
'success': False, |
|
|
'error': str(e) |
|
|
}) |
|
|
|
|
|
@app.route('/api/get_session_info') |
|
|
def get_session_info(): |
|
|
"""Retorna informações da sessão atual""" |
|
|
global current_test_session |
|
|
|
|
|
if not current_test_session: |
|
|
return jsonify({ |
|
|
'success': False, |
|
|
'error': 'Nenhuma sessão ativa' |
|
|
}) |
|
|
|
|
|
return jsonify({ |
|
|
'success': True, |
|
|
'session': current_test_session |
|
|
}) |
|
|
|
|
|
@app.route('/api/run_tests', methods=['POST']) |
|
|
def run_tests(): |
|
|
"""Executa os testes da sessão atual""" |
|
|
global test_runner, current_test_session |
|
|
|
|
|
try: |
|
|
if not current_test_session: |
|
|
return jsonify({ |
|
|
'success': False, |
|
|
'error': 'Nenhuma sessão de teste ativa' |
|
|
}) |
|
|
|
|
|
if not current_test_session['groups']: |
|
|
return jsonify({ |
|
|
'success': False, |
|
|
'error': 'Nenhum grupo de teste configurado' |
|
|
}) |
|
|
|
|
|
data = request.json |
|
|
validation_method = data.get('validation_method', 'llm') |
|
|
expected_content = data.get('expected_content', '') if validation_method == 'keyword' else None |
|
|
|
|
|
|
|
|
current_test_session['status'] = 'running' |
|
|
current_test_session['started_at'] = datetime.now().isoformat() |
|
|
|
|
|
|
|
|
test_runner = MassiveTestRunner(max_workers=5) |
|
|
|
|
|
|
|
|
def run_async_tests(): |
|
|
print(f"\n🚀 INICIANDO EXECUÇÃO DE TESTES - {datetime.now().strftime('%H:%M:%S')}") |
|
|
print(f"📊 Sessão: {current_test_session['id']}") |
|
|
print(f"❓ Pergunta: {current_test_session['question']}") |
|
|
print(f"👥 Grupos: {len(current_test_session['groups'])}") |
|
|
print(f"🔢 Total de testes: {sum(g['iterations'] for g in current_test_session['groups'])}") |
|
|
print("=" * 60) |
|
|
|
|
|
loop = asyncio.new_event_loop() |
|
|
asyncio.set_event_loop(loop) |
|
|
try: |
|
|
result = loop.run_until_complete( |
|
|
test_runner.run_test_session( |
|
|
current_test_session, |
|
|
validation_method, |
|
|
expected_content |
|
|
) |
|
|
) |
|
|
|
|
|
current_test_session['status'] = 'completed' |
|
|
current_test_session['completed_at'] = datetime.now().isoformat() |
|
|
|
|
|
print(f"\n🎉 TESTES CONCLUÍDOS COM SUCESSO - {datetime.now().strftime('%H:%M:%S')}") |
|
|
print(f"✅ Sessão: {current_test_session['id']}") |
|
|
print("=" * 60) |
|
|
|
|
|
logging.info(f"✅ Testes concluídos com sucesso: {current_test_session['id']}") |
|
|
return result |
|
|
except Exception as e: |
|
|
current_test_session['status'] = 'error' |
|
|
current_test_session['error'] = str(e) |
|
|
|
|
|
print(f"\n❌ ERRO NA EXECUÇÃO DOS TESTES - {datetime.now().strftime('%H:%M:%S')}") |
|
|
print(f"💥 Erro: {e}") |
|
|
print("=" * 60) |
|
|
|
|
|
logging.error(f"❌ Erro na execução dos testes: {e}") |
|
|
raise |
|
|
finally: |
|
|
loop.close() |
|
|
|
|
|
|
|
|
import threading |
|
|
test_thread = threading.Thread(target=run_async_tests) |
|
|
test_thread.daemon = False |
|
|
test_thread.start() |
|
|
|
|
|
return jsonify({ |
|
|
'success': True, |
|
|
'message': 'Testes iniciados com sucesso', |
|
|
'session_id': current_test_session['id'] |
|
|
}) |
|
|
|
|
|
except Exception as e: |
|
|
logging.error(f"Erro ao executar testes: {e}") |
|
|
current_test_session['status'] = 'error' |
|
|
return jsonify({ |
|
|
'success': False, |
|
|
'error': str(e) |
|
|
}) |
|
|
|
|
|
@app.route('/api/test_status') |
|
|
def get_test_status(): |
|
|
"""Retorna status atual dos testes""" |
|
|
global test_runner, current_test_session |
|
|
|
|
|
if not current_test_session: |
|
|
return jsonify({ |
|
|
'success': False, |
|
|
'error': 'Nenhuma sessão ativa' |
|
|
}) |
|
|
|
|
|
status_info = { |
|
|
'session_id': current_test_session['id'], |
|
|
'status': current_test_session.get('status', 'unknown'), |
|
|
'progress': 0, |
|
|
'current_group': None, |
|
|
'completed_tests': 0, |
|
|
'total_tests': sum(group['iterations'] for group in current_test_session['groups']), |
|
|
'estimated_remaining': None |
|
|
} |
|
|
|
|
|
if test_runner: |
|
|
try: |
|
|
runner_status = test_runner.get_status() |
|
|
status_info.update(runner_status) |
|
|
|
|
|
|
|
|
if runner_status.get('current_status') == 'completed' and current_test_session.get('status') != 'completed': |
|
|
current_test_session['status'] = 'completed' |
|
|
current_test_session['completed_at'] = datetime.now().isoformat() |
|
|
logging.info(f"🎉 Sessão {current_test_session['id']} marcada como concluída") |
|
|
|
|
|
except Exception as e: |
|
|
logging.error(f"Erro ao obter status do runner: {e}") |
|
|
status_info['error'] = str(e) |
|
|
|
|
|
|
|
|
if test_runner: |
|
|
status_info['running_tests'] = runner_status.get('running_tests_details', []) |
|
|
status_info['running_tests_count'] = runner_status.get('running_tests_count', 0) |
|
|
status_info['current_test'] = runner_status.get('current_test') |
|
|
status_info['cancelled_tests'] = len(runner_status.get('cancelled_tests', [])) |
|
|
status_info['timeout_tests'] = len(runner_status.get('timeout_tests', [])) |
|
|
|
|
|
return jsonify({ |
|
|
'success': True, |
|
|
'status': status_info |
|
|
}) |
|
|
|
|
|
@app.route('/api/cancel_test', methods=['POST']) |
|
|
def cancel_current_test(): |
|
|
"""Cancela o teste atual ou específico""" |
|
|
global test_runner |
|
|
|
|
|
if not test_runner: |
|
|
return jsonify({ |
|
|
'success': False, |
|
|
'error': 'Nenhum teste em execução' |
|
|
}) |
|
|
|
|
|
try: |
|
|
data = request.get_json() or {} |
|
|
thread_id = data.get('thread_id') |
|
|
|
|
|
cancelled = test_runner.cancel_current_test(thread_id) |
|
|
|
|
|
if cancelled: |
|
|
return jsonify({ |
|
|
'success': True, |
|
|
'message': f'Teste {"específico" if thread_id else "atual"} cancelado com sucesso', |
|
|
'cancelled_test': thread_id |
|
|
}) |
|
|
else: |
|
|
return jsonify({ |
|
|
'success': False, |
|
|
'error': 'Nenhum teste encontrado para cancelar' |
|
|
}) |
|
|
|
|
|
except Exception as e: |
|
|
return jsonify({ |
|
|
'success': False, |
|
|
'error': f'Erro ao cancelar teste: {str(e)}' |
|
|
}) |
|
|
|
|
|
@app.route('/api/cancel_all_tests', methods=['POST']) |
|
|
def cancel_all_tests(): |
|
|
"""Cancela todos os testes em execução""" |
|
|
global test_runner |
|
|
|
|
|
if not test_runner: |
|
|
return jsonify({ |
|
|
'success': False, |
|
|
'error': 'Nenhum teste em execução' |
|
|
}) |
|
|
|
|
|
try: |
|
|
cancelled_count = test_runner.cancel_all_tests() |
|
|
|
|
|
return jsonify({ |
|
|
'success': True, |
|
|
'message': f'{cancelled_count} testes cancelados', |
|
|
'cancelled_count': cancelled_count |
|
|
}) |
|
|
|
|
|
except Exception as e: |
|
|
return jsonify({ |
|
|
'success': False, |
|
|
'error': f'Erro ao cancelar testes: {str(e)}' |
|
|
}) |
|
|
|
|
|
@app.route('/api/skip_stuck_tests', methods=['POST']) |
|
|
def skip_stuck_tests(): |
|
|
"""Marca testes travados para cancelamento""" |
|
|
global test_runner |
|
|
|
|
|
if not test_runner: |
|
|
return jsonify({ |
|
|
'success': False, |
|
|
'error': 'Nenhum teste em execução' |
|
|
}) |
|
|
|
|
|
try: |
|
|
data = request.get_json() or {} |
|
|
max_duration = data.get('max_duration', 120) |
|
|
|
|
|
stuck_count = test_runner.skip_stuck_tests(max_duration) |
|
|
|
|
|
return jsonify({ |
|
|
'success': True, |
|
|
'message': f'{stuck_count} testes travados marcados para cancelamento', |
|
|
'stuck_count': stuck_count, |
|
|
'max_duration': max_duration |
|
|
}) |
|
|
|
|
|
except Exception as e: |
|
|
return jsonify({ |
|
|
'success': False, |
|
|
'error': f'Erro ao marcar testes travados: {str(e)}' |
|
|
}) |
|
|
|
|
|
@app.route('/api/test_results') |
|
|
def get_test_results(): |
|
|
"""Retorna resultados dos testes""" |
|
|
global test_runner, current_test_session |
|
|
|
|
|
if not test_runner: |
|
|
return jsonify({ |
|
|
'success': False, |
|
|
'error': 'Nenhum teste executado' |
|
|
}) |
|
|
|
|
|
if not current_test_session: |
|
|
return jsonify({ |
|
|
'success': False, |
|
|
'error': 'Nenhuma sessão ativa' |
|
|
}) |
|
|
|
|
|
try: |
|
|
results = test_runner.get_results() |
|
|
|
|
|
|
|
|
if not results or not results.get('group_results'): |
|
|
return jsonify({ |
|
|
'success': False, |
|
|
'error': 'Resultados ainda não disponíveis' |
|
|
}) |
|
|
|
|
|
logging.info(f"📊 Enviando resultados: {len(results.get('group_results', []))} grupos, {len(results.get('individual_results', []))} testes individuais") |
|
|
|
|
|
return jsonify({ |
|
|
'success': True, |
|
|
'results': results |
|
|
}) |
|
|
except Exception as e: |
|
|
logging.error(f"Erro ao obter resultados: {e}") |
|
|
return jsonify({ |
|
|
'success': False, |
|
|
'error': str(e) |
|
|
}) |
|
|
|
|
|
@app.route('/api/download_csv') |
|
|
def download_csv(): |
|
|
"""Gera e baixa relatório CSV""" |
|
|
global test_runner |
|
|
|
|
|
if not test_runner: |
|
|
return jsonify({ |
|
|
'success': False, |
|
|
'error': 'Nenhum teste executado' |
|
|
}) |
|
|
|
|
|
try: |
|
|
|
|
|
report_generator = ReportGenerator() |
|
|
csv_path = report_generator.generate_csv_report(test_runner.get_results()) |
|
|
|
|
|
return send_file( |
|
|
csv_path, |
|
|
as_attachment=True, |
|
|
download_name=f'teste_agentgraph_{datetime.now().strftime("%Y%m%d_%H%M%S")}.csv' |
|
|
) |
|
|
except Exception as e: |
|
|
logging.error(f"Erro ao gerar CSV: {e}") |
|
|
return jsonify({ |
|
|
'success': False, |
|
|
'error': str(e) |
|
|
}) |
|
|
|
|
|
if __name__ == '__main__': |
|
|
print("🧪 Sistema de Testes Massivos - AgentGraph") |
|
|
print("=" * 50) |
|
|
print("🌐 Acesse: http://localhost:5001") |
|
|
print("📊 Interface de testes disponível") |
|
|
print("=" * 50) |
|
|
|
|
|
app.run( |
|
|
host='0.0.0.0', |
|
|
port=5001, |
|
|
debug=True, |
|
|
threaded=True |
|
|
) |
|
|
|