CI / build-and-test (push) Successful in 38s
- 8 tests: --features and --config flags (add, update, idempotent, file verification) - 3 tests: transitive dependency resolution (auto-resolve chain) - 4 tests: kappa search (name, substring, repo source, no-match) - 3 tests: kappa upgrade (detection, version display, dry-run) Total: 85 → 103 in test-full.sh (30+23+103 = 156 overall)
1140 lines
27 KiB
Bash
Executable File
1140 lines
27 KiB
Bash
Executable File
#!/bin/sh
|
|
# kappa comprehensive test suite — DSL, ops, error handling, edge cases
|
|
set -e
|
|
|
|
KAPPA="${KAPPA_BIN:-./build/kappa}"
|
|
PASS=0
|
|
FAIL=0
|
|
|
|
check() {
|
|
local desc="$1" cmd="$2" expect="$3"
|
|
local out
|
|
out=$(eval "$cmd" 2>&1) || true
|
|
if echo "$out" | grep -q "$expect"; then
|
|
echo " ✓ $desc"
|
|
PASS=$((PASS + 1))
|
|
else
|
|
echo " ✗ $desc"
|
|
echo " expected: $expect"
|
|
echo " got: $(echo "$out" | head -3 | tr '\n' ' ')"
|
|
FAIL=$((FAIL + 1))
|
|
fi
|
|
}
|
|
|
|
check_fail() {
|
|
local desc="$1" cmd="$2" expect="$3"
|
|
local out rc=0
|
|
out=$(eval "$cmd" 2>&1) || rc=$?
|
|
if [ "$rc" -ne 0 ] && echo "$out" | grep -q "$expect"; then
|
|
echo " ✓ $desc"
|
|
PASS=$((PASS + 1))
|
|
else
|
|
echo " ✗ $desc"
|
|
echo " expected exit≠0 with: $expect"
|
|
echo " exit: $rc, got: $(echo "$out" | head -3 | tr '\n' ' ')"
|
|
FAIL=$((FAIL + 1))
|
|
fi
|
|
}
|
|
|
|
TMPDIR=$(mktemp -d /tmp/kappa-full-test-XXXXX)
|
|
trap 'rm -rf $TMPDIR' EXIT
|
|
export KAPPA_ROOT="$TMPDIR/root"
|
|
mkdir -p "$KAPPA_ROOT"/{bin,temp,db,system,builds,cache/{packages,indexes}}
|
|
|
|
# ═══════════════════════════════════════════
|
|
echo ""
|
|
echo "=== CLI: help, version, error handling ==="
|
|
echo ""
|
|
|
|
check "kappa --help" \
|
|
"$KAPPA --help" \
|
|
'Usage'
|
|
|
|
check "kappa -h" \
|
|
"$KAPPA -h" \
|
|
'Subcommands'
|
|
|
|
check "kappa -V" \
|
|
"$KAPPA -V" \
|
|
'kappa 0.1.0'
|
|
|
|
check_fail "missing subcommand" \
|
|
"$KAPPA" \
|
|
'missing subcommand'
|
|
|
|
check_fail "unknown subcommand" \
|
|
"$KAPPA fakething" \
|
|
"unknown subcommand 'fakething'"
|
|
|
|
check_fail "missing file arg" \
|
|
"$KAPPA parse-package" \
|
|
'no input file'
|
|
|
|
check_fail "nonexistent file" \
|
|
"$KAPPA parse-package /nonexistent/file.kap" \
|
|
'cannot open'
|
|
|
|
# ═══════════════════════════════════════════
|
|
echo ""
|
|
echo "=== DSL: parse-package and validate ==="
|
|
echo ""
|
|
|
|
cat > "$TMPDIR/basic.kap" << 'KAPEOF'
|
|
package "testpkg" {
|
|
version = "1.0"
|
|
source = "https://example.com/test-1.0.tar.gz"
|
|
sha256 = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
|
|
license = "MIT"
|
|
provides = ["libtest.so.1"]
|
|
build { make -j${jobs} }
|
|
install { make DESTDIR=${destdir} install }
|
|
}
|
|
KAPEOF
|
|
|
|
check "basic package parse" \
|
|
"$KAPPA parse-package $TMPDIR/basic.kap" \
|
|
'package "testpkg" 1.0 — valid'
|
|
|
|
check "validate package" \
|
|
"$KAPPA validate $TMPDIR/basic.kap" \
|
|
'valid package'
|
|
|
|
|
|
check_fail "unclosed brace" \
|
|
"printf 'package \"bad\" {\n version = \"1\"\n' | $KAPPA parse-package /dev/stdin" \
|
|
'unclosed'
|
|
|
|
# ═══════════════════════════════════════════
|
|
echo ""
|
|
echo "=== DSL: all keywords parse and format ==="
|
|
echo ""
|
|
|
|
cat > "$TMPDIR/full.kap" << 'KAPEOF'
|
|
package "fullpkg" {
|
|
const version = "2.0"
|
|
const source = "https://example.com/full-v2.0.tar.gz"
|
|
sha256 = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
|
|
sha512 = "abc"
|
|
md5 = "def"
|
|
license = "BSD-2"
|
|
provides = ["full", "libfull.so"]
|
|
outputs = ["bin", "lib"]
|
|
conflicts = ["incompatible"]
|
|
depends = [{ name = "zlib", version = ">=1.2" }, "openssl"]
|
|
features {
|
|
ssl = { enabled = true, flag = "--with-ssl" }
|
|
debug = false
|
|
}
|
|
config {
|
|
file "etc/full.conf" mode = "default" {
|
|
port = "8080"
|
|
}
|
|
}
|
|
env {
|
|
CFLAGS = "-O2"
|
|
CFLAGS += "-pipe"
|
|
LDFLAGS ?= "-s"
|
|
}
|
|
patches = [{ url = "https://example.com/fix.patch", sha256 = "abc", level = 1 }]
|
|
service {
|
|
exec = "/usr/bin/full"
|
|
type = "forking"
|
|
ports = [80, 443]
|
|
}
|
|
assert {
|
|
"ssl required" : features.ssl == "true"
|
|
}
|
|
prepare { echo prepare }
|
|
build { make -j4 }
|
|
check { make check }
|
|
install { make DESTDIR=/tmp/dest install }
|
|
}
|
|
KAPEOF
|
|
|
|
check "full keyword parse" \
|
|
"$KAPPA parse-package $TMPDIR/full.kap" \
|
|
'package "fullpkg"'
|
|
|
|
check "format full roundtrip" \
|
|
"$KAPPA format $TMPDIR/full.kap" \
|
|
'package "fullpkg"'
|
|
|
|
check "format preserves provides" \
|
|
"$KAPPA format $TMPDIR/full.kap" \
|
|
'libfull.so'
|
|
|
|
check "format preserves features" \
|
|
"$KAPPA format $TMPDIR/full.kap" \
|
|
'features {'
|
|
|
|
check "format preserves env +=" \
|
|
"$KAPPA format $TMPDIR/full.kap" \
|
|
'CFLAGS +='
|
|
|
|
check "format preserves env ?=" \
|
|
"$KAPPA format $TMPDIR/full.kap" \
|
|
'LDFLAGS ?='
|
|
|
|
check "format preserves service" \
|
|
"$KAPPA format $TMPDIR/full.kap" \
|
|
'/usr/bin/full'
|
|
|
|
check "format preserves asserts" \
|
|
"$KAPPA format $TMPDIR/full.kap" \
|
|
'assert {'
|
|
|
|
# ═══════════════════════════════════════════
|
|
echo ""
|
|
echo "=== Config: parse-config and format ==="
|
|
echo ""
|
|
|
|
cat > "$TMPDIR/simple-config.kap" << 'KAPEOF'
|
|
system {
|
|
hostname = "test"
|
|
}
|
|
packages {
|
|
testpkg {}
|
|
}
|
|
services {}
|
|
boot {
|
|
kernel = "linux"
|
|
init = "s6"
|
|
root = "/dev/sda1"
|
|
bootloader = "limine"
|
|
}
|
|
KAPEOF
|
|
|
|
check "parse-config basic" \
|
|
"$KAPPA parse-config $TMPDIR/simple-config.kap" \
|
|
'valid'
|
|
|
|
check "parse-config shows init" \
|
|
"$KAPPA parse-config $TMPDIR/simple-config.kap" \
|
|
's6'
|
|
|
|
check "validate config" \
|
|
"$KAPPA validate $TMPDIR/simple-config.kap" \
|
|
'valid system configuration'
|
|
|
|
# ═══════════════════════════════════════════
|
|
echo ""
|
|
echo "=== Config: users and groups ==="
|
|
echo ""
|
|
|
|
cat > "$TMPDIR/users-config.kap" << 'KAPEOF'
|
|
system {
|
|
hostname = "test"
|
|
}
|
|
packages {}
|
|
services {}
|
|
boot {
|
|
kernel = "linux"
|
|
init = "s6"
|
|
root = "/dev/sda1"
|
|
bootloader = "limine"
|
|
}
|
|
groups {
|
|
wheel { gid = 998 }
|
|
audio {}
|
|
}
|
|
users {
|
|
root {
|
|
shell = "/bin/zsh"
|
|
groups = ["wheel", "audio"]
|
|
}
|
|
}
|
|
KAPEOF
|
|
|
|
check "parse-config with users" \
|
|
"$KAPPA parse-config $TMPDIR/users-config.kap" \
|
|
'1 users'
|
|
|
|
check "format users roundtrip" \
|
|
"$KAPPA format $TMPDIR/users-config.kap" \
|
|
'/bin/zsh'
|
|
|
|
# ═══════════════════════════════════════════
|
|
echo ""
|
|
echo "=== Config: assertions ==="
|
|
echo ""
|
|
|
|
cat > "$TMPDIR/assert-pass.kap" << 'KAPEOF'
|
|
system { hostname = "test" }
|
|
packages { testpkg {} }
|
|
services {}
|
|
boot {
|
|
kernel = "linux"
|
|
init = "s6"
|
|
root = "/dev/sda1"
|
|
bootloader = "limine"
|
|
}
|
|
assert {
|
|
"root required" : boot.root != ""
|
|
"init must be s6" : boot.init == "s6"
|
|
}
|
|
KAPEOF
|
|
|
|
check "validate passes assertions" \
|
|
"$KAPPA validate $TMPDIR/assert-pass.kap" \
|
|
'valid system configuration'
|
|
|
|
cat > "$TMPDIR/assert-fail.kap" << 'KAPEOF'
|
|
system { hostname = "test" }
|
|
packages {}
|
|
services {}
|
|
boot {
|
|
kernel = "linux"
|
|
init = "openrc"
|
|
root = "/dev/sda1"
|
|
bootloader = "limine"
|
|
}
|
|
assert {
|
|
"must be s6" : boot.init == "s6"
|
|
}
|
|
KAPEOF
|
|
|
|
check_fail "validate fails assertion" \
|
|
"$KAPPA validate $TMPDIR/assert-fail.kap" \
|
|
'assertion failures'
|
|
|
|
# ═══════════════════════════════════════════
|
|
echo ""
|
|
echo "=== Config: imports ==="
|
|
echo ""
|
|
|
|
cat > "$TMPDIR/imported.kap" << 'KAPEOF'
|
|
system { hostname = "imported.local" }
|
|
packages {}
|
|
services {}
|
|
boot {
|
|
kernel = "linux"
|
|
init = "s6"
|
|
root = "/dev/sda1"
|
|
bootloader = "limine"
|
|
}
|
|
KAPEOF
|
|
|
|
cat > "$TMPDIR/importer.kap" << 'KAPEOF'
|
|
imports = ["imported.kap"]
|
|
packages {}
|
|
services {}
|
|
boot {
|
|
kernel = "linux"
|
|
init = "s6"
|
|
root = "/dev/sda1"
|
|
bootloader = "limine"
|
|
}
|
|
KAPEOF
|
|
|
|
check "imports merges hostname" \
|
|
"$KAPPA parse-config $TMPDIR/importer.kap" \
|
|
'valid'
|
|
|
|
# ═══════════════════════════════════════════
|
|
echo ""
|
|
echo "=== Doctor diagnostics ==="
|
|
echo ""
|
|
|
|
cat > "$TMPDIR/doctor-bad.kap" << 'KAPEOF'
|
|
package "bad" {
|
|
version = ""
|
|
source = ""
|
|
build { echo ok }
|
|
install { echo ok }
|
|
}
|
|
KAPEOF
|
|
|
|
check_fail "doctor catches empty version" \
|
|
"$KAPPA doctor $TMPDIR/doctor-bad.kap" \
|
|
'version is not set'
|
|
|
|
check_fail "doctor catches empty source" \
|
|
"$KAPPA doctor $TMPDIR/doctor-bad.kap" \
|
|
'source URL is not set'
|
|
|
|
check_fail "doctor warns missing license" \
|
|
"$KAPPA doctor $TMPDIR/doctor-bad.kap" \
|
|
'license is not specified'
|
|
|
|
cat > "$TMPDIR/doctor-badcfg.kap" << 'KAPEOF'
|
|
system {}
|
|
packages {}
|
|
services {}
|
|
boot {
|
|
kernel = "linux"
|
|
init = "fakenit"
|
|
root = "/dev/sda1"
|
|
bootloader = "lilo"
|
|
}
|
|
KAPEOF
|
|
|
|
check_fail "doctor warns missing hostname" \
|
|
"$KAPPA doctor $TMPDIR/doctor-badcfg.kap" \
|
|
'hostname is not set'
|
|
|
|
check_fail "doctor warns unknown init" \
|
|
"$KAPPA doctor $TMPDIR/doctor-badcfg.kap" \
|
|
'not a recognized init system'
|
|
|
|
check_fail "doctor warns unknown bootloader" \
|
|
"$KAPPA doctor $TMPDIR/doctor-badcfg.kap" \
|
|
'not a recognized bootloader'
|
|
|
|
# ═══════════════════════════════════════════
|
|
echo ""
|
|
echo "=== Repos: parsing and formatting ==="
|
|
echo ""
|
|
|
|
cat > "$TMPDIR/repos-config.kap" << 'KAPEOF'
|
|
system { hostname = "test" }
|
|
repos {
|
|
upstream {
|
|
url = "https://packages.example.com/"
|
|
channels = ["stable", "testing"]
|
|
mirrors = [
|
|
"https://cdn.example.com/",
|
|
"https://eu.example.com/",
|
|
]
|
|
priority = 100
|
|
}
|
|
local {
|
|
url = "file:///usr/local/kappa/repo/"
|
|
channels = ["default"]
|
|
}
|
|
}
|
|
packages { testpkg {} }
|
|
services {}
|
|
boot {
|
|
kernel = "linux"
|
|
init = "s6"
|
|
root = "/dev/sda1"
|
|
bootloader = "limine"
|
|
}
|
|
KAPEOF
|
|
|
|
check "parse-config with repos" \
|
|
"$KAPPA parse-config $TMPDIR/repos-config.kap" \
|
|
'repos: 2'
|
|
|
|
check "format repos roundtrip" \
|
|
"$KAPPA format $TMPDIR/repos-config.kap" \
|
|
'upstream {'
|
|
|
|
check "format repos preserves channels" \
|
|
"$KAPPA format $TMPDIR/repos-config.kap" \
|
|
'channels = \['
|
|
|
|
check "format repos preserves mirrors" \
|
|
"$KAPPA format $TMPDIR/repos-config.kap" \
|
|
'cdn.example.com'
|
|
|
|
check "format repos preserves priority" \
|
|
"$KAPPA format $TMPDIR/repos-config.kap" \
|
|
'priority = 100'
|
|
|
|
# ═══════════════════════════════════════════
|
|
echo ""
|
|
echo "=== Backward compat: remotes flat list ==="
|
|
echo ""
|
|
|
|
cat > "$TMPDIR/remotes-config.kap" << 'KAPEOF'
|
|
remotes = [
|
|
"https://old.example.com/",
|
|
"https://old2.example.com/",
|
|
]
|
|
system { hostname = "test" }
|
|
packages {}
|
|
services {}
|
|
boot {
|
|
kernel = "linux"
|
|
init = "s6"
|
|
root = "/dev/sda1"
|
|
bootloader = "limine"
|
|
}
|
|
KAPEOF
|
|
|
|
check "flat remotes still parse" \
|
|
"$KAPPA parse-config $TMPDIR/remotes-config.kap" \
|
|
'valid'
|
|
|
|
check "format remotes roundtrip" \
|
|
"$KAPPA format $TMPDIR/remotes-config.kap" \
|
|
'remotes = \['
|
|
|
|
# ═══════════════════════════════════════════
|
|
echo ""
|
|
echo "=== Index: generation and validation ==="
|
|
echo ""
|
|
|
|
check "kappa index generates index.kap" \
|
|
"$KAPPA index $TMPDIR" \
|
|
'packages indexed'
|
|
|
|
check "validate index.kap" \
|
|
"$KAPPA validate $TMPDIR/index.kap" \
|
|
'valid index'
|
|
|
|
check "format index roundtrip" \
|
|
"$KAPPA format $TMPDIR/index.kap" \
|
|
'index "'
|
|
|
|
check_fail "index needs directory arg" \
|
|
"$KAPPA index" \
|
|
'no directory'
|
|
|
|
# ═══════════════════════════════════════════
|
|
echo ""
|
|
echo "=== Package operations: add and remove ==="
|
|
echo ""
|
|
|
|
rm -f "$KAPPA_ROOT/system/config.kap"
|
|
|
|
check "add creates config" \
|
|
"$KAPPA add newpkg" \
|
|
'added newpkg'
|
|
|
|
check "add with version" \
|
|
"$KAPPA add verpkg 2.0" \
|
|
'added verpkg 2.0'
|
|
|
|
check "add existing updates version" \
|
|
"$KAPPA add newpkg 3.0" \
|
|
'updated newpkg'
|
|
|
|
check "add existing idempotent" \
|
|
"$KAPPA add verpkg" \
|
|
'already in packages'
|
|
|
|
check "remove package" \
|
|
"$KAPPA remove verpkg" \
|
|
'removed verpkg'
|
|
|
|
check "remove nonexistent idempotent" \
|
|
"$KAPPA remove nonexistent" \
|
|
'not in packages'
|
|
|
|
check_fail "add needs name" \
|
|
"$KAPPA add" \
|
|
'no package name'
|
|
|
|
check_fail "remove needs name" \
|
|
"$KAPPA remove" \
|
|
'no package name'
|
|
|
|
|
|
# --features and --config flags
|
|
check "add with --features" \
|
|
"$KAPPA add featpkg --features 'ssl=on,gzip=off'" \
|
|
'added featpkg'
|
|
|
|
check "add features present in config" \
|
|
"grep -c 'ssl = true' $KAPPA_ROOT/system/config.kap" \
|
|
'1'
|
|
|
|
check "update existing with --features" \
|
|
"$KAPPA add featpkg --features 'http2=on'" \
|
|
'updated featpkg'
|
|
|
|
check "add with --config" \
|
|
"$KAPPA add cfgpkg --config 'port=9090,worker=4'" \
|
|
'added cfgpkg'
|
|
|
|
check "config values written to file" \
|
|
"grep 'port = 9090' $KAPPA_ROOT/system/config.kap" \
|
|
'port = 9090'
|
|
|
|
check "add with features and config together" \
|
|
"$KAPPA add combopkg --features 'ssl=on' --config 'host=localhost'" \
|
|
'added combopkg'
|
|
|
|
check "features and config both in file" \
|
|
"grep -c ssl $KAPPA_ROOT/system/config.kap" \
|
|
'2'
|
|
|
|
check "add idempotent with no flags" \
|
|
"$KAPPA add cfgpkg" \
|
|
'already in packages'
|
|
# ═══════════════════════════════════════════
|
|
echo ""
|
|
echo "=== Install: list and rollback ==="
|
|
echo ""
|
|
|
|
check "list empty" \
|
|
"$KAPPA list" \
|
|
'no packages installed'
|
|
|
|
mkdir -p "$KAPPA_ROOT/db"
|
|
cat > "$KAPPA_ROOT/db/installed" << 'EOF'
|
|
testpkg 1.0 a1b2c3d4e5f6a7b8
|
|
otherpkg 2.0 b2c3d4e5f6a7b8c9 libother.so
|
|
EOF
|
|
|
|
check "list shows installed" \
|
|
"$KAPPA list" \
|
|
'2 packages installed'
|
|
|
|
check "list shows provides" \
|
|
"$KAPPA list" \
|
|
'libother.so'
|
|
|
|
mkdir -p "$KAPPA_ROOT/db/generations"
|
|
echo "a1b2c3d4e5f6a7b8" > "$KAPPA_ROOT/db/generations/gen-0001"
|
|
echo "b2c3d4e5f6a7b8c9" > "$KAPPA_ROOT/db/generations/gen-0002"
|
|
|
|
check "rollback shows generations" \
|
|
"$KAPPA rollback" \
|
|
'2 generations'
|
|
|
|
check "rollback shows gen IDs" \
|
|
"$KAPPA rollback" \
|
|
'gen-0001'
|
|
|
|
# ═══════════════════════════════════════════
|
|
echo ""
|
|
echo "=== Resolver: conflicts ==="
|
|
echo ""
|
|
|
|
mkdir -p "$KAPPA_ROOT/cache/packages"
|
|
|
|
cat > "$KAPPA_ROOT/cache/packages/a.kap" << 'KAPEOF'
|
|
package "a" {
|
|
version = "1.0"
|
|
source = "a.tar.gz"
|
|
conflicts = ["b"]
|
|
build { echo a }
|
|
install { echo a > ${destdir}/tmp/a }
|
|
}
|
|
KAPEOF
|
|
cat > "$KAPPA_ROOT/cache/packages/b.kap" << 'KAPEOF'
|
|
package "b" {
|
|
version = "1.0"
|
|
source = "b.tar.gz"
|
|
conflicts = ["a"]
|
|
build { echo b }
|
|
install { echo b > ${destdir}/tmp/b }
|
|
}
|
|
KAPEOF
|
|
|
|
cat > "$TMPDIR/conflict-config.kap" << 'KAPEOF'
|
|
system { hostname = "test" }
|
|
packages { a {} b {} }
|
|
services {}
|
|
boot {
|
|
kernel = "linux"
|
|
init = "s6"
|
|
root = "/dev/sda1"
|
|
bootloader = "limine"
|
|
}
|
|
KAPEOF
|
|
|
|
check_fail "resolve catches conflicts" \
|
|
"$KAPPA resolve $TMPDIR/conflict-config.kap" \
|
|
'conflicts detected'
|
|
|
|
check_fail "rebuild catches conflicts" \
|
|
"$KAPPA rebuild $TMPDIR/conflict-config.kap" \
|
|
'conflicts detected'
|
|
|
|
cat > "$TMPDIR/missing-config.kap" << 'KAPEOF'
|
|
system { hostname = "test" }
|
|
packages { nonexistent {} }
|
|
services {}
|
|
boot {
|
|
kernel = "linux"
|
|
init = "s6"
|
|
root = "/dev/sda1"
|
|
bootloader = "limine"
|
|
}
|
|
KAPEOF
|
|
|
|
check "resolve warns on missing" \
|
|
"$KAPPA resolve $TMPDIR/missing-config.kap 2>&1" \
|
|
'not found in registry'
|
|
|
|
# ═══════════════════════════════════════════
|
|
echo ""
|
|
echo "=== Scheduler: deptree ordering ==="
|
|
echo ""
|
|
|
|
cat > "$KAPPA_ROOT/cache/packages/dep-a.kap" << 'KAPEOF'
|
|
package "dep-a" {
|
|
version = "1.0"
|
|
source = "a.tar.gz"
|
|
build { echo a }
|
|
install { echo a > ${destdir}/tmp/a }
|
|
}
|
|
KAPEOF
|
|
cat > "$KAPPA_ROOT/cache/packages/dep-b.kap" << 'KAPEOF'
|
|
package "dep-b" {
|
|
version = "1.0"
|
|
source = "b.tar.gz"
|
|
depends = [{ name = "dep-a" }]
|
|
build { echo b }
|
|
install { echo b > ${destdir}/tmp/b }
|
|
}
|
|
KAPEOF
|
|
cat > "$KAPPA_ROOT/cache/packages/dep-c.kap" << 'KAPEOF'
|
|
package "dep-c" {
|
|
version = "1.0"
|
|
source = "c.tar.gz"
|
|
depends = [{ name = "dep-b" }]
|
|
build { echo c }
|
|
install { echo c > ${destdir}/tmp/c }
|
|
}
|
|
KAPEOF
|
|
|
|
cat > "$TMPDIR/deptree-config.kap" << 'KAPEOF'
|
|
system { hostname = "test" }
|
|
packages { dep-a {} dep-b {} dep-c {} }
|
|
services {}
|
|
boot {
|
|
kernel = "linux"
|
|
init = "s6"
|
|
root = "/dev/sda1"
|
|
bootloader = "limine"
|
|
}
|
|
KAPEOF
|
|
|
|
check "resolve deptree (a=0 deps)" \
|
|
"$KAPPA resolve $TMPDIR/deptree-config.kap 2>&1" \
|
|
'dep-a (0 deps)'
|
|
|
|
check "resolve deptree (c has dep)" \
|
|
"$KAPPA resolve $TMPDIR/deptree-config.kap 2>&1" \
|
|
'dep-c (1 deps)'
|
|
|
|
# Transitive dependency resolution
|
|
cat > "$KAPPA_ROOT/cache/packages/trans-a.kap" << 'TRANSEOF'
|
|
package "trans-a" {
|
|
version = "1.0"
|
|
source = "a.tar.gz"
|
|
sha256 = "abc"
|
|
license = "MIT"
|
|
build { echo a }
|
|
install { echo a }
|
|
}
|
|
TRANSEOF
|
|
cat > "$KAPPA_ROOT/cache/packages/trans-b.kap" << 'TRANSEOF'
|
|
package "trans-b" {
|
|
version = "1.0"
|
|
source = "b.tar.gz"
|
|
sha256 = "abc"
|
|
license = "MIT"
|
|
depends = [{ name = "trans-a" }]
|
|
build { echo b }
|
|
install { echo b }
|
|
}
|
|
TRANSEOF
|
|
cat > "$KAPPA_ROOT/cache/packages/trans-c.kap" << 'TRANSEOF'
|
|
package "trans-c" {
|
|
version = "1.0"
|
|
source = "c.tar.gz"
|
|
sha256 = "abc"
|
|
license = "MIT"
|
|
depends = [{ name = "trans-b" }]
|
|
build { echo c }
|
|
install { echo c }
|
|
}
|
|
TRANSEOF
|
|
|
|
cat > "$TMPDIR/transitive-config.kap" << 'TRANSEOF'
|
|
system { hostname = "test" }
|
|
packages { trans-c {} }
|
|
services {}
|
|
boot {
|
|
kernel = "linux"
|
|
init = "s6"
|
|
root = "/dev/sda1"
|
|
bootloader = "limine"
|
|
}
|
|
TRANSEOF
|
|
|
|
check "transitive deps auto-resolve (only c declared)" \
|
|
"$KAPPA resolve $TMPDIR/transitive-config.kap 2>&1" \
|
|
'trans-a (0 deps)'
|
|
|
|
check "transitive deps include intermediate" \
|
|
"$KAPPA resolve $TMPDIR/transitive-config.kap 2>&1" \
|
|
'trans-b (1 deps)'
|
|
|
|
check "transitive deps resolve root last" \
|
|
"$KAPPA resolve $TMPDIR/transitive-config.kap 2>&1" \
|
|
'trans-c (1 deps)'
|
|
|
|
# ═══════════════════════════════════════════
|
|
echo ""
|
|
echo "=== Build: simple execution ==="
|
|
echo ""
|
|
|
|
cat > "$TMPDIR/simple-build.kap" << 'KAPEOF'
|
|
package "simple" {
|
|
version = "1.0"
|
|
source = "simple-1.0.tar.gz"
|
|
sha256 = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
|
|
license = "MIT"
|
|
build { echo built }
|
|
check { echo checked }
|
|
install {
|
|
mkdir -p ${destdir}/tmp
|
|
echo done > ${destdir}/tmp/simple
|
|
}
|
|
}
|
|
KAPEOF
|
|
|
|
check "build simple package" \
|
|
"$KAPPA build $TMPDIR/simple-build.kap" \
|
|
'build successful'
|
|
|
|
check "installed after build" \
|
|
"$KAPPA list" \
|
|
'simple'
|
|
|
|
# ═══════════════════════════════════════════
|
|
echo ""
|
|
echo "=== Build: error handling ==="
|
|
echo ""
|
|
|
|
cat > "$TMPDIR/fail-build.kap" << 'KAPEOF'
|
|
package "failpkg" {
|
|
version = "1.0"
|
|
source = "fail.tar.gz"
|
|
sha256 = "abc"
|
|
license = "MIT"
|
|
build { exit 42 }
|
|
install { echo ok }
|
|
}
|
|
KAPEOF
|
|
|
|
check_fail "build reports exit code" \
|
|
"$KAPPA build $TMPDIR/fail-build.kap" \
|
|
'exited with code 42'
|
|
|
|
check_fail "build reports failed phase" \
|
|
"$KAPPA build $TMPDIR/fail-build.kap" \
|
|
'build'
|
|
|
|
# ═══════════════════════════════════════════
|
|
echo ""
|
|
echo "=== Build: env operators ==="
|
|
echo ""
|
|
|
|
cat > "$TMPDIR/env-ops.kap" << 'KAPEOF'
|
|
package "envops" {
|
|
version = "1.0"
|
|
source = "envops.tar.gz"
|
|
sha256 = "abc"
|
|
license = "MIT"
|
|
env {
|
|
FLAGS = "base"
|
|
FLAGS += "extra"
|
|
FLAGS ?= "soft"
|
|
}
|
|
build { echo ok }
|
|
install { mkdir -p ${destdir}/tmp }
|
|
}
|
|
KAPEOF
|
|
|
|
check "format shows env =" \
|
|
"$KAPPA format $TMPDIR/env-ops.kap" \
|
|
'FLAGS = "base"'
|
|
|
|
check "format shows env +=" \
|
|
"$KAPPA format $TMPDIR/env-ops.kap" \
|
|
'FLAGS += "extra"'
|
|
|
|
check "format shows env ?=" \
|
|
"$KAPPA format $TMPDIR/env-ops.kap" \
|
|
'FLAGS ?= "soft"'
|
|
|
|
# ═══════════════════════════════════════════
|
|
echo ""
|
|
echo "=== Init systems: all 5 recognized ==="
|
|
echo ""
|
|
|
|
for init in systemd openrc s6 runit dinit; do
|
|
cat > "$TMPDIR/init-$init.kap" << KAPEOF
|
|
system { hostname = "test" }
|
|
packages {}
|
|
services {}
|
|
boot {
|
|
kernel = "linux"
|
|
init = "$init"
|
|
root = "/dev/sda1"
|
|
bootloader = "limine"
|
|
}
|
|
users {
|
|
root { shell = "/bin/sh" }
|
|
}
|
|
KAPEOF
|
|
check "parse-config recognizes $init" \
|
|
"$KAPPA parse-config $TMPDIR/init-$init.kap" \
|
|
'valid'
|
|
done
|
|
|
|
# ═══════════════════════════════════════════
|
|
echo ""
|
|
echo "=== Bootloaders: both recognized ==="
|
|
echo ""
|
|
|
|
for bl in grub limine; do
|
|
cat > "$TMPDIR/bl-$bl.kap" << KAPEOF
|
|
system { hostname = "test" }
|
|
packages {}
|
|
services {}
|
|
boot {
|
|
kernel = "linux"
|
|
init = "s6"
|
|
root = "/dev/sda1"
|
|
bootloader = "$bl"
|
|
}
|
|
users {
|
|
root { shell = "/bin/sh" }
|
|
}
|
|
KAPEOF
|
|
check "parse-config recognizes $bl" \
|
|
"$KAPPA parse-config $TMPDIR/bl-$bl.kap" \
|
|
'valid'
|
|
done
|
|
|
|
# ═══════════════════════════════════════════
|
|
echo ""
|
|
echo "=== Variable interpolation: format preserves ==="
|
|
echo ""
|
|
|
|
cat > "$TMPDIR/vars.kap" << 'KAPEOF'
|
|
package "vars" {
|
|
version = "1.0"
|
|
source = "https://example.com/vars-1.0.tar.gz"
|
|
sha256 = "abc"
|
|
license = "MIT"
|
|
build {
|
|
./configure --prefix=${prefix}
|
|
make -j${jobs}
|
|
}
|
|
install {
|
|
make DESTDIR=${destdir} install
|
|
}
|
|
}
|
|
KAPEOF
|
|
|
|
check "format preserves prefix var" \
|
|
"$KAPPA format $TMPDIR/vars.kap" \
|
|
'prefix=${prefix}'
|
|
|
|
check "format preserves jobs var" \
|
|
"$KAPPA format $TMPDIR/vars.kap" \
|
|
'j${jobs}'
|
|
|
|
check "format preserves destdir var" \
|
|
"$KAPPA format $TMPDIR/vars.kap" \
|
|
'DESTDIR=${destdir}'
|
|
|
|
# ═══════════════════════════════════════════
|
|
echo ""
|
|
echo "=== Paths: --root override ==="
|
|
echo ""
|
|
|
|
ALTROOT="$TMPDIR/altroot"
|
|
mkdir -p "$ALTROOT/system"
|
|
|
|
cat > "$ALTROOT/system/config.kap" << 'KAPEOF'
|
|
system { hostname = "alt" }
|
|
packages {}
|
|
services {}
|
|
boot {
|
|
kernel = "linux"
|
|
init = "s6"
|
|
root = "/dev/sda1"
|
|
bootloader = "limine"
|
|
}
|
|
KAPEOF
|
|
|
|
check "--root flag works" \
|
|
"$KAPPA --root $ALTROOT parse-config $ALTROOT/system/config.kap" \
|
|
'valid'
|
|
|
|
$KAPPA --root "$ALTROOT" add rootpkg 2>/dev/null
|
|
check "verify add wrote to alt root" \
|
|
"grep -c rootpkg $ALTROOT/system/config.kap" \
|
|
'1'
|
|
|
|
# ═══════════════════════════════════════════
|
|
echo ""
|
|
echo "=== Config: rollback config ==="
|
|
echo ""
|
|
|
|
cat > "$TMPDIR/rollback-cfg.kap" << 'KAPEOF'
|
|
system {
|
|
hostname = "test"
|
|
rollback {
|
|
keep = 3
|
|
}
|
|
}
|
|
packages {}
|
|
services {}
|
|
boot {
|
|
kernel = "linux"
|
|
init = "s6"
|
|
root = "/dev/sda1"
|
|
bootloader = "limine"
|
|
}
|
|
KAPEOF
|
|
|
|
check "format preserves rollback keep" \
|
|
"$KAPPA format $TMPDIR/rollback-cfg.kap" \
|
|
'keep = 3'
|
|
|
|
# ═══════════════════════════════════════════
|
|
echo ""
|
|
echo "=== Config: system env block ==="
|
|
echo ""
|
|
|
|
cat > "$TMPDIR/sysenv-config.kap" << 'KAPEOF'
|
|
system {
|
|
hostname = "test"
|
|
env {
|
|
CFLAGS = "-O2"
|
|
CFLAGS += "-pipe"
|
|
}
|
|
}
|
|
packages {}
|
|
services {}
|
|
boot {
|
|
kernel = "linux"
|
|
init = "s6"
|
|
root = "/dev/sda1"
|
|
bootloader = "limine"
|
|
}
|
|
KAPEOF
|
|
|
|
check "parse-config with system env" \
|
|
"$KAPPA parse-config $TMPDIR/sysenv-config.kap" \
|
|
'valid'
|
|
|
|
# ═══════════════════════════════════════════
|
|
echo ""
|
|
echo "=== DSL: feature flags ==="
|
|
echo ""
|
|
|
|
cat > "$TMPDIR/features-config.kap" << 'KAPEOF'
|
|
system {
|
|
hostname = "test"
|
|
features {
|
|
ssl = true
|
|
wayland = false
|
|
}
|
|
}
|
|
packages {
|
|
testpkg {
|
|
features {
|
|
debug = true
|
|
}
|
|
}
|
|
}
|
|
services {}
|
|
boot {
|
|
kernel = "linux"
|
|
init = "s6"
|
|
root = "/dev/sda1"
|
|
bootloader = "limine"
|
|
}
|
|
KAPEOF
|
|
|
|
check "parse-config with features" \
|
|
"$KAPPA parse-config $TMPDIR/features-config.kap" \
|
|
'valid'
|
|
|
|
# ═══════════════════════════════════════════
|
|
echo ""
|
|
|
|
|
|
echo ""
|
|
echo "=== Search: cached index queries ==="
|
|
echo ""
|
|
|
|
mkdir -p "$KAPPA_ROOT/cache/indexes"
|
|
cat > "$KAPPA_ROOT/cache/indexes/test-repo-stable.kap" << 'SRCHEOF'
|
|
index "test-repo/stable" {
|
|
trans-a { version = "1.0" }
|
|
trans-b { version = "1.0" }
|
|
trans-c { version = "1.0" }
|
|
testpkg { version = "1.0" }
|
|
}
|
|
SRCHEOF
|
|
|
|
check "search finds package by name" \
|
|
"$KAPPA search trans-a" \
|
|
'trans-a'
|
|
|
|
check "search finds by substring" \
|
|
"$KAPPA search ans" \
|
|
'trans-a'
|
|
|
|
check "search shows repo source" \
|
|
"$KAPPA search trans-a" \
|
|
'test-repo/stable'
|
|
|
|
check "search no match" \
|
|
"$KAPPA search zzz999" \
|
|
'no packages matching'
|
|
|
|
echo ""
|
|
echo "=== Upgrade: version comparison ==="
|
|
echo ""
|
|
|
|
mkdir -p "$KAPPA_ROOT/db"
|
|
cat > "$KAPPA_ROOT/db/installed" << 'UPGEOF'
|
|
trans-a 0.9 a1b2c3d4e5f6a7b8
|
|
trans-b 0.9 b2c3d4e5f6a7b8c9
|
|
trans-c 1.0 c3d4e5f6a7b8c9d0
|
|
UPGEOF
|
|
|
|
check "upgrade detects newer versions" \
|
|
"$KAPPA upgrade 2>&1" \
|
|
'trans-a'
|
|
|
|
check "upgrade shows old version" \
|
|
"$KAPPA upgrade 2>&1" \
|
|
'0.9'
|
|
|
|
check "upgrade dry-run does not apply" \
|
|
"$KAPPA upgrade --dry-run 2>&1" \
|
|
'run kappa add'
|
|
|
|
rm -f "$KAPPA_ROOT/db/installed"
|
|
echo "=== Rebuild: dry-run detection ==="
|
|
echo ""
|
|
|
|
# Package must be in registry + installed DB for rebuild
|
|
cp "$TMPDIR/basic.kap" "$KAPPA_ROOT/cache/packages/testpkg.kap"
|
|
mkdir -p "$KAPPA_ROOT/db"
|
|
echo "testpkg 1.0 0000000000000000" > "$KAPPA_ROOT/db/installed"
|
|
|
|
cat > "$TMPDIR/rebuild-config.kap" << 'KAPEOF'
|
|
system { hostname = "test" }
|
|
packages { testpkg {} }
|
|
services {}
|
|
boot {
|
|
kernel = "linux"
|
|
init = "s6"
|
|
root = "/dev/sda1"
|
|
bootloader = "limine"
|
|
}
|
|
KAPEOF
|
|
|
|
check "rebuild dry-run detects changes" \
|
|
"$KAPPA rebuild --dry-run $TMPDIR/rebuild-config.kap 2>&1" \
|
|
'nothing to rebuild'
|
|
|
|
check "resolve shows build plan" \
|
|
"$KAPPA resolve $TMPDIR/rebuild-config.kap 2>&1" \
|
|
'packages in build order'
|
|
|
|
# ═══════════════════════════════════════════
|
|
echo ""
|
|
echo "=========================================="
|
|
echo " Results: $PASS passed, $FAIL failed"
|
|
echo "=========================================="
|
|
|
|
[ "$FAIL" -eq 0 ] || exit 1
|