mirror of
https://github.com/cozystack/cozystack.git
synced 2026-11-03 10:37:21 +09:00
The layer below moved the unit suite to bats(1), which enforces `set -e` but not `set -u`. cozytest.sh ran every test body under `set -eu -x`, so without this a test that reads an unset variable silently sees an empty string and passes. There is no runner-level way to inject it, which is why this needs a per-file load rather than a Makefile flag. Two mechanisms were measured and rejected: a setup() defined in --setup-suite-file does not reach test files at all, and SHELLOPTS=nounset does reach them but is exported, so it also applies to the hack/*.sh scripts the tests exercise as subprocesses -- changing the behaviour of the code under test rather than the tests, and dropping two tests outright. `set -u` inside setup() is a shell option rather than an exported one, so it stays scoped to the test body. hack/test_helper.bash therefore defines the shared setup(), and all 32 unit files load it. The restoration is mutation-checked, not assumed: a canary test reading an unset variable aborts with "unbound variable" with the load in place and passes vacuously without it. Suite is 328/328 green either way, so nothing in the tree was relying on the laxer behaviour. The headers are the other half. 27 of the 32 files documented the cozytest contract in prose -- "there is no bats `run` or `$status`", "setup()/teardown() are not honored", "Run with: hack/cozytest.sh ...", title-sanitization rules from the awk parser, and instructions to keep `}` off column 0 so the parser would not truncate a test. Every one of those statements is now false or vacuous, and a false comment about the harness is worse than none: the next author reads "setup() is not honored" and hand-rolls per-test cleanup that the runner would have done. Where a constraint merely stopped binding rather than reversing, the comment says so instead of deleting the line -- the column-0 heredoc indentation and the manual status capture in migration-seaweedfs-db-adopt are both kept, now marked belt-and-braces. Claims that survived the move are left alone: `! cmd` really is still vacuous under `set -e`, since bash suppresses errexit for a negated command exactly as dash did. hack/seaweedfs-naming-audit.bats gains a note that its subject is POSIX sh executed by /bin/sh but sourced into bats' bash, so shellcheck's shell=sh directive is now the only thing guarding that gap. Refs: #3453 Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
231 lines
8.6 KiB
Bash
231 lines
8.6 KiB
Bash
#!/usr/bin/env bats
|
|
# -----------------------------------------------------------------------------
|
|
# Unit tests for hack/select-install.sh
|
|
#
|
|
# Runs under bats(1). Assertions are direct shell tests that exit non-zero on
|
|
# failure; `run` and `$status` are available but unused here. bats supplies
|
|
# `set -e`, and hack/test_helper.bash restores the `set -u` that cozytest.sh
|
|
# applied before #3453. Per-test `trap ... EXIT` cleanup is honored. 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: bats hack/select-install_test.bats
|
|
# -----------------------------------------------------------------------------
|
|
|
|
load test_helper
|
|
|
|
@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)
|
|
trap 'rm -f "$out" "$err"' EXIT
|
|
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" ]
|
|
}
|
|
|
|
@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)
|
|
trap 'rm -rf "$tmp"' EXIT
|
|
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
|
|
}
|
|
|
|
@test "validate detects a dependency cycle" {
|
|
tmp=$(mktemp -d)
|
|
trap 'rm -rf "$tmp"' EXIT
|
|
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
|
|
}
|
|
|
|
@test "validate detects a suite directory with no source mapping" {
|
|
tmp=$(mktemp -d)
|
|
trap 'rm -rf "$tmp"' EXIT
|
|
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
|
|
}
|
|
|
|
@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)
|
|
trap 'rm -rf "$tmp"' EXIT
|
|
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"
|
|
}
|
|
|
|
@test "validate fails when the suites dir does not exist" {
|
|
tmp=$(mktemp -d)
|
|
trap 'rm -rf "$tmp"' EXIT
|
|
# 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
|
|
}
|
|
|
|
@test "closure fails closed when the engine PackageSource is absent" {
|
|
tmp=$(mktemp -d)
|
|
trap 'rm -rf "$tmp"' EXIT
|
|
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
|
|
}
|