Files
mhoennigandClaude Fable 5 e4bfeacf5a Werkdock skeleton: doctor, load, run over the bwrap engine (Go)
The minimal build-capable CLI decided in RFC 0002's outcome: stdlib-only
Go module, one static binary.

- engine: RunSpec behind the Engine interface (RFC 0001); the Bwrap
  engine ports Werkator's hardened invocation — uid-0 mapping, read-only
  rootfs at /, proc/dev/tmp/root before the user binds so binds below
  them land inside, mountpoint pre-creation in the rootfs including file
  mountpoints, and a guard against binds escaping the rootfs. --clearenv
  gives docker-style clean environments (HOME/PATH set explicitly).
- store: images under $WERKDOCK_HOME (default ~/.werkdock), load
  unpacks via the tar CLI into a tmp dir and renames atomically.
- cli: docker-shaped run flags (-v/-e/-w/--rm); refused docker flags
  (-p, --network, --memory, --cpus, --user, -d) fail loudly with the
  reason; exit codes follow docker (125 CLI errors, child code through).
- doctor: port of werkator-build-prerequisites.sh — userns probe with
  the three signals, tar/zstd, free space and group-quota headroom via
  testable df/quota parsers, same PASS/FAIL output.
- tests: argv golden test, mountpoint and escape tests, flag refusals,
  store round trip, doctor parsers — plus real-sandbox integration
  tests that skip where bwrap or userns are unavailable.

Also records in step 21: RFC 0002 levels 2/3 deferred; next goal is
sandbox builds of Werkator, Werkbaum, and Werkdock itself.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-09-01 07:01:21 +02:00

162 lines
4.7 KiB
Go

package doctor
import (
"reflect"
"strings"
"testing"
)
func TestEvaluateSandboxAllSignalsPass(t *testing.T) {
r := &Report{}
output := "0\n 0 120957 1\ntouch: cannot touch '/usr/ro-test': Read-only file system\n"
EvaluateSandbox(r, output, 120957)
if !r.OK() {
t.Errorf("expected all signals to pass, got %+v", r.Checks)
}
if len(r.Checks) != 3 {
t.Errorf("expected 3 checks, got %d", len(r.Checks))
}
}
func TestEvaluateSandboxFailures(t *testing.T) {
tests := []struct {
name string
output string
selfUID int
wantFail string
}{
{
"not root inside",
"1000\n 0 120957 1\nRead-only file system\n",
120957,
"expected uid 0",
},
{
"uid_map maps someone else",
"0\n 0 999999 1\nRead-only file system\n",
120957,
"expected uid_map",
},
{
"writable root bind",
"0\n 0 120957 1\n",
120957,
"did not reject a write",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Report{}
EvaluateSandbox(r, tt.output, tt.selfUID)
found := false
for _, c := range r.Checks {
if !c.OK && strings.Contains(c.Msg, tt.wantFail) {
found = true
}
}
if !found {
t.Errorf("expected a failing check containing %q, got %+v", tt.wantFail, r.Checks)
}
})
}
}
func TestParseDF(t *testing.T) {
output := "Filesystem 1024-blocks Used Available Capacity Mounted on\n" +
"/dev/mapper/vg0-home 959786032 447013936 463941300 50% /home\n"
device, avail, mount := ParseDF(output)
if device != "/dev/mapper/vg0-home" || avail != 463941300 || mount != "/home" {
t.Errorf("got %q %d %q", device, avail, mount)
}
if d, a, m := ParseDF("garbage"); d != "" || a != 0 || m != "" {
t.Errorf("expected empty result for garbage, got %q %d %q", d, a, m)
}
}
func TestParseQuotaPlainAndWrappedLines(t *testing.T) {
output := `Disk quotas for group g123456 (gid 123456):
Filesystem blocks quota limit grace files quota limit grace
/dev/vdb1 123456 900000 1000000 1234 0 0
/dev/mapper/very-long-device-name-that-wraps
654321* 4500000 5000000 4321 0 0
`
want := []QuotaLine{
{FS: "/dev/vdb1", Blocks: 123456, Limit: 1000000},
{FS: "/dev/mapper/very-long-device-name-that-wraps", Blocks: 654321, Limit: 5000000},
}
if got := ParseQuota(output); !reflect.DeepEqual(got, want) {
t.Errorf("got %+v\nwant %+v", got, want)
}
}
func TestParseQuotaIgnoresUnparsableOutput(t *testing.T) {
if got := ParseQuota("no quotas here\n"); len(got) != 0 {
t.Errorf("expected no lines, got %+v", got)
}
}
// fakeRunner serves canned outputs keyed by command name.
func fakeRunner(outputs map[string]string) Runner {
return func(name string, args ...string) (string, error) {
return outputs[name], nil
}
}
func TestDiskChecksFailOnQuotaHeadroomOfTheTargetFilesystem(t *testing.T) {
r := &Report{}
// 1 GiB quota headroom on the home device, plenty on another one.
outputs := map[string]string{
"df": "Filesystem 1024-blocks Used Available Capacity Mounted on\n" +
"/dev/vdb1 100000000 10000000 90000000 10% /home\n",
"quota": "Disk quotas for group g1 (gid 1):\n" +
" Filesystem blocks quota limit grace\n" +
"/dev/vdb1 4000000 5000000 5048576 - - -\n" +
"/dev/other 0 0 99999999 - - -\n",
}
diskChecks(r, "/home/user", fakeRunner(outputs))
if r.OK() {
t.Fatalf("expected the quota check to fail, got %+v", r.Checks)
}
failing := ""
for _, c := range r.Checks {
if !c.OK {
failing = c.Msg
}
}
if !strings.Contains(failing, "quota headroom below") || !strings.Contains(failing, "vdb1") {
t.Errorf("unexpected failure message: %s", failing)
}
}
func TestDiskChecksPassWithSpaceAndQuota(t *testing.T) {
r := &Report{}
outputs := map[string]string{
"df": "Filesystem 1024-blocks Used Available Capacity Mounted on\n" +
"/dev/vdb1 100000000 10000000 90000000 10% /home\n",
"quota": "Disk quotas for group g1 (gid 1):\n" +
" Filesystem blocks quota limit grace\n" +
"/dev/vdb1 1000000 90000000 99000000 - - -\n",
}
diskChecks(r, "/home/user", fakeRunner(outputs))
if !r.OK() {
t.Errorf("expected disk checks to pass, got %+v", r.Checks)
}
if len(r.Checks) != 2 {
t.Errorf("expected free-space and quota checks, got %+v", r.Checks)
}
}
func TestRenderEndsWithTheResultLine(t *testing.T) {
r := &Report{}
r.pass("all good")
r.warn("just saying")
var out strings.Builder
r.Render(&out)
rendered := out.String()
if !strings.Contains(rendered, "PASS: all good\n") ||
!strings.Contains(rendered, "WARNING: just saying\n") ||
!strings.Contains(rendered, "RESULT: PASS (1/1)") {
t.Errorf("unexpected rendering:\n%s", rendered)
}
}