2026-06-26 11:50:10 +03:00
|
|
|
"""Parse Allure test-case JSON and metric attachments."""
|
|
|
|
|
|
|
|
|
|
from __future__ import annotations
|
|
|
|
|
|
|
|
|
|
import json
|
|
|
|
|
from pathlib import Path
|
|
|
|
|
from typing import Dict, List, Optional, Tuple
|
|
|
|
|
|
|
|
|
|
from benchmark_config import BenchmarkConfig, ChartTest
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def attachment_path(benchmark_dir: Path, source: str) -> Path:
|
|
|
|
|
path = benchmark_dir / 'attachments' / source
|
|
|
|
|
if not path.exists():
|
|
|
|
|
path = benchmark_dir / 'data' / 'attachments' / source
|
|
|
|
|
return path
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def parse_metric_attachment(attachment_file: Path, metric_keyword: str) -> Dict:
|
|
|
|
|
if not attachment_file.exists():
|
|
|
|
|
return {}
|
|
|
|
|
|
|
|
|
|
keyword = metric_keyword.lower()
|
|
|
|
|
try:
|
|
|
|
|
run_values = []
|
|
|
|
|
avg_value = None
|
|
|
|
|
for line in attachment_file.read_text(encoding='utf-8').split('\n'):
|
|
|
|
|
line_lower = line.lower()
|
|
|
|
|
if keyword not in line_lower:
|
|
|
|
|
continue
|
|
|
|
|
parts = line.split(':')
|
|
|
|
|
if len(parts) <= 1:
|
|
|
|
|
continue
|
|
|
|
|
try:
|
|
|
|
|
value = float(parts[1].strip().split()[0])
|
|
|
|
|
except ValueError:
|
|
|
|
|
continue
|
|
|
|
|
if 'average' in line_lower:
|
|
|
|
|
avg_value = value
|
|
|
|
|
else:
|
|
|
|
|
run_values.append(value)
|
|
|
|
|
|
|
|
|
|
if not run_values:
|
|
|
|
|
return {}
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
'min_value': min(run_values),
|
|
|
|
|
'max_value': max(run_values),
|
|
|
|
|
'avg_value': avg_value if avg_value is not None else sum(run_values) / len(run_values),
|
|
|
|
|
'run_count': len(run_values),
|
|
|
|
|
'all_runs': ','.join(map(str, run_values)),
|
|
|
|
|
}
|
|
|
|
|
except Exception as error:
|
|
|
|
|
print(f'Warning: Failed to parse {metric_keyword} attachment {attachment_file}: {error}')
|
|
|
|
|
return {}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def find_attachment_source(test_data: Dict, keyword: str) -> Optional[str]:
|
|
|
|
|
keyword = keyword.lower()
|
|
|
|
|
|
2026-07-17 23:35:28 +03:00
|
|
|
def find_in_stage(stage: Dict) -> Optional[str]:
|
|
|
|
|
for attachment in stage.get('attachments', []):
|
2026-06-26 11:50:10 +03:00
|
|
|
if keyword in attachment.get('name', '').lower():
|
|
|
|
|
source = attachment.get('source', '')
|
|
|
|
|
if source:
|
|
|
|
|
return source
|
|
|
|
|
|
2026-07-17 23:35:28 +03:00
|
|
|
for step in stage.get('steps', []):
|
|
|
|
|
source = find_in_stage(step)
|
|
|
|
|
if source:
|
|
|
|
|
return source
|
|
|
|
|
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
return find_in_stage(test_data.get('testStage', {}))
|
2026-06-26 11:50:10 +03:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def _load_time_row(test_name: str, status: str, metric_data: Dict) -> Dict:
|
|
|
|
|
return {
|
|
|
|
|
'test_name': test_name,
|
|
|
|
|
'status': status,
|
|
|
|
|
'min_time': metric_data['min_value'],
|
|
|
|
|
'max_time': metric_data['max_value'],
|
|
|
|
|
'avg_time': metric_data['avg_value'],
|
|
|
|
|
'run_count': metric_data['run_count'],
|
|
|
|
|
'all_runs': metric_data['all_runs'],
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _resource_row(chart: ChartTest, test_name: str, status: str, metric_data: Dict) -> Dict:
|
|
|
|
|
return {
|
|
|
|
|
'test_name': test_name,
|
|
|
|
|
'status': status,
|
|
|
|
|
'metric_id': chart.test_id,
|
|
|
|
|
**metric_data,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2026-07-17 19:14:11 +03:00
|
|
|
def _attachment_keyword_for_test(chart: ChartTest, test_name: str) -> str:
|
|
|
|
|
for index, pattern in enumerate(chart.historical_patterns):
|
|
|
|
|
if pattern not in test_name:
|
|
|
|
|
continue
|
|
|
|
|
if index < len(chart.historical_attachment_keywords):
|
|
|
|
|
return chart.historical_attachment_keywords[index]
|
|
|
|
|
break
|
|
|
|
|
return chart.attachment_keyword
|
|
|
|
|
|
|
|
|
|
|
2026-06-26 11:50:10 +03:00
|
|
|
def parse_test_case_json(
|
|
|
|
|
json_file: Path,
|
|
|
|
|
benchmark_dir: Path,
|
|
|
|
|
config: BenchmarkConfig,
|
2026-07-17 19:14:11 +03:00
|
|
|
) -> Tuple[Dict, List[Dict], List[Dict], List[Dict]]:
|
2026-06-26 11:50:10 +03:00
|
|
|
data = json.loads(json_file.read_text(encoding='utf-8'))
|
|
|
|
|
|
|
|
|
|
test_result = {
|
|
|
|
|
'test_name': data.get('name', ''),
|
|
|
|
|
'status': data.get('status', 'unknown'),
|
|
|
|
|
'duration_ms': data.get('time', {}).get('duration', 0),
|
|
|
|
|
'retries_count': data.get('retriesCount', 0),
|
|
|
|
|
'flaky': data.get('flaky', False),
|
|
|
|
|
}
|
|
|
|
|
test_name = test_result['test_name']
|
2026-07-17 19:14:11 +03:00
|
|
|
performance_results: List[Dict] = []
|
2026-06-26 11:50:10 +03:00
|
|
|
cpu_results: List[Dict] = []
|
|
|
|
|
ram_results: List[Dict] = []
|
|
|
|
|
|
|
|
|
|
for chart in config.charts:
|
2026-07-17 19:14:11 +03:00
|
|
|
patterns = (
|
|
|
|
|
chart.source_pattern or chart.pattern,
|
|
|
|
|
*chart.historical_patterns,
|
|
|
|
|
)
|
2026-07-17 12:26:36 +03:00
|
|
|
if not any(pattern in test_name for pattern in patterns):
|
2026-06-26 11:50:10 +03:00
|
|
|
continue
|
2026-07-17 19:14:11 +03:00
|
|
|
attachment_keyword = _attachment_keyword_for_test(chart, test_name)
|
|
|
|
|
attachment_source = find_attachment_source(data, attachment_keyword)
|
2026-06-26 11:50:10 +03:00
|
|
|
if not attachment_source:
|
|
|
|
|
continue
|
|
|
|
|
metric_data = parse_metric_attachment(
|
|
|
|
|
attachment_path(benchmark_dir, attachment_source),
|
2026-07-17 19:14:11 +03:00
|
|
|
attachment_keyword,
|
2026-06-26 11:50:10 +03:00
|
|
|
)
|
|
|
|
|
if not metric_data:
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
|
|
if chart.metrics_kind == 'performance':
|
2026-07-17 19:14:11 +03:00
|
|
|
performance_results.append(
|
|
|
|
|
_load_time_row(chart.pattern, test_result['status'], metric_data)
|
|
|
|
|
)
|
2026-06-26 11:50:10 +03:00
|
|
|
elif chart.metrics_kind == 'cpu':
|
2026-07-17 19:14:11 +03:00
|
|
|
cpu_results.append(
|
|
|
|
|
_resource_row(chart, chart.pattern, test_result['status'], metric_data)
|
|
|
|
|
)
|
2026-06-26 11:50:10 +03:00
|
|
|
else:
|
2026-07-17 19:14:11 +03:00
|
|
|
ram_results.append(
|
|
|
|
|
_resource_row(chart, chart.pattern, test_result['status'], metric_data)
|
|
|
|
|
)
|
2026-06-26 11:50:10 +03:00
|
|
|
|
2026-07-17 19:14:11 +03:00
|
|
|
return test_result, performance_results, cpu_results, ram_results
|