use perl_module_resolution::{ModuleUriResolution, resolve_module_path, resolve_module_uri};
use std::path::PathBuf;
use std::time::Duration;
#[test]
fn path_resolves_deeply_nested_module() -> Result<(), Box<dyn std::error::Error>> {
let temp = tempfile::tempdir()?;
let root = temp.path().to_path_buf();
let deep = root.join("lib").join("A").join("B").join("C").join("D.pm");
std::fs::create_dir_all(deep.parent().ok_or("no parent")?)?;
std::fs::write(&deep, "package A::B::C::D; 1;")?;
let resolved = resolve_module_path(&root, "A::B::C::D", &["lib".to_string()]);
assert_eq!(resolved, Some(root.join("lib").join("A/B/C/D.pm")));
Ok(())
}
#[test]
fn path_first_include_path_wins() -> Result<(), Box<dyn std::error::Error>> {
let temp = tempfile::tempdir()?;
let root = temp.path().to_path_buf();
let first = root.join("first_lib").join("My").join("Mod.pm");
let second = root.join("second_lib").join("My").join("Mod.pm");
std::fs::create_dir_all(first.parent().ok_or("no parent")?)?;
std::fs::create_dir_all(second.parent().ok_or("no parent")?)?;
std::fs::write(&first, "package My::Mod; 'first';")?;
std::fs::write(&second, "package My::Mod; 'second';")?;
let resolved =
resolve_module_path(&root, "My::Mod", &["first_lib".to_string(), "second_lib".to_string()]);
assert_eq!(resolved, Some(first));
Ok(())
}
#[test]
fn path_resolves_module_with_underscores_and_numbers() -> Result<(), Box<dyn std::error::Error>> {
let temp = tempfile::tempdir()?;
let root = temp.path().to_path_buf();
let module_file = root.join("lib").join("Foo_Bar").join("Baz123.pm");
std::fs::create_dir_all(module_file.parent().ok_or("no parent")?)?;
std::fs::write(&module_file, "package Foo_Bar::Baz123; 1;")?;
let resolved = resolve_module_path(&root, "Foo_Bar::Baz123", &["lib".to_string()]);
assert_eq!(resolved, Some(module_file));
Ok(())
}
#[test]
fn path_empty_module_name_returns_fallback() -> Result<(), Box<dyn std::error::Error>> {
let temp = tempfile::tempdir()?;
let root = temp.path().to_path_buf();
let resolved = resolve_module_path(&root, "", &[]);
assert!(resolved.is_some());
Ok(())
}
#[test]
fn uri_resolves_from_second_workspace_folder() -> Result<(), Box<dyn std::error::Error>> {
let temp = tempfile::tempdir()?;
let ws1 = temp.path().join("workspace1");
let ws2 = temp.path().join("workspace2");
std::fs::create_dir_all(ws1.join("lib"))?;
let module_file = ws2.join("lib").join("Only").join("Here.pm");
std::fs::create_dir_all(module_file.parent().ok_or("no parent")?)?;
std::fs::write(&module_file, "package Only::Here; 1;")?;
let ws1_uri = url::Url::from_file_path(&ws1).map_err(|()| "failed to create URI")?.to_string();
let ws2_uri = url::Url::from_file_path(&ws2).map_err(|()| "failed to create URI")?.to_string();
let result = resolve_module_uri(
"Only::Here",
&[],
&[ws1_uri, ws2_uri],
&["lib".to_string()],
false,
&[],
Duration::from_millis(200),
);
match result {
ModuleUriResolution::Resolved(uri) => {
assert!(uri.contains("workspace2"), "should resolve from workspace2");
assert!(uri.ends_with("Only/Here.pm"));
}
other => return Err(format!("expected Resolved, got {other:?}").into()),
}
Ok(())
}
#[test]
fn uri_system_inc_enabled_resolves_module() -> Result<(), Box<dyn std::error::Error>> {
let temp = tempfile::tempdir()?;
let system_dir = temp.path().join("perl5_lib");
let module_file = system_dir.join("Sys").join("Module.pm");
std::fs::create_dir_all(module_file.parent().ok_or("no parent")?)?;
std::fs::write(&module_file, "package Sys::Module; 1;")?;
let result = resolve_module_uri(
"Sys::Module",
&[],
&[],
&["lib".to_string()],
true,
&[PathBuf::from(&system_dir)],
Duration::from_millis(200),
);
match result {
ModuleUriResolution::Resolved(uri) => {
assert!(uri.contains("Sys/Module.pm") || uri.contains("Sys%2FModule.pm"));
}
other => return Err(format!("expected Resolved via system INC, got {other:?}").into()),
}
Ok(())
}
#[test]
fn uri_system_inc_disabled_skips_system_paths() -> Result<(), Box<dyn std::error::Error>> {
let temp = tempfile::tempdir()?;
let system_dir = temp.path().join("perl5_lib");
let module_file = system_dir.join("Hidden").join("Module.pm");
std::fs::create_dir_all(module_file.parent().ok_or("no parent")?)?;
std::fs::write(&module_file, "package Hidden::Module; 1;")?;
let result = resolve_module_uri(
"Hidden::Module",
&[],
&[],
&["lib".to_string()],
false,
&[PathBuf::from(&system_dir)],
Duration::from_millis(200),
);
assert_eq!(result, ModuleUriResolution::NotFound);
Ok(())
}
#[test]
fn uri_resolves_deeply_nested_module() -> Result<(), Box<dyn std::error::Error>> {
let temp = tempfile::tempdir()?;
let workspace = temp.path().join("ws");
let deep = workspace.join("custom_lib").join("X").join("Y").join("Z").join("W.pm");
std::fs::create_dir_all(deep.parent().ok_or("no parent")?)?;
std::fs::write(&deep, "package X::Y::Z::W; 1;")?;
let ws_uri =
url::Url::from_file_path(&workspace).map_err(|()| "failed to create URI")?.to_string();
let result = resolve_module_uri(
"X::Y::Z::W",
&[],
&[ws_uri],
&["custom_lib".to_string()],
false,
&[],
Duration::from_millis(200),
);
match result {
ModuleUriResolution::Resolved(uri) => {
assert!(uri.contains("X/Y/Z/W.pm"));
}
other => return Err(format!("expected Resolved for deep module, got {other:?}").into()),
}
Ok(())
}
#[test]
fn uri_first_include_path_wins() -> Result<(), Box<dyn std::error::Error>> {
let temp = tempfile::tempdir()?;
let workspace = temp.path().join("ws");
let first = workspace.join("alpha_lib").join("Prio").join("Test.pm");
let second = workspace.join("beta_lib").join("Prio").join("Test.pm");
std::fs::create_dir_all(first.parent().ok_or("no parent")?)?;
std::fs::create_dir_all(second.parent().ok_or("no parent")?)?;
std::fs::write(&first, "package Prio::Test; 'alpha';")?;
std::fs::write(&second, "package Prio::Test; 'beta';")?;
let ws_uri =
url::Url::from_file_path(&workspace).map_err(|()| "failed to create URI")?.to_string();
let result = resolve_module_uri(
"Prio::Test",
&[],
&[ws_uri],
&["alpha_lib".to_string(), "beta_lib".to_string()],
false,
&[],
Duration::from_millis(200),
);
match result {
ModuleUriResolution::Resolved(uri) => {
assert!(uri.contains("alpha_lib"), "first include path should win, got: {uri}");
}
other => return Err(format!("expected Resolved, got {other:?}").into()),
}
Ok(())
}
#[test]
fn uri_open_document_takes_precedence_over_filesystem() -> Result<(), Box<dyn std::error::Error>> {
let temp = tempfile::tempdir()?;
let workspace = temp.path().join("ws");
let on_disk = workspace.join("lib").join("Dup").join("Mod.pm");
std::fs::create_dir_all(on_disk.parent().ok_or("no parent")?)?;
std::fs::write(&on_disk, "package Dup::Mod; 1;")?;
let ws_uri =
url::Url::from_file_path(&workspace).map_err(|()| "failed to create URI")?.to_string();
let open_doc = "file:///tmp/editor-buffer/lib/Dup/Mod.pm".to_string();
let result = resolve_module_uri(
"Dup::Mod",
std::slice::from_ref(&open_doc),
&[ws_uri],
&["lib".to_string()],
false,
&[],
Duration::from_millis(200),
);
assert_eq!(result, ModuleUriResolution::Resolved(open_doc));
Ok(())
}