use pray_core::resolve::resolve_project;
use std::fs;
use std::path::{Path, PathBuf};
use std::time::{SystemTime, UNIX_EPOCH};
fn temporary_directory(prefix: &str) -> PathBuf {
let nanos = SystemTime::now()
.duration_since(UNIX_EPOCH)
.expect("clock")
.as_nanos();
let path = std::env::temp_dir().join(format!("{prefix}-{nanos}"));
fs::create_dir_all(&path).expect("temp dir");
path
}
fn write_package(root: &Path, name: &str, version: &str, dependency: Option<&str>) {
let package_root = root.join(format!("packages/{name}"));
fs::create_dir_all(package_root.join("exports")).expect("package dirs");
let dependency_line = dependency
.map(|dep| format!(" spec.add_dependency \"{dep}\", \"~> 1.0\"\n"))
.unwrap_or_default();
fs::write(
package_root.join(format!("{name}.prayspec")),
format!(
r#"
Package::Specification.new do |spec|
spec.name = "sample/{name}"
spec.version = "{version}"
spec.summary = "{name}"
spec.files = ["README.md", "exports/{name}.md"]
spec.exports = {{
"{name}" => {{
type: "fragment",
path: "exports/{name}.md",
summary: "{name}"
}}
}}
{dependency_line}end
"#
),
)
.expect("prayspec");
fs::write(package_root.join("README.md"), "readme\n").expect("readme");
fs::write(
package_root.join(format!("exports/{name}.md")),
format!("{name}\n"),
)
.expect("export");
}
#[test]
fn resolve_rejects_two_package_dependency_cycle() {
let root = temporary_directory("pray-cycle");
write_package(&root, "alpha", "1.0.0", Some("sample/beta"));
write_package(&root, "beta", "1.0.0", Some("sample/alpha"));
fs::write(
root.join("Prayfile"),
r#"
prayfile "1"
target :tool_a do
output "INSTRUCTIONS.md"
end
pray "sample/alpha", path: "packages/alpha"
pray "sample/beta", path: "packages/beta"
"#,
)
.expect("Prayfile");
let error = resolve_project(&root.join("Prayfile")).expect_err("cycle");
let message = error.to_string();
assert!(
message.contains("dependency cycle detected"),
"unexpected error: {message}"
);
assert!(message.contains("sample/alpha") || message.contains("sample/beta"));
}
#[test]
fn resolve_accepts_acyclic_dependency_graph() {
let root = temporary_directory("pray-acyclic");
write_package(&root, "base", "1.0.0", Some("sample/common"));
write_package(&root, "common", "1.0.0", None);
fs::write(
root.join("Prayfile"),
r#"
prayfile "1"
target :tool_a do
output "INSTRUCTIONS.md"
end
pray "sample/base", path: "packages/base"
pray "sample/common", path: "packages/common"
"#,
)
.expect("Prayfile");
let project = resolve_project(&root.join("Prayfile")).expect("acyclic resolve");
assert_eq!(project.packages.len(), 2);
}
#[test]
fn resolve_loads_transitive_path_dependency() {
let root = temporary_directory("pray-transitive-dep");
write_package(&root, "base", "1.0.0", Some("sample/common"));
write_package(&root, "common", "1.0.0", None);
fs::write(
root.join("Prayfile"),
r#"
prayfile "1"
target :tool_a do
output "INSTRUCTIONS.md"
end
pray "sample/base", path: "packages/base"
"#,
)
.expect("Prayfile");
let project = resolve_project(&root.join("Prayfile")).expect("transitive resolve");
assert_eq!(project.packages.len(), 2);
assert!(project
.packages
.iter()
.any(|package| package.declaration.name == "sample/common" && !package.explicit));
assert!(project
.packages
.iter()
.any(|package| package.declaration.name == "sample/base" && package.explicit));
}
#[test]
fn resolve_rejects_missing_transitive_path_dependency() {
let root = temporary_directory("pray-missing-transitive");
write_package(&root, "base", "1.0.0", Some("sample/missing"));
fs::write(
root.join("Prayfile"),
r#"
prayfile "1"
target :tool_a do
output "INSTRUCTIONS.md"
end
pray "sample/base", path: "packages/base"
"#,
)
.expect("Prayfile");
let error = resolve_project(&root.join("Prayfile")).expect_err("missing transitive");
let message = error.to_string();
assert!(
message.contains("sample/missing") || message.contains("packages/missing"),
"unexpected error: {message}"
);
}