use std::{fs, io};
use camino::Utf8Path;
use postgresql_embedded::VersionReq;
use rstest::{fixture, rstest};
use tempfile::{TempDir, tempdir};
use super::{lookup::COMPLETION_MARKER, *};
fn create_mock_binaries(dir: &Utf8Path) -> io::Result<()> {
let bin_dir = dir.join("bin");
fs::create_dir_all(&bin_dir)?;
fs::write(bin_dir.join("postgres"), "mock postgres binary")?;
fs::write(bin_dir.join("pg_ctl"), "mock pg_ctl binary")?;
Ok(())
}
fn create_complete_cache_entry(
cache_dir: &Utf8Path,
version: &str,
) -> io::Result<camino::Utf8PathBuf> {
let version_dir = cache_dir.join(version);
create_mock_binaries(&version_dir)?;
fs::write(version_dir.join(COMPLETION_MARKER), "")?;
Ok(version_dir)
}
macro_rules! assert_cached_match {
($versions:expr, $req:expr, $expected:expr) => {{
let temp = tempdir().expect("tempdir");
let cache_dir = Utf8Path::from_path(temp.path()).expect("utf8 path");
for version in $versions {
create_complete_cache_entry(cache_dir, version).expect("create cache entry");
}
let version_req = VersionReq::parse($req).expect("parse version req");
let result = find_matching_cached_version(cache_dir, &version_req);
match $expected {
Some(ver) => {
let (version, path) = result.expect("should find cached version");
assert_eq!(version, ver);
assert!(path.ends_with(ver));
}
None => {
assert!(result.is_none());
}
}
}};
}
#[fixture]
fn cache_fixture() -> io::Result<(TempDir, camino::Utf8PathBuf)> {
let temp = tempdir()?;
let cache_dir = camino::Utf8PathBuf::from_path_buf(temp.path().to_path_buf())
.map_err(|path| io::Error::other(format!("non-UTF-8 temp path: {}", path.display())))?;
Ok((temp, cache_dir))
}
#[rstest]
fn check_cache_returns_miss_for_empty_directory(
cache_fixture: io::Result<(TempDir, camino::Utf8PathBuf)>,
) {
let (_temp, cache_dir) = cache_fixture.expect("cache fixture");
let result = check_cache(&cache_dir, "17.4.0");
assert!(matches!(result, CacheLookupResult::Miss));
}
#[rstest]
fn check_cache_returns_miss_without_marker(
cache_fixture: io::Result<(TempDir, camino::Utf8PathBuf)>,
) {
let (_temp, cache_dir) = cache_fixture.expect("cache fixture");
let version_dir = cache_dir.join("17.4.0");
create_mock_binaries(&version_dir).expect("create mock binaries");
let result = check_cache(&cache_dir, "17.4.0");
assert!(matches!(result, CacheLookupResult::Miss));
}
#[rstest]
fn check_cache_returns_miss_without_bin_directory(
cache_fixture: io::Result<(TempDir, camino::Utf8PathBuf)>,
) {
let (_temp, cache_dir) = cache_fixture.expect("cache fixture");
let version_dir = cache_dir.join("17.4.0");
fs::create_dir_all(&version_dir).expect("create version dir");
fs::write(version_dir.join(COMPLETION_MARKER), "").expect("write marker");
let result = check_cache(&cache_dir, "17.4.0");
assert!(matches!(result, CacheLookupResult::Miss));
}
#[rstest]
fn check_cache_returns_hit_with_marker_and_bin(
cache_fixture: io::Result<(TempDir, camino::Utf8PathBuf)>,
) {
let (_temp, cache_dir) = cache_fixture.expect("cache fixture");
let version_dir = create_complete_cache_entry(&cache_dir, "17.4.0").expect("cache entry");
let result = check_cache(&cache_dir, "17.4.0");
match result {
CacheLookupResult::Hit { source_dir } => {
assert_eq!(source_dir, version_dir);
}
CacheLookupResult::Miss => panic!("expected cache hit"),
}
}
#[test]
fn copy_from_cache_copies_files() {
let source_temp = tempdir().expect("source tempdir");
let target_temp = tempdir().expect("target tempdir");
let source = Utf8Path::from_path(source_temp.path()).expect("utf8 source");
let target = Utf8Path::from_path(target_temp.path()).expect("utf8 target");
create_mock_binaries(source).expect("create mock binaries");
copy_from_cache(source, target).expect("copy from cache");
assert!(target.join("bin/postgres").exists());
assert!(target.join("bin/pg_ctl").exists());
}
#[test]
fn populate_cache_creates_version_directory() {
let source_temp = tempdir().expect("source tempdir");
let cache_temp = tempdir().expect("cache tempdir");
let source = Utf8Path::from_path(source_temp.path()).expect("utf8 source");
let cache_dir = Utf8Path::from_path(cache_temp.path()).expect("utf8 cache");
create_mock_binaries(source).expect("create mock binaries");
populate_cache(source, cache_dir, "17.4.0").expect("populate cache");
let version_dir = cache_dir.join("17.4.0");
assert!(version_dir.join(COMPLETION_MARKER).exists());
assert!(version_dir.join("bin/postgres").exists());
}
macro_rules! assert_try_use_cache {
($populate_cache:expr, $expected_result:expr, $check_files_copied:expr) => {{
let cache_temp = tempdir().expect("cache tempdir");
let target_temp = tempdir().expect("target tempdir");
let cache_dir = Utf8Path::from_path(cache_temp.path()).expect("utf8 cache");
let target = Utf8Path::from_path(target_temp.path()).expect("utf8 target");
if $populate_cache {
create_complete_cache_entry(cache_dir, "17.4.0").expect("create cache entry");
}
let result = try_use_cache(cache_dir, "17.4.0", target);
assert_eq!(result, $expected_result);
if $check_files_copied {
assert!(target.join("bin/postgres").exists());
}
}};
}
#[test]
fn try_use_cache_returns_false_on_miss() {
assert_try_use_cache!(false, false, false);
}
#[test]
fn try_use_cache_returns_true_on_hit() {
assert_try_use_cache!(true, true, true);
}
#[rstest]
fn find_matching_cached_version_returns_none_for_empty_cache(
cache_fixture: io::Result<(TempDir, camino::Utf8PathBuf)>,
) {
let (_temp, cache_dir) = cache_fixture.expect("cache fixture");
let version_req = VersionReq::parse("^17").expect("parse version req");
let result = find_matching_cached_version(&cache_dir, &version_req);
assert!(result.is_none());
}
#[rstest]
#[case::exact_match(&["17.4.0"], "=17.4.0", Some("17.4.0"))]
#[case::caret_requirement(&["17.4.0"], "^17", Some("17.4.0"))]
#[case::highest_matching(&["17.2.0", "17.4.0"], "^17", Some("17.4.0"))]
fn find_matching_cached_version_scenarios(
#[case] versions: &[&str],
#[case] req: &str,
#[case] expected: Option<&str>,
) {
assert_cached_match!(versions, req, expected);
}
#[rstest]
fn find_matching_cached_version_ignores_non_matching(
cache_fixture: io::Result<(TempDir, camino::Utf8PathBuf)>,
) {
let (_temp, cache_dir) = cache_fixture.expect("cache fixture");
create_complete_cache_entry(&cache_dir, "16.0.0").expect("create cache entry");
let version_req = VersionReq::parse("^17").expect("parse version req");
let result = find_matching_cached_version(&cache_dir, &version_req);
assert!(result.is_none());
}
#[rstest]
fn find_matching_cached_version_ignores_incomplete_entries(
cache_fixture: io::Result<(TempDir, camino::Utf8PathBuf)>,
) {
let (_temp, cache_dir) = cache_fixture.expect("cache fixture");
let version_dir = cache_dir.join("17.4.0");
create_mock_binaries(&version_dir).expect("create mock binaries");
let version_req = VersionReq::parse("^17").expect("parse version req");
let result = find_matching_cached_version(&cache_dir, &version_req);
assert!(result.is_none());
}