Files
depot/src/install/scripts/tests.rs
T

444 lines
14 KiB
Rust

use super::*;
use crate::package::{
Alternatives, Build, BuildFlags, BuildType, Dependencies, PackageInfo, Source,
};
#[cfg(unix)]
use std::os::unix::fs::PermissionsExt;
fn mk_spec(spec_dir: &Path) -> PackageSpec {
PackageSpec {
package: PackageInfo {
name: "foo".into(),
real_name: None,
version: "1.0".into(),
revision: 1,
description: "d".into(),
homepage: "h".into(),
abi_breaking: false,
built_against: Vec::new(),
license: vec!["MIT".into()],
},
packages: Vec::new(),
alternatives: Alternatives::default(),
manual_sources: Vec::new(),
source: vec![Source {
url: "https://example.com/foo.tar.gz".into(),
sha256: "skip".into(),
extract_dir: "foo".into(),
patches: Vec::new(),
post_extract: Vec::new(),
cherry_pick: Vec::new(),
}],
build: Build {
build_type: BuildType::Custom,
flags: BuildFlags::default(),
},
dependencies: Dependencies::default(),
package_alternatives: Default::default(),
package_dependencies: Default::default(),
spec_dir: spec_dir.to_path_buf(),
}
}
#[test]
fn stage_scripts_from_spec_dir_copies_tree() {
let tmp = tempfile::tempdir().unwrap();
let spec_dir = tmp.path().join("spec");
let destdir = tmp.path().join("dest");
std::fs::create_dir_all(spec_dir.join("scripts/lib")).unwrap();
std::fs::create_dir_all(&destdir).unwrap();
std::fs::write(spec_dir.join("scripts/pre_install"), "echo pre").unwrap();
std::fs::write(spec_dir.join("scripts/lib/common.sh"), "echo lib").unwrap();
let spec = mk_spec(&spec_dir);
let staged = stage_scripts_from_spec_dir(&spec, &destdir).unwrap();
assert!(staged);
assert!(destdir.join("scripts/pre_install").exists());
assert!(destdir.join("scripts/lib/common.sh").exists());
#[cfg(unix)]
{
let mode = std::fs::metadata(destdir.join("scripts/pre_install"))
.unwrap()
.permissions()
.mode();
assert_ne!(mode & 0o111, 0);
}
}
#[test]
fn run_hook_if_present_executes_script() {
let tmp = tempfile::tempdir().unwrap();
let scripts = tmp.path().join("scripts");
let rootfs = tmp.path().join("root");
std::fs::create_dir_all(&scripts).unwrap();
std::fs::create_dir_all(&rootfs).unwrap();
std::fs::write(
scripts.join("pre_install"),
"echo \"$DEPOT_ACTION:$DEPOT_PHASE:$DEPOT_PACKAGE\" > \"$DEPOT_ROOTFS/hook.out\"\n",
)
.unwrap();
let ran = run_hook_if_present(&scripts, Hook::PreInstall, &rootfs, "foo").unwrap();
assert!(ran);
assert_eq!(
std::fs::read_to_string(rootfs.join("hook.out")).unwrap(),
"install:pre:foo\n"
);
}
#[test]
fn run_hook_if_present_uses_safe_script_path() {
let tmp = tempfile::tempdir().unwrap();
let scripts = tmp.path().join("scripts");
let rootfs = tmp.path().join("root");
std::fs::create_dir_all(&scripts).unwrap();
std::fs::create_dir_all(&rootfs).unwrap();
std::fs::write(
scripts.join("pre_install"),
"echo \"$PATH\" > \"$DEPOT_ROOTFS/path.out\"\n",
)
.unwrap();
let ran = run_hook_if_present(&scripts, Hook::PreInstall, &rootfs, "foo").unwrap();
assert!(ran);
assert_eq!(
std::fs::read_to_string(rootfs.join("path.out"))
.unwrap()
.trim_end(),
crate::runtime_env::safe_script_path()
);
}
#[test]
fn run_hook_if_present_accepts_compact_script_name() {
let tmp = tempfile::tempdir().unwrap();
let scripts = tmp.path().join("scripts");
let rootfs = tmp.path().join("root");
std::fs::create_dir_all(&scripts).unwrap();
std::fs::create_dir_all(&rootfs).unwrap();
std::fs::write(
scripts.join("postinstall.sh"),
"echo compact > \"$DEPOT_ROOTFS/hook.out\"\n",
)
.unwrap();
let ran = run_hook_if_present(&scripts, Hook::PostInstall, &rootfs, "foo").unwrap();
assert!(ran);
assert_eq!(
std::fs::read_to_string(rootfs.join("hook.out")).unwrap(),
"compact\n"
);
}
#[test]
fn run_hook_if_present_prefers_lib32_specific_script_name() {
let tmp = tempfile::tempdir().unwrap();
let scripts = tmp.path().join("scripts");
let rootfs = tmp.path().join("root");
std::fs::create_dir_all(&scripts).unwrap();
std::fs::create_dir_all(&rootfs).unwrap();
std::fs::write(
scripts.join("post_install"),
"echo generic > \"$DEPOT_ROOTFS/hook.out\"\n",
)
.unwrap();
std::fs::write(
scripts.join("post_install-lib32"),
"echo lib32 > \"$DEPOT_ROOTFS/hook.out\"\n",
)
.unwrap();
let ran = run_hook_if_present(&scripts, Hook::PostInstall, &rootfs, "lib32-foo").unwrap();
assert!(ran);
assert_eq!(
std::fs::read_to_string(rootfs.join("hook.out")).unwrap(),
"lib32\n"
);
}
#[test]
fn run_hook_if_present_rejects_ambiguous_names() {
let tmp = tempfile::tempdir().unwrap();
let scripts = tmp.path().join("scripts");
let rootfs = tmp.path().join("root");
std::fs::create_dir_all(&scripts).unwrap();
std::fs::create_dir_all(&rootfs).unwrap();
std::fs::write(scripts.join("pre_update"), "echo one").unwrap();
std::fs::write(scripts.join("pre-update"), "echo two").unwrap();
let err = run_hook_if_present(&scripts, Hook::PreUpdate, &rootfs, "foo")
.expect_err("expected ambiguous script names to fail");
assert!(err.to_string().contains("Ambiguous lifecycle hook"));
}
#[test]
fn run_hook_if_present_with_relative_rootfs_uses_correct_script_and_env_paths() {
let cwd = std::env::current_dir().unwrap();
let tmp = tempfile::Builder::new()
.prefix("depot-hook-rel-rootfs-")
.tempdir_in(&cwd)
.unwrap();
let rootfs_abs = tmp.path().join("root");
std::fs::create_dir_all(&rootfs_abs).unwrap();
let rootfs_rel = rootfs_abs.strip_prefix(&cwd).unwrap().to_path_buf();
let scripts = rootfs_rel.join("scripts");
std::fs::create_dir_all(&scripts).unwrap();
std::fs::write(
scripts.join("pre_install"),
"echo ok > \"$DEPOT_ROOTFS/hook.out\"\n",
)
.unwrap();
let ran = run_hook_if_present(&scripts, Hook::PreInstall, &rootfs_rel, "foo").unwrap();
assert!(ran);
assert_eq!(
std::fs::read_to_string(rootfs_abs.join("hook.out")).unwrap(),
"ok\n"
);
}
#[test]
fn should_bootstrap_host_shell_only_for_chrooted_root_installs_without_shell() {
assert!(should_bootstrap_host_shell(true, true, false));
assert!(!should_bootstrap_host_shell(true, true, true));
assert!(!should_bootstrap_host_shell(true, false, false));
assert!(!should_bootstrap_host_shell(false, true, false));
}
#[test]
fn parse_ldd_dependency_paths_extracts_absolute_paths() {
let parsed = parse_ldd_dependency_paths(
"linux-vdso.so.1 (0x0000)\nlibc.so.6 => /lib/libc.so.6 (0x0000)\n/lib64/ld-linux-x86-64.so.2 (0x0000)\nlibc.so.6 => /lib/libc.so.6 (0x0001)\n",
)
.unwrap();
assert_eq!(
parsed,
vec![
PathBuf::from("/lib/libc.so.6"),
PathBuf::from("/lib64/ld-linux-x86-64.so.2")
]
);
}
#[test]
fn parse_ldd_dependency_paths_rejects_missing_dependencies() {
let err = parse_ldd_dependency_paths("libedit.so.0 => not found\n")
.expect_err("expected ldd parse to fail when a dependency is missing");
assert!(
err.to_string()
.contains("Missing shared-library dependency")
);
}
#[test]
fn parse_hook_command_candidates_finds_commands_after_assignments_and_operators() {
let commands = parse_hook_command_candidates(
"PATH=/tmp:$PATH grep -q foo etc/shells || echo foo >> etc/shells\ncat \"$DEPOT_ROOTFS/usr/bin/find\" | sed 's/x/y/'\n",
);
assert_eq!(
commands,
vec!["cat".to_string(), "grep".to_string(), "sed".to_string()]
);
}
#[test]
fn parse_hook_command_candidates_ignores_builtins_and_control_words() {
let commands = parse_hook_command_candidates("if true; then export FOO=bar; echo hi; fi\n");
assert!(commands.is_empty());
}
#[test]
fn deferred_hooks_file_roundtrip() {
let tmp = tempfile::tempdir().unwrap();
let path = tmp.path().join("hooks.tsv");
let hooks = vec![
DeferredHook {
pkg_name: "foo".into(),
hook: Hook::PostInstall,
script_rel: PathBuf::from("usr/share/depot/foo/scripts/post_install"),
},
DeferredHook {
pkg_name: "bar".into(),
hook: Hook::PostUpdate,
script_rel: PathBuf::from("usr/share/depot/bar/scripts/post_update"),
},
];
write_deferred_hooks(&path, &hooks).unwrap();
let loaded = read_deferred_hooks(&path).unwrap();
assert_eq!(loaded.len(), hooks.len());
assert_eq!(loaded[0].pkg_name, hooks[0].pkg_name);
assert_eq!(loaded[0].hook, hooks[0].hook);
assert_eq!(loaded[0].script_rel, hooks[0].script_rel);
assert_eq!(loaded[1].pkg_name, hooks[1].pkg_name);
assert_eq!(loaded[1].hook, hooks[1].hook);
assert_eq!(loaded[1].script_rel, hooks[1].script_rel);
}
#[test]
fn queue_deferred_hook_dedupes_entries() {
let tmp = tempfile::tempdir().unwrap();
queue_deferred_hook(
tmp.path(),
"foo",
Hook::PostInstall,
Path::new("usr/share/depot/foo/scripts/post_install"),
)
.unwrap();
queue_deferred_hook(
tmp.path(),
"foo",
Hook::PostInstall,
Path::new("usr/share/depot/foo/scripts/post_install"),
)
.unwrap();
let loaded = read_deferred_hooks(&deferred_hooks_file(tmp.path())).unwrap();
assert_eq!(loaded.len(), 1);
assert_eq!(loaded[0].pkg_name, "foo");
assert_eq!(loaded[0].hook, Hook::PostInstall);
assert_eq!(
loaded[0].script_rel,
PathBuf::from("usr/share/depot/foo/scripts/post_install")
);
}
#[test]
fn sync_staged_scripts_to_rootfs_replaces_existing_tree() {
let tmp = tempfile::tempdir().unwrap();
let rootfs = tmp.path().join("root");
let staged = tmp.path().join("staged");
std::fs::create_dir_all(staged.join("scripts")).unwrap();
let installed = installed_scripts_dir(&rootfs, "foo");
std::fs::create_dir_all(&installed).unwrap();
std::fs::write(installed.join("old"), "old").unwrap();
std::fs::write(staged.join("scripts/post_install"), "echo ok").unwrap();
let has_scripts =
sync_staged_scripts_to_rootfs(&staged.join("scripts"), &rootfs, "foo").unwrap();
assert!(has_scripts);
let installed = installed_scripts_dir(&rootfs, "foo");
assert!(!installed.join("old").exists());
assert!(installed.join("post_install").exists());
#[cfg(unix)]
{
let mode = std::fs::metadata(installed.join("post_install"))
.unwrap()
.permissions()
.mode();
assert_ne!(mode & 0o111, 0);
}
}
#[test]
fn sync_staged_scripts_to_rootfs_removes_old_when_none_staged() {
let tmp = tempfile::tempdir().unwrap();
let rootfs = tmp.path().join("root");
let staged = tmp.path().join("staged");
std::fs::create_dir_all(&staged).unwrap();
let installed = installed_scripts_dir(&rootfs, "foo");
std::fs::create_dir_all(&installed).unwrap();
std::fs::write(installed.join("pre_remove"), "echo old").unwrap();
let has_scripts =
sync_staged_scripts_to_rootfs(&staged.join("scripts"), &rootfs, "foo").unwrap();
assert!(!has_scripts);
assert!(!installed_scripts_dir(&rootfs, "foo").exists());
}
#[test]
fn stage_scripts_from_spec_dir_stages_legacy_root_hook() {
let tmp = tempfile::tempdir().unwrap();
let spec_dir = tmp.path().join("spec");
let destdir = tmp.path().join("dest");
std::fs::create_dir_all(&spec_dir).unwrap();
std::fs::create_dir_all(&destdir).unwrap();
std::fs::write(spec_dir.join("postinstall.sh"), "echo post").unwrap();
let spec = mk_spec(&spec_dir);
let staged = stage_scripts_from_spec_dir(&spec, &destdir).unwrap();
assert!(staged);
assert!(destdir.join("scripts/post_install").exists());
#[cfg(unix)]
{
let mode = std::fs::metadata(destdir.join("scripts/post_install"))
.unwrap()
.permissions()
.mode();
assert_ne!(mode & 0o111, 0);
}
}
#[test]
fn stage_scripts_from_spec_dir_stages_lib32_prefixed_legacy_root_hook() {
let tmp = tempfile::tempdir().unwrap();
let spec_dir = tmp.path().join("spec");
let destdir = tmp.path().join("dest");
std::fs::create_dir_all(&spec_dir).unwrap();
std::fs::create_dir_all(&destdir).unwrap();
std::fs::write(spec_dir.join("lib32-postinstall.sh"), "echo lib32-post").unwrap();
let mut spec = mk_spec(&spec_dir);
spec.package.name = "lib32-foo".into();
let staged = stage_scripts_from_spec_dir(&spec, &destdir).unwrap();
assert!(staged);
assert!(destdir.join("scripts/post_install").exists());
#[cfg(unix)]
{
let mode = std::fs::metadata(destdir.join("scripts/post_install"))
.unwrap()
.permissions()
.mode();
assert_ne!(mode & 0o111, 0);
}
}
#[test]
fn stage_scripts_from_spec_dir_lib32_ignores_generic_legacy_root_hook() {
let tmp = tempfile::tempdir().unwrap();
let spec_dir = tmp.path().join("spec");
let destdir = tmp.path().join("dest");
std::fs::create_dir_all(&spec_dir).unwrap();
std::fs::create_dir_all(&destdir).unwrap();
// No lib32-prefixed hook; native-only scripts must NOT be staged for lib32 packages.
std::fs::write(spec_dir.join("postinstall.sh"), "echo fallback").unwrap();
let mut spec = mk_spec(&spec_dir);
spec.package.name = "lib32-foo".into();
let staged = stage_scripts_from_spec_dir(&spec, &destdir).unwrap();
assert!(!staged);
assert!(!destdir.join("scripts/post_install").exists());
}
#[test]
fn run_hook_if_present_lib32_ignores_generic_script() {
let tmp = tempfile::tempdir().unwrap();
let scripts = tmp.path().join("scripts");
let rootfs = tmp.path().join("root");
std::fs::create_dir_all(&scripts).unwrap();
std::fs::create_dir_all(&rootfs).unwrap();
// Only a generic script exists; lib32 package must NOT execute it.
std::fs::write(
scripts.join("post_install"),
"echo generic > \"$DEPOT_ROOTFS/hook.out\"\n",
)
.unwrap();
let ran = run_hook_if_present(&scripts, Hook::PostInstall, &rootfs, "lib32-foo").unwrap();
assert!(!ran);
assert!(!rootfs.join("hook.out").exists());
}