Files
llmgw/internal/agent/removeweights_test.go
T
Ilker ManapandClaude Opus 5.5 cba4897987
CI / test (push) Failing after 6m44s
Deleting unused model files says what went, and sets go whole
A sweep counted every file the nodes were asked to delete as freed as soon
as it asked, and a node refused a set's directory as not empty, so tens of
gigabytes were reported freed and nothing was. The agent removes a set's
directory whole (a link only as a link), and the control plane waits for
each node to list its files again, saying what it no longer lists and what
it kept.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 10:29:16 +01:00

129 lines
4.1 KiB
Go

package agent
import (
"log/slog"
"os"
"path/filepath"
"testing"
"gitea.mynodes.xyz/ilker/llmgw/internal/agent/weights"
)
// agentWithCache is an agent that only knows where its weights live, which is
// all removeWeights needs.
func agentWithCache(t *testing.T, dir string) *Agent {
t.Helper()
return &Agent{
cfg: Config{ModelDirs: []string{dir}},
log: slog.New(slog.DiscardHandler),
}
}
func write(t *testing.T, path string) string {
t.Helper()
if err := os.WriteFile(path, []byte("weights"), 0o644); err != nil {
t.Fatal(err)
}
return path
}
func TestTheAgentRefusesToDeleteWhatItDidNotFetch(t *testing.T) {
// The control plane computes these paths, but the node holds the files. A
// control plane that is wrong, newer than this agent, or tampered with must
// not be able to talk it into deleting a model an operator spent a week
// copying here. So the agent checks again, on its own.
dir := t.TempDir()
a := agentWithCache(t, dir)
operators := write(t, filepath.Join(dir, "qwen2.5-coder-14b-instruct-q8_0.gguf"))
fetched := write(t, weights.CachePathFor(dir, "https://models.internal/gone.gguf"))
a.removeWeights([]string{operators, fetched})
if _, err := os.Stat(operators); err != nil {
t.Errorf("a file llmgw never fetched was deleted: %v", err)
}
if _, err := os.Stat(fetched); !os.IsNotExist(err) {
t.Errorf("the fetched file was not removed: %v", err)
}
}
func TestTheAgentRefusesPathsOutsideItsCache(t *testing.T) {
// Even with a name that looks like one of ours. The cache directory is the
// boundary, and traversal out of it is the attack this closes.
dir := t.TempDir()
elsewhere := t.TempDir()
a := agentWithCache(t, dir)
// A correctly named cache file, in the wrong directory.
outside := write(t, weights.CachePathFor(elsewhere, "https://models.internal/x.gguf"))
// And the same thing reached by traversal from inside the cache.
traversal := filepath.Join(dir, "..", filepath.Base(elsewhere),
filepath.Base(outside))
a.removeWeights([]string{outside, traversal})
if _, err := os.Stat(outside); err != nil {
t.Errorf("a file outside the cache was deleted: %v", err)
}
}
func TestRemovingAFileThatIsAlreadyGoneIsNotAFailure(t *testing.T) {
// The expected outcome of a retry. It must not stop the rest of the batch.
dir := t.TempDir()
a := agentWithCache(t, dir)
missing := weights.CachePathFor(dir, "https://models.internal/never.gguf")
present := write(t, weights.CachePathFor(dir, "https://models.internal/here.gguf"))
a.removeWeights([]string{missing, present})
if _, err := os.Stat(present); !os.IsNotExist(err) {
t.Errorf("a missing file stopped the batch: %v", err)
}
}
func TestWithNoCacheConfiguredNothingIsDeleted(t *testing.T) {
dir := t.TempDir()
a := &Agent{cfg: Config{}, log: slog.New(slog.DiscardHandler)}
kept := write(t, weights.CachePathFor(dir, "https://models.internal/x.gguf"))
a.removeWeights([]string{kept})
if _, err := os.Stat(kept); err != nil {
t.Errorf("an agent with no cache directory deleted something: %v", err)
}
}
// A model of several files is fetched into a directory of its own, and an
// unused one goes whole: it was refused as not empty, and nothing was freed.
func TestAnUnusedSetIsRemovedWhole(t *testing.T) {
dir := t.TempDir()
a := agentWithCache(t, dir)
set := weights.CachePathFor(dir, "https://mirror/coder/v1")
if err := os.MkdirAll(set, 0o755); err != nil {
t.Fatal(err)
}
write(t, filepath.Join(set, "model-00001-of-00002.gguf"))
write(t, filepath.Join(set, ".complete"))
// A link in the cache is taken away, not what it points at.
target := t.TempDir()
write(t, filepath.Join(target, "keep.gguf"))
link := weights.CachePathFor(dir, "https://mirror/linked/v1")
if err := os.Symlink(target, link); err != nil {
t.Fatal(err)
}
a.removeWeights([]string{set, link})
if _, err := os.Stat(set); !os.IsNotExist(err) {
t.Errorf("the set is still there: %v", err)
}
if _, err := os.Lstat(link); !os.IsNotExist(err) {
t.Errorf("the link is still there: %v", err)
}
if _, err := os.Stat(filepath.Join(target, "keep.gguf")); err != nil {
t.Errorf("what the link pointed at was deleted: %v", err)
}
}