| git.druid.rocks | index | druid520 | mp | test/ | suites/ | hooks.sh |
test/suites/hooks.sh
# suite: HOOKS_DIR/<phase>/hooks.conf's IF_USE/AFTER pipeline, the custom
# $MP_HOOK trigger's reserved-name guard, and per-sysroot hook/mpx.conf
# isolation (both a phase-boundary hook and a $MP_HOOK-triggered one, run
# by the separate mp.hook process, must resolve against the active
# sysroot, never silently fall back to the default root's).
# hooks.conf: IF_USE gating + AFTER reordering on a HOOKS_DIR/<phase>
# file group, including a large (30-item) pipeline with a diamond
# dependency among the hooks.
test_hooks_conf_pipeline() {
t_begin "hooks_conf_pipeline"
reset_state
mkdir -p "$WD/hooks/rt11step"
i=1
while [ "$i" -le 30 ]; do
n=$(printf '%02d' "$i")
# $MP_PREFIX (single-quoted so IT expands inside the generated
# hook script at hook-run time, not here) -- these hooks fire via
# "$MP_HOOK rt11step" from inside rt11test's own install: phase,
# which mpx sandboxes by default (see src/sandbox.c: root read-
# only except the stage dir and the prefix), so /tmp isn't
# actually writable from in there.
printf '#!/bin/sh\necho h%s >> $MP_PREFIX/rt11.log\n' "$n" > "$WD/hooks/rt11step/h$n"
chmod +x "$WD/hooks/rt11step/h$n"
i=$((i + 1))
done
cat > "$WD/hooks/rt11step/hooks.conf" <<'EOF'
h10:
AFTER=h08 h09
h08:
AFTER=h07
h09:
AFTER=h07
h25:
IF_USE=rt11nonexistentflag
EOF
mkport rt11test <<'EOF'
pkg_name="rt11test"
pkg_ver="1.0"
install:
$MP_HOOK rt11step
touch $MP_PREFIX/rt11test-installed
remove:
rm -f $MP_PREFIX/rt11test-installed
EOF
rm -f "$MP_PREFIX/rt11.log"
mp install rt11test >/tmp/rt11.out 2>&1
log=$(cat "$MP_PREFIX/rt11.log" 2>/dev/null)
count=$(wc -l < "$MP_PREFIX/rt11.log" 2>/dev/null || echo 0)
rm -rf "$WD/hooks/rt11step" "$MP_PREFIX/rt11.log"
if [ "$count" -ne 29 ]; then t_fail "$CUR_TEST (expected 29 hooks ran, got $count)"; return; fi
# h07 must appear before both h08 and h09, both before h10.
l07=$(echo "$log" | grep -n '^h07$' | cut -d: -f1)
l08=$(echo "$log" | grep -n '^h08$' | cut -d: -f1)
l09=$(echo "$log" | grep -n '^h09$' | cut -d: -f1)
l10=$(echo "$log" | grep -n '^h10$' | cut -d: -f1)
if [ "$l07" -ge "$l08" ] || [ "$l07" -ge "$l09" ] || [ "$l08" -ge "$l10" ] || [ "$l09" -ge "$l10" ]; then
t_fail "$CUR_TEST (diamond ordering wrong: h07=$l07 h08=$l08 h09=$l09 h10=$l10)"
return
fi
t_pass
}
# custom hook trigger: a phase script firing $MP_HOOK with a reserved
# built-in lifecycle name must be rejected, not silently double-fire it.
test_custom_hook_reserved_name() {
t_begin "custom_hook_reserved_name"
reset_state
out=$(MP_PKG=rt14test "$(dirname "$MP_BIN")/mp.hook" pre_install 2>&1)
rc=$?
assert_exit_nonzero "$rc" "$CUR_TEST" || return
assert_contains "$out" "reserved" "$CUR_TEST" || return
t_pass
}
# with_sysroot_scope must swap HOOKSDIR too, not just WD/DBFILE/MANDIR/
# INSTPREFIX -- a phase-boundary hook and a custom $MP_HOOK-triggered one
# (a separate process, propagated via MP_SYSROOT -> --sysroot=) must both
# resolve against the sysroot's own hooks dir, never the default root's.
test_sysroot_hooks_isolation() {
t_begin "sysroot_hooks_isolation"
reset_state
mkdir -p /usr/ports/sysroots
echo 'MP_PREFIX=/usr/local-rt15-sysroot' > /usr/ports/sysroots/rt15root.conf
rm -rf /usr/local-rt15-sysroot "$WD/sysroots/rt15root"
mkdir -p /usr/local-rt15-sysroot
mkdir -p "$WD/sysroots/rt15root/hooks/post_install" "$WD/sysroots/rt15root/hooks/rt15custom"
mkdir -p "$WD/hooks/post_install"
# $MP_PREFIX, not /tmp: rt15custom fires via "$MP_HOOK rt15custom"
# from inside rt15test's own install: phase, which mpx sandboxes by
# default (root read-only except the stage dir and the prefix, see
# src/sandbox.c) -- /tmp isn't actually writable from in there. the
# two real pre_/post_* lifecycle hooks below run unsandboxed (a
# direct Perl system() in mplib::hooks::run_hook, never through mpx),
# so /tmp would work fine for them alone, but landing every marker in
# the one file this test already reads back keeps the assertions
# below simple regardless.
cat > "$WD/hooks/post_install/notify" <<'EOF'
#!/bin/sh
echo DEFAULT_FIRED >> $MP_PREFIX/rt15.log
EOF
cat > "$WD/sysroots/rt15root/hooks/post_install/notify" <<'EOF'
#!/bin/sh
echo SYSROOT_FIRED >> $MP_PREFIX/rt15.log
EOF
cat > "$WD/sysroots/rt15root/hooks/rt15custom/notify" <<'EOF'
#!/bin/sh
echo SYSROOT_CUSTOM_FIRED >> $MP_PREFIX/rt15.log
EOF
chmod +x "$WD/hooks/post_install/notify" "$WD/sysroots/rt15root/hooks/post_install/notify" "$WD/sysroots/rt15root/hooks/rt15custom/notify"
mkport rt15test <<'EOF'
pkg_name="rt15test"
pkg_ver="1.0"
install:
$MP_HOOK rt15custom
touch $MP_PREFIX/rt15test-installed
remove:
rm -f $MP_PREFIX/rt15test-installed
EOF
cat >> "$CONF" <<'EOF'
rt15test:
SYSROOT=rt15root
EOF
rm -f /usr/local-rt15-sysroot/rt15.log
mp install rt15test >/tmp/rt15.out 2>&1
log=$(cat /usr/local-rt15-sysroot/rt15.log 2>/dev/null)
rm -rf /usr/ports/sysroots/rt15root.conf /usr/local-rt15-sysroot
assert_contains "$log" "SYSROOT_FIRED" "$CUR_TEST" || return
assert_not_contains "$log" "DEFAULT_FIRED" "$CUR_TEST" || return
assert_contains "$log" "SYSROOT_CUSTOM_FIRED" "$CUR_TEST" || return
t_pass
}
# mpx.conf (the C-side backend config) must be per-sysroot too, not one
# global file shared by every sysroot and the default root alike -- a
# sysroot with its own mpx.conf (at $WD/mpx.conf, already the sysroot-
# swapped WD) gets its own TARGET_CC/JOBS/MICROARCH/SANDBOX overrides;
# the default root, and a sysroot with no override file of its own, both
# still fall back to the single global /etc/mpx.conf unchanged.
test_sysroot_mpxconf_isolation() {
t_begin "sysroot_mpxconf_isolation"
reset_state
mkdir -p /usr/ports/sysroots
echo 'MP_PREFIX=/usr/local-rt21-sysroot' > /usr/ports/sysroots/rt21root.conf
rm -rf /usr/local-rt21-sysroot "$WD/sysroots/rt21root"
mkdir -p /usr/local-rt21-sysroot "$WD/sysroots/rt21root"
cat > "$WD/sysroots/rt21root/mpx.conf" <<'EOF'
rt21test:
MICROARCH="rt21-sysroot-march"
EOF
mkport rt21test <<'EOF'
pkg_name="rt21test"
pkg_ver="1.0"
install:
echo "MPXCONF=[$MP_MPXCONF] MICROARCH=[$MP_MICROARCH]" > $MP_PREFIX/rt21-log
touch $MP_PREFIX/rt21test-installed
remove:
rm -f $MP_PREFIX/rt21test-installed $MP_PREFIX/rt21-log
EOF
cat >> "$CONF" <<'EOF'
rt21test:
SYSROOT=rt21root
EOF
mp install rt21test >/tmp/rt21.out 2>&1
log=$(cat /usr/local-rt21-sysroot/rt21-log 2>/dev/null)
rm -rf /usr/ports/sysroots/rt21root.conf /usr/local-rt21-sysroot
assert_contains "$log" "MICROARCH=[rt21-sysroot-march]" "$CUR_TEST" || return
t_pass
}
# inline hook_pre_install= config (global and per-package) both fire,
# in that order, before the package's own install phase runs.
test_inline_config_hooks() {
t_begin "inline_config_hooks"
reset_state
printf 'GLOBAL_DEPS=" "\n' >> "$CONF"
mkport rt33h <<'EOF'
pkg_name="rt33h"
pkg_ver="1.0"
install:
touch $MP_PREFIX/rt33h-installed
remove:
rm -f $MP_PREFIX/rt33h-installed
EOF
cat >> "$CONF" <<'EOF'
hook_pre_install=touch /tmp/rt33-global.log
rt33h:
hook_pre_install=touch /tmp/rt33-perpkg.log
EOF
rm -f /tmp/rt33-global.log /tmp/rt33-perpkg.log
mp install rt33h >/tmp/rt33.out 2>&1
rc=$?
assert_exit0 "$rc" "$CUR_TEST" || return
assert_file_exists /tmp/rt33-global.log "$CUR_TEST" || return
assert_file_exists /tmp/rt33-perpkg.log "$CUR_TEST" || return
assert_file_exists "$MP_PREFIX/rt33h-installed" "$CUR_TEST" || return
rm -f /tmp/rt33-global.log /tmp/rt33-perpkg.log
t_pass
}
# a hook exiting nonzero vetoes the whole install: nothing gets staged
# or installed, and the failure names the hook.
test_hook_failure_vetoes() {
t_begin "hook_failure_vetoes"
reset_state
printf 'GLOBAL_DEPS=" "\n' >> "$CONF"
mkport rt34h <<'EOF'
pkg_name="rt34h"
pkg_ver="1.0"
install:
touch $MP_PREFIX/rt34h-installed
remove:
rm -f $MP_PREFIX/rt34h-installed
EOF
echo 'hook_pre_install=false' >> "$CONF"
out=$(mp install rt34h 2>&1)
rc=$?
assert_exit_nonzero "$rc" "$CUR_TEST" || return
assert_contains "$out" "hook failed" "$CUR_TEST" || return
assert_file_absent "$MP_PREFIX/rt34h-installed" "$CUR_TEST" || return
assert_file_absent "$WD/rt34h" "$CUR_TEST" || return
t_pass
}
# a file-conflict install rejection fires the on_fail hook (best-effort
# alerting point) alongside the loud failure.
test_on_fail_hook_fires() {
t_begin "on_fail_hook_fires"
reset_state
printf 'GLOBAL_DEPS=" "\n' >> "$CONF"
echo 'hook_on_fail=touch /tmp/rt35-onfail.log' >> "$CONF"
mkport rt35a <<'EOF'
pkg_name="rt35a"
pkg_ver="1.0"
install:
mkdir -p $MP_PREFIX/share/rt35collide
echo A > $MP_PREFIX/share/rt35collide/same.txt
remove:
rm -rf $MP_PREFIX/share/rt35collide
EOF
mkport rt35b <<'EOF'
pkg_name="rt35b"
pkg_ver="1.0"
install:
mkdir -p $MP_PREFIX/share/rt35collide
echo B > $MP_PREFIX/share/rt35collide/same.txt
remove:
rm -rf $MP_PREFIX/share/rt35collide
EOF
rm -f /tmp/rt35-onfail.log
mp install rt35a >/tmp/rt35.out 2>&1
out=$(mp install rt35b 2>&1)
rc=$?
assert_exit_nonzero "$rc" "$CUR_TEST" || return
assert_contains "$out" "conflicts with rt35a on installed files" "$CUR_TEST" || return
assert_file_exists /tmp/rt35-onfail.log "$CUR_TEST" || return
rm -f /tmp/rt35-onfail.log
t_pass
}
# one hook-source scenario: a given phase fires from the given source
# (global config, per-package config, HOOKS_DIR file) and, for a
# "fails" case, vetoes the install.
hook_src_case() {
# $1 name, $2 phase, $3 source (global/perpk/hdir), $4 fails (0/1)
t_begin "$1"
reset_state
printf 'GLOBAL_DEPS=" "\n' >> "$CONF"
mkport "${1}p" <<EOF
pkg_name="${1}p"
pkg_ver="1.0"
build:
:
install:
touch \$MP_PREFIX/${1}p-installed
remove:
rm -f \$MP_PREFIX/${1}p-installed
EOF
case "$3" in
global) printf 'hook_%s=touch /tmp/%s-global.log\n' "$2" "$1" >> "$CONF" ;;
perpk) printf '%sp:\n\thook_%s=touch /tmp/%s-perpk.log\n' "$1" "$2" "$1" >> "$CONF" ;;
hdir) mkdir -p "$WD/hooks/$2"
printf '#!/bin/sh\ntouch /tmp/%s-hdir.log\n' "$1" > "$WD/hooks/$2/fire"
chmod +x "$WD/hooks/$2/fire" ;;
esac
rm -f "/tmp/$1-global.log" "/tmp/$1-perpk.log" "/tmp/$1-hdir.log"
if [ "$4" = "1" ]; then
# a global failing hook runs FIRST (before the per-package or
# directory one), so the later sources never fire at all.
printf 'hook_%s=false\n' "$2" >> "$CONF"
fi
out=$(mp install "${1}p" 2>&1)
rc=$?
if [ "$4" = "1" ]; then
assert_exit_nonzero "$rc" "$CUR_TEST" || return
assert_contains "$out" "hook failed" "$CUR_TEST" || return
assert_file_absent "/tmp/$1-global.log" "$CUR_TEST" || return
assert_file_absent "/tmp/$1-perpk.log" "$CUR_TEST" || return
assert_file_absent "/tmp/$1-hdir.log" "$CUR_TEST" || return
t_pass
return
fi
assert_exit0 "$rc" "$CUR_TEST" || return
# pre_/post_remove hooks only exist in the remove lifecycle.
if [ "$2" = "post_remove" ] || [ "$2" = "pre_remove" ]; then
mp remove --force "${1}p" >>/tmp/"$1".out 2>&1
fi
case "$3" in
global) assert_file_exists "/tmp/$1-global.log" "$CUR_TEST" || return ;;
perpk) assert_file_exists "/tmp/$1-perpk.log" "$CUR_TEST" || return ;;
hdir) assert_file_exists "/tmp/$1-hdir.log" "$CUR_TEST" || return ;;
esac
rm -f "/tmp/$1-global.log" "/tmp/$1-perpk.log" "/tmp/$1-hdir.log"
t_pass
}
# HOOKS_<phase>=: a manual override layered on top of the IF_USE-
# computed hooks.conf baseline, same +/- delta convention as USE=/
# "mp use" -- global "-name" disables one hook without touching its
# file or hooks.conf, per-package "+name" forces one back on despite
# its own IF_USE, and a bare list is a complete, explicit override
# regardless of IF_USE.
test_hooks_config_override() {
t_begin "hooks_config_override"
reset_state
printf 'GLOBAL_DEPS=" "\n' >> "$CONF"
mkdir -p "$WD/hooks/rt50step"
for n in h1 h2 h3; do
printf '#!/bin/sh\necho %s >> $MP_PREFIX/rt50.log\n' "$n" > "$WD/hooks/rt50step/$n"
chmod +x "$WD/hooks/rt50step/$n"
done
cat > "$WD/hooks/rt50step/hooks.conf" <<'EOF'
h3:
IF_USE=rt50neverset
EOF
mkport rt50p <<'EOF'
pkg_name="rt50p"
pkg_ver="1.0"
install:
$MP_HOOK rt50step
touch $MP_PREFIX/rt50p-installed
remove:
rm -f $MP_PREFIX/rt50p-installed
EOF
# baseline, no HOOKS_ override at all: h3's own IF_USE keeps it off.
rm -f "$MP_PREFIX/rt50.log"
mp install rt50p >/tmp/rt50.out 2>&1
log=$(tr '\n' ',' < "$MP_PREFIX/rt50.log" 2>/dev/null)
mp remove --force rt50p >/dev/null 2>&1
assert_contains "$log" "h1," "$CUR_TEST (baseline h1)" || return
assert_contains "$log" "h2," "$CUR_TEST (baseline h2)" || return
assert_not_contains "$log" "h3" "$CUR_TEST (baseline h3 IF_USE-gated off)" || return
# global delta: -h2 removes it from the baseline, h1 unaffected.
echo 'HOOKS_rt50step=-h2' >> "$CONF"
rm -f "$MP_PREFIX/rt50.log"
mp install rt50p >/tmp/rt50.out 2>&1
log=$(tr '\n' ',' < "$MP_PREFIX/rt50.log" 2>/dev/null)
mp remove --force rt50p >/dev/null 2>&1
assert_contains "$log" "h1," "$CUR_TEST (global delta keeps h1)" || return
assert_not_contains "$log" "h2" "$CUR_TEST (global delta -h2)" || return
# per-package delta wins over the still-present global line, and
# +h3 forces it on despite its own IF_USE.
cat >> "$CONF" <<'EOF'
rt50p:
HOOKS_rt50step=+h3
EOF
rm -f "$MP_PREFIX/rt50.log"
mp install rt50p >/tmp/rt50.out 2>&1
log=$(tr '\n' ',' < "$MP_PREFIX/rt50.log" 2>/dev/null)
mp remove --force rt50p >/dev/null 2>&1
assert_contains "$log" "h1," "$CUR_TEST (perpkg delta h1)" || return
assert_contains "$log" "h2," "$CUR_TEST (perpkg delta h2)" || return
assert_contains "$log" "h3," "$CUR_TEST (perpkg delta forces h3 on)" || return
# a bare list (no +/-) is a complete, explicit override -- only h2,
# even though h1 has no IF_USE condition at all.
cat >> "$CONF" <<'EOF'
rt50p:
HOOKS_rt50step=h2
EOF
rm -f "$MP_PREFIX/rt50.log"
mp install rt50p >/tmp/rt50.out 2>&1
log=$(tr '\n' ',' < "$MP_PREFIX/rt50.log" 2>/dev/null)
mp remove --force rt50p >/dev/null 2>&1
assert_contains "$log" "h2," "$CUR_TEST (bare list keeps h2)" || return
assert_not_contains "$log" "h1" "$CUR_TEST (bare list excludes h1)" || return
assert_not_contains "$log" "h3" "$CUR_TEST (bare list excludes h3)" || return
rm -rf "$WD/hooks/rt50step" "$MP_PREFIX/rt50.log"
t_pass
}
run_hook_source_matrix() {
hook_src_case hsrc_global_pre_install pre_install global 0
hook_src_case hsrc_perpk_post_install post_install perpk 0
hook_src_case hsrc_hdir_pre_build pre_build hdir 0
hook_src_case hsrc_global_post_remove post_remove global 0
hook_src_case hsrc_global_pre_install_fails pre_install global 1
hook_src_case hsrc_perpk_post_install_fails post_install perpk 1
}
run_hooks_suite() {
test_hooks_conf_pipeline
test_custom_hook_reserved_name
test_sysroot_hooks_isolation
test_sysroot_mpxconf_isolation
test_inline_config_hooks
test_hook_failure_vetoes
test_on_fail_hook_fires
test_hooks_config_override
run_hook_source_matrix
}