use crate::config::DebtmapConfig;
use crate::io::real::{MemoryCache, NoOpCache, RealCoverageLoader, RealFileSystem};
use crate::io::traits::{Cache, CoverageLoader, FileSystem};
use crate::progress::traits::{HasProgress, ProgressSink};
use crate::progress::SilentProgressSink;
use std::sync::Arc;
pub trait AnalysisEnv: Clone + Send + Sync {
fn file_system(&self) -> &dyn FileSystem;
fn coverage_loader(&self) -> &dyn CoverageLoader;
fn cache(&self) -> &dyn Cache;
fn config(&self) -> &DebtmapConfig;
fn with_config(self, config: DebtmapConfig) -> Self;
}
#[derive(Clone)]
pub struct RealEnv {
file_system: Arc<dyn FileSystem>,
coverage_loader: Arc<dyn CoverageLoader>,
cache: Arc<dyn Cache>,
config: DebtmapConfig,
progress: Arc<dyn ProgressSink>,
}
impl RealEnv {
pub fn new(config: DebtmapConfig) -> Self {
Self {
file_system: Arc::new(RealFileSystem::new()),
coverage_loader: Arc::new(RealCoverageLoader::new()),
cache: Arc::new(MemoryCache::new()),
config,
progress: Arc::new(SilentProgressSink),
}
}
pub fn with_progress(config: DebtmapConfig, progress: Arc<dyn ProgressSink>) -> Self {
Self {
file_system: Arc::new(RealFileSystem::new()),
coverage_loader: Arc::new(RealCoverageLoader::new()),
cache: Arc::new(MemoryCache::new()),
config,
progress,
}
}
pub fn without_cache(config: DebtmapConfig) -> Self {
Self {
file_system: Arc::new(RealFileSystem::new()),
coverage_loader: Arc::new(RealCoverageLoader::new()),
cache: Arc::new(NoOpCache::new()),
config,
progress: Arc::new(SilentProgressSink),
}
}
pub fn custom(
file_system: Arc<dyn FileSystem>,
coverage_loader: Arc<dyn CoverageLoader>,
cache: Arc<dyn Cache>,
config: DebtmapConfig,
) -> Self {
Self {
file_system,
coverage_loader,
cache,
config,
progress: Arc::new(SilentProgressSink),
}
}
pub fn custom_with_progress(
file_system: Arc<dyn FileSystem>,
coverage_loader: Arc<dyn CoverageLoader>,
cache: Arc<dyn Cache>,
config: DebtmapConfig,
progress: Arc<dyn ProgressSink>,
) -> Self {
Self {
file_system,
coverage_loader,
cache,
config,
progress,
}
}
pub fn with_config(self, config: DebtmapConfig) -> Self {
Self { config, ..self }
}
pub fn set_progress(self, progress: Arc<dyn ProgressSink>) -> Self {
Self { progress, ..self }
}
}
impl AnalysisEnv for RealEnv {
fn file_system(&self) -> &dyn FileSystem {
&*self.file_system
}
fn coverage_loader(&self) -> &dyn CoverageLoader {
&*self.coverage_loader
}
fn cache(&self) -> &dyn Cache {
&*self.cache
}
fn config(&self) -> &DebtmapConfig {
&self.config
}
fn with_config(self, config: DebtmapConfig) -> Self {
Self { config, ..self }
}
}
impl HasProgress for RealEnv {
fn progress(&self) -> &dyn ProgressSink {
&*self.progress
}
}
impl Default for RealEnv {
fn default() -> Self {
Self::new(DebtmapConfig::default())
}
}
impl std::fmt::Debug for RealEnv {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("RealEnv")
.field("config", &self.config)
.finish_non_exhaustive()
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::path::Path;
use tempfile::TempDir;
#[test]
fn test_real_env_creation() {
let config = DebtmapConfig::default();
let env = RealEnv::new(config);
let _ = env.config();
}
#[test]
fn test_real_env_file_system() {
let temp_dir = TempDir::new().unwrap();
let file_path = temp_dir.path().join("test.txt");
let env = RealEnv::default();
assert!(!env.file_system().exists(&file_path));
std::fs::write(&file_path, "test content").unwrap();
assert!(env.file_system().exists(&file_path));
assert!(env.file_system().is_file(&file_path));
let content = env.file_system().read_to_string(&file_path).unwrap();
assert_eq!(content, "test content");
}
#[test]
fn test_real_env_cache() {
let env = RealEnv::default();
env.cache().set("test_key", b"test_value").unwrap();
assert_eq!(env.cache().get("test_key"), Some(b"test_value".to_vec()));
env.cache().invalidate("test_key").unwrap();
assert!(env.cache().get("test_key").is_none());
}
#[test]
fn test_real_env_without_cache() {
let env = RealEnv::without_cache(DebtmapConfig::default());
env.cache().set("key", b"value").unwrap();
assert!(env.cache().get("key").is_none());
}
#[test]
fn test_real_env_with_config() {
let config1 = DebtmapConfig::default();
let config2 = DebtmapConfig {
ignore: Some(crate::config::IgnoreConfig {
patterns: vec!["test".to_string()],
}),
..Default::default()
};
let env = RealEnv::new(config1);
assert!(env.config().ignore.is_none());
let env = env.with_config(config2);
assert!(env.config().ignore.is_some());
}
#[test]
fn test_real_env_is_send_sync() {
fn assert_send_sync<T: Send + Sync>() {}
assert_send_sync::<RealEnv>();
}
#[test]
fn test_real_env_is_clone() {
let env1 = RealEnv::default();
let env2 = env1.clone();
assert!(!env1.file_system().exists(Path::new("/nonexistent")));
assert!(!env2.file_system().exists(Path::new("/nonexistent")));
}
#[test]
fn test_real_env_default_has_silent_progress() {
let env = RealEnv::default();
env.progress().start_stage("Test");
env.progress().report("Test", 0, 10);
env.progress().complete_stage("Test");
env.progress().warn("Warning");
}
#[test]
fn test_real_env_with_progress() {
use crate::progress::RecordingProgressSink;
let recorder = Arc::new(RecordingProgressSink::new());
let env = RealEnv::with_progress(DebtmapConfig::default(), recorder.clone());
env.progress().start_stage("Analysis");
env.progress().report("Analysis", 5, 10);
env.progress().complete_stage("Analysis");
assert_eq!(recorder.stages(), vec!["Analysis"]);
assert_eq!(recorder.completed_stages(), vec!["Analysis"]);
assert_eq!(recorder.event_count(), 3);
}
#[test]
fn test_real_env_set_progress() {
use crate::progress::RecordingProgressSink;
let env = RealEnv::default();
let recorder = Arc::new(RecordingProgressSink::new());
let env = env.set_progress(recorder.clone());
env.progress().start_stage("Test");
assert_eq!(recorder.stages(), vec!["Test"]);
}
}