mirror of
https://github.com/cozystack/cozystack.git
synced 2026-11-03 02:27:01 +09:00
Chainsaw builds step files only from directory entries that are not directories, so a directory named like 01-x.yaml makes nothing a suite. Both selectors listed it as one, and select-install demanded a mapping for it. A symlink still counts, as it does for Chainsaw. A directory named chainsaw-test.yaml keeps its suite listed: Chainsaw reads that name whatever its type and fails loudly on a directory, and an unlisted suite would hide that failure. Assisted-by: LLM Signed-off-by: Aleksei Sviridkin <f@lex.la>
337 lines
14 KiB
Bash
337 lines
14 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 "etcd closure includes the operator that serves its CRD" {
|
|
# The other half of the same missing edge. extra/etcd renders kind:
|
|
# EtcdCluster from etcd-operator.cozystack.io/v1alpha2, so an etcd suite
|
|
# installed without cozystack.etcd-operator has no CRD to apply against and
|
|
# fails on `no matches for kind "EtcdCluster"`. The forward walk reaches the
|
|
# operator only through etcd-application's dependsOn, so this asserts the
|
|
# operator AND the deps it drags in -- asserting only the app source is what
|
|
# left the gap invisible.
|
|
output=$(hack/select-install.sh "etcd")
|
|
echo "$output" | grep -wq cozystack.etcd-application
|
|
echo "$output" | grep -wq cozystack.etcd-operator
|
|
echo "$output" | grep -wq cozystack.cert-manager
|
|
echo "$output" | grep -wq cozystack.vertical-pod-autoscaler
|
|
echo "$output" | grep -wq cozystack.cozystack-engine
|
|
}
|
|
|
|
@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 sees a suite written as chainsaw-test.yml" {
|
|
# Chainsaw runs a chainsaw-test.yml suite, so the mapping check has to see
|
|
# one too, or an unmapped suite spelled that way passes validation.
|
|
tmp=$(mktemp -d)
|
|
mkdir -p "$tmp/suites/totally-unmapped-suite"
|
|
: > "$tmp/suites/totally-unmapped-suite/chainsaw-test.yml"
|
|
err=$(hack/select-install.sh --validate packages/core/platform/sources "$tmp/suites" 2>&1 1>/dev/null) && {
|
|
echo "expected validation to fail on an unmapped .yml suite" >&2
|
|
exit 1
|
|
}
|
|
echo "$err" | grep -q "suite 'totally-unmapped-suite' has no PackageSource mapping"
|
|
rm -rf "$tmp"
|
|
}
|
|
|
|
@test "validate sees a suite made only of numbered step files" {
|
|
# Chainsaw runs a directory of 01-*.yaml step files as a suite when it holds
|
|
# no chainsaw-test.*, so the mapping check has to see that layout too. The
|
|
# step file is a symlink because Chainsaw counts one.
|
|
tmp=$(mktemp -d)
|
|
mkdir -p "$tmp/suites/totally-unmapped-suite"
|
|
: > "$tmp/install.yaml"
|
|
ln -s "$tmp/install.yaml" "$tmp/suites/totally-unmapped-suite/00-install.yaml"
|
|
err=$(hack/select-install.sh --validate packages/core/platform/sources "$tmp/suites" 2>&1 1>/dev/null) && {
|
|
echo "expected validation to fail on an unmapped step-file suite" >&2
|
|
exit 1
|
|
}
|
|
echo "$err" | grep -q "suite 'totally-unmapped-suite' has no PackageSource mapping"
|
|
rm -rf "$tmp"
|
|
}
|
|
|
|
@test "validate ignores what Chainsaw does not load as a step file" {
|
|
# Chainsaw skips a step-file name when the entry is a directory, and a name
|
|
# with no leading number is not a step file, so neither makes a suite that
|
|
# needs a mapping.
|
|
tmp=$(mktemp -d)
|
|
mkdir -p "$tmp/suites/postgres" "$tmp/suites/totally-unmapped-suite/01-x.yaml"
|
|
: > "$tmp/suites/totally-unmapped-suite/-install.yaml"
|
|
: > "$tmp/suites/postgres/chainsaw-test.yaml"
|
|
hack/select-install.sh --validate packages/core/platform/sources "$tmp/suites"
|
|
rm -rf "$tmp"
|
|
}
|
|
|
|
@test "validate sees a directory named chainsaw-test.yaml or .yml" {
|
|
# Chainsaw stats chainsaw-test.yaml or .yml without checking its type, so a
|
|
# directory of that name fails discovery loudly. Listing its suite keeps
|
|
# that failure where a run reaches it instead of leaving the suite out.
|
|
for name in chainsaw-test.yaml chainsaw-test.yml; do
|
|
tmp=$(mktemp -d)
|
|
mkdir -p "$tmp/suites/totally-unmapped-suite/$name"
|
|
: > "$tmp/suites/totally-unmapped-suite/$name/x"
|
|
err=$(hack/select-install.sh --validate packages/core/platform/sources "$tmp/suites" 2>&1 1>/dev/null) && {
|
|
echo "expected validation to fail on an unmapped suite with a $name directory" >&2
|
|
exit 1
|
|
}
|
|
echo "$err" | grep -q "suite 'totally-unmapped-suite' has no PackageSource mapping"
|
|
rm -rf "$tmp"
|
|
done
|
|
}
|
|
|
|
@test "validate sees a suite nested below a top-level directory" {
|
|
# Chainsaw walks every directory under the one it is handed, so a test in a
|
|
# subdirectory runs as part of its top-level directory, which then needs a
|
|
# mapping like any other suite.
|
|
tmp=$(mktemp -d)
|
|
mkdir -p "$tmp/suites/totally-unmapped-suite/cases"
|
|
: > "$tmp/suites/totally-unmapped-suite/cases/chainsaw-test.yaml"
|
|
err=$(hack/select-install.sh --validate packages/core/platform/sources "$tmp/suites" 2>&1 1>/dev/null) && {
|
|
echo "expected validation to fail on an unmapped nested suite" >&2
|
|
exit 1
|
|
}
|
|
echo "$err" | grep -q "suite 'totally-unmapped-suite' has no PackageSource mapping"
|
|
rm -rf "$tmp"
|
|
}
|
|
|
|
@test "validate fails when the suite listing itself fails" {
|
|
# A failed find and an empty suites dir used to read the same: the error
|
|
# went to /dev/null and the pipeline reported sort's status, so the mapping
|
|
# check validated nothing and passed.
|
|
tmp=$(mktemp -d)
|
|
mkdir "$tmp/bin"
|
|
printf '#!/bin/sh\necho "find: broken fixture" >&2\nexit 1\n' > "$tmp/bin/find"
|
|
chmod +x "$tmp/bin/find"
|
|
err=$(PATH="$tmp/bin:$PATH" hack/select-install.sh --validate 2>&1 1>/dev/null) && {
|
|
echo "expected validate to fail when find fails" >&2
|
|
exit 1
|
|
}
|
|
echo "$err" | grep -q "select-install: find failed listing the suites"
|
|
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"
|
|
}
|