use codex to make the codebase MUCH more maintainable
This commit is contained in:
@@ -0,0 +1,443 @@
|
||||
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());
|
||||
}
|
||||
Reference in New Issue
Block a user