test: add 85 comprehensive tests and Gitea CI/CD workflow
CI / build-and-test (push) Failing after 56s

- test-full.sh: 85 tests covering CLI, DSL parsing (all keywords),
  config parsing, users/groups, assertions, imports, doctor diagnostics,
  repos/remotes, index generation, add/remove operations, install DB,
  resolver (conflicts + missing), scheduler (deptree ordering),
  build execution + error handling, env operators,
  init systems, bootloaders, variable interpolation, --root override,
  rollback config, system env, feature flags, rebuild dry-run

- .gitea/workflows/ci.yml: builds on Ubuntu with clang+cmake+ninja,
  runs all three test suites on push/PR to main

Total test count: 31 (DSL) + 23 (init-switch) + 85 (comprehensive) = 139
This commit is contained in:
2026-08-04 14:15:50 -04:00
parent 09dde98159
commit d10679d267
2 changed files with 1026 additions and 0 deletions
Executable
+992
View File
@@ -0,0 +1,992 @@
#!/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'
# ═══════════════════════════════════════════
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)'
# ═══════════════════════════════════════════
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 "=== 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