Files
kappa/test-full.sh
huntedbytheirs d0ff52a5d7
CI / build-and-test (push) Successful in 42s
test: add 7 tests for kappa bump and bump-all
- 4 tests for kappa bump: version update, version written to file,
  source URL preserved, requires version arg
- 3 tests for kappa bump-all: finds newer version, reports count,
  requires directory arg

Total: 103 → 110 in test-full.sh (30+23+110 = 163 overall)
2026-08-05 07:58:35 -04:00

1202 lines
28 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'
echo ""
echo "=== Bump: explicit version update ==="
echo ""
cat > "$TMPDIR/bump-test.kap" << 'BMPEOF'
package "bumpme" {
const version = "4.4.0"
const source = "https://ftp.gnu.org/gnu/make/make-${version}.tar.gz"
sha256 = "oldhash"
license = "GPL-3.0-or-later"
build { make }
install { make install }
}
BMPEOF
check "bump updates version" \
"$KAPPA bump $TMPDIR/bump-test.kap 4.4.1" \
'bumped bumpme'
check "bump writes correct version" \
"grep -c '4.4.1' $TMPDIR/bump-test.kap" \
'1'
check "bump preserves source URL" \
"grep -c 'make-' $TMPDIR/bump-test.kap" \
'1'
check_fail "bump requires version arg" \
"$KAPPA bump $TMPDIR/bump-test.kap" \
'requires'
echo ""
echo "=== Bump-all: directory scan ==="
echo ""
BPALLDIR="$TMPDIR/bumpall-pkgs"
mkdir -p "$BPALLDIR"
cat > "$BPALLDIR/make.kap" << 'BMPEOF'
package "make" {
const version = "4.4.0"
const source = "https://ftp.gnu.org/gnu/make/make-${version}.tar.gz"
sha256 = "old"
license = "GPL-3.0-or-later"
build { make }
install { make install }
}
BMPEOF
check "bump-all finds newer version" \
"$KAPPA bump-all $BPALLDIR" \
'4.4.0 → 4.4.1'
check "bump-all reports count" \
"$KAPPA bump-all $BPALLDIR" \
'bumped'
check_fail "bump-all needs directory" \
"$KAPPA bump-all" \
'requires a directory'
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