wacc 2.0.0

Web Assembly Cryptographic Constructs VM implementation
#![allow(
    clippy::string_lit_as_bytes,
    clippy::manual_string_new,
    clippy::needless_pass_by_value,
    clippy::uninlined_format_args,
    clippy::needless_collect,
    clippy::cast_sign_loss
)]
// SPDX-License-Identifier: Apache-2.0

//! Concurrency tests for WACC VM
//!
//! These tests verify thread safety and parallel execution capabilities.

use std::collections::BTreeMap;
use std::sync::Arc;
use std::thread;
use wacc::{
    storage::{Pairs, Stack},
    types::{CheckCount, ContextPath},
    vm::{Builder, Context, ModuleCache, Value},
};
use wasmtime::StoreLimitsBuilder;

const MEMORY_LIMIT: usize = 1 << 22; /* 4MB */

// Simple WASM module that returns success
const SIMPLE_WASM: &[u8] = &[
    0x00, 0x61, 0x73, 0x6d, // WASM magic
    0x01, 0x00, 0x00, 0x00, // version 1
    0x01, 0x05, 0x01, 0x60, 0x00, 0x01, 0x7f, // type section: func () -> i32
    0x03, 0x02, 0x01, 0x00, // function section
    0x07, 0x08, 0x01, 0x04, 0x74, 0x65, 0x73, 0x74, // export section: "test"
    0x00, 0x00, // export func 0
    0x0a, 0x06, 0x01, 0x04, 0x00, 0x41, 0x01, 0x0b, // code section: return 1
];

#[derive(Default, Clone)]
struct Kvp {
    pub pairs: BTreeMap<String, Value>,
}

impl Pairs for Kvp {
    fn get(&self, key: &str) -> Option<Value> {
        self.pairs.get(key).cloned()
    }

    fn put(&mut self, key: &str, value: &Value) -> Option<Value> {
        self.pairs.insert(key.to_string(), value.clone())
    }
}

#[derive(Default, Clone)]
struct Stk {
    pub stack: Vec<Value>,
}

impl Stack for Stk {
    fn push(&mut self, value: Value) {
        self.stack.push(value);
    }

    fn pop(&mut self) -> Option<Value> {
        self.stack.pop()
    }

    fn top(&self) -> Option<Value> {
        self.stack.last().cloned()
    }

    fn peek(&self, idx: usize) -> Option<Value> {
        if idx >= self.stack.len() {
            return None;
        }
        Some(self.stack[self.stack.len() - 1 - idx].clone())
    }

    fn len(&self) -> usize {
        self.stack.len()
    }

    fn is_empty(&self) -> bool {
        self.stack.is_empty()
    }
}

#[test]
fn test_parallel_instance_creation() {
    // Test that multiple instances can be created in parallel
    let handles: Vec<_> = (0..4)
        .map(|_| {
            thread::spawn(|| {
                let kvp = Kvp::default();
                let pstack = Stk::default();
                let rstack = Stk::default();

                let context = Context {
                    current: Box::new(kvp.clone()),
                    proposed: Box::new(kvp),
                    pstack: Box::new(pstack),
                    rstack: Box::new(rstack),
                    check_count: CheckCount::zero(),
                    write_idx: 0,
                    context: ContextPath::root(),
                    log: Vec::default(),
                    limiter: StoreLimitsBuilder::new().memory_size(MEMORY_LIMIT).build(),
                };

                let mut instance = Builder::new()
                    .with_context(context)
                    .with_bytes(SIMPLE_WASM)
                    .try_build()
                    .unwrap();

                instance.run("test").unwrap()
            })
        })
        .collect();

    // All instances should succeed
    let results: Vec<bool> = handles.into_iter().map(|h| h.join().unwrap()).collect();
    assert!(
        results.iter().all(|&r| r),
        "All parallel executions should succeed"
    );
    assert_eq!(results.len(), 4);
}

#[test]
fn test_module_cache_concurrent_access() {
    // Test that ModuleCache is thread-safe
    let cache = Arc::new(ModuleCache::new());
    let key = ModuleCache::hash_bytes(SIMPLE_WASM);

    // Multiple threads inserting and reading from cache
    let handles: Vec<_> = (0..8)
        .map(|i| {
            let cache = Arc::clone(&cache);
            thread::spawn(move || {
                // Try to get from cache
                if cache.get(key).is_some() {
                    return true;
                }

                // If this is the first thread, it might need to create
                // (In real usage, we'd compile here, but for test we skip that)
                // Just verify cache operations work
                if i == 0 {
                    // Simulate cache insertion
                    // (Can't actually create Module without Engine in thread)
                }

                cache.get(key).is_some()
            })
        })
        .collect();

    // All threads should complete without panicking
    let _results: Vec<_> = handles.into_iter().map(|h| h.join().unwrap()).collect();
}

#[test]
fn test_value_is_send_sync() {
    // Verify that Value can be sent across threads
    fn assert_send<T: Send>() {}
    fn assert_sync<T: Sync>() {}

    assert_send::<Value>();
    assert_sync::<Value>();
}

#[test]
fn test_cache_is_send_sync() {
    // Verify that ModuleCache can be shared across threads
    fn assert_send<T: Send>() {}
    fn assert_sync<T: Sync>() {}

    assert_send::<ModuleCache>();
    assert_sync::<ModuleCache>();
}

#[test]
fn test_concurrent_cache_operations() {
    // Test concurrent cache insertions (though our cache doesn't support Module creation in tests)
    let cache = Arc::new(ModuleCache::with_capacity(10));

    let handles: Vec<_> = (0..4)
        .map(|i| {
            let cache = Arc::clone(&cache);
            thread::spawn(move || {
                // Each thread computes different keys
                let key = (i as u64) * 1000;

                // Simulate checking cache
                let _ = cache.get(key);

                // Return cache size
                cache.len()
            })
        })
        .collect();

    // Wait for all threads
    let results: Vec<_> = handles.into_iter().map(|h| h.join().unwrap()).collect();

    // Verify no panics occurred
    assert_eq!(results.len(), 4);
}

#[test]
fn test_cache_basic_functionality() {
    // Verify cache works as expected
    let cache = ModuleCache::new();
    assert_eq!(cache.len(), 0);
    assert!(cache.is_empty());

    let key = 12345u64;
    assert!(cache.get(key).is_none());

    // Test clear
    cache.clear();
    assert_eq!(cache.len(), 0);
}

#[test]
fn test_parallel_value_sharing() {
    // Test that Values can be shared across threads via Arc
    let value: Value = vec![1, 2, 3, 4, 5].into();
    let value = Arc::new(value);

    let handles: Vec<_> = (0..4)
        .map(|_| {
            let value = Arc::clone(&value);
            thread::spawn(move || {
                // Each thread can read the value
                match &*value {
                    Value::Bin { data, .. } => data.len(),
                    _ => 0,
                }
            })
        })
        .collect();

    let results: Vec<_> = handles.into_iter().map(|h| h.join().unwrap()).collect();
    assert!(results.iter().all(|&len| len == 5));
}