1use std::fmt;
15
16#[derive(Debug, Clone, Copy)]
18pub struct RequiredHeaders;
19
20impl RequiredHeaders {
21 pub const COOP: &'static str = "same-origin";
23 pub const COEP: &'static str = "require-corp";
25}
26
27#[derive(Debug, Clone, Default)]
29pub struct WasmThreadCapabilities {
30 pub cross_origin_isolated: bool,
32
33 pub shared_array_buffer: bool,
35
36 pub atomics: bool,
38
39 pub hardware_concurrency: u32,
41
42 pub coop_header: Option<String>,
44
45 pub coep_header: Option<String>,
47
48 pub is_secure_context: bool,
50
51 pub errors: Vec<String>,
53}
54
55#[derive(Debug, Clone, PartialEq, Eq)]
57pub enum CapabilityStatus {
58 Available,
60 Unavailable(String),
62 Unknown,
64}
65
66impl WasmThreadCapabilities {
67 #[must_use]
69 pub fn full_support() -> Self {
70 Self {
71 cross_origin_isolated: true,
72 shared_array_buffer: true,
73 atomics: true,
74 hardware_concurrency: 8,
75 coop_header: Some(RequiredHeaders::COOP.to_string()),
76 coep_header: Some(RequiredHeaders::COEP.to_string()),
77 is_secure_context: true,
78 errors: vec![],
79 }
80 }
81
82 #[must_use]
84 pub fn no_support() -> Self {
85 Self {
86 cross_origin_isolated: false,
87 shared_array_buffer: false,
88 atomics: false,
89 hardware_concurrency: 1,
90 coop_header: None,
91 coep_header: None,
92 is_secure_context: false,
93 errors: vec!["SharedArrayBuffer not available".to_string()],
94 }
95 }
96
97 pub fn assert_threading_ready(&self) -> Result<(), CapabilityError> {
102 let mut errors = Vec::new();
103
104 if !self.cross_origin_isolated {
105 errors.push("crossOriginIsolated is false".to_string());
106 }
107
108 if !self.shared_array_buffer {
109 errors.push("SharedArrayBuffer is not available".to_string());
110 }
111
112 if !self.atomics {
113 errors.push("Atomics is not available".to_string());
114 }
115
116 if !self.is_secure_context {
117 errors.push("Page is not served over HTTPS".to_string());
118 }
119
120 match &self.coop_header {
122 Some(value) if value == RequiredHeaders::COOP => {}
123 Some(value) => {
124 errors.push(format!(
125 "COOP header is '{value}', expected '{}'",
126 RequiredHeaders::COOP
127 ));
128 }
129 None => {
130 errors.push(format!(
131 "COOP header missing. Add: Cross-Origin-Opener-Policy: {}",
132 RequiredHeaders::COOP
133 ));
134 }
135 }
136
137 match &self.coep_header {
139 Some(value) if value == RequiredHeaders::COEP => {}
140 Some(value) => {
141 errors.push(format!(
142 "COEP header is '{value}', expected '{}'",
143 RequiredHeaders::COEP
144 ));
145 }
146 None => {
147 errors.push(format!(
148 "COEP header missing. Add: Cross-Origin-Embedder-Policy: {}",
149 RequiredHeaders::COEP
150 ));
151 }
152 }
153
154 if errors.is_empty() {
155 Ok(())
156 } else {
157 Err(CapabilityError::ThreadingNotReady(errors))
158 }
159 }
160
161 pub fn assert_streaming_ready(&self) -> Result<(), CapabilityError> {
168 self.assert_threading_ready()?;
170
171 if self.hardware_concurrency < 2 {
173 return Err(CapabilityError::InsufficientResources(
174 "Streaming requires at least 2 CPU cores".to_string(),
175 ));
176 }
177
178 Ok(())
179 }
180
181 #[must_use]
185 pub fn optimal_threads(&self) -> u32 {
186 self.hardware_concurrency.saturating_sub(1).clamp(1, 8)
187 }
188
189 #[must_use]
191 pub fn is_threading_available(&self) -> bool {
192 self.cross_origin_isolated
193 && self.shared_array_buffer
194 && self.atomics
195 && self.is_secure_context
196 }
197
198 #[must_use]
200 pub fn shared_array_buffer_status(&self) -> CapabilityStatus {
201 if self.shared_array_buffer {
202 CapabilityStatus::Available
203 } else if !self.is_secure_context {
204 CapabilityStatus::Unavailable("Not in secure context (HTTPS required)".to_string())
205 } else if !self.cross_origin_isolated {
206 CapabilityStatus::Unavailable("crossOriginIsolated is false".to_string())
207 } else {
208 CapabilityStatus::Unavailable("Unknown reason".to_string())
209 }
210 }
211
212 #[must_use]
214 pub fn detection_js() -> &'static str {
215 r#"
216(function() {
217 const caps = {
218 crossOriginIsolated: !!self.crossOriginIsolated,
219 sharedArrayBuffer: typeof SharedArrayBuffer !== 'undefined',
220 atomics: typeof Atomics !== 'undefined',
221 hardwareConcurrency: navigator.hardwareConcurrency || 1,
222 isSecureContext: !!self.isSecureContext,
223 coopHeader: null,
224 coepHeader: null
225 };
226
227 // Try to get response headers via fetch
228 // Note: This only works if the server includes them in Access-Control-Expose-Headers
229 return JSON.stringify(caps);
230})();
231"#
232 }
233
234 pub fn from_json(json: &str) -> Result<Self, CapabilityError> {
239 let parsed: serde_json::Value =
240 serde_json::from_str(json).map_err(|e| CapabilityError::ParseError(e.to_string()))?;
241
242 Ok(Self {
243 cross_origin_isolated: parsed["crossOriginIsolated"].as_bool().unwrap_or(false),
244 shared_array_buffer: parsed["sharedArrayBuffer"].as_bool().unwrap_or(false),
245 atomics: parsed["atomics"].as_bool().unwrap_or(false),
246 hardware_concurrency: parsed["hardwareConcurrency"].as_u64().unwrap_or(1) as u32,
247 is_secure_context: parsed["isSecureContext"].as_bool().unwrap_or(false),
248 coop_header: parsed["coopHeader"].as_str().map(String::from),
249 coep_header: parsed["coepHeader"].as_str().map(String::from),
250 errors: vec![],
251 })
252 }
253
254 #[cfg(feature = "browser")]
266 pub async fn detect(page: &chromiumoxide::Page) -> Result<Self, CapabilityError> {
267 let js = Self::detection_js();
268 let result: String = page
269 .evaluate(js)
270 .await
271 .map_err(|e| CapabilityError::ParseError(format!("CDP evaluation failed: {e}")))?
272 .into_value()
273 .map_err(|e| CapabilityError::ParseError(format!("Value extraction failed: {e}")))?;
274
275 Self::from_json(&result)
276 }
277
278 #[cfg(feature = "browser")]
285 pub async fn detect_and_assert(page: &chromiumoxide::Page) -> Result<Self, CapabilityError> {
286 let caps = Self::detect(page).await?;
287 caps.assert_threading_ready()?;
288 Ok(caps)
289 }
290}
291
292#[derive(Debug, Clone)]
294pub enum CapabilityError {
295 ThreadingNotReady(Vec<String>),
297 InsufficientResources(String),
299 ParseError(String),
301 WorkerState {
303 expected: String,
305 actual: String,
307 },
308}
309
310impl fmt::Display for CapabilityError {
311 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
312 match self {
313 Self::ThreadingNotReady(errors) => {
314 writeln!(f, "Threading not ready:")?;
315 for err in errors {
316 writeln!(f, " - {err}")?;
317 }
318 Ok(())
319 }
320 Self::InsufficientResources(msg) => write!(f, "Insufficient resources: {msg}"),
321 Self::ParseError(msg) => write!(f, "Parse error: {msg}"),
322 Self::WorkerState { expected, actual } => {
323 write!(
324 f,
325 "Worker state mismatch: expected {expected}, got {actual}"
326 )
327 }
328 }
329 }
330}
331
332impl std::error::Error for CapabilityError {}
333
334#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
336pub enum WorkerState {
337 Uninitialized,
339 Loading,
341 Ready,
343 Processing,
345 Error,
347 Terminated,
349}
350
351impl Default for WorkerState {
352 fn default() -> Self {
353 Self::Uninitialized
354 }
355}
356
357impl fmt::Display for WorkerState {
358 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
359 match self {
360 Self::Uninitialized => write!(f, "Uninitialized"),
361 Self::Loading => write!(f, "Loading"),
362 Self::Ready => write!(f, "Ready"),
363 Self::Processing => write!(f, "Processing"),
364 Self::Error => write!(f, "Error"),
365 Self::Terminated => write!(f, "Terminated"),
366 }
367 }
368}
369
370#[derive(Debug, Clone)]
372pub struct WorkerMessage {
373 pub type_: String,
375 pub data: serde_json::Value,
377 pub timestamp: f64,
379}
380
381impl WorkerMessage {
382 #[must_use]
384 pub fn new(type_: impl Into<String>, data: serde_json::Value) -> Self {
385 Self {
386 type_: type_.into(),
387 data,
388 timestamp: 0.0,
389 }
390 }
391
392 #[must_use]
394 pub fn with_timestamp(mut self, timestamp: f64) -> Self {
395 self.timestamp = timestamp;
396 self
397 }
398}
399
400#[derive(Debug, Clone)]
419pub struct WorkerEmulator {
420 state: WorkerState,
422 name: String,
424 message_queue: Vec<WorkerMessage>,
426 response_queue: Vec<WorkerMessage>,
428 lamport_clock: u64,
430 simulate_delays: bool,
432 history: Vec<(u64, String, String)>, }
435
436impl Default for WorkerEmulator {
437 fn default() -> Self {
438 Self::new()
439 }
440}
441
442impl WorkerEmulator {
443 #[must_use]
445 pub fn new() -> Self {
446 Self {
447 state: WorkerState::Uninitialized,
448 name: String::new(),
449 message_queue: Vec::new(),
450 response_queue: Vec::new(),
451 lamport_clock: 0,
452 simulate_delays: false,
453 history: Vec::new(),
454 }
455 }
456
457 pub fn spawn(&mut self, name: impl Into<String>) {
459 self.name = name.into();
460 self.state = WorkerState::Loading;
461 self.lamport_clock += 1;
462 self.history
463 .push((self.lamport_clock, "spawn".to_string(), self.name.clone()));
464 }
465
466 #[must_use]
468 pub fn state(&self) -> WorkerState {
469 self.state
470 }
471
472 #[must_use]
474 pub fn name(&self) -> &str {
475 &self.name
476 }
477
478 pub fn send(&mut self, message: WorkerMessage) {
480 self.lamport_clock += 1;
481 self.history.push((
482 self.lamport_clock,
483 "send".to_string(),
484 message.type_.clone(),
485 ));
486 self.message_queue.push(message);
487
488 match self.state {
490 WorkerState::Uninitialized => {
491 self.state = WorkerState::Loading;
492 }
493 WorkerState::Ready => {
494 self.state = WorkerState::Processing;
495 }
496 _ => {}
497 }
498 }
499
500 pub fn receive_response(&mut self, response: WorkerMessage) {
502 self.lamport_clock += 1;
503 self.history.push((
504 self.lamport_clock,
505 "receive".to_string(),
506 response.type_.clone(),
507 ));
508
509 if response.type_ == "Ready" || response.type_ == "ready" {
511 self.state = WorkerState::Ready;
512 } else if response.type_ == "Error" || response.type_ == "error" {
513 self.state = WorkerState::Error;
514 } else if response.type_ == "Complete" || response.type_ == "complete" {
515 self.state = WorkerState::Ready;
516 }
517
518 self.response_queue.push(response);
519 }
520
521 pub fn terminate(&mut self) {
523 self.lamport_clock += 1;
524 self.history
525 .push((self.lamport_clock, "terminate".to_string(), String::new()));
526 self.state = WorkerState::Terminated;
527 }
528
529 #[must_use]
531 pub fn pending_messages(&self) -> &[WorkerMessage] {
532 &self.message_queue
533 }
534
535 #[must_use]
537 pub fn responses(&self) -> &[WorkerMessage] {
538 &self.response_queue
539 }
540
541 #[must_use]
543 pub fn lamport_time(&self) -> u64 {
544 self.lamport_clock
545 }
546
547 #[must_use]
549 pub fn history(&self) -> &[(u64, String, String)] {
550 &self.history
551 }
552
553 pub fn with_delays(mut self, enable: bool) -> Self {
555 self.simulate_delays = enable;
556 self
557 }
558
559 pub fn clear(&mut self) {
561 self.message_queue.clear();
562 self.response_queue.clear();
563 }
564
565 #[must_use]
569 pub fn verify_ordering(&self) -> bool {
570 let mut last_time = 0u64;
571 for (time, _, _) in &self.history {
572 if *time <= last_time {
573 return false;
574 }
575 last_time = *time;
576 }
577 true
578 }
579
580 pub fn assert_state(&self, expected: WorkerState) -> Result<(), CapabilityError> {
585 if self.state == expected {
586 Ok(())
587 } else {
588 Err(CapabilityError::WorkerState {
589 expected: format!("{}", expected),
590 actual: format!("{}", self.state),
591 })
592 }
593 }
594
595 #[must_use]
597 pub fn ready(name: impl Into<String>) -> Self {
598 let mut emulator = Self::new();
599 emulator.spawn(name);
600 emulator.receive_response(WorkerMessage::new("Ready", serde_json::json!({})));
601 emulator
602 }
603
604 #[must_use]
606 pub fn interception_js() -> &'static str {
607 r#"
608(function() {
609 const originalWorker = window.Worker;
610 window.__PROBAR_WORKERS__ = [];
611 window.__PROBAR_WORKER_REFS__ = [];
612 window.__PROBAR_WORKER_MESSAGES__ = [];
613
614 window.Worker = function(url, options) {
615 const worker = new originalWorker(url, options);
616 const id = window.__PROBAR_WORKERS__.length;
617
618 window.__PROBAR_WORKERS__.push({
619 id: id,
620 url: url,
621 state: 'loading',
622 messages: []
623 });
624 window.__PROBAR_WORKER_REFS__.push(worker);
625
626 const originalPostMessage = worker.postMessage.bind(worker);
627 worker.postMessage = function(data, transfer) {
628 window.__PROBAR_WORKER_MESSAGES__.push({
629 workerId: id,
630 direction: 'send',
631 data: JSON.stringify(data),
632 timestamp: performance.now()
633 });
634 return originalPostMessage(data, transfer);
635 };
636
637 worker.addEventListener('message', function(e) {
638 window.__PROBAR_WORKER_MESSAGES__.push({
639 workerId: id,
640 direction: 'receive',
641 data: JSON.stringify(e.data),
642 timestamp: performance.now()
643 });
644 if (e.data && (e.data.type === 'ready' || e.data.type === 'Ready')) {
645 window.__PROBAR_WORKERS__[id].state = 'ready';
646 }
647 });
648
649 worker.addEventListener('error', function(e) {
650 window.__PROBAR_WORKERS__[id].state = 'error';
651 window.__PROBAR_WORKER_MESSAGES__.push({
652 workerId: id,
653 direction: 'error',
654 data: e.message,
655 timestamp: performance.now()
656 });
657 });
658
659 return worker;
660 };
661})();
662"#
663 }
664
665 #[cfg(feature = "browser")]
678 pub async fn attach_cdp(page: &chromiumoxide::Page) -> Result<(), CapabilityError> {
679 let js = Self::interception_js();
680 page.evaluate(js)
681 .await
682 .map_err(|e| CapabilityError::ParseError(format!("CDP attach failed: {e}")))?;
683
684 Ok(())
685 }
686
687 #[cfg(feature = "browser")]
689 pub async fn get_workers_cdp(
690 page: &chromiumoxide::Page,
691 ) -> Result<Vec<serde_json::Value>, CapabilityError> {
692 let json: String = page
693 .evaluate("JSON.stringify(window.__PROBAR_WORKERS__ || [])")
694 .await
695 .map_err(|e| CapabilityError::ParseError(format!("CDP query failed: {e}")))?
696 .into_value()
697 .unwrap_or_else(|_| "[]".to_string());
698
699 serde_json::from_str(&json).map_err(|e| CapabilityError::ParseError(e.to_string()))
700 }
701
702 #[cfg(feature = "browser")]
704 pub async fn get_messages_cdp(
705 page: &chromiumoxide::Page,
706 ) -> Result<Vec<serde_json::Value>, CapabilityError> {
707 let json: String = page
708 .evaluate("JSON.stringify(window.__PROBAR_WORKER_MESSAGES__ || [])")
709 .await
710 .map_err(|e| CapabilityError::ParseError(format!("CDP query failed: {e}")))?
711 .into_value()
712 .unwrap_or_else(|_| "[]".to_string());
713
714 serde_json::from_str(&json).map_err(|e| CapabilityError::ParseError(e.to_string()))
715 }
716
717 #[cfg(feature = "browser")]
721 pub async fn verify_ordering_cdp(page: &chromiumoxide::Page) -> Result<bool, CapabilityError> {
722 let messages = Self::get_messages_cdp(page).await?;
723 let mut last_time = 0.0_f64;
724
725 for msg in messages {
726 let time = msg["timestamp"].as_f64().unwrap_or(0.0);
727 if time < last_time {
728 return Ok(false);
729 }
730 last_time = time;
731 }
732
733 Ok(true)
734 }
735
736 #[cfg(feature = "browser")]
745 pub async fn post_message_cdp(
746 page: &chromiumoxide::Page,
747 worker_id: usize,
748 data: &serde_json::Value,
749 ) -> Result<(), CapabilityError> {
750 let data_json = serde_json::to_string(data)
751 .map_err(|e| CapabilityError::ParseError(format!("JSON serialize failed: {e}")))?;
752
753 let js = format!(
754 r#"
755 (function() {{
756 const workers = window.__PROBAR_WORKERS__ || [];
757 if ({worker_id} >= workers.length) {{
758 return {{ error: 'Worker not found: {worker_id}' }};
759 }}
760 // Get the actual worker reference
761 const workerRefs = window.__PROBAR_WORKER_REFS__ || [];
762 if (workerRefs[{worker_id}]) {{
763 workerRefs[{worker_id}].postMessage({data_json});
764 return {{ success: true }};
765 }}
766 return {{ error: 'Worker reference not available' }};
767 }})()
768 "#
769 );
770
771 let result: serde_json::Value = page
772 .evaluate(js)
773 .await
774 .map_err(|e| CapabilityError::ParseError(format!("CDP call failed: {e}")))?
775 .into_value()
776 .unwrap_or_else(|_| serde_json::json!({"error": "Unknown error"}));
777
778 if result.get("error").is_some() {
779 return Err(CapabilityError::ParseError(
780 result["error"].as_str().unwrap_or("Unknown").to_string(),
781 ));
782 }
783
784 Ok(())
785 }
786
787 #[cfg(feature = "browser")]
799 pub async fn wait_for_message_cdp(
800 page: &chromiumoxide::Page,
801 message_type: &str,
802 timeout: std::time::Duration,
803 ) -> Result<WorkerMessage, CapabilityError> {
804 let start = std::time::Instant::now();
805 let poll_interval = std::time::Duration::from_millis(50);
806
807 while start.elapsed() < timeout {
808 let messages = Self::get_messages_cdp(page).await?;
809
810 for msg in messages {
811 let data_str = msg["data"].as_str().unwrap_or("{}");
812 if let Ok(data) = serde_json::from_str::<serde_json::Value>(data_str) {
813 let type_ = data["type"].as_str().unwrap_or("");
814 if type_ == message_type || type_.eq_ignore_ascii_case(message_type) {
815 return Ok(WorkerMessage {
816 type_: type_.to_string(),
817 data,
818 timestamp: msg["timestamp"].as_f64().unwrap_or(0.0),
819 });
820 }
821 }
822 }
823
824 tokio::time::sleep(poll_interval).await;
825 }
826
827 Err(CapabilityError::ParseError(format!(
828 "Timeout waiting for message type: {message_type}"
829 )))
830 }
831
832 #[cfg(feature = "browser")]
840 pub async fn terminate_cdp(
841 page: &chromiumoxide::Page,
842 worker_id: usize,
843 ) -> Result<(), CapabilityError> {
844 let js = format!(
845 r#"
846 (function() {{
847 const workerRefs = window.__PROBAR_WORKER_REFS__ || [];
848 if (workerRefs[{worker_id}]) {{
849 workerRefs[{worker_id}].terminate();
850 if (window.__PROBAR_WORKERS__ && window.__PROBAR_WORKERS__[{worker_id}]) {{
851 window.__PROBAR_WORKERS__[{worker_id}].state = 'terminated';
852 }}
853 return {{ success: true }};
854 }}
855 return {{ error: 'Worker not found: {worker_id}' }};
856 }})()
857 "#
858 );
859
860 let result: serde_json::Value = page
861 .evaluate(js)
862 .await
863 .map_err(|e| CapabilityError::ParseError(format!("CDP call failed: {e}")))?
864 .into_value()
865 .unwrap_or_else(|_| serde_json::json!({"error": "Unknown error"}));
866
867 if result.get("error").is_some() {
868 return Err(CapabilityError::ParseError(
869 result["error"].as_str().unwrap_or("Unknown").to_string(),
870 ));
871 }
872
873 Ok(())
874 }
875
876 #[cfg(feature = "browser")]
884 pub async fn assert_message_order_cdp(
885 page: &chromiumoxide::Page,
886 expected: &[&str],
887 ) -> Result<(), CapabilityError> {
888 let messages = Self::get_messages_cdp(page).await?;
889
890 let mut expected_iter = expected.iter();
891 let mut current_expected = expected_iter.next();
892
893 for msg in &messages {
894 let data_str = msg["data"].as_str().unwrap_or("{}");
895 if let Ok(data) = serde_json::from_str::<serde_json::Value>(data_str) {
896 let type_ = data["type"].as_str().unwrap_or("");
897 if let Some(exp) = current_expected {
898 if type_.eq_ignore_ascii_case(exp) {
899 current_expected = expected_iter.next();
900 }
901 }
902 }
903 }
904
905 if current_expected.is_some() {
906 return Err(CapabilityError::ParseError(format!(
907 "Message sequence not complete: still waiting for {:?}",
908 current_expected
909 )));
910 }
911
912 Ok(())
913 }
914}
915
916#[cfg(test)]
917#[allow(clippy::unwrap_used, clippy::expect_used)]
918mod tests {
919 use super::*;
920
921 #[test]
926 fn f001_cross_origin_isolated_false() {
927 let caps = WasmThreadCapabilities {
929 cross_origin_isolated: false,
930 shared_array_buffer: true,
931 atomics: true,
932 is_secure_context: true,
933 coop_header: Some("same-origin".to_string()),
934 coep_header: Some("require-corp".to_string()),
935 ..Default::default()
936 };
937 let result = caps.assert_threading_ready();
938 assert!(result.is_err());
939 let err = result.unwrap_err();
940 assert!(err.to_string().contains("crossOriginIsolated"));
941 }
942
943 #[test]
944 fn f002_shared_array_buffer_undefined() {
945 let caps = WasmThreadCapabilities {
947 cross_origin_isolated: true,
948 shared_array_buffer: false,
949 atomics: true,
950 is_secure_context: true,
951 coop_header: Some("same-origin".to_string()),
952 coep_header: Some("require-corp".to_string()),
953 ..Default::default()
954 };
955 let result = caps.assert_threading_ready();
956 assert!(result.is_err());
957 assert!(result
958 .unwrap_err()
959 .to_string()
960 .contains("SharedArrayBuffer"));
961 }
962
963 #[test]
964 fn f003_coop_header_missing() {
965 let caps = WasmThreadCapabilities {
967 cross_origin_isolated: true,
968 shared_array_buffer: true,
969 atomics: true,
970 is_secure_context: true,
971 coop_header: None,
972 coep_header: Some("require-corp".to_string()),
973 ..Default::default()
974 };
975 let result = caps.assert_threading_ready();
976 assert!(result.is_err());
977 let err = result.unwrap_err().to_string();
978 assert!(err.contains("COOP"));
979 assert!(err.contains("Cross-Origin-Opener-Policy")); }
981
982 #[test]
983 fn f004_coep_header_wrong() {
984 let caps = WasmThreadCapabilities {
986 cross_origin_isolated: true,
987 shared_array_buffer: true,
988 atomics: true,
989 is_secure_context: true,
990 coop_header: Some("same-origin".to_string()),
991 coep_header: Some("wrong-value".to_string()),
992 ..Default::default()
993 };
994 let result = caps.assert_threading_ready();
995 assert!(result.is_err());
996 let err = result.unwrap_err().to_string();
997 assert!(err.contains("COEP"));
998 assert!(err.contains("wrong-value"));
999 }
1000
1001 #[test]
1002 fn f005_atomics_blocked() {
1003 let caps = WasmThreadCapabilities {
1005 cross_origin_isolated: true,
1006 shared_array_buffer: true,
1007 atomics: false,
1008 is_secure_context: true,
1009 coop_header: Some("same-origin".to_string()),
1010 coep_header: Some("require-corp".to_string()),
1011 ..Default::default()
1012 };
1013 let result = caps.assert_threading_ready();
1014 assert!(result.is_err());
1015 assert!(result.unwrap_err().to_string().contains("Atomics"));
1016 }
1017
1018 #[test]
1023 fn f006_zero_hardware_concurrency() {
1024 let caps = WasmThreadCapabilities {
1026 hardware_concurrency: 0,
1027 ..Default::default()
1028 };
1029 assert_eq!(caps.optimal_threads(), 1);
1030 }
1031
1032 #[test]
1033 fn f007_many_cores() {
1034 let caps = WasmThreadCapabilities {
1036 hardware_concurrency: 256,
1037 ..Default::default()
1038 };
1039 assert_eq!(caps.optimal_threads(), 8);
1040 }
1041
1042 #[test]
1043 fn f008_single_core_streaming() {
1044 let mut caps = WasmThreadCapabilities::full_support();
1046 caps.hardware_concurrency = 1;
1047 let result = caps.assert_streaming_ready();
1048 assert!(result.is_err());
1049 assert!(result.unwrap_err().to_string().contains("2 CPU cores"));
1050 }
1051
1052 #[test]
1057 fn f011_worker_message_creation() {
1058 let msg = WorkerMessage::new("Init", serde_json::json!({"model": "tiny"}));
1060 assert_eq!(msg.type_, "Init");
1061 assert!(msg.timestamp.abs() < f64::EPSILON);
1062 }
1063
1064 #[test]
1065 fn f012_worker_message_timestamp() {
1066 let msg =
1068 WorkerMessage::new("Transcribe", serde_json::json!({})).with_timestamp(1234567.89);
1069 assert!((msg.timestamp - 1234567.89).abs() < f64::EPSILON);
1070 }
1071
1072 #[test]
1077 fn test_full_support() {
1078 let caps = WasmThreadCapabilities::full_support();
1079 assert!(caps.is_threading_available());
1080 assert!(caps.assert_threading_ready().is_ok());
1081 assert!(caps.assert_streaming_ready().is_ok());
1082 }
1083
1084 #[test]
1085 fn test_no_support() {
1086 let caps = WasmThreadCapabilities::no_support();
1087 assert!(!caps.is_threading_available());
1088 assert!(caps.assert_threading_ready().is_err());
1089 }
1090
1091 #[test]
1092 fn test_optimal_threads_calculation() {
1093 let caps = WasmThreadCapabilities {
1095 hardware_concurrency: 4,
1096 ..Default::default()
1097 };
1098 assert_eq!(caps.optimal_threads(), 3);
1099
1100 let caps = WasmThreadCapabilities {
1102 hardware_concurrency: 8,
1103 ..Default::default()
1104 };
1105 assert_eq!(caps.optimal_threads(), 7);
1106
1107 let caps = WasmThreadCapabilities {
1109 hardware_concurrency: 16,
1110 ..Default::default()
1111 };
1112 assert_eq!(caps.optimal_threads(), 8);
1113 }
1114
1115 #[test]
1116 fn test_capability_status() {
1117 let caps = WasmThreadCapabilities::full_support();
1118 assert_eq!(
1119 caps.shared_array_buffer_status(),
1120 CapabilityStatus::Available
1121 );
1122
1123 let caps = WasmThreadCapabilities::no_support();
1124 matches!(
1125 caps.shared_array_buffer_status(),
1126 CapabilityStatus::Unavailable(_)
1127 );
1128 }
1129
1130 #[test]
1131 fn test_from_json() {
1132 let json = r#"{
1133 "crossOriginIsolated": true,
1134 "sharedArrayBuffer": true,
1135 "atomics": true,
1136 "hardwareConcurrency": 8,
1137 "isSecureContext": true
1138 }"#;
1139
1140 let caps = WasmThreadCapabilities::from_json(json).unwrap();
1141 assert!(caps.cross_origin_isolated);
1142 assert!(caps.shared_array_buffer);
1143 assert!(caps.atomics);
1144 assert_eq!(caps.hardware_concurrency, 8);
1145 assert!(caps.is_secure_context);
1146 }
1147
1148 #[test]
1149 fn test_from_json_invalid() {
1150 let result = WasmThreadCapabilities::from_json("not json");
1151 assert!(result.is_err());
1152 }
1153
1154 #[test]
1155 fn test_worker_state_display() {
1156 assert_eq!(format!("{}", WorkerState::Uninitialized), "Uninitialized");
1157 assert_eq!(format!("{}", WorkerState::Ready), "Ready");
1158 assert_eq!(format!("{}", WorkerState::Processing), "Processing");
1159 }
1160
1161 #[test]
1162 fn test_detection_js() {
1163 let js = WasmThreadCapabilities::detection_js();
1164 assert!(js.contains("crossOriginIsolated"));
1165 assert!(js.contains("SharedArrayBuffer"));
1166 assert!(js.contains("hardwareConcurrency"));
1167 }
1168
1169 #[test]
1170 fn test_required_headers() {
1171 assert_eq!(RequiredHeaders::COOP, "same-origin");
1172 assert_eq!(RequiredHeaders::COEP, "require-corp");
1173 }
1174
1175 #[test]
1180 fn f009_worker_spawn_state() {
1181 let mut emulator = WorkerEmulator::new();
1183 assert_eq!(emulator.state(), WorkerState::Uninitialized);
1184
1185 emulator.spawn("test_worker");
1186 assert_eq!(emulator.state(), WorkerState::Loading);
1187 assert_eq!(emulator.name(), "test_worker");
1188 }
1189
1190 #[test]
1191 fn f010_worker_ready_transition() {
1192 let mut emulator = WorkerEmulator::new();
1194 emulator.spawn("audio_worker");
1195 emulator.receive_response(WorkerMessage::new("Ready", serde_json::json!({})));
1196 assert_eq!(emulator.state(), WorkerState::Ready);
1197 }
1198
1199 #[test]
1200 fn f013_worker_message_ordering() {
1201 let mut emulator = WorkerEmulator::new();
1203 emulator.spawn("worker");
1204 emulator.send(WorkerMessage::new("Init", serde_json::json!({})));
1205 emulator.receive_response(WorkerMessage::new("Ready", serde_json::json!({})));
1206 emulator.send(WorkerMessage::new("Transcribe", serde_json::json!({})));
1207 emulator.terminate();
1208
1209 assert!(emulator.verify_ordering());
1210 assert_eq!(emulator.lamport_time(), 5);
1211 }
1212
1213 #[test]
1214 fn f014_worker_error_state() {
1215 let mut emulator = WorkerEmulator::new();
1217 emulator.spawn("worker");
1218 emulator.receive_response(WorkerMessage::new(
1219 "Error",
1220 serde_json::json!({"msg": "OOM"}),
1221 ));
1222 assert_eq!(emulator.state(), WorkerState::Error);
1223 }
1224
1225 #[test]
1226 fn f015_worker_terminate_state() {
1227 let emulator = WorkerEmulator::ready("worker");
1229 assert_eq!(emulator.state(), WorkerState::Ready);
1230
1231 let mut emulator = emulator;
1232 emulator.terminate();
1233 assert_eq!(emulator.state(), WorkerState::Terminated);
1234 }
1235
1236 #[test]
1237 fn test_worker_assert_state() {
1238 let emulator = WorkerEmulator::ready("test");
1239 assert!(emulator.assert_state(WorkerState::Ready).is_ok());
1240 assert!(emulator.assert_state(WorkerState::Processing).is_err());
1241 }
1242
1243 #[test]
1244 fn test_worker_pending_messages() {
1245 let mut emulator = WorkerEmulator::ready("test");
1246 emulator.send(WorkerMessage::new(
1247 "Process",
1248 serde_json::json!({"data": [1,2,3]}),
1249 ));
1250 assert_eq!(emulator.pending_messages().len(), 1);
1251 assert_eq!(emulator.pending_messages()[0].type_, "Process");
1252 }
1253
1254 #[test]
1255 fn test_worker_clear() {
1256 let mut emulator = WorkerEmulator::ready("test");
1257 emulator.send(WorkerMessage::new("Process", serde_json::json!({})));
1258 emulator.clear();
1259 assert!(emulator.pending_messages().is_empty());
1260 }
1261
1262 #[test]
1267 fn test_capability_error_display_threading_not_ready() {
1268 let err =
1269 CapabilityError::ThreadingNotReady(vec!["Error 1".to_string(), "Error 2".to_string()]);
1270 let display = format!("{}", err);
1271 assert!(display.contains("Threading not ready"));
1272 assert!(display.contains("Error 1"));
1273 assert!(display.contains("Error 2"));
1274 }
1275
1276 #[test]
1277 fn test_capability_error_display_insufficient_resources() {
1278 let err = CapabilityError::InsufficientResources("Not enough memory".to_string());
1279 let display = format!("{}", err);
1280 assert!(display.contains("Insufficient resources"));
1281 assert!(display.contains("Not enough memory"));
1282 }
1283
1284 #[test]
1285 fn test_capability_error_display_parse_error() {
1286 let err = CapabilityError::ParseError("Invalid JSON".to_string());
1287 let display = format!("{}", err);
1288 assert!(display.contains("Parse error"));
1289 assert!(display.contains("Invalid JSON"));
1290 }
1291
1292 #[test]
1293 fn test_capability_error_display_worker_state() {
1294 let err = CapabilityError::WorkerState {
1295 expected: "Ready".to_string(),
1296 actual: "Loading".to_string(),
1297 };
1298 let display = format!("{}", err);
1299 assert!(display.contains("Worker state mismatch"));
1300 assert!(display.contains("Ready"));
1301 assert!(display.contains("Loading"));
1302 }
1303
1304 #[test]
1309 fn test_worker_state_display_all() {
1310 assert_eq!(format!("{}", WorkerState::Loading), "Loading");
1311 assert_eq!(format!("{}", WorkerState::Error), "Error");
1312 assert_eq!(format!("{}", WorkerState::Terminated), "Terminated");
1313 }
1314
1315 #[test]
1316 fn test_worker_state_default() {
1317 let state = WorkerState::default();
1318 assert_eq!(state, WorkerState::Uninitialized);
1319 }
1320
1321 #[test]
1326 fn test_sab_status_not_secure_context() {
1327 let caps = WasmThreadCapabilities {
1328 shared_array_buffer: false,
1329 is_secure_context: false,
1330 cross_origin_isolated: true,
1331 ..Default::default()
1332 };
1333 let status = caps.shared_array_buffer_status();
1334 assert!(
1335 matches!(status, CapabilityStatus::Unavailable(msg) if msg.contains("secure context") || msg.contains("HTTPS"))
1336 );
1337 }
1338
1339 #[test]
1340 fn test_sab_status_not_cross_origin_isolated() {
1341 let caps = WasmThreadCapabilities {
1342 shared_array_buffer: false,
1343 is_secure_context: true,
1344 cross_origin_isolated: false,
1345 ..Default::default()
1346 };
1347 let status = caps.shared_array_buffer_status();
1348 assert!(
1349 matches!(status, CapabilityStatus::Unavailable(msg) if msg.contains("crossOriginIsolated"))
1350 );
1351 }
1352
1353 #[test]
1354 fn test_sab_status_unknown_reason() {
1355 let caps = WasmThreadCapabilities {
1356 shared_array_buffer: false,
1357 is_secure_context: true,
1358 cross_origin_isolated: true,
1359 ..Default::default()
1360 };
1361 let status = caps.shared_array_buffer_status();
1362 assert!(matches!(status, CapabilityStatus::Unavailable(msg) if msg.contains("Unknown")));
1363 }
1364
1365 #[test]
1370 fn test_worker_with_delays() {
1371 let emulator = WorkerEmulator::new().with_delays(true);
1372 assert_eq!(emulator.state(), WorkerState::Uninitialized);
1374 }
1375
1376 #[test]
1377 fn test_worker_responses() {
1378 let emulator = WorkerEmulator::ready("test");
1379 assert!(!emulator.responses().is_empty());
1381 assert_eq!(emulator.responses()[0].type_, "Ready");
1382 }
1383
1384 #[test]
1385 fn test_worker_history() {
1386 let mut emulator = WorkerEmulator::new();
1387 emulator.spawn("worker");
1388 emulator.send(WorkerMessage::new("Test", serde_json::json!({})));
1389 let history = emulator.history();
1390 assert!(!history.is_empty());
1391 assert_eq!(history[0].1, "spawn");
1393 }
1394
1395 #[test]
1396 fn test_worker_send_from_uninitialized() {
1397 let mut emulator = WorkerEmulator::new();
1398 emulator.send(WorkerMessage::new("Init", serde_json::json!({})));
1399 assert_eq!(emulator.state(), WorkerState::Loading);
1401 }
1402
1403 #[test]
1404 fn test_worker_send_from_ready() {
1405 let mut emulator = WorkerEmulator::ready("test");
1406 emulator.send(WorkerMessage::new("Process", serde_json::json!({})));
1407 assert_eq!(emulator.state(), WorkerState::Processing);
1409 }
1410
1411 #[test]
1412 fn test_worker_receive_complete() {
1413 let mut emulator = WorkerEmulator::ready("test");
1414 emulator.send(WorkerMessage::new("Process", serde_json::json!({})));
1415 assert_eq!(emulator.state(), WorkerState::Processing);
1416 emulator.receive_response(WorkerMessage::new("Complete", serde_json::json!({})));
1417 assert_eq!(emulator.state(), WorkerState::Ready);
1419 }
1420
1421 #[test]
1422 fn test_worker_receive_lowercase() {
1423 let mut emulator = WorkerEmulator::new();
1424 emulator.spawn("test");
1425 emulator.receive_response(WorkerMessage::new("ready", serde_json::json!({})));
1427 assert_eq!(emulator.state(), WorkerState::Ready);
1428 }
1429
1430 #[test]
1431 fn test_worker_receive_lowercase_error() {
1432 let mut emulator = WorkerEmulator::new();
1433 emulator.spawn("test");
1434 emulator.receive_response(WorkerMessage::new("error", serde_json::json!({})));
1436 assert_eq!(emulator.state(), WorkerState::Error);
1437 }
1438
1439 #[test]
1440 fn test_worker_receive_lowercase_complete() {
1441 let mut emulator = WorkerEmulator::ready("test");
1442 emulator.send(WorkerMessage::new("Process", serde_json::json!({})));
1443 emulator.receive_response(WorkerMessage::new("complete", serde_json::json!({})));
1445 assert_eq!(emulator.state(), WorkerState::Ready);
1446 }
1447
1448 #[test]
1449 fn test_interception_js() {
1450 let js = WorkerEmulator::interception_js();
1451 assert!(js.contains("originalWorker"));
1452 assert!(js.contains("__PROBAR_WORKERS__"));
1453 assert!(js.contains("postMessage"));
1454 }
1455
1456 #[test]
1457 fn test_from_json_with_headers() {
1458 let json = r#"{
1459 "crossOriginIsolated": true,
1460 "sharedArrayBuffer": true,
1461 "atomics": true,
1462 "hardwareConcurrency": 4,
1463 "isSecureContext": true,
1464 "coopHeader": "same-origin",
1465 "coepHeader": "require-corp"
1466 }"#;
1467 let caps = WasmThreadCapabilities::from_json(json).unwrap();
1468 assert_eq!(caps.coop_header, Some("same-origin".to_string()));
1469 assert_eq!(caps.coep_header, Some("require-corp".to_string()));
1470 }
1471
1472 #[test]
1473 fn test_from_json_defaults() {
1474 let json = r#"{}"#;
1476 let caps = WasmThreadCapabilities::from_json(json).unwrap();
1477 assert!(!caps.cross_origin_isolated);
1478 assert!(!caps.shared_array_buffer);
1479 assert!(!caps.atomics);
1480 assert_eq!(caps.hardware_concurrency, 1);
1481 assert!(!caps.is_secure_context);
1482 }
1483
1484 #[test]
1485 fn test_capability_status_eq() {
1486 assert_eq!(CapabilityStatus::Available, CapabilityStatus::Available);
1487 assert_eq!(CapabilityStatus::Unknown, CapabilityStatus::Unknown);
1488 assert_eq!(
1489 CapabilityStatus::Unavailable("test".to_string()),
1490 CapabilityStatus::Unavailable("test".to_string())
1491 );
1492 assert_ne!(CapabilityStatus::Available, CapabilityStatus::Unknown);
1493 }
1494
1495 #[test]
1496 fn test_assert_threading_not_secure() {
1497 let caps = WasmThreadCapabilities {
1499 cross_origin_isolated: true,
1500 shared_array_buffer: true,
1501 atomics: true,
1502 is_secure_context: false,
1503 coop_header: Some("same-origin".to_string()),
1504 coep_header: Some("require-corp".to_string()),
1505 ..Default::default()
1506 };
1507 let result = caps.assert_threading_ready();
1508 assert!(result.is_err());
1509 assert!(result.unwrap_err().to_string().contains("HTTPS"));
1510 }
1511
1512 #[test]
1513 fn test_assert_threading_wrong_coop() {
1514 let caps = WasmThreadCapabilities {
1515 cross_origin_isolated: true,
1516 shared_array_buffer: true,
1517 atomics: true,
1518 is_secure_context: true,
1519 coop_header: Some("wrong-value".to_string()),
1520 coep_header: Some("require-corp".to_string()),
1521 ..Default::default()
1522 };
1523 let result = caps.assert_threading_ready();
1524 assert!(result.is_err());
1525 let err = result.unwrap_err().to_string();
1526 assert!(err.contains("COOP"));
1527 assert!(err.contains("wrong-value"));
1528 }
1529
1530 #[test]
1531 fn test_assert_threading_missing_coep() {
1532 let caps = WasmThreadCapabilities {
1533 cross_origin_isolated: true,
1534 shared_array_buffer: true,
1535 atomics: true,
1536 is_secure_context: true,
1537 coop_header: Some("same-origin".to_string()),
1538 coep_header: None,
1539 ..Default::default()
1540 };
1541 let result = caps.assert_threading_ready();
1542 assert!(result.is_err());
1543 let err = result.unwrap_err().to_string();
1544 assert!(err.contains("COEP"));
1545 assert!(err.contains("Cross-Origin-Embedder-Policy"));
1546 }
1547
1548 #[test]
1553 fn test_worker_emulator_default() {
1554 let emulator = WorkerEmulator::default();
1555 assert_eq!(emulator.state(), WorkerState::Uninitialized);
1556 assert!(emulator.name().is_empty());
1557 assert!(emulator.pending_messages().is_empty());
1558 assert!(emulator.responses().is_empty());
1559 assert_eq!(emulator.lamport_time(), 0);
1560 }
1561
1562 #[test]
1563 fn test_worker_emulator_debug() {
1564 let emulator = WorkerEmulator::new();
1565 let debug_str = format!("{:?}", emulator);
1566 assert!(debug_str.contains("WorkerEmulator"));
1567 }
1568
1569 #[test]
1570 fn test_worker_emulator_clone() {
1571 let mut emulator = WorkerEmulator::new();
1572 emulator.spawn("test-worker");
1573 let cloned = emulator.clone();
1574 assert_eq!(emulator.name(), cloned.name());
1575 assert_eq!(emulator.state(), cloned.state());
1576 }
1577
1578 #[test]
1579 fn test_worker_send_from_processing_state() {
1580 let mut emulator = WorkerEmulator::ready("test");
1581 emulator.send(WorkerMessage::new("Task1", serde_json::json!({})));
1582 assert_eq!(emulator.state(), WorkerState::Processing);
1583 emulator.send(WorkerMessage::new("Task2", serde_json::json!({})));
1585 assert_eq!(emulator.state(), WorkerState::Processing);
1586 }
1587
1588 #[test]
1589 fn test_worker_send_from_error_state() {
1590 let mut emulator = WorkerEmulator::new();
1591 emulator.spawn("test");
1592 emulator.receive_response(WorkerMessage::new("Error", serde_json::json!({})));
1593 assert_eq!(emulator.state(), WorkerState::Error);
1594 emulator.send(WorkerMessage::new("Retry", serde_json::json!({})));
1596 assert_eq!(emulator.state(), WorkerState::Error);
1597 }
1598
1599 #[test]
1600 fn test_worker_send_from_terminated_state() {
1601 let mut emulator = WorkerEmulator::ready("test");
1602 emulator.terminate();
1603 assert_eq!(emulator.state(), WorkerState::Terminated);
1604 emulator.send(WorkerMessage::new("Test", serde_json::json!({})));
1606 assert_eq!(emulator.state(), WorkerState::Terminated);
1607 }
1608
1609 #[test]
1610 fn test_worker_receive_unknown_type() {
1611 let mut emulator = WorkerEmulator::new();
1612 emulator.spawn("test");
1613 emulator.receive_response(WorkerMessage::new("CustomType", serde_json::json!({})));
1615 assert_eq!(emulator.state(), WorkerState::Loading);
1617 }
1618
1619 #[test]
1620 fn test_worker_verify_ordering_empty() {
1621 let emulator = WorkerEmulator::new();
1622 assert!(emulator.verify_ordering());
1623 }
1624
1625 #[test]
1626 fn test_worker_verify_ordering_single() {
1627 let mut emulator = WorkerEmulator::new();
1628 emulator.spawn("test");
1629 assert!(emulator.verify_ordering());
1630 }
1631
1632 #[test]
1633 fn test_worker_verify_ordering_fails_with_duplicate_timestamps() {
1634 let mut emulator = WorkerEmulator::new();
1638 emulator.spawn("test");
1641 emulator.send(WorkerMessage::new("A", serde_json::json!({})));
1642 assert!(emulator.verify_ordering());
1644 }
1645
1646 #[test]
1651 fn test_wasm_thread_capabilities_default() {
1652 let caps = WasmThreadCapabilities::default();
1653 assert!(!caps.cross_origin_isolated);
1654 assert!(!caps.shared_array_buffer);
1655 assert!(!caps.atomics);
1656 assert_eq!(caps.hardware_concurrency, 0);
1657 assert!(caps.coop_header.is_none());
1658 assert!(caps.coep_header.is_none());
1659 assert!(!caps.is_secure_context);
1660 assert!(caps.errors.is_empty());
1661 }
1662
1663 #[test]
1664 fn test_wasm_thread_capabilities_debug() {
1665 let caps = WasmThreadCapabilities::full_support();
1666 let debug_str = format!("{:?}", caps);
1667 assert!(debug_str.contains("WasmThreadCapabilities"));
1668 }
1669
1670 #[test]
1671 fn test_wasm_thread_capabilities_clone() {
1672 let caps = WasmThreadCapabilities::full_support();
1673 let cloned = caps.clone();
1674 assert_eq!(caps.cross_origin_isolated, cloned.cross_origin_isolated);
1675 assert_eq!(caps.hardware_concurrency, cloned.hardware_concurrency);
1676 }
1677
1678 #[test]
1679 fn test_no_support_has_error() {
1680 let caps = WasmThreadCapabilities::no_support();
1681 assert!(!caps.errors.is_empty());
1682 assert!(caps.errors[0].contains("SharedArrayBuffer"));
1683 }
1684
1685 #[test]
1686 fn test_optimal_threads_one_core() {
1687 let caps = WasmThreadCapabilities {
1688 hardware_concurrency: 1,
1689 ..Default::default()
1690 };
1691 assert_eq!(caps.optimal_threads(), 1);
1693 }
1694
1695 #[test]
1696 fn test_optimal_threads_two_cores() {
1697 let caps = WasmThreadCapabilities {
1698 hardware_concurrency: 2,
1699 ..Default::default()
1700 };
1701 assert_eq!(caps.optimal_threads(), 1);
1702 }
1703
1704 #[test]
1705 fn test_assert_streaming_ready_success() {
1706 let caps = WasmThreadCapabilities::full_support();
1707 assert!(caps.assert_streaming_ready().is_ok());
1708 }
1709
1710 #[test]
1711 fn test_assert_streaming_ready_threading_fails() {
1712 let caps = WasmThreadCapabilities::no_support();
1713 let result = caps.assert_streaming_ready();
1714 assert!(result.is_err());
1715 }
1716
1717 #[test]
1722 fn test_capability_status_debug() {
1723 let status = CapabilityStatus::Available;
1724 let debug_str = format!("{:?}", status);
1725 assert!(debug_str.contains("Available"));
1726
1727 let status = CapabilityStatus::Unknown;
1728 let debug_str = format!("{:?}", status);
1729 assert!(debug_str.contains("Unknown"));
1730
1731 let status = CapabilityStatus::Unavailable("test".to_string());
1732 let debug_str = format!("{:?}", status);
1733 assert!(debug_str.contains("Unavailable"));
1734 }
1735
1736 #[test]
1737 fn test_capability_status_clone() {
1738 let status = CapabilityStatus::Unavailable("reason".to_string());
1739 let cloned = status.clone();
1740 assert_eq!(status, cloned);
1741 }
1742
1743 #[test]
1748 fn test_worker_state_copy() {
1749 let state = WorkerState::Ready;
1750 let copied = state;
1751 assert_eq!(state, copied);
1752 }
1753
1754 #[test]
1755 fn test_worker_state_hash() {
1756 use std::collections::HashSet;
1757 let mut set = HashSet::new();
1758 set.insert(WorkerState::Ready);
1759 set.insert(WorkerState::Processing);
1760 assert!(set.contains(&WorkerState::Ready));
1761 assert!(set.contains(&WorkerState::Processing));
1762 assert!(!set.contains(&WorkerState::Error));
1763 }
1764
1765 #[test]
1770 fn test_worker_message_debug() {
1771 let msg = WorkerMessage::new("Test", serde_json::json!({}));
1772 let debug_str = format!("{:?}", msg);
1773 assert!(debug_str.contains("WorkerMessage"));
1774 assert!(debug_str.contains("Test"));
1775 }
1776
1777 #[test]
1778 fn test_worker_message_clone() {
1779 let msg =
1780 WorkerMessage::new("Test", serde_json::json!({"key": "value"})).with_timestamp(123.456);
1781 let cloned = msg.clone();
1782 assert_eq!(msg.type_, cloned.type_);
1783 assert_eq!(msg.data, cloned.data);
1784 assert!((msg.timestamp - cloned.timestamp).abs() < f64::EPSILON);
1785 }
1786
1787 #[test]
1792 fn test_required_headers_debug() {
1793 let headers = RequiredHeaders;
1794 let debug_str = format!("{:?}", headers);
1795 assert!(debug_str.contains("RequiredHeaders"));
1796 }
1797
1798 #[test]
1799 fn test_required_headers_clone() {
1800 let headers = RequiredHeaders;
1801 let _ = headers;
1802 let cloned = headers;
1804 let _ = cloned;
1805 }
1806
1807 #[test]
1812 fn test_capability_error_debug() {
1813 let err = CapabilityError::ParseError("test".to_string());
1814 let debug_str = format!("{:?}", err);
1815 assert!(debug_str.contains("ParseError"));
1816 }
1817
1818 #[test]
1819 fn test_capability_error_clone() {
1820 let err = CapabilityError::InsufficientResources("memory".to_string());
1821 let cloned = err.clone();
1822 assert_eq!(err.to_string(), cloned.to_string());
1823 }
1824
1825 #[test]
1826 fn test_capability_error_is_error_trait() {
1827 let err: Box<dyn std::error::Error> =
1828 Box::new(CapabilityError::ParseError("test".to_string()));
1829 assert!(err.to_string().contains("Parse error"));
1830 }
1831
1832 #[test]
1833 fn test_capability_error_source() {
1834 use std::error::Error;
1835 let err = CapabilityError::ParseError("test".to_string());
1836 assert!(err.source().is_none());
1838 }
1839
1840 #[test]
1845 fn test_from_json_partial_fields() {
1846 let json = r#"{
1847 "crossOriginIsolated": true,
1848 "atomics": false
1849 }"#;
1850 let caps = WasmThreadCapabilities::from_json(json).unwrap();
1851 assert!(caps.cross_origin_isolated);
1852 assert!(!caps.atomics);
1853 assert!(!caps.shared_array_buffer);
1855 assert_eq!(caps.hardware_concurrency, 1);
1856 }
1857
1858 #[test]
1859 fn test_from_json_null_values() {
1860 let json = r#"{
1861 "crossOriginIsolated": null,
1862 "sharedArrayBuffer": null,
1863 "hardwareConcurrency": null
1864 }"#;
1865 let caps = WasmThreadCapabilities::from_json(json).unwrap();
1866 assert!(!caps.cross_origin_isolated);
1868 assert!(!caps.shared_array_buffer);
1869 assert_eq!(caps.hardware_concurrency, 1);
1870 }
1871
1872 #[test]
1877 fn test_assert_threading_multiple_failures() {
1878 let caps = WasmThreadCapabilities {
1879 cross_origin_isolated: false,
1880 shared_array_buffer: false,
1881 atomics: false,
1882 is_secure_context: false,
1883 coop_header: None,
1884 coep_header: None,
1885 ..Default::default()
1886 };
1887 let result = caps.assert_threading_ready();
1888 assert!(result.is_err());
1889 let err = result.unwrap_err().to_string();
1890 assert!(err.contains("crossOriginIsolated"));
1892 assert!(err.contains("SharedArrayBuffer"));
1893 assert!(err.contains("Atomics"));
1894 assert!(err.contains("HTTPS"));
1895 assert!(err.contains("COOP"));
1896 assert!(err.contains("COEP"));
1897 }
1898
1899 #[test]
1904 fn test_is_threading_available_partial() {
1905 let caps = WasmThreadCapabilities {
1907 cross_origin_isolated: true,
1908 shared_array_buffer: true,
1909 atomics: false,
1910 is_secure_context: true,
1911 ..Default::default()
1912 };
1913 assert!(!caps.is_threading_available());
1914 }
1915
1916 #[test]
1917 fn test_assert_state_error_message() {
1918 let emulator = WorkerEmulator::ready("test");
1919 let result = emulator.assert_state(WorkerState::Processing);
1920 assert!(result.is_err());
1921 let err = result.unwrap_err();
1922 match err {
1923 CapabilityError::WorkerState { expected, actual } => {
1924 assert_eq!(expected, "Processing");
1925 assert_eq!(actual, "Ready");
1926 }
1927 _ => panic!("Expected WorkerState error"),
1928 }
1929 }
1930
1931 #[test]
1932 fn test_worker_send_from_loading() {
1933 let mut emulator = WorkerEmulator::new();
1934 emulator.spawn("test");
1935 assert_eq!(emulator.state(), WorkerState::Loading);
1936 emulator.send(WorkerMessage::new("Init", serde_json::json!({})));
1938 assert_eq!(emulator.state(), WorkerState::Loading);
1939 }
1940
1941 #[test]
1942 fn test_worker_multiple_responses() {
1943 let mut emulator = WorkerEmulator::new();
1944 emulator.spawn("test");
1945 emulator.receive_response(WorkerMessage::new("Progress", serde_json::json!({})));
1946 emulator.receive_response(WorkerMessage::new("Progress", serde_json::json!({})));
1947 emulator.receive_response(WorkerMessage::new("Ready", serde_json::json!({})));
1948 assert_eq!(emulator.responses().len(), 3);
1949 assert_eq!(emulator.state(), WorkerState::Ready);
1950 }
1951
1952 #[test]
1953 fn test_worker_lamport_increments() {
1954 let mut emulator = WorkerEmulator::new();
1955 assert_eq!(emulator.lamport_time(), 0);
1956 emulator.spawn("test");
1957 assert_eq!(emulator.lamport_time(), 1);
1958 emulator.send(WorkerMessage::new("A", serde_json::json!({})));
1959 assert_eq!(emulator.lamport_time(), 2);
1960 emulator.receive_response(WorkerMessage::new("B", serde_json::json!({})));
1961 assert_eq!(emulator.lamport_time(), 3);
1962 emulator.terminate();
1963 assert_eq!(emulator.lamport_time(), 4);
1964 }
1965
1966 #[test]
1967 fn test_worker_history_entries() {
1968 let mut emulator = WorkerEmulator::new();
1969 emulator.spawn("my-worker");
1970 emulator.send(WorkerMessage::new("Init", serde_json::json!({})));
1971 emulator.receive_response(WorkerMessage::new("Ready", serde_json::json!({})));
1972 emulator.terminate();
1973
1974 let history = emulator.history();
1975 assert_eq!(history.len(), 4);
1976
1977 assert_eq!(history[0].1, "spawn");
1978 assert_eq!(history[0].2, "my-worker");
1979
1980 assert_eq!(history[1].1, "send");
1981 assert_eq!(history[1].2, "Init");
1982
1983 assert_eq!(history[2].1, "receive");
1984 assert_eq!(history[2].2, "Ready");
1985
1986 assert_eq!(history[3].1, "terminate");
1987 }
1988
1989 #[test]
1990 fn test_worker_clear_preserves_state() {
1991 let mut emulator = WorkerEmulator::ready("test");
1992 emulator.send(WorkerMessage::new("Task", serde_json::json!({})));
1993 emulator.receive_response(WorkerMessage::new("Done", serde_json::json!({})));
1994
1995 let state_before = emulator.state();
1996 emulator.clear();
1997
1998 assert!(emulator.pending_messages().is_empty());
1999 assert!(emulator.responses().is_empty());
2000 assert_eq!(emulator.state(), state_before);
2002 }
2003
2004 #[test]
2009 fn test_shared_array_buffer_status_available() {
2010 let caps = WasmThreadCapabilities::full_support();
2011 assert_eq!(
2012 caps.shared_array_buffer_status(),
2013 CapabilityStatus::Available
2014 );
2015 }
2016
2017 #[test]
2018 fn test_shared_array_buffer_status_not_secure() {
2019 let caps = WasmThreadCapabilities {
2020 shared_array_buffer: false,
2021 is_secure_context: false,
2022 cross_origin_isolated: true,
2023 ..Default::default()
2024 };
2025 match caps.shared_array_buffer_status() {
2026 CapabilityStatus::Unavailable(reason) => {
2027 assert!(reason.contains("HTTPS"));
2028 }
2029 _ => panic!("Expected Unavailable"),
2030 }
2031 }
2032
2033 #[test]
2034 fn test_shared_array_buffer_status_not_cross_origin() {
2035 let caps = WasmThreadCapabilities {
2036 shared_array_buffer: false,
2037 is_secure_context: true,
2038 cross_origin_isolated: false,
2039 ..Default::default()
2040 };
2041 match caps.shared_array_buffer_status() {
2042 CapabilityStatus::Unavailable(reason) => {
2043 assert!(reason.contains("crossOriginIsolated"));
2044 }
2045 _ => panic!("Expected Unavailable"),
2046 }
2047 }
2048
2049 #[test]
2050 fn test_shared_array_buffer_status_unknown() {
2051 let caps = WasmThreadCapabilities {
2052 shared_array_buffer: false,
2053 is_secure_context: true,
2054 cross_origin_isolated: true,
2055 ..Default::default()
2056 };
2057 match caps.shared_array_buffer_status() {
2058 CapabilityStatus::Unavailable(reason) => {
2059 assert!(reason.contains("Unknown"));
2060 }
2061 _ => panic!("Expected Unavailable"),
2062 }
2063 }
2064
2065 #[test]
2066 fn test_from_json_valid_with_headers() {
2067 let json = r#"{
2068 "crossOriginIsolated": true,
2069 "sharedArrayBuffer": true,
2070 "atomics": true,
2071 "hardwareConcurrency": 8,
2072 "isSecureContext": true,
2073 "coopHeader": "same-origin",
2074 "coepHeader": "require-corp"
2075 }"#;
2076 let caps = WasmThreadCapabilities::from_json(json).unwrap();
2077 assert!(caps.cross_origin_isolated);
2078 assert!(caps.shared_array_buffer);
2079 assert!(caps.atomics);
2080 assert_eq!(caps.hardware_concurrency, 8);
2081 assert!(caps.is_secure_context);
2082 assert_eq!(caps.coop_header, Some("same-origin".to_string()));
2083 assert_eq!(caps.coep_header, Some("require-corp".to_string()));
2084 }
2085
2086 #[test]
2087 fn test_from_json_minimal_defaults() {
2088 let json = r#"{}"#;
2089 let caps = WasmThreadCapabilities::from_json(json).unwrap();
2090 assert!(!caps.cross_origin_isolated);
2091 assert!(!caps.shared_array_buffer);
2092 assert_eq!(caps.hardware_concurrency, 1);
2093 }
2094
2095 #[test]
2096 fn test_capability_status_unknown_match() {
2097 let status = CapabilityStatus::Unknown;
2098 assert!(matches!(status, CapabilityStatus::Unknown));
2099 }
2100
2101 #[test]
2102 fn test_required_headers_values() {
2103 assert_eq!(RequiredHeaders::COOP, "same-origin");
2104 assert_eq!(RequiredHeaders::COEP, "require-corp");
2105 }
2106}