Files
aego/vfs/vfs_test.go
sanspie dae4bc19e1 Stage 1: engine core, platform, gpu, vfs
Foundation for the aego engine.

core/    errs, log, handle arena, mathx, clock, stats
platform SDL3 backend plus a headless one, input state, events
gpu      device abstraction with gl33 and null backends
vfs      scheme-based virtual filesystem with overlay mounts
engine   builder, module registry, phase-ordered system loop, jobs
demos    basic, resize, input, headless; sandbox driver

OpenGL bindings are generated into gpu/gl33/gl by tools/gen-gl.sh,
so the module has no external dependencies.
2026-09-18 20:51:54 +03:00

122 lines
3.3 KiB
Go

package vfs
import (
"os"
"path/filepath"
"testing"
"aego/core/errs"
)
func write(t *testing.T, dir, name, content string) {
t.Helper()
full := filepath.Join(dir, filepath.FromSlash(name))
if err := os.MkdirAll(filepath.Dir(full), 0o755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(full, []byte(content), 0o644); err != nil {
t.Fatal(err)
}
}
func newVFS(t *testing.T, mounts ...Mount) *VFS {
t.Helper()
v, err := New(mounts...)
if err != nil {
t.Fatal(err)
}
return v
}
func TestSchemes(t *testing.T) {
res, user := t.TempDir(), t.TempDir()
write(t, res, "shaders/basic.vert", "source")
v := newVFS(t,
Mount{Scheme: "res", Root: res, ReadOnly: true},
Mount{Scheme: "user", Root: user},
)
data, err := v.ReadFile("res://shaders/basic.vert")
if err != nil || string(data) != "source" {
t.Fatalf("read res: %v %q", err, data)
}
if !v.Exists("res://shaders/basic.vert") || v.Exists("res://shaders/missing.vert") {
t.Fatal("exists wrong")
}
if err := v.WriteFile("user://saves/slot1.bin", []byte{1, 2, 3}); err != nil {
t.Fatalf("write user: %v", err)
}
got, err := v.ReadFile("user://saves/slot1.bin")
if err != nil || len(got) != 3 {
t.Fatalf("read user: %v %v", err, got)
}
}
func TestReadOnlyAndErrors(t *testing.T) {
res := t.TempDir()
v := newVFS(t, Mount{Scheme: "res", Root: res, ReadOnly: true})
if err := v.WriteFile("res://x.txt", nil); !errs.Has(err, errs.VFSReadOnly) {
t.Fatalf("readonly: %v", err)
}
if _, err := v.ReadFile("res://missing"); !errs.Has(err, errs.VFSNotFound) {
t.Fatalf("not found: %v", err)
}
if _, err := v.ReadFile("nope://x"); !errs.Has(err, errs.VFSUnknownScheme) {
t.Fatalf("scheme: %v", err)
}
for _, bad := range []string{"res:/x", "x.txt", "res://", "res://a\\b"} {
if _, err := v.ReadFile(bad); err == nil {
t.Fatalf("%q accepted", bad)
}
}
}
func TestEscapeIsClamped(t *testing.T) {
_, rel, err := Split("res://../../etc/passwd")
if err != nil {
t.Fatal(err)
}
if rel != "etc/passwd" {
t.Fatalf("rel = %q", rel)
}
_, rel, _ = Split("res://a/./b/../c.txt")
if rel != "a/c.txt" {
t.Fatalf("rel = %q", rel)
}
}
func TestOverlayOrder(t *testing.T) {
base, over := t.TempDir(), t.TempDir()
write(t, base, "a.txt", "base")
write(t, base, "b.txt", "base-b")
write(t, over, "a.txt", "over")
v := newVFS(t,
Mount{Scheme: "res", Root: base, ReadOnly: true},
Mount{Scheme: "res", Root: over, ReadOnly: true},
)
if a, _ := v.ReadFile("res://a.txt"); string(a) != "over" {
t.Fatalf("a = %q", a)
}
if b, _ := v.ReadFile("res://b.txt"); string(b) != "base-b" {
t.Fatalf("b = %q", b)
}
}
func TestExclusiveSchemeRejectsSecondMount(t *testing.T) {
v := newVFS(t, Mount{Scheme: "user", Root: t.TempDir()})
if err := v.Mount(Mount{Scheme: "user", Root: t.TempDir()}); !errs.Has(err, errs.VFSMount) {
t.Fatalf("second user mount: %v", err)
}
if err := v.Mount(Mount{Scheme: "engine", Root: t.TempDir()}); err != nil {
t.Fatalf("first engine mount: %v", err)
}
if err := v.Mount(Mount{Scheme: "engine", Root: t.TempDir()}); err != nil {
t.Fatalf("engine is an overlay scheme: %v", err)
}
if err := v.Mount(Mount{Root: t.TempDir()}); !errs.Has(err, errs.VFSMount) {
t.Fatalf("empty scheme: %v", err)
}
if _, err := New(Mount{Scheme: "user"}, Mount{Scheme: "user"}); !errs.Has(err, errs.VFSMount) {
t.Fatalf("New must surface the mount error: %v", err)
}
}