Splux Linux

source-based Linux using SPS

SPS · log · files · refs · tags · releases · people · languages · GitHub · file

tests/test_dependencies.sh


Shell

10632 bytes at 2909919 · GitHub

#!/bin/sh
set -eu

case $0 in */*) test_dir=${0%/*} ;; *) test_dir=. ;; esac
project_dir=$(CDPATH= cd "$test_dir/.." 2>/dev/null && pwd)
if [ ! -r "$project_dir/lib/common.sh" ]; then
    printf '%s\n' 'dependency tests skipped: lib/common.sh is not integrated'
    exit 0
fi

fail()
{
    printf 'test_dependencies: %s\n' "$*" >&2
    exit 1
}

contains()
{
    case $1 in
        *"$2"*) return 0 ;;
        *) fail "expected output to contain: $2" ;;
    esac
}

tmp=$(mktemp -d "${TMPDIR:-/tmp}/sps-dependencies.XXXXXX")
trap 'rm -rf "$tmp"' 0 1 2 3 15

mkdir -p "$tmp/root" "$tmp/cache" "$tmp/db/installed" "$tmp/build" \
    "$tmp/repo/base" "$tmp/repo/lib" "$tmp/repo/tool" "$tmp/repo/app" \
    "$tmp/repo/left" "$tmp/repo/right" "$tmp/repo/diamond" \
    "$tmp/repo/openssl" "$tmp/repo/cmake" "$tmp/repo/gui" \
    "$tmp/fakebin"
: > "$tmp/sps.conf"
printf 'repo test %s 10\n' "$tmp/repo" > "$tmp/repos.conf"

make_recipe()
{
    recipe_dir=$1
    recipe_name=$2
    recipe_version=$3
    shift 3
    {
        printf 'name %s\n' "$recipe_name"
        printf 'version %s\n' "$recipe_version"
        printf '%s\n' 'release 1'
        printf 'description Test package %s\n' "$recipe_name"
        for recipe_record
        do
            printf '%s\n' "$recipe_record"
        done
        printf '%s\n' 'install true'
    } > "$recipe_dir/recipe"
}

make_recipe "$tmp/repo/base" base 1.0
make_recipe "$tmp/repo/lib" lib 2.1 'depend base>=1.0'
make_recipe "$tmp/repo/tool" tool 3.0
make_recipe "$tmp/repo/app" app 4.0 'depend lib>=2.0' 'builddep tool'
make_recipe "$tmp/repo/left" left 1.0 'depend base'
make_recipe "$tmp/repo/right" right 1.0 'depend base'
make_recipe "$tmp/repo/diamond" diamond 1.0 'depend right left'
make_recipe "$tmp/repo/openssl" openssl 1.0
make_recipe "$tmp/repo/cmake" cmake 1.0 'depend openssl'
make_recipe "$tmp/repo/gui" gui 1.0 'depend base' 'builddep cmake'
mkdir -p "$tmp/repo/app/files" "$tmp/repo/app/patches" \
    "$tmp/repo/app/hooks"
printf '%s\n' 'default=true' >"$tmp/repo/app/files/app.conf"
printf '%s\n' 'initial patch input' >"$tmp/repo/app/patches/build.patch"
printf '%s\n' 'exit 0' >"$tmp/repo/app/hooks/post-install"

SPS_ROOT=$tmp/root
SPS_DB=$tmp/db
SPS_CACHE=$tmp/cache
SPS_BUILD=$tmp/build
SPS_CONFIG=$tmp/sps.conf
SPS_REPOS_CONFIG=$tmp/repos.conf
export SPS_ROOT SPS_DB SPS_CACHE SPS_BUILD SPS_CONFIG SPS_REPOS_CONFIG

src=$project_dir/bin/src
sget=$project_dir/bin/sget
"$src" update >/dev/null

tree=$("$sget" depends app)
contains "$tree" 'app'
contains "$tree" 'lib'
contains "$tree" 'base'
case $tree in
    *tool*) fail 'ordinary depends output unexpectedly included build dependencies' ;;
esac

tree_with_build=$("$sget" depends app --build)
contains "$tree_with_build" 'tool'

why_path=$("$sget" why diamond base)
[ "$why_path" = "$(printf 'diamond\nright\nbase')" ] ||
    fail "dependency path did not follow deterministic declaration order: $why_path"

plan=$("$sget" install --plan app)
plan_names=$(printf '%s\n' "$plan" |
    awk '/^  / { print $2 }')
expected_names=$(printf '%s\n' tool base lib app)
[ "$plan_names" = "$expected_names" ] ||
    fail "unexpected dependency order: $plan_names"

if command -v cmake >/dev/null 2>&1; then
    gui_plan=$("$sget" install --plan gui)
    case $gui_plan in
        *cmake*) fail 'host cmake was still planned as a build-only tool' ;;
        *openssl*) fail 'openssl was pulled only because cmake is a builddep' ;;
    esac
    contains "$gui_plan" gui
    contains "$gui_plan" base
    gui_err=$("$sget" install --plan gui 2>&1 >/dev/null) || true
    case $gui_err in
        *'using host cmake to build gui'*) ;;
        *) fail "install plan did not report using host cmake: $gui_err" ;;
    esac
fi

nodeps_plan=$("$sget" install --plan --nodeps app)
nodeps_names=$(printf '%s\n' "$nodeps_plan" |
    awk '/^  / { print $2 }')
[ "$nodeps_names" = app ] ||
    fail '--nodeps did not restrict the plan to the requested package'

# src validates the complete selected runtime/build graph before replacing the
# usable index. These fixtures exercise the same resolver used by sget.
index=$tmp/cache/indexes/packages.index
old_index=$(cksum "$index")
mkdir "$tmp/repo/broken"
make_recipe "$tmp/repo/broken" broken 1.0 'depend unavailable'
if "$src" update > "$tmp/broken.out" 2> "$tmp/broken.err"; then
    fail 'repository with a missing dependency unexpectedly indexed'
fi
contains "$(sed -n '1,80p' "$tmp/broken.err")" "dependency 'unavailable'"
[ "$(cksum "$index")" = "$old_index" ] ||
    fail 'missing dependency replaced the previous repository index'
rm -rf "$tmp/repo/broken"

mkdir "$tmp/repo/cycle-a" "$tmp/repo/cycle-b"
make_recipe "$tmp/repo/cycle-a" cycle-a 1.0 'depend cycle-b'
make_recipe "$tmp/repo/cycle-b" cycle-b 1.0 'depend cycle-a'
if "$src" update > "$tmp/cycle.out" 2> "$tmp/cycle.err"; then
    fail 'repository dependency cycle unexpectedly indexed'
fi
contains "$(sed -n '1,80p' "$tmp/cycle.err")" 'dependency cycle:'
[ "$(cksum "$index")" = "$old_index" ] ||
    fail 'dependency cycle replaced the previous repository index'
rm -rf "$tmp/repo/cycle-a" "$tmp/repo/cycle-b"

[ "$(awk -v version_action=compare -v version_a=1.10 -v version_b=1.9 \
    -f "$project_dir/lib/version.awk" /dev/null)" = 1 ] ||
    fail 'numeric version comparison is incorrect'
[ "$(awk -v version_action=compare -v version_a=1.0~rc1 -v version_b=1.0 \
    -f "$project_dir/lib/version.awk" /dev/null)" = -1 ] ||
    fail 'pre-release version comparison is incorrect'

mkdir -p "$tmp/db/installed/base"
{
    printf 'format\t1\n'
    printf 'name\tbase\n'
    printf 'version\t0.9\n'
    printf 'release\t1\n'
    printf 'arch\t%s\n' "$(uname -m)"
} >"$tmp/db/installed/base/meta"
: >"$tmp/db/installed/base/files"
: >"$tmp/db/installed/base/hashes"
outdated_plan=$("$sget" install --plan app)
case $outdated_plan in
    *'base 0.9-1 -> 1.0-1'*) ;;
    *) fail 'outdated installed dependency was incorrectly skipped' ;;
esac
sed 's/version\t0.9/version\t1.0/' "$tmp/db/installed/base/meta" >"$tmp/base.meta"
mv "$tmp/base.meta" "$tmp/db/installed/base/meta"
skipped_plan=$("$sget" install --plan app)
case $skipped_plan in
    *'base 1.0-1'*) fail 'exact installed package was not skipped from the build plan' ;;
esac
if "$sget" install --plan base > "$tmp/explicit.out" \
    2> "$tmp/explicit.err"; then
    :
else
    fail 'planning an already-installed package failed'
fi
contains "$(cat "$tmp/explicit.out")" 'explicit'

cat > "$tmp/fakebin/mkpkg" <<'EOF_FAKE_MKPKG'
#!/bin/sh
. "$SPS_LIBDIR/common.sh"
printf "mkpkg\t%s\n" "$PWD" >> "$TEST_LOG"
artifact_file=
recipe=recipe
while [ "$#" -gt 0 ]; do
    case $1 in
        --output|--output=*)
            case $1 in
                --output) shift 2 ;;
                *) shift ;;
            esac
            ;;
        --artifact-file)
            artifact_file=$2
            shift 2
            ;;
        --artifact-file=*)
            artifact_file=${1#*=}
            shift
            ;;
        --print-artifact)
            shift
            ;;
        *)
            recipe=$1
            shift
            ;;
    esac
done
name=$(awk '$1=="name" {print $2; exit}' "$recipe")
version=$(awk '$1=="version" {print $2; exit}' "$recipe")
release=$(awk '$1=="release" {print $2; exit}' "$recipe")
arch=$(awk '$1=="arch" {print $2; exit}' "$recipe")
[ -n "$arch" ] || arch=$(uname -m)
definition_sha256=$(sps_definition_sha256 "$recipe") || exit 4
work=$PWD/.fakepkg.$$
mkdir -p "$work/.SPS"
printf "format\t1\ndefinition_sha256\t%s\nname\t%s\nversion\t%s\nrelease\t%s\narch\t%s\n" \
    "$definition_sha256" "$name" "$version" "$release" "$arch" \
    > "$work/.SPS/meta"
: > "$work/.SPS/files"
: > "$work/.SPS/hashes"
artifact=$PWD/$name-$version-$release-$arch.pkg.tar
tar -cf "$artifact" -C "$work" .
rm -rf "$work"
if [ -n "$artifact_file" ]; then
    printf "%s\n" "$artifact" > "$artifact_file"
fi
EOF_FAKE_MKPKG
cat > "$tmp/fakebin/pkin" <<'EOF_FAKE_PKIN'
#!/bin/sh
printf "pkin\t%s\t%s\n" "$SPS_INSTALL_REASON" "$1" >> "$TEST_LOG"
exit 0
EOF_FAKE_PKIN
chmod +x "$tmp/fakebin/mkpkg" "$tmp/fakebin/pkin"

TEST_LOG=$tmp/install.log
export TEST_LOG
: > "$TEST_LOG"
PATH="$tmp/fakebin:$PATH" "$sget" install app > "$tmp/install.out"

[ "$(awk -F '	' '$1 == "mkpkg" { count++ } END { print count + 0 }' \
    "$TEST_LOG")" = 3 ] ||
    fail 'sget did not build exactly the unresolved packages'
[ "$(awk -F '	' '$1 == "pkin" && $2 == "dependency" { count++ }
                  END { print count + 0 }' "$TEST_LOG")" = 2 ] ||
    fail 'dependencies were not passed to pkin with dependency reason'
[ "$(awk -F '	' '$1 == "pkin" && $2 == "explicit" { count++ }
                  END { print count + 0 }' "$TEST_LOG")" = 1 ] ||
    fail 'requested package was not passed to pkin with explicit reason'

mkdir -p "$tmp/cache/packages"
app_arch=$(uname -m)
app_cache=$tmp/cache/packages/app-4.0-1-$app_arch.pkg.tar
cp "$tmp/repo/app/app-4.0-1-$app_arch.pkg.tar" "$app_cache" ||
    fail 'could not prepare cached app artifact for stale-input tests'

old_log_lines=$(wc -l < "$TEST_LOG" | awk '{ print $1 }')
expect_stale_definition()
{
    stale_label=$1
    if PATH="$tmp/fakebin:$PATH" "$sget" install --nodeps app \
        > "$tmp/stale-$stale_label.out" 2> "$tmp/stale-$stale_label.err"; then
        fail "sget reused or built an artifact after a stale $stale_label change"
    fi
    contains "$(sed -n '1,80p' "$tmp/stale-$stale_label.err")" \
        'changed since indexing'
    [ "$(wc -l < "$TEST_LOG" | awk '{ print $1 }')" = "$old_log_lines" ] ||
        fail "mkpkg or pkin ran after stale $stale_label detection"
    case $(cat "$tmp/stale-$stale_label.out") in
        *'using cached'*) fail "cached artifact was considered before stale $stale_label detection" ;;
    esac
}

printf '%s\n' 'local override=true' >>"$tmp/repo/app/files/app.conf"
expect_stale_definition file
"$src" update >/dev/null

printf '%s\n' 'changed patch input' >>"$tmp/repo/app/patches/build.patch"
expect_stale_definition patch
"$src" update >/dev/null

printf '%s\n' 'changed hook input' >>"$tmp/repo/app/hooks/post-install"
expect_stale_definition hook
"$src" update >/dev/null

printf '%s\n' '# changed after indexing' >> "$tmp/repo/app/recipe"
expect_stale_definition recipe

set +e
"$sget" install nosuch-a nosuch-b >"$tmp/miss.out" 2>"$tmp/miss.err"
miss_st=$?
set -e
[ "$miss_st" -eq 5 ] || fail "two missing packages returned $miss_st"
contains "$(cat "$tmp/miss.err")" "package 'nosuch-a' not found"
contains "$(cat "$tmp/miss.err")" "package 'nosuch-b' not found"

printf '%s\n' 'dependency resolution and install orchestration tests passed'