Files
vfs/cache_test.go
Hanzo DevandGitHub 6c3cfe9751 feat: cloud Mount() per HIP-0106 (#1)
* feat: cloud Mount() per HIP-0106

Adds pkg/vfs.Mount(*zip.App, cloud.Deps) error so the unified Hanzo
Cloud binary can blank-import and register VFS alongside every
other Hanzo subsystem.

VFS has no public HTTP surface today — it's an S3-backed virtual
block filesystem driven through FUSE/CLI/sidecar. Mount() exposes
liveness/readiness probes plus a minimal block-CRUD HTTP API so
co-resident subsystems can talk to VFS without falling back to the
CLI:

- GET    /v1/vfs/health       — always 200
- GET    /v1/vfs/readyz       — 200 only when an instance is
                                attached via vfs.SetInstance
- PUT    /v1/vfs/blocks       — payload upload
- GET    /v1/vfs/blocks/:id   — payload download
- DELETE /v1/vfs/blocks/:id   — backend delete
- GET    /v1/vfs/stats        — cache/backend counters

cmd/vfsd is a HIP-0106 thin shim that wires backend + age keys from
env (VFSD_BACKEND, VFSD_AGE_KEY) and listens on cloud.Config.ListenAddr.
The existing cmd/vfs CLI (put/get/stats/mount) is untouched.

init() registers with cloud.Register("vfs", 20, …). schema/vfs.zap
adds the minimal Health + Block ZAP interface; full multi-block
File surface lands in follow-up PRs.

Tests:
- pkg/vfs: TestMount_HealthReadyz + TestMount_PutGetRoundtrip green
- existing TestRoundTrip + TestStats unchanged

* refactor: collapse pkg/vfs/ → root for clean import path

Subsystem code lives in package vfs at repo root. Importers now use
`github.com/hanzoai/vfs` (no /pkg/vfs/ nesting). Standalone shim moved
to cmd/vfs/ where applicable.

Per Hanzo Go-stdlib pattern: repo IS the package.

* refactor: flatten import path — github.com/hanzoai/cloud (drop /pkg/cloud)
2026-05-18 23:37:45 -07:00

61 lines
1.5 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package vfs
import (
"strings"
"testing"
)
func TestCacheBasic(t *testing.T) {
c := NewCache(0) // unbounded
id1 := BlockID(strings.Repeat("a", 64))
c.Put(id1, []byte("hello"))
v, ok := c.Get(id1)
if !ok || string(v) != "hello" {
t.Fatalf("get hit: ok=%v val=%q", ok, v)
}
c.Delete(id1)
if _, ok := c.Get(id1); ok {
t.Fatal("delete should remove")
}
}
func TestCacheLRUEviction(t *testing.T) {
// Cap at 100 bytes; 4 entries × 50 bytes each → first two evict.
c := NewCache(100)
for i := 0; i < 4; i++ {
id := BlockID(strings.Repeat(string(rune('a'+i)), 64))
c.Put(id, make([]byte, 50))
}
entries, bytesIn, _ := c.Stats()
if entries > 2 {
t.Fatalf("expected ≤2 entries after eviction, got %d", entries)
}
if bytesIn > 100 {
t.Fatalf("expected ≤100 bytes after eviction, got %d", bytesIn)
}
// Oldest should be gone
if _, ok := c.Get(BlockID(strings.Repeat("a", 64))); ok {
t.Fatal("oldest entry should have been evicted")
}
}
func TestCacheTouchPromotes(t *testing.T) {
c := NewCache(100)
idA := BlockID(strings.Repeat("a", 64))
idB := BlockID(strings.Repeat("b", 64))
idC := BlockID(strings.Repeat("c", 64))
c.Put(idA, make([]byte, 50))
c.Put(idB, make([]byte, 50))
// touch A so it's most-recently-used
if _, ok := c.Get(idA); !ok {
t.Fatal("A missing pre-promote")
}
c.Put(idC, make([]byte, 50)) // should evict B, not A
if _, ok := c.Get(idA); !ok {
t.Fatal("A should survive (was touched)")
}
if _, ok := c.Get(idB); ok {
t.Fatal("B should have been evicted")
}
}