use heroforge_core::{Repository, Result};
use std::collections::HashMap;
use std::fs;
use std::path::Path;
use std::process::Command;
use std::time::Instant;
fn run_fossil(args: &[&str], cwd: &Path) -> std::result::Result<String, String> {
let output = Command::new("heroforge")
.args(args)
.current_dir(cwd)
.output()
.map_err(|e| format!("Failed to run heroforge: {}", e))?;
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
return Err(format!("Heroforge command failed: {}", stderr));
}
Ok(String::from_utf8_lossy(&output.stdout).to_string())
}
struct TestResult {
name: String,
passed: bool,
details: String,
duration_ms: u128,
}
fn main() -> Result<()> {
let test_dir = Path::new("/tmp/fossil_comprehensive_test");
if test_dir.exists() {
fs::remove_dir_all(test_dir).expect("Failed to clean up");
}
fs::create_dir_all(test_dir).expect("Failed to create test dir");
let mut results: Vec<TestResult> = Vec::new();
println!("=== heroforge Comprehensive Test Suite ===\n");
results.push(test_multiple_files(test_dir));
results.push(test_deep_directories(test_dir));
results.push(test_rapid_changes(test_dir));
results.push(test_large_files(test_dir));
results.push(test_binary_content(test_dir));
results.push(test_special_characters(test_dir));
results.push(test_empty_files(test_dir));
results.push(test_many_files(test_dir));
results.push(test_file_modifications(test_dir));
results.push(test_mixed_operations(test_dir));
println!("\n=== Test Summary ===\n");
let mut passed = 0;
let mut failed = 0;
for result in &results {
let status = if result.passed { "PASS" } else { "FAIL" };
println!("[{}] {} ({} ms)", status, result.name, result.duration_ms);
if !result.passed {
println!(" {}", result.details);
failed += 1;
} else {
passed += 1;
}
}
println!("\nTotal: {} passed, {} failed", passed, failed);
if failed > 0 {
std::process::exit(1);
}
Ok(())
}
fn test_multiple_files(base_dir: &Path) -> TestResult {
let start = Instant::now();
let name = "Multiple files in single commit".to_string();
let repo_path = base_dir.join("test_multiple_files.forge");
let result = (|| -> Result<String> {
let repo = Repository::init(&repo_path)?;
let initial = repo
.commit_builder()
.message("initial empty check-in")
.author("testuser")
.initial()
.execute()?;
let commit_hash = repo
.commit_builder()
.message("Add 5 files")
.author("testuser")
.parent(&initial)
.branch("trunk")
.file("file1.txt", b"Content of file 1")
.file("file2.txt", b"Content of file 2")
.file("file3.txt", b"Content of file 3")
.file("readme.md", b"# README\nThis is a test")
.file("data.json", b"{\"key\": \"value\"}")
.execute()?;
let files = [
("file1.txt", "Content of file 1"),
("file2.txt", "Content of file 2"),
("file3.txt", "Content of file 3"),
("readme.md", "# README\nThis is a test"),
("data.json", "{\"key\": \"value\"}"),
];
for (name, expected) in &files {
let content = repo.files().at_commit(&commit_hash).read(name)?;
if content != expected.as_bytes() {
return Err(heroforge_core::FossilError::InvalidArtifact(format!(
"Content mismatch for {}",
name
)));
}
}
let repo_str = repo_path.to_str().unwrap();
for (name, expected) in &files {
let cli_content = run_fossil(
&["cat", name, "-r", &commit_hash[..16], "-R", repo_str],
base_dir,
)
.map_err(|e| heroforge_core::FossilError::InvalidArtifact(e))?;
if cli_content.as_bytes() != expected.as_bytes() {
return Err(heroforge_core::FossilError::InvalidArtifact(format!(
"CLI content mismatch for {}",
name
)));
}
}
Ok("5 files committed and verified".to_string())
})();
TestResult {
name,
passed: result.is_ok(),
details: result.unwrap_or_else(|e| e.to_string()),
duration_ms: start.elapsed().as_millis(),
}
}
fn test_deep_directories(base_dir: &Path) -> TestResult {
let start = Instant::now();
let name = "Deep directory structures".to_string();
let repo_path = base_dir.join("test_deep_dirs.forge");
let result = (|| -> Result<String> {
let repo = Repository::init(&repo_path)?;
let initial = repo
.commit_builder()
.message("initial empty check-in")
.author("testuser")
.initial()
.execute()?;
let commit_hash = repo
.commit_builder()
.message("Add deep directory structure")
.author("testuser")
.parent(&initial)
.branch("trunk")
.file("src/main.rs", b"fn main() {}")
.file("src/lib.rs", b"pub mod utils;")
.file("src/utils/mod.rs", b"pub fn helper() {}")
.file("src/utils/helpers/string.rs", b"pub fn trim() {}")
.file("src/utils/helpers/number.rs", b"pub fn parse() {}")
.file(
"tests/integration/api/v1/test_users.rs",
b"#[test] fn test() {}",
)
.file("docs/api/v1/users/readme.md", b"# Users API")
.file("a/b/c/d/e/f/g/deep.txt", b"Very deep file")
.execute()?;
let files = repo.files().at_commit(&commit_hash).list()?;
if files.len() != 8 {
return Err(heroforge_core::FossilError::InvalidArtifact(format!(
"Expected 8 files, got {}",
files.len()
)));
}
let deep_content = repo
.files()
.at_commit(&commit_hash)
.read("a/b/c/d/e/f/g/deep.txt")?;
if deep_content != b"Very deep file" {
return Err(heroforge_core::FossilError::InvalidArtifact(
"Deep file content mismatch".into(),
));
}
Ok(format!(
"{} files in deep directories verified",
files.len()
))
})();
TestResult {
name,
passed: result.is_ok(),
details: result.unwrap_or_else(|e| e.to_string()),
duration_ms: start.elapsed().as_millis(),
}
}
fn test_rapid_changes(base_dir: &Path) -> TestResult {
let start = Instant::now();
let name = "Rapid file changes (50 quick commits)".to_string();
let repo_path = base_dir.join("test_rapid.forge");
let result = (|| -> Result<String> {
let repo = Repository::init(&repo_path)?;
let mut parent = repo
.commit_builder()
.message("initial empty check-in")
.author("testuser")
.initial()
.execute()?;
let num_commits = 50;
let mut hashes: Vec<String> = Vec::new();
for i in 1..=num_commits {
let content = format!("Version {} - timestamp {}", i, i * 1000);
let hash = repo
.commit_builder()
.message(&format!("Update {}", i))
.author("testuser")
.parent(&parent)
.branch("trunk")
.file("counter.txt", content.as_bytes())
.execute()?;
hashes.push(hash.clone());
parent = hash;
}
for i in [0, 10, 25, 40, 49] {
let expected = format!("Version {} - timestamp {}", i + 1, (i + 1) * 1000);
let content = repo
.files()
.at_commit(&hashes[i])
.read_string("counter.txt")?;
if content != expected {
return Err(heroforge_core::FossilError::InvalidArtifact(format!(
"Version {} mismatch",
i + 1
)));
}
}
let checkins = repo.history().recent(60)?;
if checkins.len() != num_commits + 1 {
return Err(heroforge_core::FossilError::InvalidArtifact(format!(
"Expected {} checkins, got {}",
num_commits + 1,
checkins.len()
)));
}
Ok(format!("{} rapid commits verified", num_commits))
})();
TestResult {
name,
passed: result.is_ok(),
details: result.unwrap_or_else(|e| e.to_string()),
duration_ms: start.elapsed().as_millis(),
}
}
fn test_large_files(base_dir: &Path) -> TestResult {
let start = Instant::now();
let name = "Large files (1MB, 5MB)".to_string();
let repo_path = base_dir.join("test_large.forge");
let result = (|| -> Result<String> {
let repo = Repository::init(&repo_path)?;
let initial = repo
.commit_builder()
.message("initial empty check-in")
.author("testuser")
.initial()
.execute()?;
let one_mb: Vec<u8> = (0..1024 * 1024).map(|i| (i % 256) as u8).collect();
let hash1 = repo
.commit_builder()
.message("Add 1MB file")
.author("testuser")
.parent(&initial)
.branch("trunk")
.file("large_1mb.bin", &one_mb)
.execute()?;
let content = repo.files().at_commit(&hash1).read("large_1mb.bin")?;
if content.len() != one_mb.len() {
return Err(heroforge_core::FossilError::InvalidArtifact(format!(
"1MB file size mismatch: {} vs {}",
content.len(),
one_mb.len()
)));
}
if content != one_mb {
return Err(heroforge_core::FossilError::InvalidArtifact(
"1MB content mismatch".into(),
));
}
let five_mb: Vec<u8> = (0..5 * 1024 * 1024)
.map(|i| ((i * 7) % 256) as u8)
.collect();
let hash2 = repo
.commit_builder()
.message("Add 5MB file")
.author("testuser")
.parent(&hash1)
.branch("trunk")
.file("large_5mb.bin", &five_mb)
.execute()?;
let content = repo.files().at_commit(&hash2).read("large_5mb.bin")?;
if content.len() != five_mb.len() {
return Err(heroforge_core::FossilError::InvalidArtifact(format!(
"5MB file size mismatch: {} vs {}",
content.len(),
five_mb.len()
)));
}
if content != five_mb {
return Err(heroforge_core::FossilError::InvalidArtifact(
"5MB content mismatch".into(),
));
}
Ok("1MB and 5MB files verified".into())
})();
TestResult {
name,
passed: result.is_ok(),
details: result.unwrap_or_else(|e| e.to_string()),
duration_ms: start.elapsed().as_millis(),
}
}
fn test_binary_content(base_dir: &Path) -> TestResult {
let start = Instant::now();
let name = "Binary content (all byte values)".to_string();
let repo_path = base_dir.join("test_binary.forge");
let result = (|| -> Result<String> {
let repo = Repository::init(&repo_path)?;
let initial = repo
.commit_builder()
.message("initial empty check-in")
.author("testuser")
.initial()
.execute()?;
let all_bytes: Vec<u8> = (0..=255).collect();
let mut special_binary: Vec<u8> = Vec::new();
special_binary.extend(&[0x00, 0x00, 0x00]); special_binary.extend(&[0xFF, 0xFF, 0xFF]); special_binary.extend(&[0x89, 0x50, 0x4E, 0x47]); special_binary.extend(&[0x1F, 0x8B]); special_binary.extend(&[0x78, 0x9C]); special_binary.extend(&[0x50, 0x4B, 0x03, 0x04]); for i in 0..1000 {
special_binary.push((i % 256) as u8);
}
let hash = repo
.commit_builder()
.message("Add binary files")
.author("testuser")
.parent(&initial)
.branch("trunk")
.file("all_bytes.bin", &all_bytes)
.file("special.bin", &special_binary)
.execute()?;
let content1 = repo.files().at_commit(&hash).read("all_bytes.bin")?;
if content1 != all_bytes {
return Err(heroforge_core::FossilError::InvalidArtifact(
"all_bytes mismatch".into(),
));
}
let content2 = repo.files().at_commit(&hash).read("special.bin")?;
if content2 != special_binary {
return Err(heroforge_core::FossilError::InvalidArtifact(
"special.bin mismatch".into(),
));
}
Ok("Binary content with all byte values verified".into())
})();
TestResult {
name,
passed: result.is_ok(),
details: result.unwrap_or_else(|e| e.to_string()),
duration_ms: start.elapsed().as_millis(),
}
}
fn test_special_characters(base_dir: &Path) -> TestResult {
let start = Instant::now();
let name = "Special characters in content".to_string();
let repo_path = base_dir.join("test_special.forge");
let result = (|| -> Result<String> {
let repo = Repository::init(&repo_path)?;
let initial = repo
.commit_builder()
.message("initial empty check-in")
.author("testuser")
.initial()
.execute()?;
let unicode_content =
"Hello 世界! 🎉 Emoji test: 🚀🔥💯\nCyrillic: Привет\nArabic: Ù…Ø±ØØ¨Ø§\n";
let escape_content = "Backslash: \\ Quote: \" Tab:\tNewline:\nCarriage:\r";
let whitespace_content = " leading spaces\ntrailing spaces \n\t\ttabs\t\t\n";
let hash = repo
.commit_builder()
.message("Add special character files")
.author("testuser")
.parent(&initial)
.branch("trunk")
.file("unicode.txt", unicode_content.as_bytes())
.file("escapes.txt", escape_content.as_bytes())
.file("whitespace.txt", whitespace_content.as_bytes())
.execute()?;
let content = repo.files().at_commit(&hash).read("unicode.txt")?;
if content != unicode_content.as_bytes() {
return Err(heroforge_core::FossilError::InvalidArtifact(
"unicode mismatch".into(),
));
}
let content = repo.files().at_commit(&hash).read("escapes.txt")?;
if content != escape_content.as_bytes() {
return Err(heroforge_core::FossilError::InvalidArtifact(
"escapes mismatch".into(),
));
}
let content = repo.files().at_commit(&hash).read("whitespace.txt")?;
if content != whitespace_content.as_bytes() {
return Err(heroforge_core::FossilError::InvalidArtifact(
"whitespace mismatch".into(),
));
}
Ok("Special characters verified".into())
})();
TestResult {
name,
passed: result.is_ok(),
details: result.unwrap_or_else(|e| e.to_string()),
duration_ms: start.elapsed().as_millis(),
}
}
fn test_empty_files(base_dir: &Path) -> TestResult {
let start = Instant::now();
let name = "Empty files".to_string();
let repo_path = base_dir.join("test_empty.forge");
let result = (|| -> Result<String> {
let repo = Repository::init(&repo_path)?;
let initial = repo
.commit_builder()
.message("initial empty check-in")
.author("testuser")
.initial()
.execute()?;
let hash = repo
.commit_builder()
.message("Add empty files")
.author("testuser")
.parent(&initial)
.branch("trunk")
.file("empty1.txt", b"")
.file("empty2.dat", b"")
.file(".gitkeep", b"")
.execute()?;
for name in &["empty1.txt", "empty2.dat", ".gitkeep"] {
let content = repo.files().at_commit(&hash).read(name)?;
if !content.is_empty() {
return Err(heroforge_core::FossilError::InvalidArtifact(format!(
"{} should be empty",
name
)));
}
}
Ok("Empty files verified".into())
})();
TestResult {
name,
passed: result.is_ok(),
details: result.unwrap_or_else(|e| e.to_string()),
duration_ms: start.elapsed().as_millis(),
}
}
fn test_many_files(base_dir: &Path) -> TestResult {
let start = Instant::now();
let name = "Many files (150 files in one commit)".to_string();
let repo_path = base_dir.join("test_many.forge");
let result = (|| -> Result<String> {
let repo = Repository::init(&repo_path)?;
let initial = repo
.commit_builder()
.message("initial empty check-in")
.author("testuser")
.initial()
.execute()?;
let mut files_data: Vec<(String, Vec<u8>)> = Vec::new();
for i in 0..150 {
let name = format!("file_{:03}.txt", i);
let content = format!("Content of file {}\nWith multiple lines\nLine 3", i);
files_data.push((name, content.into_bytes()));
}
let files: Vec<(&str, &[u8])> = files_data
.iter()
.map(|(n, c)| (n.as_str(), c.as_slice()))
.collect();
let hash = repo
.commit_builder()
.message("Add 150 files")
.author("testuser")
.parent(&initial)
.branch("trunk")
.files(&files)
.execute()?;
let all_files = repo.files().at_commit(&hash).list()?;
if all_files.len() != 150 {
return Err(heroforge_core::FossilError::InvalidArtifact(format!(
"Expected 150 files, got {}",
all_files.len()
)));
}
for i in [0, 50, 100, 149] {
let name = format!("file_{:03}.txt", i);
let expected = format!("Content of file {}\nWith multiple lines\nLine 3", i);
let content = repo.files().at_commit(&hash).read_string(&name)?;
if content != expected {
return Err(heroforge_core::FossilError::InvalidArtifact(format!(
"Content mismatch for {}",
name
)));
}
}
Ok("150 files committed and verified".into())
})();
TestResult {
name,
passed: result.is_ok(),
details: result.unwrap_or_else(|e| e.to_string()),
duration_ms: start.elapsed().as_millis(),
}
}
fn test_file_modifications(base_dir: &Path) -> TestResult {
let start = Instant::now();
let name = "File modifications across versions".to_string();
let repo_path = base_dir.join("test_mods.forge");
let result = (|| -> Result<String> {
let repo = Repository::init(&repo_path)?;
let mut parent = repo
.commit_builder()
.message("initial empty check-in")
.author("testuser")
.initial()
.execute()?;
let mut versions: HashMap<String, Vec<(String, String)>> = HashMap::new();
let v1_files = vec![
("config.json", r#"{"version": 1}"#),
("data.txt", "Initial data"),
("stable.txt", "This file never changes"),
];
let hash1 = repo
.commit_builder()
.message("Version 1")
.author("testuser")
.parent(&parent)
.branch("trunk")
.file("config.json", v1_files[0].1.as_bytes())
.file("data.txt", v1_files[1].1.as_bytes())
.file("stable.txt", v1_files[2].1.as_bytes())
.execute()?;
versions.insert(
hash1.clone(),
v1_files
.iter()
.map(|(n, c)| (n.to_string(), c.to_string()))
.collect(),
);
parent = hash1;
let v2_files = vec![
("config.json", r#"{"version": 2, "updated": true}"#),
("data.txt", "Initial data"),
("stable.txt", "This file never changes"),
];
let hash2 = repo
.commit_builder()
.message("Version 2 - update config")
.author("testuser")
.parent(&parent)
.branch("trunk")
.file("config.json", v2_files[0].1.as_bytes())
.file("data.txt", v2_files[1].1.as_bytes())
.file("stable.txt", v2_files[2].1.as_bytes())
.execute()?;
versions.insert(
hash2.clone(),
v2_files
.iter()
.map(|(n, c)| (n.to_string(), c.to_string()))
.collect(),
);
parent = hash2;
let v3_files = vec![
("config.json", r#"{"version": 2, "updated": true}"#),
("data.txt", "Modified data content"),
("stable.txt", "This file never changes"),
("new_file.txt", "Added in version 3"),
];
let hash3 = repo
.commit_builder()
.message("Version 3 - modify data, add file")
.author("testuser")
.parent(&parent)
.branch("trunk")
.file("config.json", v3_files[0].1.as_bytes())
.file("data.txt", v3_files[1].1.as_bytes())
.file("stable.txt", v3_files[2].1.as_bytes())
.file("new_file.txt", v3_files[3].1.as_bytes())
.execute()?;
versions.insert(
hash3.clone(),
v3_files
.iter()
.map(|(n, c)| (n.to_string(), c.to_string()))
.collect(),
);
for (hash, expected_files) in &versions {
for (name, expected) in expected_files {
let content = repo.files().at_commit(hash).read_string(name)?;
if content != *expected {
return Err(heroforge_core::FossilError::InvalidArtifact(format!(
"Mismatch in {} at {}",
name,
&hash[..8]
)));
}
}
}
Ok("File modifications across 3 versions verified".into())
})();
TestResult {
name,
passed: result.is_ok(),
details: result.unwrap_or_else(|e| e.to_string()),
duration_ms: start.elapsed().as_millis(),
}
}
fn test_mixed_operations(base_dir: &Path) -> TestResult {
let start = Instant::now();
let name = "Mixed operations stress test".to_string();
let repo_path = base_dir.join("test_mixed.forge");
let result = (|| -> Result<String> {
let repo = Repository::init(&repo_path)?;
let mut parent = repo
.commit_builder()
.message("initial empty check-in")
.author("testuser")
.initial()
.execute()?;
let mut all_hashes: Vec<String> = vec![parent.clone()];
for i in 1..=20 {
let mut files_data: Vec<(String, Vec<u8>)> = Vec::new();
files_data.push((
format!("commit_{}/info.txt", i),
format!("Commit {}", i).into_bytes(),
));
for j in 0..(i % 5 + 1) {
let name = format!("commit_{}/file_{}.dat", i, j);
let content: Vec<u8> = (0..((i * 100 + j * 10) % 1000))
.map(|x| (x % 256) as u8)
.collect();
files_data.push((name, content));
}
if i % 3 == 0 {
let name = format!("deep/path/level_{}/data.bin", i);
files_data.push((name, vec![i as u8; 100]));
}
let files: Vec<(&str, &[u8])> = files_data
.iter()
.map(|(n, c)| (n.as_str(), c.as_slice()))
.collect();
let hash = repo
.commit_builder()
.message(&format!("Mixed commit {}", i))
.author("testuser")
.parent(&parent)
.branch("trunk")
.files(&files)
.execute()?;
all_hashes.push(hash.clone());
parent = hash;
}
let checkins = repo.history().recent(25)?;
if checkins.len() != 21 {
return Err(heroforge_core::FossilError::InvalidArtifact(format!(
"Expected 21 checkins, got {}",
checkins.len()
)));
}
let files = repo.files().at_commit(&all_hashes[10]).list()?;
if files.is_empty() {
return Err(heroforge_core::FossilError::InvalidArtifact(
"No files in commit 10".into(),
));
}
let repo_str = repo_path.to_str().unwrap();
let last_hash = all_hashes.last().unwrap();
let cli_content = run_fossil(
&[
"cat",
"commit_20/info.txt",
"-r",
&last_hash[..16],
"-R",
repo_str,
],
base_dir,
)
.map_err(|e| heroforge_core::FossilError::InvalidArtifact(e))?;
if !cli_content.contains("Commit 20") {
return Err(heroforge_core::FossilError::InvalidArtifact(format!(
"CLI content unexpected: {}",
cli_content
)));
}
Ok(format!(
"20 mixed commits with {} total checkins verified",
checkins.len()
))
})();
TestResult {
name,
passed: result.is_ok(),
details: result.unwrap_or_else(|e| e.to_string()),
duration_ms: start.elapsed().as_millis(),
}
}