Skip to content

Commit 7a4de43

Browse files
Align new logging approach with current implementation in MCM 1
1 parent a77e503 commit 7a4de43

File tree

7 files changed

+1286
-10
lines changed

7 files changed

+1286
-10
lines changed

tests/validation/Engine/README.md

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
# CSV and HTML Reporting for Media Communications Mesh Test Framework
2+
3+
This directory contains utilities for generating CSV and HTML reports from test results.
4+
5+
## Overview
6+
7+
The reporting framework consists of the following components:
8+
9+
1. `csv_report.py` - Core functionality for recording test results in CSV format
10+
2. `logging_utils.py` - Helper functions and decorators for logging
11+
3. `output_validator.py` - Enhanced output validation with reporting integration
12+
4. `html_report.py` - HTML report generation from CSV data
13+
5. `pytest_hooks.py` - Pytest hooks for automatic report generation
14+
15+
## Usage
16+
17+
### Basic Usage
18+
19+
The reporting framework is automatically enabled when running tests. Test results will be recorded in CSV format, and an HTML report will be generated at the end of the test run.
20+
21+
```bash
22+
pytest -v tests/validation
23+
```
24+
25+
### Logging Test Results
26+
27+
To manually log test results from test functions, use the decorators from `logging_utils.py`:
28+
29+
```python
30+
from Engine.logging_utils import log_ffmpeg_test, log_media_proxy_test, log_mesh_agent_test, log_rxtx_app_test
31+
32+
@log_ffmpeg_test(test_name="test_ffmpeg_encoding", test_description="Test FFmpeg encoding performance")
33+
def test_ffmpeg_encoding():
34+
# Your test code here
35+
pass
36+
```
37+
38+
### Output Validation
39+
40+
For validating component output and logging results:
41+
42+
```python
43+
from Engine.output_validator import output_validator
44+
45+
def test_media_proxy():
46+
# Run media proxy
47+
# ...
48+
49+
# Validate output
50+
result = output_validator.validate_media_proxy(
51+
output=output_text,
52+
test_name="test_media_proxy",
53+
duration=execution_time
54+
)
55+
56+
assert result["is_pass"], f"Media proxy test failed: {result['errors']}"
57+
```
58+
59+
### Command Line Options
60+
61+
Additional command line options are available:
62+
63+
```bash
64+
pytest -v tests/validation --timestamp # Add timestamp to log directory
65+
```
66+
67+
## Report Files
68+
69+
The following report files are generated:
70+
71+
- `test_report.csv` - CSV file containing test results
72+
- `compliance_report.csv` - CSV file containing compliance results
73+
- `test_report.html` - HTML report with interactive filtering and visualization
74+
75+
## API Reference
76+
77+
### csv_report.py
78+
79+
- `initialize_csv_report(log_dir)` - Initialize CSV report files
80+
- `csv_add_test(test_name, test_description, component, result, duration, error_message)` - Add a test result
81+
- `update_compliance_result(requirement_id, is_compliant)` - Update compliance result
82+
- `csv_write_report(log_dir)` - Write test results and compliance results to CSV files
83+
- `get_test_summary()` - Get summary of test results
84+
- `get_component_summary()` - Get summary of results by component
85+
- `get_compliance_summary()` - Get summary of compliance results
86+
87+
### logging_utils.py
88+
89+
- `log_test_result(component, test_name, test_description, error_message)` - Decorator for logging test results
90+
- `log_ffmpeg_test(test_name, test_description, error_message)` - FFmpeg test decorator
91+
- `log_media_proxy_test(test_name, test_description, error_message)` - Media Proxy test decorator
92+
- `log_mesh_agent_test(test_name, test_description, error_message)` - Mesh Agent test decorator
93+
- `log_rxtx_app_test(test_name, test_description, error_message)` - RxTx App test decorator
94+
- `parse_output_for_errors(component, output, custom_error_keywords)` - Parse output for errors
95+
- `streamline_component_output_validation(component, output, test_name, duration, custom_error_keywords)` - Validate component output
96+
97+
### output_validator.py
98+
99+
- `OutputValidator` - Class for validating component output and logging results
100+
- `output_validator` - Singleton instance of OutputValidator
101+
- `set_output_validator_log_path(log_path)` - Set log path for the validator
102+
103+
### html_report.py
104+
105+
- `generate_html_report(log_dir)` - Generate HTML report from CSV data
Lines changed: 216 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,216 @@
1+
#!/usr/bin/env python3
2+
# SPDX-License-Identifier: BSD-3-Clause
3+
# Copyright 2024-2025 Intel Corporation
4+
# Media Communications Mesh
5+
6+
import csv
7+
import logging
8+
import os
9+
from datetime import datetime
10+
from pathlib import Path
11+
from typing import Dict, List, Optional
12+
13+
logger = logging.getLogger(__name__)
14+
15+
# Constants
16+
CSV_REPORT_FILE = "test_report.csv"
17+
COMPLIANCE_REPORT_FILE = "compliance_report.csv"
18+
TEST_RESULT_COLUMN = "Test Result"
19+
TEST_NAME_COLUMN = "Test Name"
20+
TEST_DESC_COLUMN = "Test Description"
21+
TEST_TIME_COLUMN = "Test Time"
22+
TEST_DURATION_COLUMN = "Test Duration"
23+
TEST_ERROR_COLUMN = "Error Message"
24+
COMPONENT_COLUMN = "Component"
25+
26+
# Components
27+
COMPONENT_FFMPEG = "FFmpeg"
28+
COMPONENT_MEDIA_PROXY = "Media Proxy"
29+
COMPONENT_MESH_AGENT = "Mesh Agent"
30+
COMPONENT_RXTX_APP = "RxTx App"
31+
32+
# Standard column headers
33+
CSV_HEADERS = [
34+
TEST_NAME_COLUMN,
35+
TEST_DESC_COLUMN,
36+
COMPONENT_COLUMN,
37+
TEST_RESULT_COLUMN,
38+
TEST_TIME_COLUMN,
39+
TEST_DURATION_COLUMN,
40+
TEST_ERROR_COLUMN,
41+
]
42+
43+
# Global storage for test results
44+
_test_results = []
45+
_compliance_results = {}
46+
47+
def initialize_csv_report(log_dir: Optional[str] = None) -> None:
48+
"""
49+
Initialize CSV report files.
50+
51+
Args:
52+
log_dir: Directory where report files will be stored
53+
"""
54+
global _test_results, _compliance_results
55+
_test_results = []
56+
_compliance_results = {}
57+
58+
# Create log directory if it doesn't exist
59+
if log_dir:
60+
Path(log_dir).mkdir(parents=True, exist_ok=True)
61+
62+
63+
def csv_add_test(
64+
test_name: str,
65+
test_description: str,
66+
component: str,
67+
result: bool,
68+
duration: float,
69+
error_message: str = "",
70+
) -> None:
71+
"""
72+
Add a test result to the CSV report.
73+
74+
Args:
75+
test_name: Name of the test
76+
test_description: Description of the test
77+
component: Component being tested (FFmpeg, Media Proxy, Mesh Agent, RxTx App)
78+
result: True if test passed, False if test failed
79+
duration: Test duration in seconds
80+
error_message: Error message if test failed
81+
"""
82+
global _test_results
83+
84+
test_result = "PASS" if result else "FAIL"
85+
test_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
86+
87+
_test_results.append({
88+
TEST_NAME_COLUMN: test_name,
89+
TEST_DESC_COLUMN: test_description,
90+
COMPONENT_COLUMN: component,
91+
TEST_RESULT_COLUMN: test_result,
92+
TEST_TIME_COLUMN: test_time,
93+
TEST_DURATION_COLUMN: f"{duration:.2f}s",
94+
TEST_ERROR_COLUMN: error_message
95+
})
96+
97+
logger.info(f"Added test result: {test_name} - {test_result}")
98+
99+
100+
def update_compliance_result(requirement_id: str, is_compliant: bool) -> None:
101+
"""
102+
Update compliance result for a specific requirement.
103+
104+
Args:
105+
requirement_id: ID of the requirement
106+
is_compliant: True if compliant, False if not
107+
"""
108+
global _compliance_results
109+
110+
_compliance_results[requirement_id] = is_compliant
111+
logger.info(f"Updated compliance for {requirement_id}: {'COMPLIANT' if is_compliant else 'NON-COMPLIANT'}")
112+
113+
114+
def csv_write_report(log_dir: str = ".") -> None:
115+
"""
116+
Write test results and compliance results to CSV files.
117+
118+
Args:
119+
log_dir: Directory where report files will be stored
120+
"""
121+
report_path = Path(log_dir) / CSV_REPORT_FILE
122+
compliance_path = Path(log_dir) / COMPLIANCE_REPORT_FILE
123+
124+
# Write test results
125+
if _test_results:
126+
try:
127+
with open(report_path, 'w', newline='') as csvfile:
128+
writer = csv.DictWriter(csvfile, fieldnames=CSV_HEADERS)
129+
writer.writeheader()
130+
for result in _test_results:
131+
writer.writerow(result)
132+
logger.info(f"Test report written to {report_path}")
133+
except Exception as e:
134+
logger.error(f"Failed to write test report: {e}")
135+
136+
# Write compliance results
137+
if _compliance_results:
138+
try:
139+
with open(compliance_path, 'w', newline='') as csvfile:
140+
writer = csv.DictWriter(csvfile, fieldnames=['Requirement ID', 'Status'])
141+
writer.writeheader()
142+
for req_id, is_compliant in _compliance_results.items():
143+
writer.writerow({
144+
'Requirement ID': req_id,
145+
'Status': 'COMPLIANT' if is_compliant else 'NON-COMPLIANT'
146+
})
147+
logger.info(f"Compliance report written to {compliance_path}")
148+
except Exception as e:
149+
logger.error(f"Failed to write compliance report: {e}")
150+
151+
152+
def get_test_summary() -> Dict[str, int | str]:
153+
"""
154+
Get a summary of test results.
155+
156+
Returns:
157+
Dictionary with total, passed, and failed test counts and pass rate
158+
"""
159+
pass_count = sum(1 for r in _test_results if r[TEST_RESULT_COLUMN] == "PASS")
160+
fail_count = sum(1 for r in _test_results if r[TEST_RESULT_COLUMN] == "FAIL")
161+
total_count = len(_test_results)
162+
163+
return {
164+
"total": total_count,
165+
"passed": pass_count,
166+
"failed": fail_count,
167+
"pass_rate": f"{(pass_count / total_count * 100):.1f}%" if total_count > 0 else "N/A"
168+
}
169+
170+
171+
def get_component_summary() -> Dict[str, Dict[str, int | str]]:
172+
"""
173+
Get a summary of test results by component.
174+
175+
Returns:
176+
Dictionary with component-wise test summaries
177+
"""
178+
components = {}
179+
180+
for result in _test_results:
181+
component = result[COMPONENT_COLUMN]
182+
if component not in components:
183+
components[component] = {"total": 0, "passed": 0, "failed": 0}
184+
185+
components[component]["total"] += 1
186+
if result[TEST_RESULT_COLUMN] == "PASS":
187+
components[component]["passed"] += 1
188+
else:
189+
components[component]["failed"] += 1
190+
191+
# Add pass rate
192+
for component in components:
193+
total = components[component]["total"]
194+
passed = components[component]["passed"]
195+
components[component]["pass_rate"] = f"{(passed / total * 100):.1f}%" if total > 0 else "N/A"
196+
197+
return components
198+
199+
200+
def get_compliance_summary() -> Dict[str, int | str]:
201+
"""
202+
Get a summary of compliance results.
203+
204+
Returns:
205+
Dictionary with total, compliant, and non-compliant counts and compliance rate
206+
"""
207+
compliant_count = sum(1 for v in _compliance_results.values() if v)
208+
non_compliant_count = sum(1 for v in _compliance_results.values() if not v)
209+
total_count = len(_compliance_results)
210+
211+
return {
212+
"total": total_count,
213+
"compliant": compliant_count,
214+
"non_compliant": non_compliant_count,
215+
"compliance_rate": f"{(compliant_count / total_count * 100):.1f}%" if total_count > 0 else "N/A"
216+
}

0 commit comments

Comments
 (0)