fix: update tests to match current API after refactor
- node: add ReadFile (fs.ReadFileFS), Walk with WalkOptions, CopyFile - node_test: fix Exists to single-return bool, FromTar as method call - cache_test: remove Medium parameter, use t.TempDir() - daemon_test: remove Medium from NewPIDFile/DaemonOptions, use os pkg Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
parent
9b7a0bc30a
commit
8172824b42
4 changed files with 132 additions and 53 deletions
13
pkg/cache/cache_test.go
vendored
13
pkg/cache/cache_test.go
vendored
|
|
@ -5,14 +5,11 @@ import (
|
|||
"time"
|
||||
|
||||
"forge.lthn.ai/core/go/pkg/cache"
|
||||
"forge.lthn.ai/core/go/pkg/io"
|
||||
)
|
||||
|
||||
func TestCache(t *testing.T) {
|
||||
m := io.NewMockMedium()
|
||||
// Use a path that MockMedium will understand
|
||||
baseDir := "/tmp/cache"
|
||||
c, err := cache.New(m, baseDir, 1*time.Minute)
|
||||
baseDir := t.TempDir()
|
||||
c, err := cache.New(baseDir, 1*time.Minute)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create cache: %v", err)
|
||||
}
|
||||
|
|
@ -57,7 +54,7 @@ func TestCache(t *testing.T) {
|
|||
}
|
||||
|
||||
// Test Expiry
|
||||
cshort, err := cache.New(m, "/tmp/cache-short", 10*time.Millisecond)
|
||||
cshort, err := cache.New(t.TempDir(), 10*time.Millisecond)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create short-lived cache: %v", err)
|
||||
}
|
||||
|
|
@ -93,8 +90,8 @@ func TestCache(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestCacheDefaults(t *testing.T) {
|
||||
// Test default Medium (io.Local) and default TTL
|
||||
c, err := cache.New(nil, "", 0)
|
||||
// Test default TTL (uses cwd/.core/cache)
|
||||
c, err := cache.New("", 0)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create cache with defaults: %v", err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,10 +3,10 @@ package cli
|
|||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"forge.lthn.ai/core/go/pkg/io"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
|
@ -27,17 +27,16 @@ func TestDetectMode(t *testing.T) {
|
|||
|
||||
func TestPIDFile(t *testing.T) {
|
||||
t.Run("acquire and release", func(t *testing.T) {
|
||||
m := io.NewMockMedium()
|
||||
pidPath := "/tmp/test.pid"
|
||||
pidPath := t.TempDir() + "/test.pid"
|
||||
|
||||
pid := NewPIDFile(m, pidPath)
|
||||
pid := NewPIDFile(pidPath)
|
||||
|
||||
// Acquire should succeed
|
||||
err := pid.Acquire()
|
||||
require.NoError(t, err)
|
||||
|
||||
// File should exist with our PID
|
||||
data, err := m.Read(pidPath)
|
||||
data, err := os.ReadFile(pidPath)
|
||||
require.NoError(t, err)
|
||||
assert.NotEmpty(t, data)
|
||||
|
||||
|
|
@ -45,18 +44,18 @@ func TestPIDFile(t *testing.T) {
|
|||
err = pid.Release()
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.False(t, m.Exists(pidPath))
|
||||
_, statErr := os.Stat(pidPath)
|
||||
assert.True(t, os.IsNotExist(statErr))
|
||||
})
|
||||
|
||||
t.Run("stale pid file", func(t *testing.T) {
|
||||
m := io.NewMockMedium()
|
||||
pidPath := "/tmp/stale.pid"
|
||||
pidPath := t.TempDir() + "/stale.pid"
|
||||
|
||||
// Write a stale PID (non-existent process)
|
||||
err := m.Write(pidPath, "999999999")
|
||||
err := os.WriteFile(pidPath, []byte("999999999"), 0644)
|
||||
require.NoError(t, err)
|
||||
|
||||
pid := NewPIDFile(m, pidPath)
|
||||
pid := NewPIDFile(pidPath)
|
||||
|
||||
// Should acquire successfully (stale PID removed)
|
||||
err = pid.Acquire()
|
||||
|
|
@ -67,23 +66,22 @@ func TestPIDFile(t *testing.T) {
|
|||
})
|
||||
|
||||
t.Run("creates parent directory", func(t *testing.T) {
|
||||
m := io.NewMockMedium()
|
||||
pidPath := "/tmp/subdir/nested/test.pid"
|
||||
pidPath := t.TempDir() + "/subdir/nested/test.pid"
|
||||
|
||||
pid := NewPIDFile(m, pidPath)
|
||||
pid := NewPIDFile(pidPath)
|
||||
|
||||
err := pid.Acquire()
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.True(t, m.Exists(pidPath))
|
||||
_, statErr := os.Stat(pidPath)
|
||||
assert.NoError(t, statErr)
|
||||
|
||||
err = pid.Release()
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("path getter", func(t *testing.T) {
|
||||
m := io.NewMockMedium()
|
||||
pid := NewPIDFile(m, "/tmp/test.pid")
|
||||
pid := NewPIDFile("/tmp/test.pid")
|
||||
assert.Equal(t, "/tmp/test.pid", pid.Path())
|
||||
})
|
||||
}
|
||||
|
|
@ -155,11 +153,9 @@ func TestHealthServer(t *testing.T) {
|
|||
|
||||
func TestDaemon(t *testing.T) {
|
||||
t.Run("start and stop", func(t *testing.T) {
|
||||
m := io.NewMockMedium()
|
||||
pidPath := "/tmp/test.pid"
|
||||
pidPath := t.TempDir() + "/test.pid"
|
||||
|
||||
d := NewDaemon(DaemonOptions{
|
||||
Medium: m,
|
||||
PIDFile: pidPath,
|
||||
HealthAddr: "127.0.0.1:0",
|
||||
ShutdownTimeout: 5 * time.Second,
|
||||
|
|
@ -182,7 +178,8 @@ func TestDaemon(t *testing.T) {
|
|||
require.NoError(t, err)
|
||||
|
||||
// PID file should be removed
|
||||
assert.False(t, m.Exists(pidPath))
|
||||
_, statErr := os.Stat(pidPath)
|
||||
assert.True(t, os.IsNotExist(statErr))
|
||||
})
|
||||
|
||||
t.Run("double start fails", func(t *testing.T) {
|
||||
|
|
|
|||
|
|
@ -118,6 +118,89 @@ func (n *Node) WalkNode(root string, fn fs.WalkDirFunc) error {
|
|||
return fs.WalkDir(n, root, fn)
|
||||
}
|
||||
|
||||
// WalkOptions configures optional behaviour for Walk.
|
||||
type WalkOptions struct {
|
||||
// MaxDepth limits traversal depth (0 = unlimited, 1 = root children only).
|
||||
MaxDepth int
|
||||
// Filter, when non-nil, is called before visiting each entry.
|
||||
// Return false to skip the entry (and its subtree if a directory).
|
||||
Filter func(path string, d fs.DirEntry) bool
|
||||
// SkipErrors suppresses errors from the root lookup and doesn't call fn.
|
||||
SkipErrors bool
|
||||
}
|
||||
|
||||
// Walk walks the in-memory tree with optional WalkOptions.
|
||||
func (n *Node) Walk(root string, fn fs.WalkDirFunc, opts ...WalkOptions) error {
|
||||
var opt WalkOptions
|
||||
if len(opts) > 0 {
|
||||
opt = opts[0]
|
||||
}
|
||||
|
||||
if opt.SkipErrors {
|
||||
// Check root exists — if not, silently skip.
|
||||
if _, err := n.Stat(root); err != nil {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
rootDepth := 0
|
||||
if root != "." && root != "" {
|
||||
rootDepth = strings.Count(root, "/") + 1
|
||||
}
|
||||
|
||||
return fs.WalkDir(n, root, func(p string, d fs.DirEntry, err error) error {
|
||||
if err != nil {
|
||||
return fn(p, d, err)
|
||||
}
|
||||
|
||||
// MaxDepth check.
|
||||
if opt.MaxDepth > 0 {
|
||||
depth := 0
|
||||
if p != "." && p != "" {
|
||||
depth = strings.Count(p, "/") + 1
|
||||
}
|
||||
if depth-rootDepth > opt.MaxDepth {
|
||||
if d.IsDir() {
|
||||
return fs.SkipDir
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Filter check.
|
||||
if opt.Filter != nil && !opt.Filter(p, d) {
|
||||
if d.IsDir() {
|
||||
return fs.SkipDir
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
return fn(p, d, err)
|
||||
})
|
||||
}
|
||||
|
||||
// CopyFile copies a single file from the node to the OS filesystem.
|
||||
func (n *Node) CopyFile(src, dst string, perm os.FileMode) error {
|
||||
src = strings.TrimPrefix(src, "/")
|
||||
f, ok := n.files[src]
|
||||
if !ok {
|
||||
// Check if it's a directory — can't copy a directory as a file.
|
||||
if info, err := n.Stat(src); err == nil && info.IsDir() {
|
||||
return &fs.PathError{Op: "copyfile", Path: src, Err: fs.ErrInvalid}
|
||||
}
|
||||
return &fs.PathError{Op: "copyfile", Path: src, Err: fs.ErrNotExist}
|
||||
}
|
||||
|
||||
dir := path.Dir(dst)
|
||||
if dir != "." {
|
||||
if err := os.MkdirAll(dir, 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return os.WriteFile(dst, f.content, perm)
|
||||
}
|
||||
|
||||
// CopyTo copies a file (or directory tree) from the node to any Medium.
|
||||
func (n *Node) CopyTo(target coreio.Medium, sourcePath, destPath string) error {
|
||||
sourcePath = strings.TrimPrefix(sourcePath, "/")
|
||||
|
|
@ -247,6 +330,20 @@ func (n *Node) ReadDir(name string) ([]fs.DirEntry, error) {
|
|||
return entries, nil
|
||||
}
|
||||
|
||||
// ReadFile returns the content of a file as a byte slice.
|
||||
// Implements fs.ReadFileFS.
|
||||
func (n *Node) ReadFile(name string) ([]byte, error) {
|
||||
name = strings.TrimPrefix(name, "/")
|
||||
f, ok := n.files[name]
|
||||
if !ok {
|
||||
return nil, fs.ErrNotExist
|
||||
}
|
||||
// Return a copy to prevent mutation of internal state.
|
||||
out := make([]byte, len(f.content))
|
||||
copy(out, f.content)
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// ---------- Medium interface: read/write ----------
|
||||
|
||||
// Read retrieves the content of a file as a string.
|
||||
|
|
|
|||
|
|
@ -243,33 +243,21 @@ func TestExists_Good(t *testing.T) {
|
|||
n.AddData("foo.txt", []byte("foo"))
|
||||
n.AddData("bar/baz.txt", []byte("baz"))
|
||||
|
||||
exists, err := n.Exists("foo.txt")
|
||||
require.NoError(t, err)
|
||||
assert.True(t, exists)
|
||||
|
||||
exists, err = n.Exists("bar")
|
||||
require.NoError(t, err)
|
||||
assert.True(t, exists)
|
||||
assert.True(t, n.Exists("foo.txt"))
|
||||
assert.True(t, n.Exists("bar"))
|
||||
}
|
||||
|
||||
func TestExists_Bad(t *testing.T) {
|
||||
n := New()
|
||||
exists, err := n.Exists("nonexistent")
|
||||
require.NoError(t, err)
|
||||
assert.False(t, exists)
|
||||
assert.False(t, n.Exists("nonexistent"))
|
||||
}
|
||||
|
||||
func TestExists_Ugly(t *testing.T) {
|
||||
n := New()
|
||||
n.AddData("dummy.txt", []byte("dummy"))
|
||||
|
||||
exists, err := n.Exists(".")
|
||||
require.NoError(t, err)
|
||||
assert.True(t, exists, "root '.' must exist")
|
||||
|
||||
exists, err = n.Exists("")
|
||||
require.NoError(t, err)
|
||||
assert.True(t, exists, "empty path (root) must exist")
|
||||
assert.True(t, n.Exists("."), "root '.' must exist")
|
||||
assert.True(t, n.Exists(""), "empty path (root) must exist")
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
|
@ -463,20 +451,19 @@ func TestFromTar_Good(t *testing.T) {
|
|||
}
|
||||
require.NoError(t, tw.Close())
|
||||
|
||||
n, err := FromTar(buf.Bytes())
|
||||
n := New()
|
||||
err := n.FromTar(buf.Bytes())
|
||||
require.NoError(t, err)
|
||||
|
||||
exists, _ := n.Exists("foo.txt")
|
||||
assert.True(t, exists, "foo.txt should exist")
|
||||
|
||||
exists, _ = n.Exists("bar/baz.txt")
|
||||
assert.True(t, exists, "bar/baz.txt should exist")
|
||||
assert.True(t, n.Exists("foo.txt"), "foo.txt should exist")
|
||||
assert.True(t, n.Exists("bar/baz.txt"), "bar/baz.txt should exist")
|
||||
}
|
||||
|
||||
func TestFromTar_Bad(t *testing.T) {
|
||||
// Truncated data that cannot be a valid tar.
|
||||
truncated := make([]byte, 100)
|
||||
_, err := FromTar(truncated)
|
||||
n := New()
|
||||
err := n.FromTar(truncated)
|
||||
assert.Error(t, err, "truncated data should produce an error")
|
||||
}
|
||||
|
||||
|
|
@ -488,7 +475,8 @@ func TestTarRoundTrip_Good(t *testing.T) {
|
|||
tarball, err := n1.ToTar()
|
||||
require.NoError(t, err)
|
||||
|
||||
n2, err := FromTar(tarball)
|
||||
n2 := New()
|
||||
err = n2.FromTar(tarball)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Verify n2 matches n1.
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue