use super::*;
use proptest::prelude::*;
use std::fs;
use tempfile::TempDir;
fn arb_project_type() -> impl Strategy<Value = ProjectType> {
prop_oneof![
Just(ProjectType::Rust),
Just(ProjectType::Node),
Just(ProjectType::Python),
Just(ProjectType::Go),
Just(ProjectType::Java),
Just(ProjectType::Ruby),
]
}
fn arb_ttl() -> impl Strategy<Value = Duration> {
(1u64..10000u64).prop_map(Duration::from_millis)
}
fn create_project_dir(project_type: ProjectType) -> TempDir {
let temp = TempDir::new().unwrap();
match project_type {
ProjectType::Rust => {
fs::write(
temp.path().join("Cargo.toml"),
r#"[package]
name = "test-project"
version = "0.1.0"
"#,
)
.unwrap();
fs::create_dir(temp.path().join("src")).unwrap();
}
ProjectType::Node => {
fs::write(
temp.path().join("package.json"),
r#"{"name": "test-project", "version": "1.0.0"}"#,
)
.unwrap();
}
ProjectType::Python => {
fs::write(
temp.path().join("pyproject.toml"),
r#"[project]
name = "test-project"
"#,
)
.unwrap();
}
ProjectType::Go => {
fs::write(
temp.path().join("go.mod"),
"module test-project\n\ngo 1.21\n",
)
.unwrap();
}
ProjectType::Java => {
fs::write(
temp.path().join("pom.xml"),
r#"<project><artifactId>test-project</artifactId></project>"#,
)
.unwrap();
}
ProjectType::Ruby => {
fs::write(
temp.path().join("Gemfile"),
"source 'https://rubygems.org'\n",
)
.unwrap();
}
_ => {}
}
temp
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(100))]
#[test]
fn prop_project_type_detection_accuracy(project_type in arb_project_type()) {
let temp = create_project_dir(project_type);
let provider = SystemInfoProvider::default()
.with_project_root(temp.path());
let detected = provider.detect_project();
prop_assert!(detected.is_some(), "Project should be detected");
let project_info = detected.unwrap();
prop_assert_eq!(
project_info.project_type,
project_type,
"Detected project type should match expected"
);
}
#[test]
fn prop_system_info_caching(ttl_ms in 100u64..5000u64) {
let ttl = Duration::from_millis(ttl_ms);
let mut provider = SystemInfoProvider::new(ttl);
let info1 = provider.get().unwrap();
let collected_at1 = info1.collected_at;
prop_assert!(provider.is_cache_valid(), "Cache should be valid after first call");
let info2 = provider.get().unwrap();
let collected_at2 = info2.collected_at;
prop_assert_eq!(
collected_at1,
collected_at2,
"Cached value should have same timestamp"
);
prop_assert_eq!(info1.os, info2.os, "Cached OS info should match");
prop_assert_eq!(info1.shell, info2.shell, "Cached shell info should match");
}
#[test]
fn prop_cache_invalidation(ttl_ms in 100u64..5000u64) {
let ttl = Duration::from_millis(ttl_ms);
let mut provider = SystemInfoProvider::new(ttl);
let _info1 = provider.get().unwrap();
prop_assert!(provider.is_cache_valid(), "Cache should be valid");
provider.invalidate();
prop_assert!(!provider.is_cache_valid(), "Cache should be invalid after invalidation");
let _info2 = provider.get().unwrap();
prop_assert!(provider.is_cache_valid(), "Cache should be valid after re-detection");
}
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(100))]
#[test]
fn prop_os_detection_consistency(num_calls in 1usize..10usize) {
let provider = SystemInfoProvider::default();
let first_os = provider.detect_os().unwrap();
for _ in 0..num_calls {
let os = provider.detect_os().unwrap();
prop_assert_eq!(&os.name, &first_os.name, "OS name should be consistent");
prop_assert_eq!(&os.arch, &first_os.arch, "OS arch should be consistent");
prop_assert_eq!(&os.family, &first_os.family, "OS family should be consistent");
}
}
#[test]
fn prop_shell_detection_consistency(num_calls in 1usize..10usize) {
let provider = SystemInfoProvider::default();
let first_shell = provider.detect_shell().unwrap();
for _ in 0..num_calls {
let shell = provider.detect_shell().unwrap();
prop_assert_eq!(&shell.name, &first_shell.name, "Shell name should be consistent");
prop_assert_eq!(&shell.path, &first_shell.path, "Shell path should be consistent");
}
}
#[test]
fn prop_cache_ttl_expiry(ttl_ms in 1u64..50u64) {
let ttl = Duration::from_millis(ttl_ms);
let mut cache = SystemInfoCache::new();
let info = SystemInfo {
os: OsInfo {
name: "test".to_string(),
version: "1.0".to_string(),
arch: "x86_64".to_string(),
family: "unix".to_string(),
},
shell: ShellInfo {
name: "bash".to_string(),
version: None,
path: PathBuf::from("/bin/bash"),
},
languages: vec![],
package_managers: vec![],
project: None,
git: None,
build_tools: vec![],
test_frameworks: vec![],
collected_at: std::time::SystemTime::now(),
};
cache.set(info);
prop_assert!(cache.is_valid(ttl), "Cache should be valid immediately after set");
std::thread::sleep(ttl + Duration::from_millis(10));
prop_assert!(!cache.is_valid(ttl), "Cache should be invalid after TTL expires");
}
}
#[cfg(test)]
mod additional_tests {
use super::*;
#[test]
fn test_project_markers_exist() {
let rust_markers = detectors::get_project_markers(ProjectType::Rust);
assert!(!rust_markers.is_empty());
assert!(rust_markers.contains(&"Cargo.toml"));
let node_markers = detectors::get_project_markers(ProjectType::Node);
assert!(!node_markers.is_empty());
assert!(node_markers.contains(&"package.json"));
}
#[test]
fn test_mixed_project_detection() {
let temp = TempDir::new().unwrap();
fs::write(temp.path().join("Cargo.toml"), "[package]").unwrap();
fs::write(temp.path().join("package.json"), "{}").unwrap();
let detected = detect_project_type(temp.path());
assert_eq!(detected, ProjectType::Mixed);
}
#[test]
fn test_unknown_project_detection() {
let temp = TempDir::new().unwrap();
let detected = detect_project_type(temp.path());
assert_eq!(detected, ProjectType::Unknown);
}
}