402 lines
11 KiB
Rust
402 lines
11 KiB
Rust
//! WASM Tests for RuvLLM
|
|
//!
|
|
//! These tests run in a browser environment using wasm-bindgen-test.
|
|
//! Run with: `wasm-pack test --headless --chrome`
|
|
|
|
#![cfg(target_arch = "wasm32")]
|
|
|
|
use wasm_bindgen_test::*;
|
|
|
|
wasm_bindgen_test_configure!(run_in_browser);
|
|
|
|
use ruvllm_wasm::{
|
|
BufferPoolWasm, ChatMessageWasm, ChatTemplateWasm, GenerateConfig, InferenceArenaWasm,
|
|
KvCacheConfigWasm, KvCacheWasm, RuvLLMWasm, Timer,
|
|
};
|
|
|
|
// ============================================================================
|
|
// GenerateConfig Tests
|
|
// ============================================================================
|
|
|
|
#[wasm_bindgen_test]
|
|
fn test_generate_config_defaults() {
|
|
let config = GenerateConfig::new();
|
|
|
|
assert_eq!(config.max_tokens(), 256);
|
|
assert!((config.temperature() - 0.7).abs() < 0.01);
|
|
assert!((config.top_p() - 0.9).abs() < 0.01);
|
|
assert_eq!(config.top_k(), 40);
|
|
}
|
|
|
|
#[wasm_bindgen_test]
|
|
fn test_generate_config_setters() {
|
|
let mut config = GenerateConfig::new();
|
|
|
|
config.set_max_tokens(512);
|
|
config.set_temperature(0.5);
|
|
config.set_top_p(0.95);
|
|
config.set_top_k(50);
|
|
config.set_repetition_penalty(1.2);
|
|
|
|
assert_eq!(config.max_tokens(), 512);
|
|
assert!((config.temperature() - 0.5).abs() < 0.01);
|
|
assert!((config.top_p() - 0.95).abs() < 0.01);
|
|
assert_eq!(config.top_k(), 50);
|
|
assert!((config.repetition_penalty() - 1.2).abs() < 0.01);
|
|
}
|
|
|
|
#[wasm_bindgen_test]
|
|
fn test_generate_config_json() {
|
|
let config = GenerateConfig::new();
|
|
let json = config.to_json().expect("JSON serialization failed");
|
|
|
|
assert!(json.contains("max_tokens"));
|
|
assert!(json.contains("temperature"));
|
|
|
|
let parsed = GenerateConfig::from_json(&json).expect("JSON parsing failed");
|
|
assert_eq!(parsed.max_tokens(), config.max_tokens());
|
|
}
|
|
|
|
#[wasm_bindgen_test]
|
|
fn test_generate_config_stop_sequences() {
|
|
let mut config = GenerateConfig::new();
|
|
|
|
config.add_stop_sequence("</s>");
|
|
config.add_stop_sequence("\n\n");
|
|
|
|
// Stop sequences are stored internally
|
|
config.clear_stop_sequences();
|
|
// After clearing, should work without error
|
|
}
|
|
|
|
// ============================================================================
|
|
// Chat Message Tests
|
|
// ============================================================================
|
|
|
|
#[wasm_bindgen_test]
|
|
fn test_chat_message_creation() {
|
|
let system = ChatMessageWasm::system("You are helpful.");
|
|
assert_eq!(system.role(), "system");
|
|
assert_eq!(system.content(), "You are helpful.");
|
|
|
|
let user = ChatMessageWasm::user("Hello!");
|
|
assert_eq!(user.role(), "user");
|
|
assert_eq!(user.content(), "Hello!");
|
|
|
|
let assistant = ChatMessageWasm::assistant("Hi there!");
|
|
assert_eq!(assistant.role(), "assistant");
|
|
assert_eq!(assistant.content(), "Hi there!");
|
|
}
|
|
|
|
// ============================================================================
|
|
// Chat Template Tests
|
|
// ============================================================================
|
|
|
|
#[wasm_bindgen_test]
|
|
fn test_chat_template_llama3() {
|
|
let template = ChatTemplateWasm::llama3();
|
|
assert_eq!(template.name(), "llama3");
|
|
|
|
let messages = vec![
|
|
ChatMessageWasm::system("Be helpful."),
|
|
ChatMessageWasm::user("Hello"),
|
|
];
|
|
|
|
let formatted = template.format(messages);
|
|
assert!(formatted.contains("<|begin_of_text|>"));
|
|
assert!(formatted.contains("Be helpful."));
|
|
assert!(formatted.contains("Hello"));
|
|
}
|
|
|
|
#[wasm_bindgen_test]
|
|
fn test_chat_template_chatml() {
|
|
let template = ChatTemplateWasm::chatml();
|
|
assert_eq!(template.name(), "chatml");
|
|
|
|
let messages = vec![ChatMessageWasm::user("Hi")];
|
|
|
|
let formatted = template.format(messages);
|
|
assert!(formatted.contains("<|im_start|>user"));
|
|
assert!(formatted.contains("Hi"));
|
|
assert!(formatted.contains("<|im_end|>"));
|
|
}
|
|
|
|
#[wasm_bindgen_test]
|
|
fn test_chat_template_detection() {
|
|
let llama = ChatTemplateWasm::detect_from_model_id("meta-llama/Llama-3-8B");
|
|
assert_eq!(llama.name(), "llama3");
|
|
|
|
let mistral = ChatTemplateWasm::detect_from_model_id("mistralai/Mistral-7B");
|
|
assert_eq!(mistral.name(), "mistral");
|
|
|
|
let qwen = ChatTemplateWasm::detect_from_model_id("Qwen/Qwen2.5-0.5B");
|
|
assert_eq!(qwen.name(), "qwen");
|
|
}
|
|
|
|
#[wasm_bindgen_test]
|
|
fn test_chat_template_custom() {
|
|
let template = ChatTemplateWasm::custom("USER: {user}\nASSISTANT:");
|
|
assert_eq!(template.name(), "custom");
|
|
}
|
|
|
|
// ============================================================================
|
|
// KV Cache Tests
|
|
// ============================================================================
|
|
|
|
#[wasm_bindgen_test]
|
|
fn test_kv_cache_config() {
|
|
let mut config = KvCacheConfigWasm::new();
|
|
|
|
config.set_tail_length(512);
|
|
config.set_max_tokens(8192);
|
|
config.set_num_kv_heads(16);
|
|
config.set_head_dim(64);
|
|
|
|
assert_eq!(config.tail_length(), 512);
|
|
assert_eq!(config.max_tokens(), 8192);
|
|
assert_eq!(config.num_kv_heads(), 16);
|
|
assert_eq!(config.head_dim(), 64);
|
|
}
|
|
|
|
#[wasm_bindgen_test]
|
|
fn test_kv_cache_basic() {
|
|
let cache = KvCacheWasm::with_defaults();
|
|
|
|
let stats = cache.stats();
|
|
assert_eq!(stats.total_tokens(), 0);
|
|
assert_eq!(stats.tail_tokens(), 0);
|
|
}
|
|
|
|
#[wasm_bindgen_test]
|
|
fn test_kv_cache_append() {
|
|
let mut config = KvCacheConfigWasm::new();
|
|
config.set_num_kv_heads(2);
|
|
config.set_head_dim(4);
|
|
|
|
let cache = KvCacheWasm::new(&config);
|
|
|
|
// Append one token (stride = 2 * 4 = 8)
|
|
let keys: Vec<f32> = vec![0.1; 8];
|
|
let values: Vec<f32> = vec![0.2; 8];
|
|
|
|
cache.append(&keys, &values).expect("append failed");
|
|
|
|
let stats = cache.stats();
|
|
assert_eq!(stats.total_tokens(), 1);
|
|
}
|
|
|
|
#[wasm_bindgen_test]
|
|
fn test_kv_cache_clear() {
|
|
let cache = KvCacheWasm::with_defaults();
|
|
cache.clear();
|
|
|
|
assert_eq!(cache.token_count(), 0);
|
|
}
|
|
|
|
#[wasm_bindgen_test]
|
|
fn test_kv_cache_stats_json() {
|
|
let cache = KvCacheWasm::with_defaults();
|
|
let json = cache.stats().to_json().expect("JSON failed");
|
|
|
|
assert!(json.contains("total_tokens"));
|
|
assert!(json.contains("compression_ratio"));
|
|
}
|
|
|
|
// ============================================================================
|
|
// Memory Arena Tests
|
|
// ============================================================================
|
|
|
|
#[wasm_bindgen_test]
|
|
fn test_arena_creation() {
|
|
let arena = InferenceArenaWasm::new(4096);
|
|
|
|
assert!(arena.capacity() >= 4096);
|
|
assert_eq!(arena.used(), 0);
|
|
assert_eq!(arena.remaining(), arena.capacity());
|
|
}
|
|
|
|
#[wasm_bindgen_test]
|
|
fn test_arena_for_model() {
|
|
let arena = InferenceArenaWasm::for_model(4096, 32000, 1);
|
|
|
|
// Should have reasonable capacity for these dimensions
|
|
assert!(arena.capacity() > 0);
|
|
}
|
|
|
|
#[wasm_bindgen_test]
|
|
fn test_arena_reset() {
|
|
let arena = InferenceArenaWasm::new(4096);
|
|
|
|
// Arena starts empty
|
|
assert_eq!(arena.used(), 0);
|
|
|
|
// Reset should work even on empty arena
|
|
arena.reset();
|
|
assert_eq!(arena.used(), 0);
|
|
}
|
|
|
|
#[wasm_bindgen_test]
|
|
fn test_arena_stats_json() {
|
|
let arena = InferenceArenaWasm::new(4096);
|
|
let json = arena.stats_json().expect("JSON failed");
|
|
|
|
assert!(json.contains("capacity"));
|
|
assert!(json.contains("used"));
|
|
assert!(json.contains("utilization"));
|
|
}
|
|
|
|
// ============================================================================
|
|
// Buffer Pool Tests
|
|
// ============================================================================
|
|
|
|
#[wasm_bindgen_test]
|
|
fn test_buffer_pool_creation() {
|
|
let pool = BufferPoolWasm::new();
|
|
|
|
// Hit rate should be 0 initially (no hits or misses)
|
|
assert!(pool.hit_rate() >= 0.0);
|
|
}
|
|
|
|
#[wasm_bindgen_test]
|
|
fn test_buffer_pool_prewarm() {
|
|
let pool = BufferPoolWasm::new();
|
|
pool.prewarm_all(4);
|
|
|
|
let json = pool.stats_json().expect("JSON failed");
|
|
assert!(json.contains("free_buffers"));
|
|
}
|
|
|
|
#[wasm_bindgen_test]
|
|
fn test_buffer_pool_clear() {
|
|
let pool = BufferPoolWasm::new();
|
|
pool.prewarm_all(2);
|
|
pool.clear();
|
|
|
|
// After clear, pool should be empty
|
|
}
|
|
|
|
#[wasm_bindgen_test]
|
|
fn test_buffer_pool_with_capacity() {
|
|
let pool = BufferPoolWasm::with_capacity(16);
|
|
|
|
let json = pool.stats_json().expect("JSON failed");
|
|
assert!(json.contains("hit_rate"));
|
|
}
|
|
|
|
// ============================================================================
|
|
// RuvLLMWasm Tests
|
|
// ============================================================================
|
|
|
|
#[wasm_bindgen_test]
|
|
fn test_ruvllm_creation() {
|
|
let llm = RuvLLMWasm::new();
|
|
assert!(!llm.is_initialized());
|
|
}
|
|
|
|
#[wasm_bindgen_test]
|
|
fn test_ruvllm_initialize() {
|
|
let mut llm = RuvLLMWasm::new();
|
|
llm.initialize().expect("initialization failed");
|
|
|
|
assert!(llm.is_initialized());
|
|
}
|
|
|
|
#[wasm_bindgen_test]
|
|
fn test_ruvllm_initialize_with_config() {
|
|
let mut llm = RuvLLMWasm::new();
|
|
let config = KvCacheConfigWasm::new();
|
|
|
|
llm.initialize_with_config(&config)
|
|
.expect("initialization failed");
|
|
|
|
assert!(llm.is_initialized());
|
|
}
|
|
|
|
#[wasm_bindgen_test]
|
|
fn test_ruvllm_reset() {
|
|
let mut llm = RuvLLMWasm::new();
|
|
llm.initialize().expect("initialization failed");
|
|
llm.reset();
|
|
|
|
// Should still be initialized after reset
|
|
assert!(llm.is_initialized());
|
|
}
|
|
|
|
#[wasm_bindgen_test]
|
|
fn test_ruvllm_version() {
|
|
let version = RuvLLMWasm::version();
|
|
assert!(!version.is_empty());
|
|
assert!(version.contains('.'));
|
|
}
|
|
|
|
#[wasm_bindgen_test]
|
|
fn test_ruvllm_pool_stats() {
|
|
let mut llm = RuvLLMWasm::new();
|
|
llm.initialize().expect("initialization failed");
|
|
|
|
let stats = llm.get_pool_stats().expect("stats failed");
|
|
assert!(stats.contains("hit_rate"));
|
|
}
|
|
|
|
#[wasm_bindgen_test]
|
|
fn test_ruvllm_format_chat() {
|
|
let template = ChatTemplateWasm::chatml();
|
|
let messages = vec![
|
|
ChatMessageWasm::system("Be helpful."),
|
|
ChatMessageWasm::user("Hello"),
|
|
];
|
|
|
|
let formatted = RuvLLMWasm::format_chat(&template, messages);
|
|
assert!(formatted.contains("<|im_start|>"));
|
|
assert!(formatted.contains("Be helpful."));
|
|
}
|
|
|
|
// ============================================================================
|
|
// Utility Tests
|
|
// ============================================================================
|
|
|
|
#[wasm_bindgen_test]
|
|
fn test_timer() {
|
|
let timer = Timer::new("test_timer");
|
|
|
|
// Elapsed should be non-negative
|
|
assert!(timer.elapsed_ms() >= 0.0);
|
|
}
|
|
|
|
#[wasm_bindgen_test]
|
|
fn test_timer_reset() {
|
|
let mut timer = Timer::new("test_timer");
|
|
|
|
// Wait a tiny bit (if possible in test environment)
|
|
let initial = timer.elapsed_ms();
|
|
|
|
timer.reset();
|
|
let after_reset = timer.elapsed_ms();
|
|
|
|
// After reset, elapsed should be less than or equal to initial
|
|
// (accounting for timing variations)
|
|
assert!(after_reset <= initial + 1.0);
|
|
}
|
|
|
|
#[wasm_bindgen_test]
|
|
fn test_get_version() {
|
|
let version = ruvllm_wasm::get_version();
|
|
assert!(!version.is_empty());
|
|
}
|
|
|
|
#[wasm_bindgen_test]
|
|
fn test_is_ready() {
|
|
assert!(ruvllm_wasm::is_ready());
|
|
}
|
|
|
|
#[wasm_bindgen_test]
|
|
fn test_detect_chat_template() {
|
|
let template = ruvllm_wasm::detect_chat_template("Qwen/Qwen2.5-0.5B-Instruct");
|
|
assert_eq!(template.name(), "qwen");
|
|
}
|
|
|
|
#[wasm_bindgen_test]
|
|
fn test_health_check() {
|
|
assert!(ruvllm_wasm::health_check());
|
|
}
|