#![allow(
clippy::all,
unused_imports,
unused_variables,
dead_code,
unused_mut,
unused_assignments,
non_camel_case_types,
clippy::approx_constant,
unexpected_cfgs,
unused_must_use,
unused_parens
)]
use std::env;
use std::path::{Path, PathBuf};
use std::time::Duration;
const MODEL_SEARCH_PATHS: &[&str] = &[
"./test_models",
"../test_models",
"../../test_models",
"./models",
"../models",
"~/.cache/ruvllm/models",
"~/.cache/huggingface/hub",
];
const TINYLLAMA_PATTERNS: &[&str] = &["tinyllama*.gguf", "TinyLlama*.gguf", "*tinyllama*.gguf"];
const PHI3_PATTERNS: &[&str] = &["phi-3*.gguf", "Phi-3*.gguf", "*phi3*.gguf", "*phi-3*.gguf"];
const QWEN_PATTERNS: &[&str] = &["qwen*.gguf", "Qwen*.gguf", "*qwen*.gguf"];
#[allow(dead_code)]
type TestResult<T> = std::result::Result<T, Box<dyn std::error::Error>>;
pub fn find_test_model(patterns: &[&str]) -> Option<PathBuf> {
if let Ok(path) = env::var("TEST_MODEL_PATH") {
let path = PathBuf::from(path);
if path.exists() && path.is_file() {
return Some(path);
}
}
if let Ok(dir) = env::var("TEST_MODEL_DIR") {
if let Some(found) = search_directory(&PathBuf::from(dir), patterns) {
return Some(found);
}
}
for search_path in MODEL_SEARCH_PATHS {
let expanded = expand_path(search_path);
if expanded.exists() && expanded.is_dir() {
if let Some(found) = search_directory(&expanded, patterns) {
return Some(found);
}
}
}
None
}
fn search_directory(dir: &Path, patterns: &[&str]) -> Option<PathBuf> {
if !dir.exists() || !dir.is_dir() {
return None;
}
let entries = match std::fs::read_dir(dir) {
Ok(e) => e,
Err(_) => return None,
};
for entry in entries.flatten() {
let path = entry.path();
if !path.is_file() {
continue;
}
let file_name = match path.file_name().and_then(|n| n.to_str()) {
Some(n) => n.to_lowercase(),
None => continue,
};
for pattern in patterns {
if matches_glob_pattern(&file_name, &pattern.to_lowercase()) {
return Some(path);
}
}
}
None
}
fn matches_glob_pattern(name: &str, pattern: &str) -> bool {
if !pattern.contains('*') {
return name == pattern;
}
let parts: Vec<&str> = pattern.split('*').collect();
if parts.is_empty() {
return true;
}
let mut remaining = name;
if !parts[0].is_empty() {
if !remaining.starts_with(parts[0]) {
return false;
}
remaining = &remaining[parts[0].len()..];
}
if parts.len() > 1 {
let last = parts[parts.len() - 1];
if !last.is_empty() && !remaining.ends_with(last) {
return false;
}
}
for part in &parts[1..parts.len().saturating_sub(1)] {
if part.is_empty() {
continue;
}
match remaining.find(part) {
Some(pos) => remaining = &remaining[pos + part.len()..],
None => return false,
}
}
true
}
fn expand_path(path: &str) -> PathBuf {
if path.starts_with("~/") {
if let Some(home) = dirs::home_dir() {
return home.join(&path[2..]);
}
}
PathBuf::from(path)
}
pub fn skip_if_no_model(patterns: &[&str], model_name: &str) -> Option<PathBuf> {
match find_test_model(patterns) {
Some(path) => {
println!("Using model: {}", path.display());
Some(path)
}
None => {
println!("SKIPPED: No {} model found.", model_name);
println!("To run this test:");
println!(" 1. Download the model:");
println!(
" cargo run -p ruvllm --example download_test_model -- --model {}",
model_name.to_lowercase().replace(' ', "")
);
println!(" 2. Or set TEST_MODEL_PATH environment variable");
println!(" 3. Or place model in ./test_models/ directory");
None
}
}
}
pub struct GenerationMetrics {
pub total_tokens: usize,
pub total_duration: Duration,
pub first_token_latency: Duration,
pub token_latencies: Vec<Duration>,
}
impl GenerationMetrics {
pub fn tokens_per_second(&self) -> f64 {
if self.total_duration.as_secs_f64() > 0.0 {
self.total_tokens as f64 / self.total_duration.as_secs_f64()
} else {
0.0
}
}
pub fn latency_p50(&self) -> Duration {
self.percentile_latency(50)
}
pub fn latency_p95(&self) -> Duration {
self.percentile_latency(95)
}
pub fn latency_p99(&self) -> Duration {
self.percentile_latency(99)
}
fn percentile_latency(&self, p: usize) -> Duration {
if self.token_latencies.is_empty() {
return Duration::ZERO;
}
let mut sorted = self.token_latencies.clone();
sorted.sort();
let idx = (p * sorted.len() / 100).min(sorted.len() - 1);
sorted[idx]
}
pub fn summary(&self) -> String {
format!(
"Tokens: {}, Duration: {:.2}s, Speed: {:.2} tok/s, TTFT: {:.2}ms, P50: {:.2}ms, P95: {:.2}ms, P99: {:.2}ms",
self.total_tokens,
self.total_duration.as_secs_f64(),
self.tokens_per_second(),
self.first_token_latency.as_secs_f64() * 1000.0,
self.latency_p50().as_secs_f64() * 1000.0,
self.latency_p95().as_secs_f64() * 1000.0,
self.latency_p99().as_secs_f64() * 1000.0,
)
}
}
#[test]
#[ignore = "Requires model file - run with --ignored"]
fn test_gguf_file_validation() {
let all_patterns = ["*.gguf"];
let model_path = match skip_if_no_model(&all_patterns, "any GGUF") {
Some(p) => p,
None => return,
};
let file = std::fs::File::open(&model_path).expect("Failed to open model file");
let mut reader = std::io::BufReader::new(file);
use std::io::Read;
let mut magic = [0u8; 4];
reader.read_exact(&mut magic).expect("Failed to read magic");
assert_eq!(&magic, b"GGUF", "Invalid GGUF magic number");
let mut version_bytes = [0u8; 4];
reader
.read_exact(&mut version_bytes)
.expect("Failed to read version");
let version = u32::from_le_bytes(version_bytes);
assert!(
version >= 2 && version <= 3,
"Unexpected GGUF version: {}",
version
);
println!("GGUF file validated:");
println!(" Path: {}", model_path.display());
println!(" Magic: GGUF");
println!(" Version: {}", version);
}
#[test]
#[ignore = "Requires TinyLlama model file"]
fn test_tinyllama_load() {
let model_path = match skip_if_no_model(TINYLLAMA_PATTERNS, "TinyLlama") {
Some(p) => p,
None => return,
};
println!("Would load TinyLlama from: {}", model_path.display());
let metadata = std::fs::metadata(&model_path).expect("Failed to get file metadata");
let size_mb = metadata.len() as f64 / (1024.0 * 1024.0);
println!("Model size: {:.2} MB", size_mb);
assert!(
size_mb > 100.0 && size_mb < 2000.0,
"Unexpected model size: {:.2} MB (expected 100-2000 MB for TinyLlama)",
size_mb
);
}
#[test]
#[ignore = "Requires TinyLlama model file"]
fn test_tinyllama_generation() {
let model_path = match skip_if_no_model(TINYLLAMA_PATTERNS, "TinyLlama") {
Some(p) => p,
None => return,
};
println!(
"Testing generation with TinyLlama: {}",
model_path.display()
);
println!("TinyLlama generation test placeholder - implement with actual backend");
}
#[test]
#[ignore = "Requires TinyLlama model file"]
fn test_tinyllama_streaming() {
let model_path = match skip_if_no_model(TINYLLAMA_PATTERNS, "TinyLlama") {
Some(p) => p,
None => return,
};
println!("Testing streaming with TinyLlama: {}", model_path.display());
println!("TinyLlama streaming test placeholder - implement with actual backend");
}
#[test]
#[ignore = "Requires Phi-3 model file"]
fn test_phi3_load() {
let model_path = match skip_if_no_model(PHI3_PATTERNS, "Phi-3") {
Some(p) => p,
None => return,
};
println!("Would load Phi-3 from: {}", model_path.display());
let metadata = std::fs::metadata(&model_path).expect("Failed to get file metadata");
let size_mb = metadata.len() as f64 / (1024.0 * 1024.0);
println!("Model size: {:.2} MB", size_mb);
assert!(
size_mb > 500.0 && size_mb < 5000.0,
"Unexpected model size: {:.2} MB (expected 500-5000 MB for Phi-3)",
size_mb
);
}
#[test]
#[ignore = "Requires Phi-3 model file"]
fn test_phi3_generation() {
let model_path = match skip_if_no_model(PHI3_PATTERNS, "Phi-3") {
Some(p) => p,
None => return,
};
println!("Testing generation with Phi-3: {}", model_path.display());
println!("Phi-3 generation test placeholder - implement with actual backend");
}
#[test]
#[ignore = "Requires Phi-3 model file"]
fn test_phi3_code_completion() {
let model_path = match skip_if_no_model(PHI3_PATTERNS, "Phi-3") {
Some(p) => p,
None => return,
};
println!(
"Testing code completion with Phi-3: {}",
model_path.display()
);
let _prompts = [
"def fibonacci(n):\n \"\"\"Calculate the nth Fibonacci number.\"\"\"\n ",
"// Function to reverse a string in Rust\nfn reverse_string(s: &str) -> String {\n ",
"# Python function to check if a number is prime\ndef is_prime(n):\n ",
];
println!("Phi-3 code completion test placeholder - implement with actual backend");
}
#[test]
#[ignore = "Requires Qwen model file"]
fn test_qwen_load() {
let model_path = match skip_if_no_model(QWEN_PATTERNS, "Qwen") {
Some(p) => p,
None => return,
};
println!("Would load Qwen from: {}", model_path.display());
let metadata = std::fs::metadata(&model_path).expect("Failed to get file metadata");
let size_mb = metadata.len() as f64 / (1024.0 * 1024.0);
println!("Model size: {:.2} MB", size_mb);
assert!(
size_mb > 50.0 && size_mb < 1000.0,
"Unexpected model size: {:.2} MB (expected 50-1000 MB for Qwen-0.5B)",
size_mb
);
}
#[test]
#[ignore = "Requires Qwen model file"]
fn test_qwen_generation() {
let model_path = match skip_if_no_model(QWEN_PATTERNS, "Qwen") {
Some(p) => p,
None => return,
};
println!("Testing generation with Qwen: {}", model_path.display());
println!("Qwen generation test placeholder - implement with actual backend");
}
#[test]
#[ignore = "Requires Qwen model file"]
fn test_qwen_multilingual() {
let model_path = match skip_if_no_model(QWEN_PATTERNS, "Qwen") {
Some(p) => p,
None => return,
};
println!("Testing multilingual with Qwen: {}", model_path.display());
let _prompts = [
"Hello, how are you today?", "Bonjour, comment allez-vous?", "Hallo, wie geht es Ihnen?", "Translate 'hello' to Chinese: ", ];
println!("Qwen multilingual test placeholder - implement with actual backend");
}
#[test]
#[ignore = "Requires model file - run with --ignored"]
fn test_benchmark_generation_speed() {
let patterns = ["*.gguf"];
let model_path = match skip_if_no_model(&patterns, "any GGUF") {
Some(p) => p,
None => return,
};
println!(
"Benchmarking generation speed with: {}",
model_path.display()
);
let warmup_iterations = 3;
let benchmark_iterations = 10;
let max_tokens = 50;
println!("Warmup: {} iterations", warmup_iterations);
println!("Benchmark: {} iterations", benchmark_iterations);
println!("Max tokens per generation: {}", max_tokens);
println!("Benchmark placeholder - implement with actual backend");
}
#[test]
#[ignore = "Requires model file"]
fn test_memory_usage() {
let patterns = ["*.gguf"];
let model_path = match skip_if_no_model(&patterns, "any GGUF") {
Some(p) => p,
None => return,
};
println!("Testing memory usage with: {}", model_path.display());
#[cfg(target_os = "macos")]
{
use std::process::Command;
let output = Command::new("ps")
.args(["-o", "rss=", "-p", &std::process::id().to_string()])
.output()
.ok();
if let Some(output) = output {
if let Ok(rss) = String::from_utf8_lossy(&output.stdout)
.trim()
.parse::<u64>()
{
println!("Initial RSS: {} KB", rss);
}
}
}
println!("Memory usage test placeholder - implement with actual backend");
}
#[test]
#[ignore = "Requires multiple model files"]
fn test_model_comparison() {
println!("Model comparison test");
let test_prompts = [
"What is the capital of France?",
"Write a haiku about programming.",
"Explain quantum computing in simple terms.",
];
let models: Vec<(&str, Option<PathBuf>)> = vec![
("TinyLlama", find_test_model(TINYLLAMA_PATTERNS)),
("Phi-3", find_test_model(PHI3_PATTERNS)),
("Qwen", find_test_model(QWEN_PATTERNS)),
];
let available: Vec<_> = models.iter().filter(|(_, path)| path.is_some()).collect();
if available.is_empty() {
println!("SKIPPED: No models available for comparison");
return;
}
println!("Available models for comparison:");
for (name, path) in &available {
if let Some(p) = path {
println!(" - {}: {}", name, p.display());
}
}
println!("\nTest prompts:");
for (i, prompt) in test_prompts.iter().enumerate() {
println!(" {}. {}", i + 1, prompt);
}
println!("\nModel comparison placeholder - implement with actual backend");
}
#[cfg(test)]
mod helper_tests {
use super::*;
#[test]
fn test_glob_pattern_matching() {
assert!(matches_glob_pattern("tinyllama.gguf", "*.gguf"));
assert!(matches_glob_pattern("tinyllama.gguf", "tinyllama*"));
assert!(matches_glob_pattern(
"tinyllama-1.1b.gguf",
"*tinyllama*.gguf"
));
assert!(matches_glob_pattern("model.gguf", "model.gguf"));
assert!(!matches_glob_pattern("tinyllama.bin", "*.gguf"));
assert!(!matches_glob_pattern("other.gguf", "tinyllama*"));
}
#[test]
fn test_expand_path_no_tilde() {
let path = expand_path("/usr/local/models");
assert_eq!(path, PathBuf::from("/usr/local/models"));
}
#[test]
fn test_expand_path_relative() {
let path = expand_path("./models");
assert_eq!(path, PathBuf::from("./models"));
}
#[test]
fn test_metrics_percentile() {
let metrics = GenerationMetrics {
total_tokens: 100,
total_duration: Duration::from_secs(10),
first_token_latency: Duration::from_millis(50),
token_latencies: (0..100).map(|i| Duration::from_millis(i as u64)).collect(),
};
assert_eq!(metrics.tokens_per_second(), 10.0);
assert!(metrics.latency_p50() >= Duration::from_millis(49));
assert!(metrics.latency_p50() <= Duration::from_millis(51));
assert!(metrics.latency_p99() >= Duration::from_millis(98));
}
#[test]
fn test_metrics_empty_latencies() {
let metrics = GenerationMetrics {
total_tokens: 0,
total_duration: Duration::ZERO,
first_token_latency: Duration::ZERO,
token_latencies: vec![],
};
assert_eq!(metrics.tokens_per_second(), 0.0);
assert_eq!(metrics.latency_p50(), Duration::ZERO);
}
}