use std::io;
use std::path::Path;
pub trait ProgressReporter {
fn phase_starting(&mut self, phase: &str);
fn phase_progress(&mut self, progress: f64);
fn phase_complete(&mut self);
fn warn(&mut self, message: &str);
fn info(&mut self, message: &str);
}
pub trait FileSystem {
fn read_file(&self, path: &Path) -> io::Result<Vec<u8>>;
fn read_file_string(&self, path: &Path) -> io::Result<String> {
let bytes = self.read_file(path)?;
String::from_utf8(bytes).map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))
}
fn file_exists(&self, path: &Path) -> bool;
}
pub trait AnalysisEnv: ProgressReporter + FileSystem {}
impl<T: ProgressReporter + FileSystem> AnalysisEnv for T {}
pub struct RealAnalysisEnv {
quiet_mode: bool,
}
impl RealAnalysisEnv {
pub fn new() -> Self {
let quiet_mode = std::env::var("DEBTMAP_QUIET").is_ok();
Self { quiet_mode }
}
pub fn with_quiet(quiet: bool) -> Self {
Self { quiet_mode: quiet }
}
}
impl Default for RealAnalysisEnv {
fn default() -> Self {
Self::new()
}
}
impl ProgressReporter for RealAnalysisEnv {
fn phase_starting(&mut self, phase: &str) {
log::info!("Phase starting: {}", phase);
}
fn phase_progress(&mut self, progress: f64) {
if let Some(manager) = crate::progress::ProgressManager::global() {
manager.tui_set_progress(progress);
}
}
fn phase_complete(&mut self) {
log::debug!("Phase complete");
}
fn warn(&mut self, message: &str) {
if !self.quiet_mode {
log::warn!("{}", message);
}
}
fn info(&mut self, message: &str) {
if !self.quiet_mode {
log::info!("{}", message);
}
}
}
impl FileSystem for RealAnalysisEnv {
fn read_file(&self, path: &Path) -> io::Result<Vec<u8>> {
std::fs::read(path)
}
fn file_exists(&self, path: &Path) -> bool {
path.exists()
}
}
#[cfg(test)]
pub struct MockAnalysisEnv {
pub phases: Vec<String>,
pub progress_updates: Vec<f64>,
pub warnings: Vec<String>,
pub infos: Vec<String>,
pub files: std::collections::HashMap<std::path::PathBuf, Vec<u8>>,
}
#[cfg(test)]
impl MockAnalysisEnv {
pub fn new() -> Self {
Self {
phases: vec![],
progress_updates: vec![],
warnings: vec![],
infos: vec![],
files: std::collections::HashMap::new(),
}
}
pub fn with_file(
mut self,
path: impl Into<std::path::PathBuf>,
content: impl AsRef<[u8]>,
) -> Self {
self.files.insert(path.into(), content.as_ref().to_vec());
self
}
pub fn phase_started(&self, phase: &str) -> bool {
self.phases
.iter()
.any(|p| p.starts_with(&format!("start:{}", phase)))
}
pub fn completion_count(&self) -> usize {
self.phases.iter().filter(|p| *p == "complete").count()
}
}
#[cfg(test)]
impl Default for MockAnalysisEnv {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
impl ProgressReporter for MockAnalysisEnv {
fn phase_starting(&mut self, phase: &str) {
self.phases.push(format!("start:{}", phase));
}
fn phase_progress(&mut self, progress: f64) {
self.progress_updates.push(progress);
}
fn phase_complete(&mut self) {
self.phases.push("complete".to_string());
}
fn warn(&mut self, message: &str) {
self.warnings.push(message.to_string());
}
fn info(&mut self, message: &str) {
self.infos.push(message.to_string());
}
}
#[cfg(test)]
impl FileSystem for MockAnalysisEnv {
fn read_file(&self, path: &Path) -> io::Result<Vec<u8>> {
self.files.get(path).cloned().ok_or_else(|| {
io::Error::new(
io::ErrorKind::NotFound,
format!("file not found: {}", path.display()),
)
})
}
fn file_exists(&self, path: &Path) -> bool {
self.files.contains_key(path)
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::path::PathBuf;
#[test]
fn test_mock_env_phase_tracking() {
let mut env = MockAnalysisEnv::new();
env.phase_starting("Building call graph");
env.phase_progress(0.5);
env.phase_complete();
assert!(env.phase_started("Building call graph"));
assert_eq!(env.progress_updates, vec![0.5]);
assert_eq!(env.completion_count(), 1);
}
#[test]
fn test_mock_env_file_system() {
let env = MockAnalysisEnv::new()
.with_file("test.rs", "fn main() {}")
.with_file(PathBuf::from("src/lib.rs"), b"pub fn lib() {}");
assert!(env.file_exists(Path::new("test.rs")));
assert!(env.file_exists(Path::new("src/lib.rs")));
assert!(!env.file_exists(Path::new("nonexistent.rs")));
let content = env.read_file(Path::new("test.rs")).unwrap();
assert_eq!(content, b"fn main() {}");
let string_content = env.read_file_string(Path::new("test.rs")).unwrap();
assert_eq!(string_content, "fn main() {}");
}
#[test]
fn test_mock_env_file_not_found() {
let env = MockAnalysisEnv::new();
let result = env.read_file(Path::new("nonexistent.rs"));
assert!(result.is_err());
assert_eq!(result.unwrap_err().kind(), io::ErrorKind::NotFound);
}
#[test]
fn test_mock_env_warnings() {
let mut env = MockAnalysisEnv::new();
env.warn("Warning 1");
env.warn("Warning 2");
env.info("Info message");
assert_eq!(env.warnings.len(), 2);
assert_eq!(env.warnings[0], "Warning 1");
assert_eq!(env.infos.len(), 1);
}
#[test]
fn test_real_env_file_system() {
let temp_dir = tempfile::tempdir().unwrap();
let test_file = temp_dir.path().join("test.rs");
std::fs::write(&test_file, "fn test() {}").unwrap();
let env = RealAnalysisEnv::new();
assert!(env.file_exists(&test_file));
assert!(!env.file_exists(&temp_dir.path().join("nonexistent.rs")));
let content = env.read_file(&test_file).unwrap();
assert_eq!(content, b"fn test() {}");
}
}