Skip to main content

jugar_probar/
tracing_support.rs

1//! Execution Tracing (Feature 9)
2//!
3//! Comprehensive tracing of test execution for debugging.
4//!
5//! ## EXTREME TDD: Tests written FIRST per spec
6//!
7//! ## Toyota Way Application
8//!
9//! - **Genchi Genbutsu**: Go and see - trace shows actual execution
10//! - **Jidoka**: Fail-fast with detailed context
11//! - **Mieruka**: Visual representation of test flow
12
13use crate::result::ProbarResult;
14use serde::{Deserialize, Serialize};
15use std::collections::HashMap;
16use std::fs;
17use std::path::Path;
18use std::time::{Instant, SystemTime};
19use uuid::Uuid;
20
21/// Configuration for tracing
22#[derive(Debug, Clone, Serialize, Deserialize)]
23pub struct TracingConfig {
24    /// Capture screenshots during tracing
25    pub capture_screenshots: bool,
26    /// Capture network events
27    pub capture_network: bool,
28    /// Capture console output
29    pub capture_console: bool,
30    /// Capture performance metrics
31    pub capture_performance: bool,
32    /// Maximum events to store
33    pub max_events: usize,
34    /// Include timestamps
35    pub include_timestamps: bool,
36}
37
38impl Default for TracingConfig {
39    fn default() -> Self {
40        Self {
41            capture_screenshots: true,
42            capture_network: true,
43            capture_console: true,
44            capture_performance: true,
45            max_events: 10000,
46            include_timestamps: true,
47        }
48    }
49}
50
51impl TracingConfig {
52    /// Create a new config
53    #[must_use]
54    pub fn new() -> Self {
55        Self::default()
56    }
57
58    /// Enable all capture options
59    #[must_use]
60    pub const fn capture_all(mut self) -> Self {
61        self.capture_screenshots = true;
62        self.capture_network = true;
63        self.capture_console = true;
64        self.capture_performance = true;
65        self
66    }
67
68    /// Disable all capture options
69    #[must_use]
70    pub const fn capture_none(mut self) -> Self {
71        self.capture_screenshots = false;
72        self.capture_network = false;
73        self.capture_console = false;
74        self.capture_performance = false;
75        self
76    }
77
78    /// Set maximum events
79    #[must_use]
80    pub const fn with_max_events(mut self, max: usize) -> Self {
81        self.max_events = max;
82        self
83    }
84}
85
86/// A traced span (a named section of execution)
87#[derive(Debug, Clone, Serialize, Deserialize)]
88pub struct TracedSpan {
89    /// Unique span ID
90    pub id: String,
91    /// Parent span ID (if nested)
92    pub parent_id: Option<String>,
93    /// Span name
94    pub name: String,
95    /// Start timestamp (ms since trace start)
96    pub start_ms: u64,
97    /// End timestamp (ms since trace start)
98    pub end_ms: Option<u64>,
99    /// Span duration
100    pub duration_ms: Option<u64>,
101    /// Span attributes
102    pub attributes: HashMap<String, String>,
103    /// Span status
104    pub status: SpanStatus,
105}
106
107impl TracedSpan {
108    /// Create a new span
109    #[must_use]
110    pub fn new(name: &str, start_ms: u64) -> Self {
111        Self {
112            id: Uuid::new_v4().to_string(),
113            parent_id: None,
114            name: name.to_string(),
115            start_ms,
116            end_ms: None,
117            duration_ms: None,
118            attributes: HashMap::new(),
119            status: SpanStatus::Running,
120        }
121    }
122
123    /// Set parent ID
124    #[must_use]
125    pub fn with_parent(mut self, parent_id: &str) -> Self {
126        self.parent_id = Some(parent_id.to_string());
127        self
128    }
129
130    /// Add an attribute
131    pub fn add_attribute(&mut self, key: &str, value: &str) {
132        self.attributes.insert(key.to_string(), value.to_string());
133    }
134
135    /// End the span
136    pub fn end(&mut self, end_ms: u64) {
137        self.end_ms = Some(end_ms);
138        self.duration_ms = Some(end_ms.saturating_sub(self.start_ms));
139        if self.status == SpanStatus::Running {
140            self.status = SpanStatus::Ok;
141        }
142    }
143
144    /// Mark as error
145    pub fn mark_error(&mut self, message: &str) {
146        self.status = SpanStatus::Error;
147        self.add_attribute("error.message", message);
148    }
149
150    /// Check if span is complete
151    #[must_use]
152    pub const fn is_complete(&self) -> bool {
153        self.end_ms.is_some()
154    }
155}
156
157/// Status of a span
158#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
159pub enum SpanStatus {
160    /// Span is running
161    Running,
162    /// Span completed successfully
163    Ok,
164    /// Span completed with error
165    Error,
166    /// Span was cancelled
167    Cancelled,
168}
169
170/// A traced event (a point-in-time occurrence)
171#[derive(Debug, Clone, Serialize, Deserialize)]
172pub struct TracedEvent {
173    /// Event timestamp (ms since trace start)
174    pub timestamp_ms: u64,
175    /// Event name
176    pub name: String,
177    /// Event category
178    pub category: EventCategory,
179    /// Event level
180    pub level: EventLevel,
181    /// Event message
182    pub message: String,
183    /// Event attributes
184    pub attributes: HashMap<String, serde_json::Value>,
185}
186
187impl TracedEvent {
188    /// Create a new event
189    #[must_use]
190    pub fn new(name: &str, category: EventCategory, timestamp_ms: u64) -> Self {
191        Self {
192            timestamp_ms,
193            name: name.to_string(),
194            category,
195            level: EventLevel::Info,
196            message: String::new(),
197            attributes: HashMap::new(),
198        }
199    }
200
201    /// Set message
202    #[must_use]
203    pub fn with_message(mut self, message: &str) -> Self {
204        self.message = message.to_string();
205        self
206    }
207
208    /// Set level
209    #[must_use]
210    pub const fn with_level(mut self, level: EventLevel) -> Self {
211        self.level = level;
212        self
213    }
214
215    /// Add an attribute
216    pub fn add_attribute(&mut self, key: &str, value: serde_json::Value) {
217        self.attributes.insert(key.to_string(), value);
218    }
219}
220
221/// Category of traced events
222#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
223pub enum EventCategory {
224    /// Test lifecycle
225    Test,
226    /// User interaction
227    Interaction,
228    /// Network activity
229    Network,
230    /// Console output
231    Console,
232    /// Screenshot capture
233    Screenshot,
234    /// Performance metric
235    Performance,
236    /// Assertion
237    Assertion,
238    /// Custom event
239    Custom,
240}
241
242/// Level of traced events
243#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
244pub enum EventLevel {
245    /// Trace level (most verbose)
246    Trace,
247    /// Debug level
248    Debug,
249    /// Info level
250    Info,
251    /// Warning level
252    Warn,
253    /// Error level
254    Error,
255}
256
257/// Network event for tracing
258#[derive(Debug, Clone, Serialize, Deserialize)]
259pub struct NetworkEvent {
260    /// Timestamp (ms since trace start)
261    pub timestamp_ms: u64,
262    /// Request URL
263    pub url: String,
264    /// HTTP method
265    pub method: String,
266    /// Response status code
267    pub status: Option<u16>,
268    /// Request duration in ms
269    pub duration_ms: Option<u64>,
270    /// Request size in bytes
271    pub request_size: Option<u64>,
272    /// Response size in bytes
273    pub response_size: Option<u64>,
274    /// Content type
275    pub content_type: Option<String>,
276    /// Whether request failed
277    pub failed: bool,
278    /// Error message if failed
279    pub error: Option<String>,
280}
281
282impl NetworkEvent {
283    /// Create a new network event
284    #[must_use]
285    pub fn new(url: &str, method: &str, timestamp_ms: u64) -> Self {
286        Self {
287            timestamp_ms,
288            url: url.to_string(),
289            method: method.to_string(),
290            status: None,
291            duration_ms: None,
292            request_size: None,
293            response_size: None,
294            content_type: None,
295            failed: false,
296            error: None,
297        }
298    }
299
300    /// Complete the network event
301    pub fn complete(&mut self, status: u16, duration_ms: u64) {
302        self.status = Some(status);
303        self.duration_ms = Some(duration_ms);
304    }
305
306    /// Mark as failed
307    pub fn fail(&mut self, error: &str) {
308        self.failed = true;
309        self.error = Some(error.to_string());
310    }
311}
312
313/// Console message for tracing
314#[derive(Debug, Clone, Serialize, Deserialize)]
315pub struct ConsoleMessage {
316    /// Timestamp (ms since trace start)
317    pub timestamp_ms: u64,
318    /// Message level
319    pub level: ConsoleLevel,
320    /// Message text
321    pub text: String,
322    /// Source URL
323    pub source: Option<String>,
324    /// Line number
325    pub line: Option<u32>,
326}
327
328/// Console message level
329#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
330pub enum ConsoleLevel {
331    /// Log level
332    Log,
333    /// Info level
334    Info,
335    /// Warning level
336    Warn,
337    /// Error level
338    Error,
339    /// Debug level
340    Debug,
341}
342
343/// Metadata for a trace archive
344#[derive(Debug, Clone, Serialize, Deserialize)]
345pub struct TraceMetadata {
346    /// Trace ID
347    pub trace_id: String,
348    /// Test name
349    pub test_name: String,
350    /// Start time
351    pub start_time: SystemTime,
352    /// End time
353    pub end_time: Option<SystemTime>,
354    /// Total duration in ms
355    pub duration_ms: Option<u64>,
356    /// Number of spans
357    pub span_count: usize,
358    /// Number of events
359    pub event_count: usize,
360    /// Probar version
361    pub probar_version: String,
362}
363
364impl TraceMetadata {
365    /// Create new metadata
366    #[must_use]
367    pub fn new(test_name: &str) -> Self {
368        Self {
369            trace_id: Uuid::new_v4().to_string(),
370            test_name: test_name.to_string(),
371            start_time: SystemTime::now(),
372            end_time: None,
373            duration_ms: None,
374            span_count: 0,
375            event_count: 0,
376            probar_version: env!("CARGO_PKG_VERSION").to_string(),
377        }
378    }
379}
380
381/// Complete trace archive
382#[derive(Debug, Clone, Serialize, Deserialize)]
383pub struct TraceArchive {
384    /// Trace metadata
385    pub metadata: TraceMetadata,
386    /// All traced spans
387    pub spans: Vec<TracedSpan>,
388    /// All traced events
389    pub events: Vec<TracedEvent>,
390    /// Network events
391    pub network_events: Vec<NetworkEvent>,
392    /// Console messages
393    pub console_messages: Vec<ConsoleMessage>,
394}
395
396impl TraceArchive {
397    /// Create a new archive
398    #[must_use]
399    pub fn new(metadata: TraceMetadata) -> Self {
400        Self {
401            metadata,
402            spans: Vec::new(),
403            events: Vec::new(),
404            network_events: Vec::new(),
405            console_messages: Vec::new(),
406        }
407    }
408
409    /// Save archive to JSON file
410    pub fn save_json(&self, path: &Path) -> ProbarResult<()> {
411        let json = serde_json::to_string_pretty(self)?;
412
413        if let Some(parent) = path.parent() {
414            fs::create_dir_all(parent)?;
415        }
416
417        fs::write(path, json)?;
418        Ok(())
419    }
420
421    /// Load archive from JSON file
422    pub fn load_json(path: &Path) -> ProbarResult<Self> {
423        let json = fs::read_to_string(path)?;
424        let archive: TraceArchive = serde_json::from_str(&json)?;
425        Ok(archive)
426    }
427
428    /// Get spans by name
429    #[must_use]
430    pub fn spans_by_name(&self, name: &str) -> Vec<&TracedSpan> {
431        self.spans.iter().filter(|s| s.name == name).collect()
432    }
433
434    /// Get events by category
435    #[must_use]
436    pub fn events_by_category(&self, category: EventCategory) -> Vec<&TracedEvent> {
437        self.events
438            .iter()
439            .filter(|e| e.category == category)
440            .collect()
441    }
442
443    /// Get failed network requests
444    #[must_use]
445    pub fn failed_requests(&self) -> Vec<&NetworkEvent> {
446        self.network_events.iter().filter(|n| n.failed).collect()
447    }
448
449    /// Get error spans
450    #[must_use]
451    pub fn error_spans(&self) -> Vec<&TracedSpan> {
452        self.spans
453            .iter()
454            .filter(|s| s.status == SpanStatus::Error)
455            .collect()
456    }
457}
458
459/// Execution tracer
460#[derive(Debug)]
461pub struct ExecutionTracer {
462    config: TracingConfig,
463    start_time: Instant,
464    metadata: TraceMetadata,
465    spans: Vec<TracedSpan>,
466    events: Vec<TracedEvent>,
467    network_events: Vec<NetworkEvent>,
468    console_messages: Vec<ConsoleMessage>,
469    current_span_id: Option<String>,
470    running: bool,
471}
472
473impl ExecutionTracer {
474    /// Create a new execution tracer
475    #[must_use]
476    pub fn new(test_name: &str, config: TracingConfig) -> Self {
477        Self {
478            config,
479            start_time: Instant::now(),
480            metadata: TraceMetadata::new(test_name),
481            spans: Vec::new(),
482            events: Vec::new(),
483            network_events: Vec::new(),
484            console_messages: Vec::new(),
485            current_span_id: None,
486            running: false,
487        }
488    }
489
490    /// Start tracing
491    pub fn start(&mut self) {
492        self.running = true;
493        self.start_time = Instant::now();
494        self.metadata.start_time = SystemTime::now();
495    }
496
497    /// Stop tracing and return archive
498    #[must_use]
499    pub fn stop(&mut self) -> TraceArchive {
500        self.running = false;
501        self.metadata.end_time = Some(SystemTime::now());
502        self.metadata.duration_ms = Some(self.elapsed_ms());
503        self.metadata.span_count = self.spans.len();
504        self.metadata.event_count = self.events.len();
505
506        // Close any open spans
507        let end_ms = self.elapsed_ms();
508        for span in &mut self.spans {
509            if !span.is_complete() {
510                span.end(end_ms);
511                span.status = SpanStatus::Cancelled;
512            }
513        }
514
515        TraceArchive {
516            metadata: self.metadata.clone(),
517            spans: self.spans.clone(),
518            events: self.events.clone(),
519            network_events: self.network_events.clone(),
520            console_messages: self.console_messages.clone(),
521        }
522    }
523
524    /// Get elapsed time in milliseconds
525    #[must_use]
526    pub fn elapsed_ms(&self) -> u64 {
527        self.start_time.elapsed().as_millis() as u64
528    }
529
530    /// Start a new span
531    pub fn start_span(&mut self, name: &str) -> String {
532        let span = TracedSpan::new(name, self.elapsed_ms())
533            .with_parent(self.current_span_id.as_deref().unwrap_or(""));
534
535        let id = span.id.clone();
536        self.current_span_id = Some(id.clone());
537        self.spans.push(span);
538        id
539    }
540
541    /// End a span
542    pub fn end_span(&mut self, span_id: &str) {
543        let end_ms = self.elapsed_ms();
544        if let Some(span) = self.spans.iter_mut().find(|s| s.id == span_id) {
545            span.end(end_ms);
546
547            // Restore parent as current
548            if let Some(parent_id) = &span.parent_id {
549                if !parent_id.is_empty() {
550                    self.current_span_id = Some(parent_id.clone());
551                } else {
552                    self.current_span_id = None;
553                }
554            }
555        }
556    }
557
558    /// Mark a span as error
559    pub fn error_span(&mut self, span_id: &str, message: &str) {
560        if let Some(span) = self.spans.iter_mut().find(|s| s.id == span_id) {
561            span.mark_error(message);
562        }
563    }
564
565    /// Record an event
566    pub fn record_event(&mut self, event: TracedEvent) {
567        if self.events.len() < self.config.max_events {
568            self.events.push(event);
569        }
570    }
571
572    /// Record a network event
573    pub fn record_network(&mut self, event: NetworkEvent) {
574        if self.config.capture_network && self.network_events.len() < self.config.max_events {
575            self.network_events.push(event);
576        }
577    }
578
579    /// Record a console message
580    pub fn record_console(&mut self, message: ConsoleMessage) {
581        if self.config.capture_console && self.console_messages.len() < self.config.max_events {
582            self.console_messages.push(message);
583        }
584    }
585
586    /// Log an info event
587    pub fn info(&mut self, name: &str, message: &str) {
588        let event = TracedEvent::new(name, EventCategory::Custom, self.elapsed_ms())
589            .with_message(message)
590            .with_level(EventLevel::Info);
591        self.record_event(event);
592    }
593
594    /// Log a warning event
595    pub fn warn(&mut self, name: &str, message: &str) {
596        let event = TracedEvent::new(name, EventCategory::Custom, self.elapsed_ms())
597            .with_message(message)
598            .with_level(EventLevel::Warn);
599        self.record_event(event);
600    }
601
602    /// Log an error event
603    pub fn error(&mut self, name: &str, message: &str) {
604        let event = TracedEvent::new(name, EventCategory::Custom, self.elapsed_ms())
605            .with_message(message)
606            .with_level(EventLevel::Error);
607        self.record_event(event);
608    }
609
610    /// Check if tracing is running
611    #[must_use]
612    pub const fn is_running(&self) -> bool {
613        self.running
614    }
615
616    /// Get current span count
617    #[must_use]
618    pub fn span_count(&self) -> usize {
619        self.spans.len()
620    }
621
622    /// Get current event count
623    #[must_use]
624    pub fn event_count(&self) -> usize {
625        self.events.len()
626    }
627}
628
629#[cfg(test)]
630#[allow(clippy::unwrap_used, clippy::expect_used)]
631mod tests {
632    use super::*;
633
634    mod tracing_config_tests {
635        use super::*;
636
637        #[test]
638        fn test_default() {
639            let config = TracingConfig::default();
640            assert!(config.capture_screenshots);
641            assert!(config.capture_network);
642            assert!(config.capture_console);
643            assert!(config.capture_performance);
644        }
645
646        #[test]
647        fn test_capture_all() {
648            let config = TracingConfig::new().capture_none().capture_all();
649            assert!(config.capture_screenshots);
650            assert!(config.capture_network);
651        }
652
653        #[test]
654        fn test_capture_none() {
655            let config = TracingConfig::new().capture_none();
656            assert!(!config.capture_screenshots);
657            assert!(!config.capture_network);
658            assert!(!config.capture_console);
659            assert!(!config.capture_performance);
660        }
661
662        #[test]
663        fn test_with_max_events() {
664            let config = TracingConfig::new().with_max_events(5000);
665            assert_eq!(config.max_events, 5000);
666        }
667    }
668
669    mod traced_span_tests {
670        use super::*;
671
672        #[test]
673        fn test_new() {
674            let span = TracedSpan::new("test_span", 100);
675            assert_eq!(span.name, "test_span");
676            assert_eq!(span.start_ms, 100);
677            assert!(span.parent_id.is_none());
678            assert_eq!(span.status, SpanStatus::Running);
679        }
680
681        #[test]
682        fn test_with_parent() {
683            let span = TracedSpan::new("child", 100).with_parent("parent_id");
684            assert_eq!(span.parent_id, Some("parent_id".to_string()));
685        }
686
687        #[test]
688        fn test_add_attribute() {
689            let mut span = TracedSpan::new("test", 0);
690            span.add_attribute("key", "value");
691            assert_eq!(span.attributes.get("key"), Some(&"value".to_string()));
692        }
693
694        #[test]
695        fn test_end() {
696            let mut span = TracedSpan::new("test", 100);
697            span.end(200);
698
699            assert_eq!(span.end_ms, Some(200));
700            assert_eq!(span.duration_ms, Some(100));
701            assert_eq!(span.status, SpanStatus::Ok);
702            assert!(span.is_complete());
703        }
704
705        #[test]
706        fn test_mark_error() {
707            let mut span = TracedSpan::new("test", 0);
708            span.mark_error("Something went wrong");
709
710            assert_eq!(span.status, SpanStatus::Error);
711            assert_eq!(
712                span.attributes.get("error.message"),
713                Some(&"Something went wrong".to_string())
714            );
715        }
716    }
717
718    mod traced_event_tests {
719        use super::*;
720
721        #[test]
722        fn test_new() {
723            let event = TracedEvent::new("click", EventCategory::Interaction, 500);
724            assert_eq!(event.name, "click");
725            assert_eq!(event.category, EventCategory::Interaction);
726            assert_eq!(event.timestamp_ms, 500);
727        }
728
729        #[test]
730        fn test_with_message() {
731            let event =
732                TracedEvent::new("test", EventCategory::Test, 0).with_message("Test started");
733            assert_eq!(event.message, "Test started");
734        }
735
736        #[test]
737        fn test_with_level() {
738            let event =
739                TracedEvent::new("test", EventCategory::Test, 0).with_level(EventLevel::Error);
740            assert_eq!(event.level, EventLevel::Error);
741        }
742
743        #[test]
744        fn test_add_attribute() {
745            let mut event = TracedEvent::new("test", EventCategory::Test, 0);
746            event.add_attribute("count", serde_json::json!(42));
747            assert_eq!(event.attributes.get("count"), Some(&serde_json::json!(42)));
748        }
749    }
750
751    mod network_event_tests {
752        use super::*;
753
754        #[test]
755        fn test_new() {
756            let event = NetworkEvent::new("https://example.com", "GET", 1000);
757            assert_eq!(event.url, "https://example.com");
758            assert_eq!(event.method, "GET");
759            assert_eq!(event.timestamp_ms, 1000);
760            assert!(!event.failed);
761        }
762
763        #[test]
764        fn test_complete() {
765            let mut event = NetworkEvent::new("https://example.com", "GET", 1000);
766            event.complete(200, 150);
767
768            assert_eq!(event.status, Some(200));
769            assert_eq!(event.duration_ms, Some(150));
770        }
771
772        #[test]
773        fn test_fail() {
774            let mut event = NetworkEvent::new("https://example.com", "GET", 1000);
775            event.fail("Connection timeout");
776
777            assert!(event.failed);
778            assert_eq!(event.error, Some("Connection timeout".to_string()));
779        }
780    }
781
782    mod trace_archive_tests {
783        use super::*;
784        use tempfile::TempDir;
785
786        #[test]
787        fn test_new() {
788            let metadata = TraceMetadata::new("test");
789            let archive = TraceArchive::new(metadata);
790
791            assert!(archive.spans.is_empty());
792            assert!(archive.events.is_empty());
793        }
794
795        #[test]
796        fn test_spans_by_name() {
797            let mut archive = TraceArchive::new(TraceMetadata::new("test"));
798            archive.spans.push(TracedSpan::new("click", 0));
799            archive.spans.push(TracedSpan::new("type", 100));
800            archive.spans.push(TracedSpan::new("click", 200));
801
802            let clicks = archive.spans_by_name("click");
803            assert_eq!(clicks.len(), 2);
804        }
805
806        #[test]
807        fn test_events_by_category() {
808            let mut archive = TraceArchive::new(TraceMetadata::new("test"));
809            archive
810                .events
811                .push(TracedEvent::new("e1", EventCategory::Network, 0));
812            archive
813                .events
814                .push(TracedEvent::new("e2", EventCategory::Console, 100));
815            archive
816                .events
817                .push(TracedEvent::new("e3", EventCategory::Network, 200));
818
819            let network = archive.events_by_category(EventCategory::Network);
820            assert_eq!(network.len(), 2);
821        }
822
823        #[test]
824        fn test_failed_requests() {
825            let mut archive = TraceArchive::new(TraceMetadata::new("test"));
826
827            let mut success = NetworkEvent::new("https://example.com", "GET", 0);
828            success.complete(200, 100);
829            archive.network_events.push(success);
830
831            let mut failure = NetworkEvent::new("https://error.com", "GET", 100);
832            failure.fail("404");
833            archive.network_events.push(failure);
834
835            let failed = archive.failed_requests();
836            assert_eq!(failed.len(), 1);
837        }
838
839        #[test]
840        fn test_error_spans() {
841            let mut archive = TraceArchive::new(TraceMetadata::new("test"));
842
843            let mut ok_span = TracedSpan::new("ok", 0);
844            ok_span.end(100);
845            archive.spans.push(ok_span);
846
847            let mut error_span = TracedSpan::new("error", 100);
848            error_span.mark_error("Failed");
849            archive.spans.push(error_span);
850
851            let errors = archive.error_spans();
852            assert_eq!(errors.len(), 1);
853        }
854
855        #[test]
856        fn test_save_and_load() {
857            let temp_dir = TempDir::new().unwrap();
858            let path = temp_dir.path().join("trace.json");
859
860            let mut archive = TraceArchive::new(TraceMetadata::new("test"));
861            archive.spans.push(TracedSpan::new("span1", 0));
862            archive
863                .events
864                .push(TracedEvent::new("event1", EventCategory::Test, 0));
865
866            archive.save_json(&path).unwrap();
867            assert!(path.exists());
868
869            let loaded = TraceArchive::load_json(&path).unwrap();
870            assert_eq!(loaded.spans.len(), 1);
871            assert_eq!(loaded.events.len(), 1);
872        }
873    }
874
875    mod execution_tracer_tests {
876        use super::*;
877
878        #[test]
879        fn test_new() {
880            let tracer = ExecutionTracer::new("test", TracingConfig::default());
881            assert!(!tracer.is_running());
882            assert_eq!(tracer.span_count(), 0);
883            assert_eq!(tracer.event_count(), 0);
884        }
885
886        #[test]
887        fn test_start_stop() {
888            let mut tracer = ExecutionTracer::new("test", TracingConfig::default());
889            tracer.start();
890            assert!(tracer.is_running());
891
892            let archive = tracer.stop();
893            assert!(!tracer.is_running());
894            assert!(archive.metadata.duration_ms.is_some());
895        }
896
897        #[test]
898        fn test_start_and_end_span() {
899            let mut tracer = ExecutionTracer::new("test", TracingConfig::default());
900            tracer.start();
901
902            let span_id = tracer.start_span("my_span");
903            assert_eq!(tracer.span_count(), 1);
904
905            tracer.end_span(&span_id);
906
907            let archive = tracer.stop();
908            assert!(archive.spans[0].is_complete());
909        }
910
911        #[test]
912        fn test_nested_spans() {
913            let mut tracer = ExecutionTracer::new("test", TracingConfig::default());
914            tracer.start();
915
916            let parent_id = tracer.start_span("parent");
917            let child_id = tracer.start_span("child");
918
919            tracer.end_span(&child_id);
920            tracer.end_span(&parent_id);
921
922            let archive = tracer.stop();
923            assert_eq!(archive.spans.len(), 2);
924
925            let child = &archive.spans[1];
926            assert!(child.parent_id.is_some());
927        }
928
929        #[test]
930        fn test_info_warn_error() {
931            let mut tracer = ExecutionTracer::new("test", TracingConfig::default());
932            tracer.start();
933
934            tracer.info("test", "Info message");
935            tracer.warn("test", "Warning message");
936            tracer.error("test", "Error message");
937
938            let archive = tracer.stop();
939            assert_eq!(archive.events.len(), 3);
940            assert_eq!(archive.events[0].level, EventLevel::Info);
941            assert_eq!(archive.events[1].level, EventLevel::Warn);
942            assert_eq!(archive.events[2].level, EventLevel::Error);
943        }
944
945        #[test]
946        fn test_record_network() {
947            let mut tracer = ExecutionTracer::new("test", TracingConfig::default());
948            tracer.start();
949
950            let event = NetworkEvent::new("https://example.com", "GET", tracer.elapsed_ms());
951            tracer.record_network(event);
952
953            let archive = tracer.stop();
954            assert_eq!(archive.network_events.len(), 1);
955        }
956
957        #[test]
958        fn test_record_console() {
959            let mut tracer = ExecutionTracer::new("test", TracingConfig::default());
960            tracer.start();
961
962            let message = ConsoleMessage {
963                timestamp_ms: tracer.elapsed_ms(),
964                level: ConsoleLevel::Log,
965                text: "Hello".to_string(),
966                source: None,
967                line: None,
968            };
969            tracer.record_console(message);
970
971            let archive = tracer.stop();
972            assert_eq!(archive.console_messages.len(), 1);
973        }
974
975        #[test]
976        fn test_max_events_limit() {
977            let config = TracingConfig::default().with_max_events(3);
978            let mut tracer = ExecutionTracer::new("test", config);
979            tracer.start();
980
981            for i in 0..10 {
982                tracer.info("test", &format!("Event {}", i));
983            }
984
985            let archive = tracer.stop();
986            assert_eq!(archive.events.len(), 3);
987        }
988    }
989
990    // =========================================================================
991    // Hâ‚€ EXTREME TDD: Tracing Support Tests (G.7 P1)
992    // =========================================================================
993
994    mod h0_tracing_config_tests {
995        use super::*;
996
997        #[test]
998        fn h0_trace_01_config_default_capture_screenshots() {
999            let config = TracingConfig::default();
1000            assert!(config.capture_screenshots);
1001        }
1002
1003        #[test]
1004        fn h0_trace_02_config_default_capture_network() {
1005            let config = TracingConfig::default();
1006            assert!(config.capture_network);
1007        }
1008
1009        #[test]
1010        fn h0_trace_03_config_default_capture_console() {
1011            let config = TracingConfig::default();
1012            assert!(config.capture_console);
1013        }
1014
1015        #[test]
1016        fn h0_trace_04_config_default_capture_performance() {
1017            let config = TracingConfig::default();
1018            assert!(config.capture_performance);
1019        }
1020
1021        #[test]
1022        fn h0_trace_05_config_default_max_events() {
1023            let config = TracingConfig::default();
1024            assert_eq!(config.max_events, 10000);
1025        }
1026
1027        #[test]
1028        fn h0_trace_06_config_default_include_timestamps() {
1029            let config = TracingConfig::default();
1030            assert!(config.include_timestamps);
1031        }
1032
1033        #[test]
1034        fn h0_trace_07_config_capture_none() {
1035            let config = TracingConfig::new().capture_none();
1036            assert!(!config.capture_screenshots);
1037            assert!(!config.capture_network);
1038            assert!(!config.capture_console);
1039            assert!(!config.capture_performance);
1040        }
1041
1042        #[test]
1043        fn h0_trace_08_config_capture_all() {
1044            let config = TracingConfig::new().capture_none().capture_all();
1045            assert!(config.capture_screenshots);
1046            assert!(config.capture_network);
1047        }
1048
1049        #[test]
1050        fn h0_trace_09_config_with_max_events() {
1051            let config = TracingConfig::new().with_max_events(500);
1052            assert_eq!(config.max_events, 500);
1053        }
1054
1055        #[test]
1056        fn h0_trace_10_config_new() {
1057            let config = TracingConfig::new();
1058            assert!(config.capture_screenshots);
1059        }
1060    }
1061
1062    mod h0_span_status_tests {
1063        use super::*;
1064
1065        #[test]
1066        fn h0_trace_11_span_status_running() {
1067            let span = TracedSpan::new("test", 0);
1068            assert_eq!(span.status, SpanStatus::Running);
1069        }
1070
1071        #[test]
1072        fn h0_trace_12_span_status_ok_after_end() {
1073            let mut span = TracedSpan::new("test", 0);
1074            span.end(100);
1075            assert_eq!(span.status, SpanStatus::Ok);
1076        }
1077
1078        #[test]
1079        fn h0_trace_13_span_status_error() {
1080            let mut span = TracedSpan::new("test", 0);
1081            span.mark_error("Failed");
1082            assert_eq!(span.status, SpanStatus::Error);
1083        }
1084
1085        #[test]
1086        fn h0_trace_14_span_new_name() {
1087            let span = TracedSpan::new("my_span", 0);
1088            assert_eq!(span.name, "my_span");
1089        }
1090
1091        #[test]
1092        fn h0_trace_15_span_new_start_ms() {
1093            let span = TracedSpan::new("test", 250);
1094            assert_eq!(span.start_ms, 250);
1095        }
1096
1097        #[test]
1098        fn h0_trace_16_span_with_parent() {
1099            let span = TracedSpan::new("child", 0).with_parent("parent_123");
1100            assert_eq!(span.parent_id, Some("parent_123".to_string()));
1101        }
1102
1103        #[test]
1104        fn h0_trace_17_span_add_attribute() {
1105            let mut span = TracedSpan::new("test", 0);
1106            span.add_attribute("action", "click");
1107            assert_eq!(span.attributes.get("action"), Some(&"click".to_string()));
1108        }
1109
1110        #[test]
1111        fn h0_trace_18_span_end_duration() {
1112            let mut span = TracedSpan::new("test", 100);
1113            span.end(300);
1114            assert_eq!(span.duration_ms, Some(200));
1115        }
1116
1117        #[test]
1118        fn h0_trace_19_span_is_complete_false() {
1119            let span = TracedSpan::new("test", 0);
1120            assert!(!span.is_complete());
1121        }
1122
1123        #[test]
1124        fn h0_trace_20_span_is_complete_true() {
1125            let mut span = TracedSpan::new("test", 0);
1126            span.end(100);
1127            assert!(span.is_complete());
1128        }
1129    }
1130
1131    mod h0_traced_event_tests {
1132        use super::*;
1133
1134        #[test]
1135        fn h0_trace_21_event_new_name() {
1136            let event = TracedEvent::new("test_event", EventCategory::Test, 0);
1137            assert_eq!(event.name, "test_event");
1138        }
1139
1140        #[test]
1141        fn h0_trace_22_event_new_category() {
1142            let event = TracedEvent::new("test", EventCategory::Network, 0);
1143            assert_eq!(event.category, EventCategory::Network);
1144        }
1145
1146        #[test]
1147        fn h0_trace_23_event_new_timestamp() {
1148            let event = TracedEvent::new("test", EventCategory::Test, 500);
1149            assert_eq!(event.timestamp_ms, 500);
1150        }
1151
1152        #[test]
1153        fn h0_trace_24_event_default_level() {
1154            let event = TracedEvent::new("test", EventCategory::Test, 0);
1155            assert_eq!(event.level, EventLevel::Info);
1156        }
1157
1158        #[test]
1159        fn h0_trace_25_event_with_message() {
1160            let event =
1161                TracedEvent::new("test", EventCategory::Test, 0).with_message("Hello world");
1162            assert_eq!(event.message, "Hello world");
1163        }
1164
1165        #[test]
1166        fn h0_trace_26_event_with_level() {
1167            let event =
1168                TracedEvent::new("test", EventCategory::Test, 0).with_level(EventLevel::Error);
1169            assert_eq!(event.level, EventLevel::Error);
1170        }
1171
1172        #[test]
1173        fn h0_trace_27_event_add_attribute() {
1174            let mut event = TracedEvent::new("test", EventCategory::Test, 0);
1175            event.add_attribute("count", serde_json::json!(42));
1176            assert!(event.attributes.contains_key("count"));
1177        }
1178
1179        #[test]
1180        fn h0_trace_28_event_category_interaction() {
1181            let event = TracedEvent::new("click", EventCategory::Interaction, 0);
1182            assert_eq!(event.category, EventCategory::Interaction);
1183        }
1184
1185        #[test]
1186        fn h0_trace_29_event_category_console() {
1187            let event = TracedEvent::new("log", EventCategory::Console, 0);
1188            assert_eq!(event.category, EventCategory::Console);
1189        }
1190
1191        #[test]
1192        fn h0_trace_30_event_level_ordering() {
1193            assert!(EventLevel::Trace < EventLevel::Debug);
1194            assert!(EventLevel::Debug < EventLevel::Info);
1195            assert!(EventLevel::Info < EventLevel::Warn);
1196            assert!(EventLevel::Warn < EventLevel::Error);
1197        }
1198    }
1199
1200    mod h0_network_event_tests {
1201        use super::*;
1202
1203        #[test]
1204        fn h0_trace_31_network_event_new_url() {
1205            let event = NetworkEvent::new("https://api.example.com", "POST", 0);
1206            assert_eq!(event.url, "https://api.example.com");
1207        }
1208
1209        #[test]
1210        fn h0_trace_32_network_event_new_method() {
1211            let event = NetworkEvent::new("https://example.com", "PUT", 0);
1212            assert_eq!(event.method, "PUT");
1213        }
1214
1215        #[test]
1216        fn h0_trace_33_network_event_complete() {
1217            let mut event = NetworkEvent::new("https://example.com", "GET", 0);
1218            event.complete(201, 250);
1219            assert_eq!(event.status, Some(201));
1220            assert_eq!(event.duration_ms, Some(250));
1221        }
1222
1223        #[test]
1224        fn h0_trace_34_network_event_fail() {
1225            let mut event = NetworkEvent::new("https://example.com", "GET", 0);
1226            event.fail("Timeout");
1227            assert!(event.failed);
1228            assert_eq!(event.error, Some("Timeout".to_string()));
1229        }
1230
1231        #[test]
1232        fn h0_trace_35_network_event_not_failed_initially() {
1233            let event = NetworkEvent::new("https://example.com", "GET", 0);
1234            assert!(!event.failed);
1235        }
1236
1237        #[test]
1238        fn h0_trace_36_console_level_log() {
1239            let msg = ConsoleMessage {
1240                timestamp_ms: 0,
1241                level: ConsoleLevel::Log,
1242                text: "test".to_string(),
1243                source: None,
1244                line: None,
1245            };
1246            assert_eq!(msg.level, ConsoleLevel::Log);
1247        }
1248
1249        #[test]
1250        fn h0_trace_37_console_level_warn() {
1251            let msg = ConsoleMessage {
1252                timestamp_ms: 0,
1253                level: ConsoleLevel::Warn,
1254                text: "warning".to_string(),
1255                source: None,
1256                line: None,
1257            };
1258            assert_eq!(msg.level, ConsoleLevel::Warn);
1259        }
1260
1261        #[test]
1262        fn h0_trace_38_console_level_error() {
1263            let msg = ConsoleMessage {
1264                timestamp_ms: 0,
1265                level: ConsoleLevel::Error,
1266                text: "error".to_string(),
1267                source: None,
1268                line: None,
1269            };
1270            assert_eq!(msg.level, ConsoleLevel::Error);
1271        }
1272
1273        #[test]
1274        fn h0_trace_39_console_message_source() {
1275            let msg = ConsoleMessage {
1276                timestamp_ms: 0,
1277                level: ConsoleLevel::Log,
1278                text: "test".to_string(),
1279                source: Some("main.js".to_string()),
1280                line: Some(42),
1281            };
1282            assert_eq!(msg.source, Some("main.js".to_string()));
1283            assert_eq!(msg.line, Some(42));
1284        }
1285
1286        #[test]
1287        fn h0_trace_40_trace_metadata_new() {
1288            let metadata = TraceMetadata::new("my_test");
1289            assert_eq!(metadata.test_name, "my_test");
1290            assert!(!metadata.trace_id.is_empty());
1291        }
1292    }
1293
1294    mod h0_execution_tracer_tests {
1295        use super::*;
1296
1297        #[test]
1298        fn h0_trace_41_tracer_new_not_running() {
1299            let tracer = ExecutionTracer::new("test", TracingConfig::default());
1300            assert!(!tracer.is_running());
1301        }
1302
1303        #[test]
1304        fn h0_trace_42_tracer_start_running() {
1305            let mut tracer = ExecutionTracer::new("test", TracingConfig::default());
1306            tracer.start();
1307            assert!(tracer.is_running());
1308        }
1309
1310        #[test]
1311        fn h0_trace_43_tracer_stop_not_running() {
1312            let mut tracer = ExecutionTracer::new("test", TracingConfig::default());
1313            tracer.start();
1314            let _ = tracer.stop();
1315            assert!(!tracer.is_running());
1316        }
1317
1318        #[test]
1319        fn h0_trace_44_tracer_span_count_initial() {
1320            let tracer = ExecutionTracer::new("test", TracingConfig::default());
1321            assert_eq!(tracer.span_count(), 0);
1322        }
1323
1324        #[test]
1325        fn h0_trace_45_tracer_event_count_initial() {
1326            let tracer = ExecutionTracer::new("test", TracingConfig::default());
1327            assert_eq!(tracer.event_count(), 0);
1328        }
1329
1330        #[test]
1331        fn h0_trace_46_tracer_start_span() {
1332            let mut tracer = ExecutionTracer::new("test", TracingConfig::default());
1333            tracer.start();
1334            let span_id = tracer.start_span("action");
1335            assert!(!span_id.is_empty());
1336            assert_eq!(tracer.span_count(), 1);
1337        }
1338
1339        #[test]
1340        fn h0_trace_47_tracer_info_event() {
1341            let mut tracer = ExecutionTracer::new("test", TracingConfig::default());
1342            tracer.start();
1343            tracer.info("test", "Info message");
1344            assert_eq!(tracer.event_count(), 1);
1345        }
1346
1347        #[test]
1348        fn h0_trace_48_tracer_warn_event() {
1349            let mut tracer = ExecutionTracer::new("test", TracingConfig::default());
1350            tracer.start();
1351            tracer.warn("test", "Warning message");
1352            let archive = tracer.stop();
1353            assert_eq!(archive.events[0].level, EventLevel::Warn);
1354        }
1355
1356        #[test]
1357        fn h0_trace_49_tracer_error_event() {
1358            let mut tracer = ExecutionTracer::new("test", TracingConfig::default());
1359            tracer.start();
1360            tracer.error("test", "Error message");
1361            let archive = tracer.stop();
1362            assert_eq!(archive.events[0].level, EventLevel::Error);
1363        }
1364
1365        #[test]
1366        fn h0_trace_50_tracer_archive_metadata() {
1367            let mut tracer = ExecutionTracer::new("my_test", TracingConfig::default());
1368            tracer.start();
1369            let archive = tracer.stop();
1370            assert_eq!(archive.metadata.test_name, "my_test");
1371            assert!(archive.metadata.duration_ms.is_some());
1372        }
1373    }
1374
1375    mod h0_archive_tests {
1376        use super::*;
1377
1378        #[test]
1379        fn h0_trace_51_archive_new_empty() {
1380            let archive = TraceArchive::new(TraceMetadata::new("test"));
1381            assert!(archive.spans.is_empty());
1382            assert!(archive.events.is_empty());
1383            assert!(archive.network_events.is_empty());
1384            assert!(archive.console_messages.is_empty());
1385        }
1386
1387        #[test]
1388        fn h0_trace_52_archive_spans_by_name() {
1389            let mut archive = TraceArchive::new(TraceMetadata::new("test"));
1390            archive.spans.push(TracedSpan::new("click", 0));
1391            archive.spans.push(TracedSpan::new("click", 100));
1392            let clicks = archive.spans_by_name("click");
1393            assert_eq!(clicks.len(), 2);
1394        }
1395
1396        #[test]
1397        fn h0_trace_53_archive_events_by_category() {
1398            let mut archive = TraceArchive::new(TraceMetadata::new("test"));
1399            archive
1400                .events
1401                .push(TracedEvent::new("e1", EventCategory::Network, 0));
1402            archive
1403                .events
1404                .push(TracedEvent::new("e2", EventCategory::Network, 50));
1405            let network = archive.events_by_category(EventCategory::Network);
1406            assert_eq!(network.len(), 2);
1407        }
1408
1409        #[test]
1410        fn h0_trace_54_archive_failed_requests() {
1411            let mut archive = TraceArchive::new(TraceMetadata::new("test"));
1412            let mut failed = NetworkEvent::new("https://fail.com", "GET", 0);
1413            failed.fail("404");
1414            archive.network_events.push(failed);
1415            assert_eq!(archive.failed_requests().len(), 1);
1416        }
1417
1418        #[test]
1419        fn h0_trace_55_archive_error_spans() {
1420            let mut archive = TraceArchive::new(TraceMetadata::new("test"));
1421            let mut error_span = TracedSpan::new("err", 0);
1422            error_span.mark_error("Failed");
1423            archive.spans.push(error_span);
1424            assert_eq!(archive.error_spans().len(), 1);
1425        }
1426
1427        #[test]
1428        fn h0_trace_56_event_category_screenshot() {
1429            let event = TracedEvent::new("capture", EventCategory::Screenshot, 0);
1430            assert_eq!(event.category, EventCategory::Screenshot);
1431        }
1432
1433        #[test]
1434        fn h0_trace_57_event_category_performance() {
1435            let event = TracedEvent::new("metric", EventCategory::Performance, 0);
1436            assert_eq!(event.category, EventCategory::Performance);
1437        }
1438
1439        #[test]
1440        fn h0_trace_58_event_category_assertion() {
1441            let event = TracedEvent::new("assert", EventCategory::Assertion, 0);
1442            assert_eq!(event.category, EventCategory::Assertion);
1443        }
1444
1445        #[test]
1446        fn h0_trace_59_event_category_custom() {
1447            let event = TracedEvent::new("custom", EventCategory::Custom, 0);
1448            assert_eq!(event.category, EventCategory::Custom);
1449        }
1450
1451        #[test]
1452        fn h0_trace_60_console_level_debug() {
1453            let msg = ConsoleMessage {
1454                timestamp_ms: 0,
1455                level: ConsoleLevel::Debug,
1456                text: "debug".to_string(),
1457                source: None,
1458                line: None,
1459            };
1460            assert_eq!(msg.level, ConsoleLevel::Debug);
1461        }
1462    }
1463}