use std::fmt;
#[derive(Debug, Clone, Copy)]
pub struct RequiredHeaders;
impl RequiredHeaders {
pub const COOP: &'static str = "same-origin";
pub const COEP: &'static str = "require-corp";
}
#[derive(Debug, Clone, Default)]
pub struct WasmThreadCapabilities {
pub cross_origin_isolated: bool,
pub shared_array_buffer: bool,
pub atomics: bool,
pub hardware_concurrency: u32,
pub coop_header: Option<String>,
pub coep_header: Option<String>,
pub is_secure_context: bool,
pub errors: Vec<String>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum CapabilityStatus {
Available,
Unavailable(String),
Unknown,
}
impl WasmThreadCapabilities {
#[must_use]
pub fn full_support() -> Self {
Self {
cross_origin_isolated: true,
shared_array_buffer: true,
atomics: true,
hardware_concurrency: 8,
coop_header: Some(RequiredHeaders::COOP.to_string()),
coep_header: Some(RequiredHeaders::COEP.to_string()),
is_secure_context: true,
errors: vec![],
}
}
#[must_use]
pub fn no_support() -> Self {
Self {
cross_origin_isolated: false,
shared_array_buffer: false,
atomics: false,
hardware_concurrency: 1,
coop_header: None,
coep_header: None,
is_secure_context: false,
errors: vec!["SharedArrayBuffer not available".to_string()],
}
}
pub fn assert_threading_ready(&self) -> Result<(), CapabilityError> {
let mut errors = Vec::new();
if !self.cross_origin_isolated {
errors.push("crossOriginIsolated is false".to_string());
}
if !self.shared_array_buffer {
errors.push("SharedArrayBuffer is not available".to_string());
}
if !self.atomics {
errors.push("Atomics is not available".to_string());
}
if !self.is_secure_context {
errors.push("Page is not served over HTTPS".to_string());
}
match &self.coop_header {
Some(value) if value == RequiredHeaders::COOP => {}
Some(value) => {
errors.push(format!(
"COOP header is '{value}', expected '{}'",
RequiredHeaders::COOP
));
}
None => {
errors.push(format!(
"COOP header missing. Add: Cross-Origin-Opener-Policy: {}",
RequiredHeaders::COOP
));
}
}
match &self.coep_header {
Some(value) if value == RequiredHeaders::COEP => {}
Some(value) => {
errors.push(format!(
"COEP header is '{value}', expected '{}'",
RequiredHeaders::COEP
));
}
None => {
errors.push(format!(
"COEP header missing. Add: Cross-Origin-Embedder-Policy: {}",
RequiredHeaders::COEP
));
}
}
if errors.is_empty() {
Ok(())
} else {
Err(CapabilityError::ThreadingNotReady(errors))
}
}
pub fn assert_streaming_ready(&self) -> Result<(), CapabilityError> {
self.assert_threading_ready()?;
if self.hardware_concurrency < 2 {
return Err(CapabilityError::InsufficientResources(
"Streaming requires at least 2 CPU cores".to_string(),
));
}
Ok(())
}
#[must_use]
pub fn optimal_threads(&self) -> u32 {
self.hardware_concurrency.saturating_sub(1).clamp(1, 8)
}
#[must_use]
pub fn is_threading_available(&self) -> bool {
self.cross_origin_isolated
&& self.shared_array_buffer
&& self.atomics
&& self.is_secure_context
}
#[must_use]
pub fn shared_array_buffer_status(&self) -> CapabilityStatus {
if self.shared_array_buffer {
CapabilityStatus::Available
} else if !self.is_secure_context {
CapabilityStatus::Unavailable("Not in secure context (HTTPS required)".to_string())
} else if !self.cross_origin_isolated {
CapabilityStatus::Unavailable("crossOriginIsolated is false".to_string())
} else {
CapabilityStatus::Unavailable("Unknown reason".to_string())
}
}
#[must_use]
pub fn detection_js() -> &'static str {
r#"
(function() {
const caps = {
crossOriginIsolated: !!self.crossOriginIsolated,
sharedArrayBuffer: typeof SharedArrayBuffer !== 'undefined',
atomics: typeof Atomics !== 'undefined',
hardwareConcurrency: navigator.hardwareConcurrency || 1,
isSecureContext: !!self.isSecureContext,
coopHeader: null,
coepHeader: null
};
// Try to get response headers via fetch
// Note: This only works if the server includes them in Access-Control-Expose-Headers
return JSON.stringify(caps);
})();
"#
}
pub fn from_json(json: &str) -> Result<Self, CapabilityError> {
let parsed: serde_json::Value =
serde_json::from_str(json).map_err(|e| CapabilityError::ParseError(e.to_string()))?;
Ok(Self {
cross_origin_isolated: parsed["crossOriginIsolated"].as_bool().unwrap_or(false),
shared_array_buffer: parsed["sharedArrayBuffer"].as_bool().unwrap_or(false),
atomics: parsed["atomics"].as_bool().unwrap_or(false),
hardware_concurrency: parsed["hardwareConcurrency"].as_u64().unwrap_or(1) as u32,
is_secure_context: parsed["isSecureContext"].as_bool().unwrap_or(false),
coop_header: parsed["coopHeader"].as_str().map(String::from),
coep_header: parsed["coepHeader"].as_str().map(String::from),
errors: vec![],
})
}
#[cfg(feature = "browser")]
pub async fn detect(page: &chromiumoxide::Page) -> Result<Self, CapabilityError> {
let js = Self::detection_js();
let result: String = page
.evaluate(js)
.await
.map_err(|e| CapabilityError::ParseError(format!("CDP evaluation failed: {e}")))?
.into_value()
.map_err(|e| CapabilityError::ParseError(format!("Value extraction failed: {e}")))?;
Self::from_json(&result)
}
#[cfg(feature = "browser")]
pub async fn detect_and_assert(page: &chromiumoxide::Page) -> Result<Self, CapabilityError> {
let caps = Self::detect(page).await?;
caps.assert_threading_ready()?;
Ok(caps)
}
}
#[derive(Debug, Clone)]
pub enum CapabilityError {
ThreadingNotReady(Vec<String>),
InsufficientResources(String),
ParseError(String),
WorkerState {
expected: String,
actual: String,
},
}
impl fmt::Display for CapabilityError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::ThreadingNotReady(errors) => {
writeln!(f, "Threading not ready:")?;
for err in errors {
writeln!(f, " - {err}")?;
}
Ok(())
}
Self::InsufficientResources(msg) => write!(f, "Insufficient resources: {msg}"),
Self::ParseError(msg) => write!(f, "Parse error: {msg}"),
Self::WorkerState { expected, actual } => {
write!(
f,
"Worker state mismatch: expected {expected}, got {actual}"
)
}
}
}
}
impl std::error::Error for CapabilityError {}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum WorkerState {
Uninitialized,
Loading,
Ready,
Processing,
Error,
Terminated,
}
impl Default for WorkerState {
fn default() -> Self {
Self::Uninitialized
}
}
impl fmt::Display for WorkerState {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Uninitialized => write!(f, "Uninitialized"),
Self::Loading => write!(f, "Loading"),
Self::Ready => write!(f, "Ready"),
Self::Processing => write!(f, "Processing"),
Self::Error => write!(f, "Error"),
Self::Terminated => write!(f, "Terminated"),
}
}
}
#[derive(Debug, Clone)]
pub struct WorkerMessage {
pub type_: String,
pub data: serde_json::Value,
pub timestamp: f64,
}
impl WorkerMessage {
#[must_use]
pub fn new(type_: impl Into<String>, data: serde_json::Value) -> Self {
Self {
type_: type_.into(),
data,
timestamp: 0.0,
}
}
#[must_use]
pub fn with_timestamp(mut self, timestamp: f64) -> Self {
self.timestamp = timestamp;
self
}
}
#[derive(Debug, Clone)]
pub struct WorkerEmulator {
state: WorkerState,
name: String,
message_queue: Vec<WorkerMessage>,
response_queue: Vec<WorkerMessage>,
lamport_clock: u64,
simulate_delays: bool,
history: Vec<(u64, String, String)>, }
impl Default for WorkerEmulator {
fn default() -> Self {
Self::new()
}
}
impl WorkerEmulator {
#[must_use]
pub fn new() -> Self {
Self {
state: WorkerState::Uninitialized,
name: String::new(),
message_queue: Vec::new(),
response_queue: Vec::new(),
lamport_clock: 0,
simulate_delays: false,
history: Vec::new(),
}
}
pub fn spawn(&mut self, name: impl Into<String>) {
self.name = name.into();
self.state = WorkerState::Loading;
self.lamport_clock += 1;
self.history
.push((self.lamport_clock, "spawn".to_string(), self.name.clone()));
}
#[must_use]
pub fn state(&self) -> WorkerState {
self.state
}
#[must_use]
pub fn name(&self) -> &str {
&self.name
}
pub fn send(&mut self, message: WorkerMessage) {
self.lamport_clock += 1;
self.history.push((
self.lamport_clock,
"send".to_string(),
message.type_.clone(),
));
self.message_queue.push(message);
match self.state {
WorkerState::Uninitialized => {
self.state = WorkerState::Loading;
}
WorkerState::Ready => {
self.state = WorkerState::Processing;
}
_ => {}
}
}
pub fn receive_response(&mut self, response: WorkerMessage) {
self.lamport_clock += 1;
self.history.push((
self.lamport_clock,
"receive".to_string(),
response.type_.clone(),
));
if response.type_ == "Ready" || response.type_ == "ready" {
self.state = WorkerState::Ready;
} else if response.type_ == "Error" || response.type_ == "error" {
self.state = WorkerState::Error;
} else if response.type_ == "Complete" || response.type_ == "complete" {
self.state = WorkerState::Ready;
}
self.response_queue.push(response);
}
pub fn terminate(&mut self) {
self.lamport_clock += 1;
self.history
.push((self.lamport_clock, "terminate".to_string(), String::new()));
self.state = WorkerState::Terminated;
}
#[must_use]
pub fn pending_messages(&self) -> &[WorkerMessage] {
&self.message_queue
}
#[must_use]
pub fn responses(&self) -> &[WorkerMessage] {
&self.response_queue
}
#[must_use]
pub fn lamport_time(&self) -> u64 {
self.lamport_clock
}
#[must_use]
pub fn history(&self) -> &[(u64, String, String)] {
&self.history
}
pub fn with_delays(mut self, enable: bool) -> Self {
self.simulate_delays = enable;
self
}
pub fn clear(&mut self) {
self.message_queue.clear();
self.response_queue.clear();
}
#[must_use]
pub fn verify_ordering(&self) -> bool {
let mut last_time = 0u64;
for (time, _, _) in &self.history {
if *time <= last_time {
return false;
}
last_time = *time;
}
true
}
pub fn assert_state(&self, expected: WorkerState) -> Result<(), CapabilityError> {
if self.state == expected {
Ok(())
} else {
Err(CapabilityError::WorkerState {
expected: format!("{}", expected),
actual: format!("{}", self.state),
})
}
}
#[must_use]
pub fn ready(name: impl Into<String>) -> Self {
let mut emulator = Self::new();
emulator.spawn(name);
emulator.receive_response(WorkerMessage::new("Ready", serde_json::json!({})));
emulator
}
#[must_use]
pub fn interception_js() -> &'static str {
r#"
(function() {
const originalWorker = window.Worker;
window.__PROBAR_WORKERS__ = [];
window.__PROBAR_WORKER_REFS__ = [];
window.__PROBAR_WORKER_MESSAGES__ = [];
window.Worker = function(url, options) {
const worker = new originalWorker(url, options);
const id = window.__PROBAR_WORKERS__.length;
window.__PROBAR_WORKERS__.push({
id: id,
url: url,
state: 'loading',
messages: []
});
window.__PROBAR_WORKER_REFS__.push(worker);
const originalPostMessage = worker.postMessage.bind(worker);
worker.postMessage = function(data, transfer) {
window.__PROBAR_WORKER_MESSAGES__.push({
workerId: id,
direction: 'send',
data: JSON.stringify(data),
timestamp: performance.now()
});
return originalPostMessage(data, transfer);
};
worker.addEventListener('message', function(e) {
window.__PROBAR_WORKER_MESSAGES__.push({
workerId: id,
direction: 'receive',
data: JSON.stringify(e.data),
timestamp: performance.now()
});
if (e.data && (e.data.type === 'ready' || e.data.type === 'Ready')) {
window.__PROBAR_WORKERS__[id].state = 'ready';
}
});
worker.addEventListener('error', function(e) {
window.__PROBAR_WORKERS__[id].state = 'error';
window.__PROBAR_WORKER_MESSAGES__.push({
workerId: id,
direction: 'error',
data: e.message,
timestamp: performance.now()
});
});
return worker;
};
})();
"#
}
#[cfg(feature = "browser")]
pub async fn attach_cdp(page: &chromiumoxide::Page) -> Result<(), CapabilityError> {
let js = Self::interception_js();
page.evaluate(js)
.await
.map_err(|e| CapabilityError::ParseError(format!("CDP attach failed: {e}")))?;
Ok(())
}
#[cfg(feature = "browser")]
pub async fn get_workers_cdp(
page: &chromiumoxide::Page,
) -> Result<Vec<serde_json::Value>, CapabilityError> {
let json: String = page
.evaluate("JSON.stringify(window.__PROBAR_WORKERS__ || [])")
.await
.map_err(|e| CapabilityError::ParseError(format!("CDP query failed: {e}")))?
.into_value()
.unwrap_or_else(|_| "[]".to_string());
serde_json::from_str(&json).map_err(|e| CapabilityError::ParseError(e.to_string()))
}
#[cfg(feature = "browser")]
pub async fn get_messages_cdp(
page: &chromiumoxide::Page,
) -> Result<Vec<serde_json::Value>, CapabilityError> {
let json: String = page
.evaluate("JSON.stringify(window.__PROBAR_WORKER_MESSAGES__ || [])")
.await
.map_err(|e| CapabilityError::ParseError(format!("CDP query failed: {e}")))?
.into_value()
.unwrap_or_else(|_| "[]".to_string());
serde_json::from_str(&json).map_err(|e| CapabilityError::ParseError(e.to_string()))
}
#[cfg(feature = "browser")]
pub async fn verify_ordering_cdp(page: &chromiumoxide::Page) -> Result<bool, CapabilityError> {
let messages = Self::get_messages_cdp(page).await?;
let mut last_time = 0.0_f64;
for msg in messages {
let time = msg["timestamp"].as_f64().unwrap_or(0.0);
if time < last_time {
return Ok(false);
}
last_time = time;
}
Ok(true)
}
#[cfg(feature = "browser")]
pub async fn post_message_cdp(
page: &chromiumoxide::Page,
worker_id: usize,
data: &serde_json::Value,
) -> Result<(), CapabilityError> {
let data_json = serde_json::to_string(data)
.map_err(|e| CapabilityError::ParseError(format!("JSON serialize failed: {e}")))?;
let js = format!(
r#"
(function() {{
const workers = window.__PROBAR_WORKERS__ || [];
if ({worker_id} >= workers.length) {{
return {{ error: 'Worker not found: {worker_id}' }};
}}
// Get the actual worker reference
const workerRefs = window.__PROBAR_WORKER_REFS__ || [];
if (workerRefs[{worker_id}]) {{
workerRefs[{worker_id}].postMessage({data_json});
return {{ success: true }};
}}
return {{ error: 'Worker reference not available' }};
}})()
"#
);
let result: serde_json::Value = page
.evaluate(js)
.await
.map_err(|e| CapabilityError::ParseError(format!("CDP call failed: {e}")))?
.into_value()
.unwrap_or_else(|_| serde_json::json!({"error": "Unknown error"}));
if result.get("error").is_some() {
return Err(CapabilityError::ParseError(
result["error"].as_str().unwrap_or("Unknown").to_string(),
));
}
Ok(())
}
#[cfg(feature = "browser")]
pub async fn wait_for_message_cdp(
page: &chromiumoxide::Page,
message_type: &str,
timeout: std::time::Duration,
) -> Result<WorkerMessage, CapabilityError> {
let start = std::time::Instant::now();
let poll_interval = std::time::Duration::from_millis(50);
while start.elapsed() < timeout {
let messages = Self::get_messages_cdp(page).await?;
for msg in messages {
let data_str = msg["data"].as_str().unwrap_or("{}");
if let Ok(data) = serde_json::from_str::<serde_json::Value>(data_str) {
let type_ = data["type"].as_str().unwrap_or("");
if type_ == message_type || type_.eq_ignore_ascii_case(message_type) {
return Ok(WorkerMessage {
type_: type_.to_string(),
data,
timestamp: msg["timestamp"].as_f64().unwrap_or(0.0),
});
}
}
}
tokio::time::sleep(poll_interval).await;
}
Err(CapabilityError::ParseError(format!(
"Timeout waiting for message type: {message_type}"
)))
}
#[cfg(feature = "browser")]
pub async fn terminate_cdp(
page: &chromiumoxide::Page,
worker_id: usize,
) -> Result<(), CapabilityError> {
let js = format!(
r#"
(function() {{
const workerRefs = window.__PROBAR_WORKER_REFS__ || [];
if (workerRefs[{worker_id}]) {{
workerRefs[{worker_id}].terminate();
if (window.__PROBAR_WORKERS__ && window.__PROBAR_WORKERS__[{worker_id}]) {{
window.__PROBAR_WORKERS__[{worker_id}].state = 'terminated';
}}
return {{ success: true }};
}}
return {{ error: 'Worker not found: {worker_id}' }};
}})()
"#
);
let result: serde_json::Value = page
.evaluate(js)
.await
.map_err(|e| CapabilityError::ParseError(format!("CDP call failed: {e}")))?
.into_value()
.unwrap_or_else(|_| serde_json::json!({"error": "Unknown error"}));
if result.get("error").is_some() {
return Err(CapabilityError::ParseError(
result["error"].as_str().unwrap_or("Unknown").to_string(),
));
}
Ok(())
}
#[cfg(feature = "browser")]
pub async fn assert_message_order_cdp(
page: &chromiumoxide::Page,
expected: &[&str],
) -> Result<(), CapabilityError> {
let messages = Self::get_messages_cdp(page).await?;
let mut expected_iter = expected.iter();
let mut current_expected = expected_iter.next();
for msg in &messages {
let data_str = msg["data"].as_str().unwrap_or("{}");
if let Ok(data) = serde_json::from_str::<serde_json::Value>(data_str) {
let type_ = data["type"].as_str().unwrap_or("");
if let Some(exp) = current_expected {
if type_.eq_ignore_ascii_case(exp) {
current_expected = expected_iter.next();
}
}
}
}
if current_expected.is_some() {
return Err(CapabilityError::ParseError(format!(
"Message sequence not complete: still waiting for {:?}",
current_expected
)));
}
Ok(())
}
}
#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::expect_used)]
mod tests {
use super::*;
#[test]
fn f001_cross_origin_isolated_false() {
let caps = WasmThreadCapabilities {
cross_origin_isolated: false,
shared_array_buffer: true,
atomics: true,
is_secure_context: true,
coop_header: Some("same-origin".to_string()),
coep_header: Some("require-corp".to_string()),
..Default::default()
};
let result = caps.assert_threading_ready();
assert!(result.is_err());
let err = result.unwrap_err();
assert!(err.to_string().contains("crossOriginIsolated"));
}
#[test]
fn f002_shared_array_buffer_undefined() {
let caps = WasmThreadCapabilities {
cross_origin_isolated: true,
shared_array_buffer: false,
atomics: true,
is_secure_context: true,
coop_header: Some("same-origin".to_string()),
coep_header: Some("require-corp".to_string()),
..Default::default()
};
let result = caps.assert_threading_ready();
assert!(result.is_err());
assert!(result
.unwrap_err()
.to_string()
.contains("SharedArrayBuffer"));
}
#[test]
fn f003_coop_header_missing() {
let caps = WasmThreadCapabilities {
cross_origin_isolated: true,
shared_array_buffer: true,
atomics: true,
is_secure_context: true,
coop_header: None,
coep_header: Some("require-corp".to_string()),
..Default::default()
};
let result = caps.assert_threading_ready();
assert!(result.is_err());
let err = result.unwrap_err().to_string();
assert!(err.contains("COOP"));
assert!(err.contains("Cross-Origin-Opener-Policy")); }
#[test]
fn f004_coep_header_wrong() {
let caps = WasmThreadCapabilities {
cross_origin_isolated: true,
shared_array_buffer: true,
atomics: true,
is_secure_context: true,
coop_header: Some("same-origin".to_string()),
coep_header: Some("wrong-value".to_string()),
..Default::default()
};
let result = caps.assert_threading_ready();
assert!(result.is_err());
let err = result.unwrap_err().to_string();
assert!(err.contains("COEP"));
assert!(err.contains("wrong-value"));
}
#[test]
fn f005_atomics_blocked() {
let caps = WasmThreadCapabilities {
cross_origin_isolated: true,
shared_array_buffer: true,
atomics: false,
is_secure_context: true,
coop_header: Some("same-origin".to_string()),
coep_header: Some("require-corp".to_string()),
..Default::default()
};
let result = caps.assert_threading_ready();
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("Atomics"));
}
#[test]
fn f006_zero_hardware_concurrency() {
let caps = WasmThreadCapabilities {
hardware_concurrency: 0,
..Default::default()
};
assert_eq!(caps.optimal_threads(), 1);
}
#[test]
fn f007_many_cores() {
let caps = WasmThreadCapabilities {
hardware_concurrency: 256,
..Default::default()
};
assert_eq!(caps.optimal_threads(), 8);
}
#[test]
fn f008_single_core_streaming() {
let mut caps = WasmThreadCapabilities::full_support();
caps.hardware_concurrency = 1;
let result = caps.assert_streaming_ready();
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("2 CPU cores"));
}
#[test]
fn f011_worker_message_creation() {
let msg = WorkerMessage::new("Init", serde_json::json!({"model": "tiny"}));
assert_eq!(msg.type_, "Init");
assert!(msg.timestamp.abs() < f64::EPSILON);
}
#[test]
fn f012_worker_message_timestamp() {
let msg =
WorkerMessage::new("Transcribe", serde_json::json!({})).with_timestamp(1234567.89);
assert!((msg.timestamp - 1234567.89).abs() < f64::EPSILON);
}
#[test]
fn test_full_support() {
let caps = WasmThreadCapabilities::full_support();
assert!(caps.is_threading_available());
assert!(caps.assert_threading_ready().is_ok());
assert!(caps.assert_streaming_ready().is_ok());
}
#[test]
fn test_no_support() {
let caps = WasmThreadCapabilities::no_support();
assert!(!caps.is_threading_available());
assert!(caps.assert_threading_ready().is_err());
}
#[test]
fn test_optimal_threads_calculation() {
let caps = WasmThreadCapabilities {
hardware_concurrency: 4,
..Default::default()
};
assert_eq!(caps.optimal_threads(), 3);
let caps = WasmThreadCapabilities {
hardware_concurrency: 8,
..Default::default()
};
assert_eq!(caps.optimal_threads(), 7);
let caps = WasmThreadCapabilities {
hardware_concurrency: 16,
..Default::default()
};
assert_eq!(caps.optimal_threads(), 8);
}
#[test]
fn test_capability_status() {
let caps = WasmThreadCapabilities::full_support();
assert_eq!(
caps.shared_array_buffer_status(),
CapabilityStatus::Available
);
let caps = WasmThreadCapabilities::no_support();
matches!(
caps.shared_array_buffer_status(),
CapabilityStatus::Unavailable(_)
);
}
#[test]
fn test_from_json() {
let json = r#"{
"crossOriginIsolated": true,
"sharedArrayBuffer": true,
"atomics": true,
"hardwareConcurrency": 8,
"isSecureContext": true
}"#;
let caps = WasmThreadCapabilities::from_json(json).unwrap();
assert!(caps.cross_origin_isolated);
assert!(caps.shared_array_buffer);
assert!(caps.atomics);
assert_eq!(caps.hardware_concurrency, 8);
assert!(caps.is_secure_context);
}
#[test]
fn test_from_json_invalid() {
let result = WasmThreadCapabilities::from_json("not json");
assert!(result.is_err());
}
#[test]
fn test_worker_state_display() {
assert_eq!(format!("{}", WorkerState::Uninitialized), "Uninitialized");
assert_eq!(format!("{}", WorkerState::Ready), "Ready");
assert_eq!(format!("{}", WorkerState::Processing), "Processing");
}
#[test]
fn test_detection_js() {
let js = WasmThreadCapabilities::detection_js();
assert!(js.contains("crossOriginIsolated"));
assert!(js.contains("SharedArrayBuffer"));
assert!(js.contains("hardwareConcurrency"));
}
#[test]
fn test_required_headers() {
assert_eq!(RequiredHeaders::COOP, "same-origin");
assert_eq!(RequiredHeaders::COEP, "require-corp");
}
#[test]
fn f009_worker_spawn_state() {
let mut emulator = WorkerEmulator::new();
assert_eq!(emulator.state(), WorkerState::Uninitialized);
emulator.spawn("test_worker");
assert_eq!(emulator.state(), WorkerState::Loading);
assert_eq!(emulator.name(), "test_worker");
}
#[test]
fn f010_worker_ready_transition() {
let mut emulator = WorkerEmulator::new();
emulator.spawn("audio_worker");
emulator.receive_response(WorkerMessage::new("Ready", serde_json::json!({})));
assert_eq!(emulator.state(), WorkerState::Ready);
}
#[test]
fn f013_worker_message_ordering() {
let mut emulator = WorkerEmulator::new();
emulator.spawn("worker");
emulator.send(WorkerMessage::new("Init", serde_json::json!({})));
emulator.receive_response(WorkerMessage::new("Ready", serde_json::json!({})));
emulator.send(WorkerMessage::new("Transcribe", serde_json::json!({})));
emulator.terminate();
assert!(emulator.verify_ordering());
assert_eq!(emulator.lamport_time(), 5);
}
#[test]
fn f014_worker_error_state() {
let mut emulator = WorkerEmulator::new();
emulator.spawn("worker");
emulator.receive_response(WorkerMessage::new(
"Error",
serde_json::json!({"msg": "OOM"}),
));
assert_eq!(emulator.state(), WorkerState::Error);
}
#[test]
fn f015_worker_terminate_state() {
let emulator = WorkerEmulator::ready("worker");
assert_eq!(emulator.state(), WorkerState::Ready);
let mut emulator = emulator;
emulator.terminate();
assert_eq!(emulator.state(), WorkerState::Terminated);
}
#[test]
fn test_worker_assert_state() {
let emulator = WorkerEmulator::ready("test");
assert!(emulator.assert_state(WorkerState::Ready).is_ok());
assert!(emulator.assert_state(WorkerState::Processing).is_err());
}
#[test]
fn test_worker_pending_messages() {
let mut emulator = WorkerEmulator::ready("test");
emulator.send(WorkerMessage::new(
"Process",
serde_json::json!({"data": [1,2,3]}),
));
assert_eq!(emulator.pending_messages().len(), 1);
assert_eq!(emulator.pending_messages()[0].type_, "Process");
}
#[test]
fn test_worker_clear() {
let mut emulator = WorkerEmulator::ready("test");
emulator.send(WorkerMessage::new("Process", serde_json::json!({})));
emulator.clear();
assert!(emulator.pending_messages().is_empty());
}
#[test]
fn test_capability_error_display_threading_not_ready() {
let err =
CapabilityError::ThreadingNotReady(vec!["Error 1".to_string(), "Error 2".to_string()]);
let display = format!("{}", err);
assert!(display.contains("Threading not ready"));
assert!(display.contains("Error 1"));
assert!(display.contains("Error 2"));
}
#[test]
fn test_capability_error_display_insufficient_resources() {
let err = CapabilityError::InsufficientResources("Not enough memory".to_string());
let display = format!("{}", err);
assert!(display.contains("Insufficient resources"));
assert!(display.contains("Not enough memory"));
}
#[test]
fn test_capability_error_display_parse_error() {
let err = CapabilityError::ParseError("Invalid JSON".to_string());
let display = format!("{}", err);
assert!(display.contains("Parse error"));
assert!(display.contains("Invalid JSON"));
}
#[test]
fn test_capability_error_display_worker_state() {
let err = CapabilityError::WorkerState {
expected: "Ready".to_string(),
actual: "Loading".to_string(),
};
let display = format!("{}", err);
assert!(display.contains("Worker state mismatch"));
assert!(display.contains("Ready"));
assert!(display.contains("Loading"));
}
#[test]
fn test_worker_state_display_all() {
assert_eq!(format!("{}", WorkerState::Loading), "Loading");
assert_eq!(format!("{}", WorkerState::Error), "Error");
assert_eq!(format!("{}", WorkerState::Terminated), "Terminated");
}
#[test]
fn test_worker_state_default() {
let state = WorkerState::default();
assert_eq!(state, WorkerState::Uninitialized);
}
#[test]
fn test_sab_status_not_secure_context() {
let caps = WasmThreadCapabilities {
shared_array_buffer: false,
is_secure_context: false,
cross_origin_isolated: true,
..Default::default()
};
let status = caps.shared_array_buffer_status();
assert!(
matches!(status, CapabilityStatus::Unavailable(msg) if msg.contains("secure context") || msg.contains("HTTPS"))
);
}
#[test]
fn test_sab_status_not_cross_origin_isolated() {
let caps = WasmThreadCapabilities {
shared_array_buffer: false,
is_secure_context: true,
cross_origin_isolated: false,
..Default::default()
};
let status = caps.shared_array_buffer_status();
assert!(
matches!(status, CapabilityStatus::Unavailable(msg) if msg.contains("crossOriginIsolated"))
);
}
#[test]
fn test_sab_status_unknown_reason() {
let caps = WasmThreadCapabilities {
shared_array_buffer: false,
is_secure_context: true,
cross_origin_isolated: true,
..Default::default()
};
let status = caps.shared_array_buffer_status();
assert!(matches!(status, CapabilityStatus::Unavailable(msg) if msg.contains("Unknown")));
}
#[test]
fn test_worker_with_delays() {
let emulator = WorkerEmulator::new().with_delays(true);
assert_eq!(emulator.state(), WorkerState::Uninitialized);
}
#[test]
fn test_worker_responses() {
let emulator = WorkerEmulator::ready("test");
assert!(!emulator.responses().is_empty());
assert_eq!(emulator.responses()[0].type_, "Ready");
}
#[test]
fn test_worker_history() {
let mut emulator = WorkerEmulator::new();
emulator.spawn("worker");
emulator.send(WorkerMessage::new("Test", serde_json::json!({})));
let history = emulator.history();
assert!(!history.is_empty());
assert_eq!(history[0].1, "spawn");
}
#[test]
fn test_worker_send_from_uninitialized() {
let mut emulator = WorkerEmulator::new();
emulator.send(WorkerMessage::new("Init", serde_json::json!({})));
assert_eq!(emulator.state(), WorkerState::Loading);
}
#[test]
fn test_worker_send_from_ready() {
let mut emulator = WorkerEmulator::ready("test");
emulator.send(WorkerMessage::new("Process", serde_json::json!({})));
assert_eq!(emulator.state(), WorkerState::Processing);
}
#[test]
fn test_worker_receive_complete() {
let mut emulator = WorkerEmulator::ready("test");
emulator.send(WorkerMessage::new("Process", serde_json::json!({})));
assert_eq!(emulator.state(), WorkerState::Processing);
emulator.receive_response(WorkerMessage::new("Complete", serde_json::json!({})));
assert_eq!(emulator.state(), WorkerState::Ready);
}
#[test]
fn test_worker_receive_lowercase() {
let mut emulator = WorkerEmulator::new();
emulator.spawn("test");
emulator.receive_response(WorkerMessage::new("ready", serde_json::json!({})));
assert_eq!(emulator.state(), WorkerState::Ready);
}
#[test]
fn test_worker_receive_lowercase_error() {
let mut emulator = WorkerEmulator::new();
emulator.spawn("test");
emulator.receive_response(WorkerMessage::new("error", serde_json::json!({})));
assert_eq!(emulator.state(), WorkerState::Error);
}
#[test]
fn test_worker_receive_lowercase_complete() {
let mut emulator = WorkerEmulator::ready("test");
emulator.send(WorkerMessage::new("Process", serde_json::json!({})));
emulator.receive_response(WorkerMessage::new("complete", serde_json::json!({})));
assert_eq!(emulator.state(), WorkerState::Ready);
}
#[test]
fn test_interception_js() {
let js = WorkerEmulator::interception_js();
assert!(js.contains("originalWorker"));
assert!(js.contains("__PROBAR_WORKERS__"));
assert!(js.contains("postMessage"));
}
#[test]
fn test_from_json_with_headers() {
let json = r#"{
"crossOriginIsolated": true,
"sharedArrayBuffer": true,
"atomics": true,
"hardwareConcurrency": 4,
"isSecureContext": true,
"coopHeader": "same-origin",
"coepHeader": "require-corp"
}"#;
let caps = WasmThreadCapabilities::from_json(json).unwrap();
assert_eq!(caps.coop_header, Some("same-origin".to_string()));
assert_eq!(caps.coep_header, Some("require-corp".to_string()));
}
#[test]
fn test_from_json_defaults() {
let json = r#"{}"#;
let caps = WasmThreadCapabilities::from_json(json).unwrap();
assert!(!caps.cross_origin_isolated);
assert!(!caps.shared_array_buffer);
assert!(!caps.atomics);
assert_eq!(caps.hardware_concurrency, 1);
assert!(!caps.is_secure_context);
}
#[test]
fn test_capability_status_eq() {
assert_eq!(CapabilityStatus::Available, CapabilityStatus::Available);
assert_eq!(CapabilityStatus::Unknown, CapabilityStatus::Unknown);
assert_eq!(
CapabilityStatus::Unavailable("test".to_string()),
CapabilityStatus::Unavailable("test".to_string())
);
assert_ne!(CapabilityStatus::Available, CapabilityStatus::Unknown);
}
#[test]
fn test_assert_threading_not_secure() {
let caps = WasmThreadCapabilities {
cross_origin_isolated: true,
shared_array_buffer: true,
atomics: true,
is_secure_context: false,
coop_header: Some("same-origin".to_string()),
coep_header: Some("require-corp".to_string()),
..Default::default()
};
let result = caps.assert_threading_ready();
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("HTTPS"));
}
#[test]
fn test_assert_threading_wrong_coop() {
let caps = WasmThreadCapabilities {
cross_origin_isolated: true,
shared_array_buffer: true,
atomics: true,
is_secure_context: true,
coop_header: Some("wrong-value".to_string()),
coep_header: Some("require-corp".to_string()),
..Default::default()
};
let result = caps.assert_threading_ready();
assert!(result.is_err());
let err = result.unwrap_err().to_string();
assert!(err.contains("COOP"));
assert!(err.contains("wrong-value"));
}
#[test]
fn test_assert_threading_missing_coep() {
let caps = WasmThreadCapabilities {
cross_origin_isolated: true,
shared_array_buffer: true,
atomics: true,
is_secure_context: true,
coop_header: Some("same-origin".to_string()),
coep_header: None,
..Default::default()
};
let result = caps.assert_threading_ready();
assert!(result.is_err());
let err = result.unwrap_err().to_string();
assert!(err.contains("COEP"));
assert!(err.contains("Cross-Origin-Embedder-Policy"));
}
#[test]
fn test_worker_emulator_default() {
let emulator = WorkerEmulator::default();
assert_eq!(emulator.state(), WorkerState::Uninitialized);
assert!(emulator.name().is_empty());
assert!(emulator.pending_messages().is_empty());
assert!(emulator.responses().is_empty());
assert_eq!(emulator.lamport_time(), 0);
}
#[test]
fn test_worker_emulator_debug() {
let emulator = WorkerEmulator::new();
let debug_str = format!("{:?}", emulator);
assert!(debug_str.contains("WorkerEmulator"));
}
#[test]
fn test_worker_emulator_clone() {
let mut emulator = WorkerEmulator::new();
emulator.spawn("test-worker");
let cloned = emulator.clone();
assert_eq!(emulator.name(), cloned.name());
assert_eq!(emulator.state(), cloned.state());
}
#[test]
fn test_worker_send_from_processing_state() {
let mut emulator = WorkerEmulator::ready("test");
emulator.send(WorkerMessage::new("Task1", serde_json::json!({})));
assert_eq!(emulator.state(), WorkerState::Processing);
emulator.send(WorkerMessage::new("Task2", serde_json::json!({})));
assert_eq!(emulator.state(), WorkerState::Processing);
}
#[test]
fn test_worker_send_from_error_state() {
let mut emulator = WorkerEmulator::new();
emulator.spawn("test");
emulator.receive_response(WorkerMessage::new("Error", serde_json::json!({})));
assert_eq!(emulator.state(), WorkerState::Error);
emulator.send(WorkerMessage::new("Retry", serde_json::json!({})));
assert_eq!(emulator.state(), WorkerState::Error);
}
#[test]
fn test_worker_send_from_terminated_state() {
let mut emulator = WorkerEmulator::ready("test");
emulator.terminate();
assert_eq!(emulator.state(), WorkerState::Terminated);
emulator.send(WorkerMessage::new("Test", serde_json::json!({})));
assert_eq!(emulator.state(), WorkerState::Terminated);
}
#[test]
fn test_worker_receive_unknown_type() {
let mut emulator = WorkerEmulator::new();
emulator.spawn("test");
emulator.receive_response(WorkerMessage::new("CustomType", serde_json::json!({})));
assert_eq!(emulator.state(), WorkerState::Loading);
}
#[test]
fn test_worker_verify_ordering_empty() {
let emulator = WorkerEmulator::new();
assert!(emulator.verify_ordering());
}
#[test]
fn test_worker_verify_ordering_single() {
let mut emulator = WorkerEmulator::new();
emulator.spawn("test");
assert!(emulator.verify_ordering());
}
#[test]
fn test_worker_verify_ordering_fails_with_duplicate_timestamps() {
let mut emulator = WorkerEmulator::new();
emulator.spawn("test");
emulator.send(WorkerMessage::new("A", serde_json::json!({})));
assert!(emulator.verify_ordering());
}
#[test]
fn test_wasm_thread_capabilities_default() {
let caps = WasmThreadCapabilities::default();
assert!(!caps.cross_origin_isolated);
assert!(!caps.shared_array_buffer);
assert!(!caps.atomics);
assert_eq!(caps.hardware_concurrency, 0);
assert!(caps.coop_header.is_none());
assert!(caps.coep_header.is_none());
assert!(!caps.is_secure_context);
assert!(caps.errors.is_empty());
}
#[test]
fn test_wasm_thread_capabilities_debug() {
let caps = WasmThreadCapabilities::full_support();
let debug_str = format!("{:?}", caps);
assert!(debug_str.contains("WasmThreadCapabilities"));
}
#[test]
fn test_wasm_thread_capabilities_clone() {
let caps = WasmThreadCapabilities::full_support();
let cloned = caps.clone();
assert_eq!(caps.cross_origin_isolated, cloned.cross_origin_isolated);
assert_eq!(caps.hardware_concurrency, cloned.hardware_concurrency);
}
#[test]
fn test_no_support_has_error() {
let caps = WasmThreadCapabilities::no_support();
assert!(!caps.errors.is_empty());
assert!(caps.errors[0].contains("SharedArrayBuffer"));
}
#[test]
fn test_optimal_threads_one_core() {
let caps = WasmThreadCapabilities {
hardware_concurrency: 1,
..Default::default()
};
assert_eq!(caps.optimal_threads(), 1);
}
#[test]
fn test_optimal_threads_two_cores() {
let caps = WasmThreadCapabilities {
hardware_concurrency: 2,
..Default::default()
};
assert_eq!(caps.optimal_threads(), 1);
}
#[test]
fn test_assert_streaming_ready_success() {
let caps = WasmThreadCapabilities::full_support();
assert!(caps.assert_streaming_ready().is_ok());
}
#[test]
fn test_assert_streaming_ready_threading_fails() {
let caps = WasmThreadCapabilities::no_support();
let result = caps.assert_streaming_ready();
assert!(result.is_err());
}
#[test]
fn test_capability_status_debug() {
let status = CapabilityStatus::Available;
let debug_str = format!("{:?}", status);
assert!(debug_str.contains("Available"));
let status = CapabilityStatus::Unknown;
let debug_str = format!("{:?}", status);
assert!(debug_str.contains("Unknown"));
let status = CapabilityStatus::Unavailable("test".to_string());
let debug_str = format!("{:?}", status);
assert!(debug_str.contains("Unavailable"));
}
#[test]
fn test_capability_status_clone() {
let status = CapabilityStatus::Unavailable("reason".to_string());
let cloned = status.clone();
assert_eq!(status, cloned);
}
#[test]
fn test_worker_state_copy() {
let state = WorkerState::Ready;
let copied = state;
assert_eq!(state, copied);
}
#[test]
fn test_worker_state_hash() {
use std::collections::HashSet;
let mut set = HashSet::new();
set.insert(WorkerState::Ready);
set.insert(WorkerState::Processing);
assert!(set.contains(&WorkerState::Ready));
assert!(set.contains(&WorkerState::Processing));
assert!(!set.contains(&WorkerState::Error));
}
#[test]
fn test_worker_message_debug() {
let msg = WorkerMessage::new("Test", serde_json::json!({}));
let debug_str = format!("{:?}", msg);
assert!(debug_str.contains("WorkerMessage"));
assert!(debug_str.contains("Test"));
}
#[test]
fn test_worker_message_clone() {
let msg =
WorkerMessage::new("Test", serde_json::json!({"key": "value"})).with_timestamp(123.456);
let cloned = msg.clone();
assert_eq!(msg.type_, cloned.type_);
assert_eq!(msg.data, cloned.data);
assert!((msg.timestamp - cloned.timestamp).abs() < f64::EPSILON);
}
#[test]
fn test_required_headers_debug() {
let headers = RequiredHeaders;
let debug_str = format!("{:?}", headers);
assert!(debug_str.contains("RequiredHeaders"));
}
#[test]
fn test_required_headers_clone() {
let headers = RequiredHeaders;
let _ = headers;
let cloned = headers;
let _ = cloned;
}
#[test]
fn test_capability_error_debug() {
let err = CapabilityError::ParseError("test".to_string());
let debug_str = format!("{:?}", err);
assert!(debug_str.contains("ParseError"));
}
#[test]
fn test_capability_error_clone() {
let err = CapabilityError::InsufficientResources("memory".to_string());
let cloned = err.clone();
assert_eq!(err.to_string(), cloned.to_string());
}
#[test]
fn test_capability_error_is_error_trait() {
let err: Box<dyn std::error::Error> =
Box::new(CapabilityError::ParseError("test".to_string()));
assert!(err.to_string().contains("Parse error"));
}
#[test]
fn test_capability_error_source() {
use std::error::Error;
let err = CapabilityError::ParseError("test".to_string());
assert!(err.source().is_none());
}
#[test]
fn test_from_json_partial_fields() {
let json = r#"{
"crossOriginIsolated": true,
"atomics": false
}"#;
let caps = WasmThreadCapabilities::from_json(json).unwrap();
assert!(caps.cross_origin_isolated);
assert!(!caps.atomics);
assert!(!caps.shared_array_buffer);
assert_eq!(caps.hardware_concurrency, 1);
}
#[test]
fn test_from_json_null_values() {
let json = r#"{
"crossOriginIsolated": null,
"sharedArrayBuffer": null,
"hardwareConcurrency": null
}"#;
let caps = WasmThreadCapabilities::from_json(json).unwrap();
assert!(!caps.cross_origin_isolated);
assert!(!caps.shared_array_buffer);
assert_eq!(caps.hardware_concurrency, 1);
}
#[test]
fn test_assert_threading_multiple_failures() {
let caps = WasmThreadCapabilities {
cross_origin_isolated: false,
shared_array_buffer: false,
atomics: false,
is_secure_context: false,
coop_header: None,
coep_header: None,
..Default::default()
};
let result = caps.assert_threading_ready();
assert!(result.is_err());
let err = result.unwrap_err().to_string();
assert!(err.contains("crossOriginIsolated"));
assert!(err.contains("SharedArrayBuffer"));
assert!(err.contains("Atomics"));
assert!(err.contains("HTTPS"));
assert!(err.contains("COOP"));
assert!(err.contains("COEP"));
}
#[test]
fn test_is_threading_available_partial() {
let caps = WasmThreadCapabilities {
cross_origin_isolated: true,
shared_array_buffer: true,
atomics: false,
is_secure_context: true,
..Default::default()
};
assert!(!caps.is_threading_available());
}
#[test]
fn test_assert_state_error_message() {
let emulator = WorkerEmulator::ready("test");
let result = emulator.assert_state(WorkerState::Processing);
assert!(result.is_err());
let err = result.unwrap_err();
match err {
CapabilityError::WorkerState { expected, actual } => {
assert_eq!(expected, "Processing");
assert_eq!(actual, "Ready");
}
_ => panic!("Expected WorkerState error"),
}
}
#[test]
fn test_worker_send_from_loading() {
let mut emulator = WorkerEmulator::new();
emulator.spawn("test");
assert_eq!(emulator.state(), WorkerState::Loading);
emulator.send(WorkerMessage::new("Init", serde_json::json!({})));
assert_eq!(emulator.state(), WorkerState::Loading);
}
#[test]
fn test_worker_multiple_responses() {
let mut emulator = WorkerEmulator::new();
emulator.spawn("test");
emulator.receive_response(WorkerMessage::new("Progress", serde_json::json!({})));
emulator.receive_response(WorkerMessage::new("Progress", serde_json::json!({})));
emulator.receive_response(WorkerMessage::new("Ready", serde_json::json!({})));
assert_eq!(emulator.responses().len(), 3);
assert_eq!(emulator.state(), WorkerState::Ready);
}
#[test]
fn test_worker_lamport_increments() {
let mut emulator = WorkerEmulator::new();
assert_eq!(emulator.lamport_time(), 0);
emulator.spawn("test");
assert_eq!(emulator.lamport_time(), 1);
emulator.send(WorkerMessage::new("A", serde_json::json!({})));
assert_eq!(emulator.lamport_time(), 2);
emulator.receive_response(WorkerMessage::new("B", serde_json::json!({})));
assert_eq!(emulator.lamport_time(), 3);
emulator.terminate();
assert_eq!(emulator.lamport_time(), 4);
}
#[test]
fn test_worker_history_entries() {
let mut emulator = WorkerEmulator::new();
emulator.spawn("my-worker");
emulator.send(WorkerMessage::new("Init", serde_json::json!({})));
emulator.receive_response(WorkerMessage::new("Ready", serde_json::json!({})));
emulator.terminate();
let history = emulator.history();
assert_eq!(history.len(), 4);
assert_eq!(history[0].1, "spawn");
assert_eq!(history[0].2, "my-worker");
assert_eq!(history[1].1, "send");
assert_eq!(history[1].2, "Init");
assert_eq!(history[2].1, "receive");
assert_eq!(history[2].2, "Ready");
assert_eq!(history[3].1, "terminate");
}
#[test]
fn test_worker_clear_preserves_state() {
let mut emulator = WorkerEmulator::ready("test");
emulator.send(WorkerMessage::new("Task", serde_json::json!({})));
emulator.receive_response(WorkerMessage::new("Done", serde_json::json!({})));
let state_before = emulator.state();
emulator.clear();
assert!(emulator.pending_messages().is_empty());
assert!(emulator.responses().is_empty());
assert_eq!(emulator.state(), state_before);
}
#[test]
fn test_shared_array_buffer_status_available() {
let caps = WasmThreadCapabilities::full_support();
assert_eq!(
caps.shared_array_buffer_status(),
CapabilityStatus::Available
);
}
#[test]
fn test_shared_array_buffer_status_not_secure() {
let caps = WasmThreadCapabilities {
shared_array_buffer: false,
is_secure_context: false,
cross_origin_isolated: true,
..Default::default()
};
match caps.shared_array_buffer_status() {
CapabilityStatus::Unavailable(reason) => {
assert!(reason.contains("HTTPS"));
}
_ => panic!("Expected Unavailable"),
}
}
#[test]
fn test_shared_array_buffer_status_not_cross_origin() {
let caps = WasmThreadCapabilities {
shared_array_buffer: false,
is_secure_context: true,
cross_origin_isolated: false,
..Default::default()
};
match caps.shared_array_buffer_status() {
CapabilityStatus::Unavailable(reason) => {
assert!(reason.contains("crossOriginIsolated"));
}
_ => panic!("Expected Unavailable"),
}
}
#[test]
fn test_shared_array_buffer_status_unknown() {
let caps = WasmThreadCapabilities {
shared_array_buffer: false,
is_secure_context: true,
cross_origin_isolated: true,
..Default::default()
};
match caps.shared_array_buffer_status() {
CapabilityStatus::Unavailable(reason) => {
assert!(reason.contains("Unknown"));
}
_ => panic!("Expected Unavailable"),
}
}
#[test]
fn test_from_json_valid_with_headers() {
let json = r#"{
"crossOriginIsolated": true,
"sharedArrayBuffer": true,
"atomics": true,
"hardwareConcurrency": 8,
"isSecureContext": true,
"coopHeader": "same-origin",
"coepHeader": "require-corp"
}"#;
let caps = WasmThreadCapabilities::from_json(json).unwrap();
assert!(caps.cross_origin_isolated);
assert!(caps.shared_array_buffer);
assert!(caps.atomics);
assert_eq!(caps.hardware_concurrency, 8);
assert!(caps.is_secure_context);
assert_eq!(caps.coop_header, Some("same-origin".to_string()));
assert_eq!(caps.coep_header, Some("require-corp".to_string()));
}
#[test]
fn test_from_json_minimal_defaults() {
let json = r#"{}"#;
let caps = WasmThreadCapabilities::from_json(json).unwrap();
assert!(!caps.cross_origin_isolated);
assert!(!caps.shared_array_buffer);
assert_eq!(caps.hardware_concurrency, 1);
}
#[test]
fn test_capability_status_unknown_match() {
let status = CapabilityStatus::Unknown;
assert!(matches!(status, CapabilityStatus::Unknown));
}
#[test]
fn test_required_headers_values() {
assert_eq!(RequiredHeaders::COOP, "same-origin");
assert_eq!(RequiredHeaders::COEP, "require-corp");
}
}