use crate::{AnalysisContext, WorkspaceFilePath};
use im::HashMap as ImHashMap;
use ryo_source::pure::PureFile;
use std::collections::HashMap;
use std::path::{Path, PathBuf};
use std::sync::{Arc, OnceLock};
fn test_workspace_root() -> &'static Path {
static WORKSPACE: OnceLock<PathBuf> = OnceLock::new();
WORKSPACE.get_or_init(|| {
let dir = std::env::temp_dir().join("ryo-test-workspace");
std::fs::create_dir_all(dir.join("src"))
.expect("Failed to create test workspace src directory");
std::fs::write(
dir.join("Cargo.toml"),
"[package]\nname = \"test_crate\"\nversion = \"0.0.0\"\nedition = \"2021\"\n",
)
.expect("Failed to write test Cargo.toml");
std::fs::write(dir.join("src").join("lib.rs"), "").expect("Failed to write test lib.rs");
dir
})
}
#[derive(Debug)]
pub struct ContextBuilder {
files: HashMap<PathBuf, PureFile>,
workspace_root: PathBuf,
crate_name: String,
}
impl Default for ContextBuilder {
fn default() -> Self {
Self::new()
}
}
impl ContextBuilder {
pub fn new() -> Self {
Self {
files: HashMap::new(),
workspace_root: test_workspace_root().to_path_buf(),
crate_name: "test_crate".to_string(),
}
}
pub fn with_workspace_root(mut self, root: impl Into<PathBuf>) -> Self {
self.workspace_root = root.into();
self
}
pub fn with_crate_name(mut self, name: &str) -> Self {
self.crate_name = name.to_string();
self
}
pub fn with_file(mut self, path: &str, source: &str) -> Self {
let file = PureFile::from_source(source).expect("invalid source code");
self.files.insert(PathBuf::from(path), file);
self
}
pub fn with_pure_file(mut self, path: &str, file: PureFile) -> Self {
self.files.insert(PathBuf::from(path), file);
self
}
pub fn build(self) -> AnalysisContext {
let workspace_files: HashMap<WorkspaceFilePath, PureFile> = self
.files
.into_iter()
.map(|(path, file)| {
let wp =
WorkspaceFilePath::new_for_test(path, &self.workspace_root, &self.crate_name);
(wp, file)
})
.collect();
AnalysisContext::from_workspace_files(workspace_files)
}
pub fn build_minimal(self) -> AnalysisContext {
let workspace_files: ImHashMap<WorkspaceFilePath, Arc<PureFile>> = self
.files
.into_iter()
.map(|(path, file)| {
let wp =
WorkspaceFilePath::new_for_test(path, &self.workspace_root, &self.crate_name);
(wp, Arc::new(file))
})
.collect();
AnalysisContext::from_im_files(workspace_files)
}
}
pub trait ContextTestExt {
fn test_file(&self, path: &str) -> Option<&PureFile>;
fn test_file_path(&self, path: &str) -> Option<WorkspaceFilePath>;
fn test_has_file(&self, path: &str) -> bool;
}
impl ContextTestExt for AnalysisContext {
fn test_file(&self, path: &str) -> Option<&PureFile> {
let file_path = self.test_file_path(path)?;
self.file(&file_path)
}
fn test_file_path(&self, path: &str) -> Option<WorkspaceFilePath> {
let path = Path::new(path);
self.files()
.keys()
.find(|wp| wp.as_relative() == path)
.cloned()
}
fn test_has_file(&self, path: &str) -> bool {
self.test_file_path(path).is_some()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_context_builder_basic() {
let ctx = ContextBuilder::new()
.with_file("src/lib.rs", "pub struct Foo {}")
.build();
assert_eq!(ctx.file_count(), 1);
assert!(ctx.test_has_file("src/lib.rs"));
}
#[test]
fn test_context_builder_multiple_files() {
let ctx = ContextBuilder::new()
.with_file("src/lib.rs", "mod foo;")
.with_file("src/foo.rs", "pub fn bar() {}")
.build();
assert_eq!(ctx.file_count(), 2);
assert!(ctx.test_has_file("src/lib.rs"));
assert!(ctx.test_has_file("src/foo.rs"));
}
#[test]
fn test_context_builder_with_crate_name() {
let ctx = ContextBuilder::new()
.with_crate_name("mylib")
.with_file("src/lib.rs", "pub struct Foo {}")
.build();
let file_path = ctx.test_file_path("src/lib.rs").unwrap();
assert_eq!(file_path.crate_name().as_str(), "mylib");
}
#[test]
fn test_context_test_ext() {
let ctx = ContextBuilder::new()
.with_file("src/lib.rs", "pub fn test() {}")
.build();
let file = ctx.test_file("src/lib.rs").unwrap();
assert!(file.to_source().unwrap().contains("test"));
}
}