mirror of
https://github.com/cozystack/cozystack.git
synced 2026-11-03 10:37:21 +09:00
Chainsaw loads chainsaw-test.yml when a directory has no chainsaw-test.yaml, but select-e2e and select-install enumerated the .yaml name only. A suite spelled that way ran whenever the whole directory was handed to chainsaw, yet no escalation listed it and no edit to it could select it, so it silently dropped out of every narrowed run. select-install also sent find's errors to /dev/null inside a pipeline that reported sort's status, so a failed listing validated no suite at all and passed. It is now captured and fails validation. Assisted-by: LLM Signed-off-by: Aleksei Sviridkin <f@lex.la>
277 lines
11 KiB
Bash
277 lines
11 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 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"
|
|
}
|