Files
cozystack/hack/select-install_test.bats
T
Aleksei Sviridkin ceccac27b0 test(hack): clean up bats temp dirs without EXIT traps
A `trap ... EXIT` inside an `@test` replaces the one the bats binary
installs for its own bookkeeping, so a test that then fails prints no
TAP line at all. Not `not ok`, nothing: the run ends with a warning
that N of M tests executed, and anyone judging it by grepping for
`not ok` reads a green suite. Cleanup moves to the last line of each
body instead. Both runners set -e, so a failed test leaves its scratch
directory behind, which is what you want to look at anyway.

hack/promote-retag_test.bats was failing this way already, reporting
five passes and no failures while six of its eleven tests printed
nothing. Those six invoke the script for real, and it requires
sha256sum; they pin PATH to the stub directory plus /usr/bin and /bin,
which is where coreutils installs that binary and where macOS does not,
so the failure was of the platform rather than of the code and CI never
saw it. The stub directory already supplied yq and skopeo for the same
reason and now supplies sha256sum too, resolved from absolute
candidates because the stub itself is first on PATH.

Two file headers described the trap arrangement they no longer use and
are corrected rather than left to outlive it.

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Aleksei Sviridkin <f@lex.la>
2026-08-05 16:03:57 +03:00

231 lines
8.6 KiB
Bash

#!/usr/bin/env bats
# -----------------------------------------------------------------------------
# Unit tests for hack/select-install.sh
#
# cozytest.sh's awk parser recognizes only @test blocks and a bare `}` on its
# own line; there is no bats `run` or `$status`. Each test runs as a shell
# function under `set -eu -x`, so assertions are direct shell tests that exit
# non-zero on failure. setup()/teardown() are not honored, and cleanup goes on
# the last line of the body rather than in an EXIT trap -- a trap there replaces
# the bats binary's own, and a failing test then prints no TAP line at all. To
# assert a NON-zero exit, invert with `if` so `set -e` doesn't abort the test.
#
# Tests that only READ the production sources call the script directly (it
# defaults sources-dir to packages/core/platform/sources); only the tests that
# build a synthetic graph or suite tree use a scratch dir.
#
# Run with: hack/cozytest.sh hack/select-install_test.bats
# -----------------------------------------------------------------------------
@test "single app selects its forward dependency closure" {
output=$(hack/select-install.sh "postgres")
echo "$output" | grep -wq cozystack.postgres-application
echo "$output" | grep -wq cozystack.postgres-operator
echo "$output" | grep -wq cozystack.networking
# engine edge is KEPT on the install walk (unlike select-e2e.sh)
echo "$output" | grep -wq cozystack.cozystack-engine
}
@test "closure is transitive (deps of deps are pulled in)" {
output=$(hack/select-install.sh "postgres")
# cert-manager is a 2-hop dep (via postgres-operator and via the engine)
echo "$output" | grep -wq cozystack.cert-manager
# gateway-api-crds is a 3-hop dep (postgres-application -> networking -> gateway-api-crds)
echo "$output" | grep -wq cozystack.gateway-api-crds
}
@test "app pulls its direct operator dependencies" {
output=$(hack/select-install.sh "harbor")
echo "$output" | grep -wq cozystack.harbor-application
echo "$output" | grep -wq cozystack.postgres-operator
echo "$output" | grep -wq cozystack.redis-operator
echo "$output" | grep -wq cozystack.objectstorage-controller
}
@test "kubernetes suites map back to the kubernetes application source" {
output=$(hack/select-install.sh "kubernetes-latest")
echo "$output" | grep -wq cozystack.kubernetes-application
}
@test "multiple suites union their closures" {
output=$(hack/select-install.sh "postgres kafka")
echo "$output" | grep -wq cozystack.postgres-application
echo "$output" | grep -wq cozystack.kafka-application
}
@test "suite list can be read from stdin with -" {
output=$(printf '%s\n' "postgres kafka" | hack/select-install.sh -)
echo "$output" | grep -wq cozystack.postgres-application
echo "$output" | grep -wq cozystack.kafka-application
echo "$output" | grep -wq cozystack.cozystack-engine
}
@test "empty suites select nothing" {
output=$(hack/select-install.sh "")
[ -z "$output" ]
}
@test "unmapped suites are a hard error, all reported, no partial output" {
# Fail closed: a suite that maps to nothing must abort, not silently emit an
# empty set (which would install nothing and let the run pass vacuously).
# Also lock the contract that EVERY bad suite is reported in one run (not
# just the first) and that no partial closure leaks to stdout.
out=$(mktemp); err=$(mktemp)
if hack/select-install.sh "bad-one postgres bad-two" >"$out" 2>"$err"; then
echo "expected a non-zero exit for unmapped suites" >&2
exit 1
fi
grep -q "bad-one" "$err"
grep -q "bad-two" "$err"
[ ! -s "$out" ]
rm -f "$out" "$err"
}
@test "suite whose source omits the -application suffix resolves via fallback" {
# kuberture's PackageSource is cozystack.kuberture (no -application suffix)
output=$(hack/select-install.sh "kuberture")
echo "$output" | grep -wq cozystack.kuberture
}
@test "securitygroup closure includes the engine that serves sdn.cozystack.io" {
# Regression for the under-selection bug: the securitygroup suite applies
# sdn.cozystack.io/v1alpha1, served by the aggregated apiserver
# (a cozystack-engine component). The forward walk from the controller can't
# reach the engine, so it must be seeded. Assert the controller AND the
# engine AND the engine's stable prerequisites — asserting only the
# controller (a single member) is what let the bug through.
output=$(hack/select-install.sh "securitygroup")
echo "$output" | grep -wq cozystack.securitygroup-controller
echo "$output" | grep -wq cozystack.cozystack-engine
echo "$output" | grep -wq cozystack.cert-manager
echo "$output" | grep -wq cozystack.networking
echo "$output" | grep -wq cozystack.gateway-api-crds
}
@test "validate passes on the real source graph and suite mapping" {
hack/select-install.sh --validate
}
@test "validate detects a dangling dependency" {
tmp=$(mktemp -d)
mkdir -p "$tmp/sources" "$tmp/suites"
cat > "$tmp/sources/foo-application.yaml" <<'YAML'
apiVersion: cozystack.io/v1alpha1
kind: PackageSource
metadata:
name: cozystack.foo-application
spec:
variants:
- name: default
dependsOn:
- cozystack.does-not-exist
YAML
# empty suites-dir isolates this to the graph check
if hack/select-install.sh --validate "$tmp/sources" "$tmp/suites" 2>/dev/null; then
echo "expected validation to fail on a dangling dependency" >&2
exit 1
fi
rm -rf "$tmp"
}
@test "validate detects a dependency cycle" {
tmp=$(mktemp -d)
mkdir -p "$tmp/sources" "$tmp/suites"
cat > "$tmp/sources/a.yaml" <<'YAML'
apiVersion: cozystack.io/v1alpha1
kind: PackageSource
metadata:
name: cozystack.a
spec:
variants:
- name: default
dependsOn:
- cozystack.b
YAML
cat > "$tmp/sources/b.yaml" <<'YAML'
apiVersion: cozystack.io/v1alpha1
kind: PackageSource
metadata:
name: cozystack.b
spec:
variants:
- name: default
dependsOn:
- cozystack.a
YAML
# empty suites-dir isolates this to the graph check
if hack/select-install.sh --validate "$tmp/sources" "$tmp/suites" 2>/dev/null; then
echo "expected validation to fail on a dependency cycle" >&2
exit 1
fi
rm -rf "$tmp"
}
@test "validate detects a suite directory with no source mapping" {
tmp=$(mktemp -d)
mkdir -p "$tmp/suites/totally-unmapped-suite"
: > "$tmp/suites/totally-unmapped-suite/chainsaw-test.yaml"
# real graph (so graph checks pass); the only failure is the unmapped suite
if hack/select-install.sh --validate packages/core/platform/sources "$tmp/suites" 2>/dev/null; then
echo "expected validation to fail on an unmapped suite dir" >&2
exit 1
fi
rm -rf "$tmp"
}
@test "validate detects a suite mapped to a source absent from the graph" {
# suite_to_source() hardcodes securitygroup -> cozystack.securitygroup-controller;
# a sources dir without that PackageSource must fail validation (fail closed),
# pinning that the elif validates every mapping, including hardcoded ones,
# against the graph.
tmp=$(mktemp -d)
mkdir -p "$tmp/sources" "$tmp/suites/securitygroup"
: > "$tmp/suites/securitygroup/chainsaw-test.yaml"
cat > "$tmp/sources/standalone.yaml" <<'YAML'
apiVersion: cozystack.io/v1alpha1
kind: PackageSource
metadata:
name: cozystack.standalone
spec:
variants:
- name: default
YAML
err=$(hack/select-install.sh --validate "$tmp/sources" "$tmp/suites" 2>&1 1>/dev/null) && {
echo "expected validation to fail on a mapping to a missing source" >&2
exit 1
}
echo "$err" | grep -q "maps to 'cozystack.securitygroup-controller', not a PackageSource"
rm -rf "$tmp"
}
@test "validate fails when the suites dir does not exist" {
tmp=$(mktemp -d)
# real graph passes the graph checks; a missing suites dir must still FAIL
# (find would yield nothing and silently pass the mapping check otherwise).
if hack/select-install.sh --validate packages/core/platform/sources "$tmp/does-not-exist" 2>/dev/null; then
echo "expected validate to fail on a missing suites dir" >&2
exit 1
fi
rm -rf "$tmp"
}
@test "closure fails closed when the engine PackageSource is absent" {
tmp=$(mktemp -d)
mkdir -p "$tmp/sources"
# a lone app source, with no cozystack.cozystack-engine in the graph
cat > "$tmp/sources/foo-application.yaml" <<'YAML'
apiVersion: cozystack.io/v1alpha1
kind: PackageSource
metadata:
name: cozystack.foo-application
spec:
variants:
- name: default
YAML
if hack/select-install.sh "foo" "$tmp/sources" 2>/dev/null; then
echo "expected a non-zero exit when the engine source is missing" >&2
exit 1
fi
rm -rf "$tmp"
}