#!/usr/bin/env python3 """ Unit tests for the timing module. Tests TimingReport, TimingContext, and related utilities. """ import sys import time import json import tempfile import os from pathlib import Path # Add src to path for imports sys.path.insert(0, str(Path(__file__).parent.parent / "src")) from timing import ( TimingReport, TimingEntry, get_timing_report, reset_timing_report, time_step ) class TestTimingEntry: """Tests for TimingEntry dataclass.""" def test_to_dict(self): """Test conversion to dictionary.""" entry = TimingEntry(name="test_step", duration=1.5, parent="parent_step") d = entry.to_dict() assert d["name"] == "test_step" assert d["duration_seconds"] == 1.5 assert d["parent"] == "parent_step" class TestTimingReport: """Tests for TimingReport class.""" def test_basic_timing(self): """Test basic timing functionality.""" report = TimingReport() report.start() with report.time("Step 1"): time.sleep(0.1) report.stop() assert "Step 1" in report.entries assert report.entries["Step 1"].duration >= 0.1 assert report.total_duration >= 0.1 def test_nested_timing(self): """Test hierarchical timing with parent.""" report = TimingReport() report.start() with report.time("Parent Step"): time.sleep(0.05) with report.time("Child Step", parent="Parent Step"): time.sleep(0.05) report.stop() assert "Parent Step" in report.entries assert "Child Step" in report.entries assert report.entries["Child Step"].parent == "Parent Step" def test_format_duration(self): """Test duration formatting.""" assert TimingReport._format_duration(30) == "30.00s" assert TimingReport._format_duration(90) == "1m 30.0s" assert TimingReport._format_duration(3661) == "1h 1m 1s" def test_format_summary(self): """Test summary generation.""" report = TimingReport() report.start() with report.time("Step 1"): time.sleep(0.01) with report.time("Step 2"): time.sleep(0.01) report.stop() summary = report.format_summary() assert "PIPELINE TIMING REPORT" in summary assert "Step 1" in summary assert "Step 2" in summary assert "Total Pipeline Time" in summary def test_to_dict(self): """Test conversion to dictionary.""" report = TimingReport() report.start() with report.time("Step 1"): time.sleep(0.01) report.stop() d = report.to_dict() assert "total_duration_seconds" in d assert "total_duration_formatted" in d assert "entries" in d assert len(d["entries"]) == 1 assert d["entries"][0]["name"] == "Step 1" def test_save_json(self): """Test saving to JSON file.""" report = TimingReport() report.start() with report.time("Step 1"): time.sleep(0.01) report.stop() with tempfile.TemporaryDirectory() as tmpdir: filepath = os.path.join(tmpdir, "timing.json") report.save_json(filepath) assert os.path.exists(filepath) with open(filepath, 'r') as f: data = json.load(f) assert "total_duration_seconds" in data assert len(data["entries"]) == 1 def test_save_csv(self): """Test saving to CSV file.""" report = TimingReport() report.start() with report.time("Step 1"): time.sleep(0.01) report.stop() with tempfile.TemporaryDirectory() as tmpdir: filepath = os.path.join(tmpdir, "timing.csv") report.save_csv(filepath) assert os.path.exists(filepath) with open(filepath, 'r') as f: lines = f.readlines() assert len(lines) >= 2 # Header + at least 1 data row assert "step,parent,duration_seconds" in lines[0] def test_add_entry_manually(self): """Test manually adding entries.""" report = TimingReport() report.add_entry("Manual Step", duration=5.0, parent=None) assert "Manual Step" in report.entries assert report.entries["Manual Step"].duration == 5.0 class TestGlobalReport: """Tests for global timing report functions.""" def test_get_timing_report(self): """Test getting global report.""" reset_timing_report() report1 = get_timing_report() report2 = get_timing_report() assert report1 is report2 def test_reset_timing_report(self): """Test resetting global report.""" reset_timing_report() report1 = get_timing_report() reset_timing_report() report2 = get_timing_report() assert report1 is not report2 def test_time_step_convenience(self): """Test time_step convenience function.""" reset_timing_report() report = get_timing_report() with time_step("Convenience Step"): time.sleep(0.01) assert "Convenience Step" in report.entries def run_tests(): """Run all tests and print results.""" import traceback test_classes = [TestTimingEntry, TestTimingReport, TestGlobalReport] passed = 0 failed = 0 print("=" * 60) print("TIMING MODULE UNIT TESTS") print("=" * 60) for test_class in test_classes: print(f"\n{test_class.__name__}:") instance = test_class() for method_name in dir(instance): if method_name.startswith("test_"): try: method = getattr(instance, method_name) method() print(f" ✓ {method_name}") passed += 1 except Exception as e: print(f" ✗ {method_name}: {e}") traceback.print_exc() failed += 1 print("\n" + "=" * 60) print(f"RESULTS: {passed} passed, {failed} failed") print("=" * 60) return failed == 0 if __name__ == "__main__": success = run_tests() sys.exit(0 if success else 1)