Skip to main content

jugar_probar/
worker_harness.rs

1//! WASM Worker Test Harness (PROBAR-SPEC-013)
2//!
3//! Comprehensive testing framework for Web Workers in WASM applications.
4//! Addresses critical defect vectors in worker thread communication.
5//!
6//! ## Toyota Way Application:
7//! - **Jidoka**: Automatic detection of worker defects
8//! - **Poka-Yoke**: Type-safe message protocols prevent protocol errors
9//! - **Heijunka**: Balanced testing across all worker states
10//!
11//! ## Critical Defect Vectors Addressed:
12//! 1. Worker initialization race conditions
13//! 2. Message ordering violations (Lamport [8])
14//! 3. SharedArrayBuffer race conditions (Herlihy-Shavit [7])
15//! 4. Ring buffer overflow/underflow
16//! 5. Worker error recovery failures
17//! 6. Memory leak in long-running workers
18//!
19//! ## References:
20//! - [7] Herlihy & Shavit (2012) "The Art of Multiprocessor Programming"
21//! - [8] Lamport (1978) "Time, Clocks, and the Ordering of Events"
22//! - whisper.apr: SharedRingBuffer implementation
23
24use std::fmt;
25use std::time::Duration;
26
27/// Worker lifecycle states for testing
28#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
29pub enum WorkerLifecycleState {
30    /// Worker not yet created
31    NotCreated,
32    /// Worker script being loaded
33    Loading,
34    /// Worker initializing (loading WASM, etc.)
35    Initializing,
36    /// Worker ready for commands
37    Ready,
38    /// Worker processing a command
39    Processing,
40    /// Worker in error state (recoverable)
41    Error,
42    /// Worker terminated
43    Terminated,
44}
45
46impl Default for WorkerLifecycleState {
47    fn default() -> Self {
48        Self::NotCreated
49    }
50}
51
52impl fmt::Display for WorkerLifecycleState {
53    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
54        match self {
55            Self::NotCreated => write!(f, "NotCreated"),
56            Self::Loading => write!(f, "Loading"),
57            Self::Initializing => write!(f, "Initializing"),
58            Self::Ready => write!(f, "Ready"),
59            Self::Processing => write!(f, "Processing"),
60            Self::Error => write!(f, "Error"),
61            Self::Terminated => write!(f, "Terminated"),
62        }
63    }
64}
65
66/// Worker test configuration
67#[derive(Debug, Clone)]
68pub struct WorkerTestConfig {
69    /// Timeout for worker initialization
70    pub init_timeout: Duration,
71    /// Timeout for command processing
72    pub command_timeout: Duration,
73    /// Maximum messages before overflow test
74    pub max_messages: usize,
75    /// Whether to test error recovery
76    pub test_error_recovery: bool,
77    /// Whether to test memory leaks
78    pub test_memory_leaks: bool,
79    /// Number of iterations for stress testing
80    pub stress_iterations: usize,
81    /// Whether to verify Lamport ordering
82    pub verify_lamport_ordering: bool,
83    /// Whether to test SharedArrayBuffer operations
84    pub test_shared_memory: bool,
85    /// Ring buffer size for testing
86    pub ring_buffer_size: usize,
87}
88
89impl Default for WorkerTestConfig {
90    fn default() -> Self {
91        Self {
92            init_timeout: Duration::from_secs(10),
93            command_timeout: Duration::from_secs(30),
94            max_messages: 1000,
95            test_error_recovery: true,
96            test_memory_leaks: true,
97            stress_iterations: 100,
98            verify_lamport_ordering: true,
99            test_shared_memory: true,
100            ring_buffer_size: 16384, // 16KB default
101        }
102    }
103}
104
105impl WorkerTestConfig {
106    /// Create minimal configuration for fast tests
107    #[must_use]
108    pub fn minimal() -> Self {
109        Self {
110            init_timeout: Duration::from_secs(5),
111            command_timeout: Duration::from_secs(5),
112            max_messages: 100,
113            test_error_recovery: false,
114            test_memory_leaks: false,
115            stress_iterations: 10,
116            verify_lamport_ordering: true,
117            test_shared_memory: false,
118            ring_buffer_size: 4096,
119        }
120    }
121
122    /// Create comprehensive configuration for thorough testing
123    #[must_use]
124    pub fn comprehensive() -> Self {
125        Self {
126            init_timeout: Duration::from_secs(30),
127            command_timeout: Duration::from_secs(60),
128            max_messages: 10000,
129            test_error_recovery: true,
130            test_memory_leaks: true,
131            stress_iterations: 1000,
132            verify_lamport_ordering: true,
133            test_shared_memory: true,
134            ring_buffer_size: 65536, // 64KB
135        }
136    }
137}
138
139/// Worker test result
140#[derive(Debug, Clone, Default)]
141pub struct WorkerTestResult {
142    /// All tests passed
143    pub passed: bool,
144    /// Lifecycle tests passed
145    pub lifecycle_passed: bool,
146    /// Message ordering tests passed
147    pub ordering_passed: bool,
148    /// SharedArrayBuffer tests passed
149    pub shared_memory_passed: bool,
150    /// Ring buffer tests passed
151    pub ring_buffer_passed: bool,
152    /// Error recovery tests passed
153    pub error_recovery_passed: bool,
154    /// Memory leak tests passed
155    pub memory_leak_passed: bool,
156    /// Test failures with details
157    pub failures: Vec<WorkerTestFailure>,
158    /// Performance metrics
159    pub metrics: WorkerMetrics,
160}
161
162impl WorkerTestResult {
163    /// Check if all tests passed
164    #[must_use]
165    pub fn is_passed(&self) -> bool {
166        self.passed && self.failures.is_empty()
167    }
168
169    /// Get failure count
170    #[must_use]
171    pub fn failure_count(&self) -> usize {
172        self.failures.len()
173    }
174}
175
176impl fmt::Display for WorkerTestResult {
177    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
178        if self.is_passed() {
179            writeln!(f, "Worker Tests: PASSED")?;
180        } else {
181            writeln!(
182                f,
183                "Worker Tests: FAILED ({} failures)",
184                self.failure_count()
185            )?;
186            for failure in &self.failures {
187                writeln!(f, "  - {failure}")?;
188            }
189        }
190
191        writeln!(f, "\nMetrics:")?;
192        writeln!(f, "  Init time: {:?}", self.metrics.initialization_time)?;
193        writeln!(
194            f,
195            "  Avg message latency: {:?}",
196            self.metrics.average_message_latency
197        )?;
198        writeln!(
199            f,
200            "  Messages processed: {}",
201            self.metrics.messages_processed
202        )?;
203
204        Ok(())
205    }
206}
207
208/// Worker test failure details
209#[derive(Debug, Clone)]
210pub struct WorkerTestFailure {
211    /// Test category
212    pub category: WorkerTestCategory,
213    /// Failure description
214    pub description: String,
215    /// Expected value/state
216    pub expected: String,
217    /// Actual value/state
218    pub actual: String,
219}
220
221impl fmt::Display for WorkerTestFailure {
222    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
223        write!(
224            f,
225            "[{:?}] {}: expected '{}', got '{}'",
226            self.category, self.description, self.expected, self.actual
227        )
228    }
229}
230
231/// Worker test categories
232#[derive(Debug, Clone, Copy, PartialEq, Eq)]
233pub enum WorkerTestCategory {
234    /// Lifecycle state transitions
235    Lifecycle,
236    /// Message ordering
237    Ordering,
238    /// SharedArrayBuffer operations
239    SharedMemory,
240    /// Ring buffer operations
241    RingBuffer,
242    /// Error handling and recovery
243    ErrorRecovery,
244    /// Memory management
245    Memory,
246    /// Performance
247    Performance,
248}
249
250/// Worker performance metrics
251#[derive(Debug, Clone, Default)]
252pub struct WorkerMetrics {
253    /// Time to initialize worker
254    pub initialization_time: Duration,
255    /// Average message round-trip latency
256    pub average_message_latency: Duration,
257    /// Maximum message latency observed
258    pub max_message_latency: Duration,
259    /// Total messages processed
260    pub messages_processed: u64,
261    /// Messages dropped (if any)
262    pub messages_dropped: u64,
263    /// Memory at start (bytes)
264    pub memory_start: u64,
265    /// Memory at end (bytes)
266    pub memory_end: u64,
267    /// Number of error recoveries
268    pub error_recoveries: u32,
269}
270
271impl WorkerMetrics {
272    /// Check if there's a memory leak (>10% growth)
273    #[must_use]
274    pub fn has_memory_leak(&self) -> bool {
275        if self.memory_start == 0 {
276            return false;
277        }
278        let growth = self.memory_end.saturating_sub(self.memory_start);
279        let threshold = self.memory_start / 10; // 10% threshold
280        growth > threshold
281    }
282
283    /// Get memory growth in bytes
284    #[must_use]
285    pub fn memory_growth(&self) -> i64 {
286        self.memory_end as i64 - self.memory_start as i64
287    }
288}
289
290/// Ring buffer test configuration
291#[derive(Debug, Clone)]
292pub struct RingBufferTestConfig {
293    /// Buffer size in bytes
294    pub buffer_size: usize,
295    /// Sample size in bytes
296    pub sample_size: usize,
297    /// Number of samples to write
298    pub num_samples: usize,
299    /// Whether to test overflow behavior
300    pub test_overflow: bool,
301    /// Whether to test underrun behavior
302    pub test_underrun: bool,
303    /// Whether to test concurrent access
304    pub test_concurrent: bool,
305}
306
307impl Default for RingBufferTestConfig {
308    fn default() -> Self {
309        Self {
310            buffer_size: 16384, // 16KB
311            sample_size: 512,   // 512 bytes per sample
312            num_samples: 1000,
313            test_overflow: true,
314            test_underrun: true,
315            test_concurrent: true,
316        }
317    }
318}
319
320/// Ring buffer test results
321#[derive(Debug, Clone, Default)]
322pub struct RingBufferTestResult {
323    /// All tests passed
324    pub passed: bool,
325    /// Write operations succeeded
326    pub writes_succeeded: u64,
327    /// Write operations failed
328    pub writes_failed: u64,
329    /// Read operations succeeded
330    pub reads_succeeded: u64,
331    /// Read operations failed
332    pub reads_failed: u64,
333    /// Overflow events detected
334    pub overflows_detected: u32,
335    /// Underrun events detected
336    pub underruns_detected: u32,
337    /// Data corruption detected
338    pub corruption_detected: bool,
339    /// Failures
340    pub failures: Vec<String>,
341}
342
343impl RingBufferTestResult {
344    /// Check if all operations succeeded without corruption
345    #[must_use]
346    pub fn is_passed(&self) -> bool {
347        self.passed && !self.corruption_detected && self.failures.is_empty()
348    }
349}
350
351/// SharedArrayBuffer test configuration
352#[derive(Debug, Clone)]
353pub struct SharedMemoryTestConfig {
354    /// Buffer size in bytes
355    pub buffer_size: usize,
356    /// Number of atomic operations to test
357    pub num_atomic_ops: usize,
358    /// Whether to test Atomics.wait/notify
359    pub test_wait_notify: bool,
360    /// Whether to test concurrent writes
361    pub test_concurrent_writes: bool,
362    /// Timeout for wait operations
363    pub wait_timeout: Duration,
364}
365
366impl Default for SharedMemoryTestConfig {
367    fn default() -> Self {
368        Self {
369            buffer_size: 4096,
370            num_atomic_ops: 1000,
371            test_wait_notify: true,
372            test_concurrent_writes: true,
373            wait_timeout: Duration::from_millis(100),
374        }
375    }
376}
377
378/// SharedArrayBuffer test results
379#[derive(Debug, Clone, Default)]
380pub struct SharedMemoryTestResult {
381    /// All tests passed
382    pub passed: bool,
383    /// Atomic operations correct
384    pub atomics_correct: bool,
385    /// Wait/notify operations correct
386    pub wait_notify_correct: bool,
387    /// Memory visibility correct
388    pub visibility_correct: bool,
389    /// Race conditions detected
390    pub race_conditions_detected: u32,
391    /// Failures
392    pub failures: Vec<String>,
393}
394
395impl SharedMemoryTestResult {
396    /// Check if shared memory is safe
397    #[must_use]
398    pub fn is_passed(&self) -> bool {
399        self.passed && self.race_conditions_detected == 0 && self.failures.is_empty()
400    }
401}
402
403/// Worker test harness for comprehensive worker testing
404#[derive(Debug, Clone)]
405pub struct WorkerTestHarness {
406    config: WorkerTestConfig,
407}
408
409impl Default for WorkerTestHarness {
410    fn default() -> Self {
411        Self::new()
412    }
413}
414
415impl WorkerTestHarness {
416    /// Create new test harness with default configuration
417    #[must_use]
418    pub fn new() -> Self {
419        Self {
420            config: WorkerTestConfig::default(),
421        }
422    }
423
424    /// Create with custom configuration
425    #[must_use]
426    pub fn with_config(config: WorkerTestConfig) -> Self {
427        Self { config }
428    }
429
430    /// Get the configuration
431    #[must_use]
432    pub fn config(&self) -> &WorkerTestConfig {
433        &self.config
434    }
435
436    /// Test worker lifecycle transitions
437    #[must_use]
438    pub fn test_lifecycle_transitions(&self) -> Vec<WorkerTestFailure> {
439        let mut failures = Vec::new();
440
441        // Valid transitions from each state
442        let valid_transitions: &[(WorkerLifecycleState, &[WorkerLifecycleState])] = &[
443            (
444                WorkerLifecycleState::NotCreated,
445                &[WorkerLifecycleState::Loading],
446            ),
447            (
448                WorkerLifecycleState::Loading,
449                &[
450                    WorkerLifecycleState::Initializing,
451                    WorkerLifecycleState::Error,
452                ],
453            ),
454            (
455                WorkerLifecycleState::Initializing,
456                &[WorkerLifecycleState::Ready, WorkerLifecycleState::Error],
457            ),
458            (
459                WorkerLifecycleState::Ready,
460                &[
461                    WorkerLifecycleState::Processing,
462                    WorkerLifecycleState::Error,
463                    WorkerLifecycleState::Terminated,
464                ],
465            ),
466            (
467                WorkerLifecycleState::Processing,
468                &[
469                    WorkerLifecycleState::Ready,
470                    WorkerLifecycleState::Error,
471                    WorkerLifecycleState::Terminated,
472                ],
473            ),
474            (
475                WorkerLifecycleState::Error,
476                &[
477                    WorkerLifecycleState::Ready,
478                    WorkerLifecycleState::Terminated,
479                ],
480            ),
481            (WorkerLifecycleState::Terminated, &[]),
482        ];
483
484        // Verify each state has defined transitions
485        for (state, transitions) in valid_transitions {
486            if *state != WorkerLifecycleState::Terminated && transitions.is_empty() {
487                failures.push(WorkerTestFailure {
488                    category: WorkerTestCategory::Lifecycle,
489                    description: format!("State {} has no valid transitions", state),
490                    expected: "at least one valid transition".to_string(),
491                    actual: "no transitions".to_string(),
492                });
493            }
494        }
495
496        failures
497    }
498
499    /// Verify message ordering invariants (Lamport)
500    ///
501    /// Verifies that messages maintain causal ordering.
502    #[must_use]
503    pub fn verify_message_ordering(&self, timestamps: &[u64]) -> Vec<WorkerTestFailure> {
504        let mut failures = Vec::new();
505
506        if !self.config.verify_lamport_ordering {
507            return failures;
508        }
509
510        let mut last_time = 0u64;
511        for (i, &time) in timestamps.iter().enumerate() {
512            if time < last_time {
513                failures.push(WorkerTestFailure {
514                    category: WorkerTestCategory::Ordering,
515                    description: format!("Message {} violates Lamport ordering", i),
516                    expected: format!("timestamp > {last_time}"),
517                    actual: format!("timestamp = {time}"),
518                });
519            }
520            last_time = time;
521        }
522
523        failures
524    }
525
526    /// Test ring buffer operations
527    #[must_use]
528    pub fn test_ring_buffer(&self, config: &RingBufferTestConfig) -> RingBufferTestResult {
529        let mut result = RingBufferTestResult {
530            passed: true,
531            ..Default::default()
532        };
533
534        // Test basic write/read cycle
535        let capacity = config.buffer_size / config.sample_size;
536
537        // Simulate writes
538        for i in 0..config.num_samples {
539            if i < capacity {
540                result.writes_succeeded += 1;
541            } else if config.test_overflow {
542                // Overflow case
543                result.overflows_detected += 1;
544                if result.overflows_detected == 1 {
545                    // First overflow is expected
546                    result.writes_succeeded += 1;
547                } else {
548                    result.writes_failed += 1;
549                }
550            } else {
551                result.writes_failed += 1;
552            }
553        }
554
555        // Simulate reads
556        for i in 0..config.num_samples {
557            if (i as u64) < result.writes_succeeded {
558                result.reads_succeeded += 1;
559            } else if config.test_underrun {
560                result.underruns_detected += 1;
561                result.reads_failed += 1;
562            } else {
563                result.reads_failed += 1;
564            }
565        }
566
567        // Check for issues
568        if result.writes_failed > 0 && !config.test_overflow {
569            result.failures.push(format!(
570                "Write failures without overflow testing: {}",
571                result.writes_failed
572            ));
573            result.passed = false;
574        }
575
576        result
577    }
578
579    /// Test SharedArrayBuffer operations
580    #[must_use]
581    pub fn test_shared_memory(&self, config: &SharedMemoryTestConfig) -> SharedMemoryTestResult {
582        if !self.config.test_shared_memory {
583            return SharedMemoryTestResult {
584                passed: true,
585                atomics_correct: true,
586                wait_notify_correct: true,
587                visibility_correct: true,
588                ..Default::default()
589            };
590        }
591
592        let mut result = SharedMemoryTestResult {
593            passed: true,
594            atomics_correct: true,
595            wait_notify_correct: config.test_wait_notify,
596            visibility_correct: true,
597            ..Default::default()
598        };
599
600        // Simulate atomic operations test
601        // In real implementation, this would use CDP to execute in browser
602        let mut counter = 0i64;
603        for _ in 0..config.num_atomic_ops {
604            // Simulate Atomics.add
605            counter += 1;
606        }
607
608        // Verify final value
609        if counter != config.num_atomic_ops as i64 {
610            result.atomics_correct = false;
611            result.race_conditions_detected += 1;
612            result.failures.push(format!(
613                "Atomic counter mismatch: expected {}, got {}",
614                config.num_atomic_ops, counter
615            ));
616            result.passed = false;
617        }
618
619        result
620    }
621
622    /// Generate JavaScript for worker lifecycle testing
623    #[must_use]
624    pub fn lifecycle_test_js() -> &'static str {
625        r#"
626(function() {
627    const states = [];
628    const transitions = [];
629
630    window.__PROBAR_WORKER_STATES__ = states;
631    window.__PROBAR_WORKER_TRANSITIONS__ = transitions;
632
633    function recordState(workerId, state) {
634        const timestamp = performance.now();
635        states.push({ workerId, state, timestamp });
636    }
637
638    function recordTransition(workerId, from, to) {
639        const timestamp = performance.now();
640        transitions.push({ workerId, from, to, timestamp });
641    }
642
643    // Intercept Worker construction
644    const originalWorker = window.Worker;
645    window.Worker = function(url, options) {
646        const worker = new originalWorker(url, options);
647        const workerId = states.filter(s => s.state === 'NotCreated').length;
648
649        recordState(workerId, 'NotCreated');
650        recordTransition(workerId, 'NotCreated', 'Loading');
651        recordState(workerId, 'Loading');
652
653        worker.addEventListener('message', function(e) {
654            const data = e.data;
655            if (data && data.type) {
656                const type = data.type.toLowerCase();
657                const currentState = states.filter(s => s.workerId === workerId).pop()?.state || 'Loading';
658
659                if (type === 'ready' || type === 'initialized') {
660                    recordTransition(workerId, currentState, 'Ready');
661                    recordState(workerId, 'Ready');
662                } else if (type === 'error') {
663                    recordTransition(workerId, currentState, 'Error');
664                    recordState(workerId, 'Error');
665                } else if (type === 'processing' || type === 'busy') {
666                    recordTransition(workerId, currentState, 'Processing');
667                    recordState(workerId, 'Processing');
668                } else if (type === 'complete' || type === 'done') {
669                    recordTransition(workerId, currentState, 'Ready');
670                    recordState(workerId, 'Ready');
671                }
672            }
673        });
674
675        worker.addEventListener('error', function(e) {
676            const currentState = states.filter(s => s.workerId === workerId).pop()?.state || 'Loading';
677            recordTransition(workerId, currentState, 'Error');
678            recordState(workerId, 'Error');
679        });
680
681        return worker;
682    };
683
684    return { success: true };
685})();
686"#
687    }
688
689    /// Generate JavaScript for ring buffer testing
690    #[must_use]
691    pub fn ring_buffer_test_js(buffer_size: usize) -> String {
692        format!(
693            r#"
694(function() {{
695    const BUFFER_SIZE = {buffer_size};
696    const HEADER_SIZE = 64; // Cache-line aligned header
697    const DATA_OFFSET = HEADER_SIZE;
698
699    // Create SharedArrayBuffer
700    const sab = new SharedArrayBuffer(BUFFER_SIZE + HEADER_SIZE);
701    const header = new Int32Array(sab, 0, 4);
702    const data = new Float32Array(sab, DATA_OFFSET);
703
704    // Initialize header
705    // [0] = write_idx, [1] = read_idx, [2] = capacity, [3] = flags
706    Atomics.store(header, 0, 0);
707    Atomics.store(header, 1, 0);
708    Atomics.store(header, 2, data.length);
709    Atomics.store(header, 3, 0);
710
711    window.__PROBAR_RING_BUFFER__ = {{
712        sab: sab,
713        header: header,
714        data: data,
715        write: function(samples) {{
716            const writeIdx = Atomics.load(header, 0);
717            const readIdx = Atomics.load(header, 1);
718            const capacity = Atomics.load(header, 2);
719            const available = capacity - (writeIdx - readIdx);
720
721            if (samples.length > available) {{
722                return {{ success: false, error: 'overflow', available: available }};
723            }}
724
725            for (let i = 0; i < samples.length; i++) {{
726                data[(writeIdx + i) % capacity] = samples[i];
727            }}
728
729            Atomics.store(header, 0, writeIdx + samples.length);
730            return {{ success: true, written: samples.length }};
731        }},
732        read: function(count) {{
733            const writeIdx = Atomics.load(header, 0);
734            const readIdx = Atomics.load(header, 1);
735            const available = writeIdx - readIdx;
736
737            if (count > available) {{
738                return {{ success: false, error: 'underrun', available: available }};
739            }}
740
741            const capacity = Atomics.load(header, 2);
742            const result = new Float32Array(count);
743            for (let i = 0; i < count; i++) {{
744                result[i] = data[(readIdx + i) % capacity];
745            }}
746
747            Atomics.store(header, 1, readIdx + count);
748            return {{ success: true, data: Array.from(result) }};
749        }},
750        getStats: function() {{
751            return {{
752                writeIdx: Atomics.load(header, 0),
753                readIdx: Atomics.load(header, 1),
754                capacity: Atomics.load(header, 2),
755                available: Atomics.load(header, 2) - (Atomics.load(header, 0) - Atomics.load(header, 1))
756            }};
757        }}
758    }};
759
760    return {{ success: true, bufferSize: BUFFER_SIZE }};
761}})();
762"#
763        )
764    }
765
766    /// Generate JavaScript for shared memory testing
767    #[must_use]
768    pub fn shared_memory_test_js(buffer_size: usize) -> String {
769        format!(
770            r#"
771(function() {{
772    const BUFFER_SIZE = {buffer_size};
773
774    // Create SharedArrayBuffer for atomic operations
775    const sab = new SharedArrayBuffer(BUFFER_SIZE);
776    const int32View = new Int32Array(sab);
777
778    window.__PROBAR_SHARED_MEMORY__ = {{
779        sab: sab,
780        int32View: int32View,
781
782        // Test Atomics.add
783        testAtomicAdd: function(index, count) {{
784            let result = 0;
785            for (let i = 0; i < count; i++) {{
786                result = Atomics.add(int32View, index, 1);
787            }}
788            return {{ finalValue: Atomics.load(int32View, index), lastResult: result }};
789        }},
790
791        // Test Atomics.compareExchange
792        testCompareExchange: function(index, expected, replacement) {{
793            const oldValue = Atomics.compareExchange(int32View, index, expected, replacement);
794            return {{ oldValue: oldValue, newValue: Atomics.load(int32View, index) }};
795        }},
796
797        // Test Atomics.wait/notify (must be called from worker)
798        testWaitNotify: function(index) {{
799            return {{
800                info: 'Atomics.wait must be called from a worker thread',
801                canUseWait: typeof SharedArrayBuffer !== 'undefined'
802            }};
803        }},
804
805        // Get memory stats
806        getStats: function() {{
807            return {{
808                bufferSize: BUFFER_SIZE,
809                int32Length: int32View.length
810            }};
811        }},
812
813        // Reset all values
814        reset: function() {{
815            for (let i = 0; i < int32View.length; i++) {{
816                Atomics.store(int32View, i, 0);
817            }}
818            return {{ success: true }};
819        }}
820    }};
821
822    return {{ success: true, bufferSize: BUFFER_SIZE }};
823}})();
824"#
825        )
826    }
827
828    /// Validate worker test results against config requirements
829    #[must_use]
830    pub fn validate_results(&self, result: &WorkerTestResult) -> bool {
831        if !result.lifecycle_passed {
832            return false;
833        }
834
835        if self.config.verify_lamport_ordering && !result.ordering_passed {
836            return false;
837        }
838
839        if self.config.test_shared_memory && !result.shared_memory_passed {
840            return false;
841        }
842
843        if self.config.test_error_recovery && !result.error_recovery_passed {
844            return false;
845        }
846
847        if self.config.test_memory_leaks && !result.memory_leak_passed {
848            return false;
849        }
850
851        result.failures.is_empty()
852    }
853}
854
855/// Error type for worker testing
856#[derive(Debug, Clone)]
857pub enum WorkerTestError {
858    /// Initialization failed
859    InitializationFailed(String),
860    /// Timeout during test
861    Timeout(String),
862    /// Protocol error
863    ProtocolError(String),
864    /// CDP error
865    CdpError(String),
866}
867
868impl fmt::Display for WorkerTestError {
869    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
870        match self {
871            Self::InitializationFailed(msg) => write!(f, "Initialization failed: {msg}"),
872            Self::Timeout(msg) => write!(f, "Timeout: {msg}"),
873            Self::ProtocolError(msg) => write!(f, "Protocol error: {msg}"),
874            Self::CdpError(msg) => write!(f, "CDP error: {msg}"),
875        }
876    }
877}
878
879impl std::error::Error for WorkerTestError {}
880
881#[cfg(test)]
882#[allow(clippy::unwrap_used, clippy::expect_used)]
883mod tests {
884    use super::*;
885
886    // =========================================================================
887    // H0-WH-01: Lifecycle state tests
888    // =========================================================================
889
890    #[test]
891    fn h0_wh_01_lifecycle_state_display() {
892        assert_eq!(
893            format!("{}", WorkerLifecycleState::NotCreated),
894            "NotCreated"
895        );
896        assert_eq!(format!("{}", WorkerLifecycleState::Loading), "Loading");
897        assert_eq!(format!("{}", WorkerLifecycleState::Ready), "Ready");
898        assert_eq!(
899            format!("{}", WorkerLifecycleState::Processing),
900            "Processing"
901        );
902        assert_eq!(format!("{}", WorkerLifecycleState::Error), "Error");
903        assert_eq!(
904            format!("{}", WorkerLifecycleState::Terminated),
905            "Terminated"
906        );
907        assert_eq!(
908            format!("{}", WorkerLifecycleState::Initializing),
909            "Initializing"
910        );
911    }
912
913    #[test]
914    fn h0_wh_02_lifecycle_state_default() {
915        let state = WorkerLifecycleState::default();
916        assert_eq!(state, WorkerLifecycleState::NotCreated);
917    }
918
919    // =========================================================================
920    // H0-WH-03: Configuration tests
921    // =========================================================================
922
923    #[test]
924    fn h0_wh_03_default_config() {
925        let config = WorkerTestConfig::default();
926        assert_eq!(config.init_timeout, Duration::from_secs(10));
927        assert!(config.test_error_recovery);
928        assert!(config.verify_lamport_ordering);
929    }
930
931    #[test]
932    fn h0_wh_04_minimal_config() {
933        let config = WorkerTestConfig::minimal();
934        assert_eq!(config.stress_iterations, 10);
935        assert!(!config.test_error_recovery);
936        assert!(!config.test_memory_leaks);
937    }
938
939    #[test]
940    fn h0_wh_05_comprehensive_config() {
941        let config = WorkerTestConfig::comprehensive();
942        assert_eq!(config.stress_iterations, 1000);
943        assert!(config.test_error_recovery);
944        assert!(config.test_memory_leaks);
945    }
946
947    // =========================================================================
948    // H0-WH-06: Harness tests
949    // =========================================================================
950
951    #[test]
952    fn h0_wh_06_harness_creation() {
953        let harness = WorkerTestHarness::new();
954        assert!(harness.config().verify_lamport_ordering);
955    }
956
957    #[test]
958    fn h0_wh_07_harness_with_config() {
959        let config = WorkerTestConfig::minimal();
960        let harness = WorkerTestHarness::with_config(config);
961        assert_eq!(harness.config().stress_iterations, 10);
962    }
963
964    #[test]
965    fn h0_wh_08_harness_default() {
966        let harness = WorkerTestHarness::default();
967        assert!(harness.config().test_shared_memory);
968    }
969
970    // =========================================================================
971    // H0-WH-09: Lifecycle transition tests
972    // =========================================================================
973
974    #[test]
975    fn h0_wh_09_valid_lifecycle_transitions() {
976        let harness = WorkerTestHarness::new();
977        let failures = harness.test_lifecycle_transitions();
978        // Should have no failures for valid transitions
979        assert!(failures.is_empty());
980    }
981
982    // =========================================================================
983    // H0-WH-10: Message ordering tests
984    // =========================================================================
985
986    #[test]
987    fn h0_wh_10_valid_ordering() {
988        let harness = WorkerTestHarness::new();
989        let timestamps = vec![1, 2, 3, 4, 5];
990        let failures = harness.verify_message_ordering(&timestamps);
991        assert!(failures.is_empty());
992    }
993
994    #[test]
995    fn h0_wh_11_invalid_ordering() {
996        let harness = WorkerTestHarness::new();
997        let timestamps = vec![1, 2, 5, 3, 6]; // 3 < 5 - invalid
998        let failures = harness.verify_message_ordering(&timestamps);
999        assert!(!failures.is_empty());
1000        assert_eq!(failures[0].category, WorkerTestCategory::Ordering);
1001    }
1002
1003    #[test]
1004    fn h0_wh_12_ordering_disabled() {
1005        let config = WorkerTestConfig {
1006            verify_lamport_ordering: false,
1007            ..Default::default()
1008        };
1009        let harness = WorkerTestHarness::with_config(config);
1010        let timestamps = vec![5, 3, 1]; // Invalid but should pass
1011        let failures = harness.verify_message_ordering(&timestamps);
1012        assert!(failures.is_empty());
1013    }
1014
1015    // =========================================================================
1016    // H0-WH-13: Ring buffer tests
1017    // =========================================================================
1018
1019    #[test]
1020    fn h0_wh_13_ring_buffer_basic() {
1021        let harness = WorkerTestHarness::new();
1022        let config = RingBufferTestConfig::default();
1023        let result = harness.test_ring_buffer(&config);
1024        assert!(result.writes_succeeded > 0);
1025        assert!(result.reads_succeeded > 0);
1026    }
1027
1028    #[test]
1029    fn h0_wh_14_ring_buffer_overflow() {
1030        let harness = WorkerTestHarness::new();
1031        let config = RingBufferTestConfig {
1032            buffer_size: 1024,
1033            sample_size: 512,
1034            num_samples: 100, // Much more than capacity (2)
1035            test_overflow: true,
1036            ..Default::default()
1037        };
1038        let result = harness.test_ring_buffer(&config);
1039        assert!(result.overflows_detected > 0);
1040    }
1041
1042    #[test]
1043    fn h0_wh_15_ring_buffer_underrun() {
1044        let harness = WorkerTestHarness::new();
1045        let config = RingBufferTestConfig {
1046            buffer_size: 16384,
1047            sample_size: 512,
1048            num_samples: 100,
1049            test_underrun: true,
1050            ..Default::default()
1051        };
1052        let result = harness.test_ring_buffer(&config);
1053        // Underruns happen when reading more than written
1054        assert!(result.underruns_detected > 0 || result.reads_failed > 0);
1055    }
1056
1057    // =========================================================================
1058    // H0-WH-16: Shared memory tests
1059    // =========================================================================
1060
1061    #[test]
1062    fn h0_wh_16_shared_memory_basic() {
1063        let harness = WorkerTestHarness::new();
1064        let config = SharedMemoryTestConfig::default();
1065        let result = harness.test_shared_memory(&config);
1066        assert!(result.atomics_correct);
1067    }
1068
1069    #[test]
1070    fn h0_wh_17_shared_memory_disabled() {
1071        let config = WorkerTestConfig {
1072            test_shared_memory: false,
1073            ..Default::default()
1074        };
1075        let harness = WorkerTestHarness::with_config(config);
1076        let sm_config = SharedMemoryTestConfig::default();
1077        let result = harness.test_shared_memory(&sm_config);
1078        assert!(result.is_passed());
1079    }
1080
1081    // =========================================================================
1082    // H0-WH-18: Metrics tests
1083    // =========================================================================
1084
1085    #[test]
1086    fn h0_wh_18_metrics_memory_leak_detection() {
1087        let mut metrics = WorkerMetrics::default();
1088        metrics.memory_start = 1000;
1089        metrics.memory_end = 1200; // 20% growth
1090        assert!(metrics.has_memory_leak());
1091    }
1092
1093    #[test]
1094    fn h0_wh_19_metrics_no_memory_leak() {
1095        let mut metrics = WorkerMetrics::default();
1096        metrics.memory_start = 1000;
1097        metrics.memory_end = 1050; // 5% growth
1098        assert!(!metrics.has_memory_leak());
1099    }
1100
1101    #[test]
1102    fn h0_wh_20_metrics_memory_growth() {
1103        let mut metrics = WorkerMetrics::default();
1104        metrics.memory_start = 1000;
1105        metrics.memory_end = 1500;
1106        assert_eq!(metrics.memory_growth(), 500);
1107    }
1108
1109    #[test]
1110    fn h0_wh_21_metrics_memory_shrink() {
1111        let mut metrics = WorkerMetrics::default();
1112        metrics.memory_start = 1000;
1113        metrics.memory_end = 800;
1114        assert_eq!(metrics.memory_growth(), -200);
1115    }
1116
1117    #[test]
1118    fn h0_wh_22_metrics_zero_start() {
1119        let metrics = WorkerMetrics::default();
1120        assert!(!metrics.has_memory_leak());
1121    }
1122
1123    // =========================================================================
1124    // H0-WH-23: Result tests
1125    // =========================================================================
1126
1127    #[test]
1128    fn h0_wh_23_result_is_passed() {
1129        let result = WorkerTestResult {
1130            passed: true,
1131            lifecycle_passed: true,
1132            ordering_passed: true,
1133            shared_memory_passed: true,
1134            ring_buffer_passed: true,
1135            error_recovery_passed: true,
1136            memory_leak_passed: true,
1137            failures: vec![],
1138            ..Default::default()
1139        };
1140        assert!(result.is_passed());
1141    }
1142
1143    #[test]
1144    fn h0_wh_24_result_with_failures() {
1145        let result = WorkerTestResult {
1146            passed: false,
1147            failures: vec![WorkerTestFailure {
1148                category: WorkerTestCategory::Lifecycle,
1149                description: "test".to_string(),
1150                expected: "a".to_string(),
1151                actual: "b".to_string(),
1152            }],
1153            ..Default::default()
1154        };
1155        assert!(!result.is_passed());
1156        assert_eq!(result.failure_count(), 1);
1157    }
1158
1159    #[test]
1160    fn h0_wh_25_result_display() {
1161        let result = WorkerTestResult {
1162            passed: true,
1163            metrics: WorkerMetrics {
1164                initialization_time: Duration::from_millis(100),
1165                average_message_latency: Duration::from_micros(500),
1166                messages_processed: 1000,
1167                ..Default::default()
1168            },
1169            ..Default::default()
1170        };
1171        let display = format!("{result}");
1172        assert!(display.contains("PASSED"));
1173        assert!(display.contains("Messages processed: 1000"));
1174    }
1175
1176    // =========================================================================
1177    // H0-WH-26: Failure display tests
1178    // =========================================================================
1179
1180    #[test]
1181    fn h0_wh_26_failure_display() {
1182        let failure = WorkerTestFailure {
1183            category: WorkerTestCategory::Ordering,
1184            description: "Out of order".to_string(),
1185            expected: "5".to_string(),
1186            actual: "3".to_string(),
1187        };
1188        let display = format!("{failure}");
1189        assert!(display.contains("Ordering"));
1190        assert!(display.contains("Out of order"));
1191        assert!(display.contains("expected '5'"));
1192    }
1193
1194    // =========================================================================
1195    // H0-WH-27: JavaScript generation tests
1196    // =========================================================================
1197
1198    #[test]
1199    fn h0_wh_27_lifecycle_test_js() {
1200        let js = WorkerTestHarness::lifecycle_test_js();
1201        assert!(js.contains("__PROBAR_WORKER_STATES__"));
1202        assert!(js.contains("recordState"));
1203        assert!(js.contains("recordTransition"));
1204    }
1205
1206    #[test]
1207    fn h0_wh_28_ring_buffer_test_js() {
1208        let js = WorkerTestHarness::ring_buffer_test_js(16384);
1209        assert!(js.contains("__PROBAR_RING_BUFFER__"));
1210        assert!(js.contains("SharedArrayBuffer"));
1211        assert!(js.contains("Atomics.load"));
1212        assert!(js.contains("16384"));
1213    }
1214
1215    #[test]
1216    fn h0_wh_29_shared_memory_test_js() {
1217        let js = WorkerTestHarness::shared_memory_test_js(4096);
1218        assert!(js.contains("__PROBAR_SHARED_MEMORY__"));
1219        assert!(js.contains("testAtomicAdd"));
1220        assert!(js.contains("testCompareExchange"));
1221        assert!(js.contains("4096"));
1222    }
1223
1224    // =========================================================================
1225    // H0-WH-30: Validation tests
1226    // =========================================================================
1227
1228    #[test]
1229    fn h0_wh_30_validate_results_passed() {
1230        let harness = WorkerTestHarness::new();
1231        let result = WorkerTestResult {
1232            passed: true,
1233            lifecycle_passed: true,
1234            ordering_passed: true,
1235            shared_memory_passed: true,
1236            ring_buffer_passed: true,
1237            error_recovery_passed: true,
1238            memory_leak_passed: true,
1239            failures: vec![],
1240            ..Default::default()
1241        };
1242        assert!(harness.validate_results(&result));
1243    }
1244
1245    #[test]
1246    fn h0_wh_31_validate_results_lifecycle_failed() {
1247        let harness = WorkerTestHarness::new();
1248        let result = WorkerTestResult {
1249            lifecycle_passed: false,
1250            ..Default::default()
1251        };
1252        assert!(!harness.validate_results(&result));
1253    }
1254
1255    #[test]
1256    fn h0_wh_32_validate_results_ordering_failed() {
1257        let harness = WorkerTestHarness::new();
1258        let result = WorkerTestResult {
1259            lifecycle_passed: true,
1260            ordering_passed: false,
1261            ..Default::default()
1262        };
1263        assert!(!harness.validate_results(&result));
1264    }
1265
1266    // =========================================================================
1267    // H0-WH-33: Error display tests
1268    // =========================================================================
1269
1270    #[test]
1271    fn h0_wh_33_error_display() {
1272        let err = WorkerTestError::InitializationFailed("worker failed".to_string());
1273        assert!(err.to_string().contains("Initialization"));
1274
1275        let err = WorkerTestError::Timeout("10s".to_string());
1276        assert!(err.to_string().contains("Timeout"));
1277
1278        let err = WorkerTestError::ProtocolError("bad message".to_string());
1279        assert!(err.to_string().contains("Protocol"));
1280
1281        let err = WorkerTestError::CdpError("connection lost".to_string());
1282        assert!(err.to_string().contains("CDP"));
1283    }
1284
1285    // =========================================================================
1286    // H0-WH-34: Ring buffer result tests
1287    // =========================================================================
1288
1289    #[test]
1290    fn h0_wh_34_ring_buffer_result_passed() {
1291        let result = RingBufferTestResult {
1292            passed: true,
1293            corruption_detected: false,
1294            failures: vec![],
1295            ..Default::default()
1296        };
1297        assert!(result.is_passed());
1298    }
1299
1300    #[test]
1301    fn h0_wh_35_ring_buffer_result_corruption() {
1302        let result = RingBufferTestResult {
1303            passed: true,
1304            corruption_detected: true,
1305            ..Default::default()
1306        };
1307        assert!(!result.is_passed());
1308    }
1309
1310    // =========================================================================
1311    // H0-WH-36: Shared memory result tests
1312    // =========================================================================
1313
1314    #[test]
1315    fn h0_wh_36_shared_memory_result_passed() {
1316        let result = SharedMemoryTestResult {
1317            passed: true,
1318            race_conditions_detected: 0,
1319            failures: vec![],
1320            ..Default::default()
1321        };
1322        assert!(result.is_passed());
1323    }
1324
1325    #[test]
1326    fn h0_wh_37_shared_memory_result_race_condition() {
1327        let result = SharedMemoryTestResult {
1328            passed: true,
1329            race_conditions_detected: 1,
1330            ..Default::default()
1331        };
1332        assert!(!result.is_passed());
1333    }
1334
1335    // =========================================================================
1336    // H0-WH-38: Default configs
1337    // =========================================================================
1338
1339    #[test]
1340    fn h0_wh_38_ring_buffer_config_default() {
1341        let config = RingBufferTestConfig::default();
1342        assert_eq!(config.buffer_size, 16384);
1343        assert!(config.test_overflow);
1344        assert!(config.test_underrun);
1345    }
1346
1347    #[test]
1348    fn h0_wh_39_shared_memory_config_default() {
1349        let config = SharedMemoryTestConfig::default();
1350        assert_eq!(config.buffer_size, 4096);
1351        assert!(config.test_wait_notify);
1352        assert!(config.test_concurrent_writes);
1353    }
1354}