Files
magpie/internal/library/carry_binary_test.go
yetone e04824714d library: a skill's templates and other big files go with a backup and a sync (Crispin)
Crispin on Discord (#help): a skill's template files, a PPT among them,
didn't reach the other computer. Collect read each skill file with a 2 MB
cap and left everything bigger out, so a .pptx template with its pictures
never travelled. Links, copies and WSL copies on one computer already carry
every file (copyDir and hashFiles have no filter); only the backup/sync
carry dropped them.

- A file goes up to 16 MiB, all skills together up to 32 MiB. A sealed
  backup is JSON with the bytes base64'd twice (~x16/9), and a sync reads
  at most 64 MiB of one, so 32 MiB raw is what still fits.
- readSkills fills that budget smallest file first across every skill, so
  one skill's big file never keeps another skill's SKILL.md from coming.
- What is left out (Left) and the computer being put to already has is
  kept (keepLeft): a Put no longer sets the folder aside without it. A Left
  name is checked like a carried one.

Run:
- New tests fail on the old code: TestCarryTemplates (template left),
  TestCarrySmallFirst (zzz's notes.md left), TestCarryKeepsWhatWasLeft
  (the local file went), TestSkillTemplatesTravel (0 bytes of 6291456).
  Pass on the new. TestSkillTemplateReachesEveryAgent covers link, copy
  and WSL copies of a pptx.
- Break check: maxCarriedAll = 48 MiB makes TestSkillTemplatesTravel fail
  ("the backup is 64 MiB, more than a sync reads").
- Real use: built nogui binary, sandbox HOMEs, a real 2.8 MB and a real
  14 MB .pptx in a library skill, `magpie backup` then `restore
  --no-agents` on another HOME: sha256 identical both times.
- go test -tags nogui ./... (temp HOME, pinned caches): all ok.
- go vet (darwin, GOOS=linux, GOOS=windows) on internal/library and
  internal/backup; gofmt -l clean.
2026-10-10 14:52:17 +08:00

179 lines
6.9 KiB
Go

package library
import (
"archive/zip"
"bytes"
"encoding/json"
"math/rand/v2"
"os"
"path/filepath"
"slices"
"strings"
"testing"
)
// pptx is a PowerPoint file as PowerPoint saves one: a zip of the
// presentation's XML parts and its media, which are already compressed
// (the pictures in a template), so the file is about as big as its media.
func pptx(t *testing.T, media int) []byte {
t.Helper()
var buf bytes.Buffer
z := zip.NewWriter(&buf)
part := func(name string, data []byte, method uint16) {
w, err := z.CreateHeader(&zip.FileHeader{Name: name, Method: method})
if err != nil {
t.Fatal(err)
}
w.Write(data)
}
part("[Content_Types].xml", []byte(`<?xml version="1.0" encoding="UTF-8" standalone="yes"?><Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types"><Default Extension="png" ContentType="image/png"/><Override PartName="/ppt/presentation.xml" ContentType="application/vnd.openxmlformats-officedocument.presentationml.presentation.main+xml"/></Types>`), zip.Deflate)
part("ppt/presentation.xml", []byte(`<?xml version="1.0" encoding="UTF-8" standalone="yes"?><p:presentation xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main"/>`), zip.Deflate)
img := make([]byte, media)
r := rand.New(rand.NewPCG(1, 2))
for i := range img {
img[i] = byte(r.Uint32())
}
part("ppt/media/image1.png", img, zip.Store)
if err := z.Close(); err != nil {
t.Fatal(err)
}
return buf.Bytes()
}
// Crispin on Discord (Skills内的template文件可以都被同步吗?好像发现无法同步PPT
// 文件诶): a skill's PowerPoint template didn't reach the other computer.
// A backup and a sync left out every file over 2 MB, and a template with
// its pictures is more than that. A skill's template comes with it, bytes
// and all, and the text of every skill still goes before what is big.
func TestCarryTemplates(t *testing.T) {
h := sandbox(t)
setUpLibrary(t, h)
deck := pptx(t, 6<<20)
write(t, filepath.Join(h, "src/pdf/templates/report.pptx"), string(deck))
b, err := Collect()
if err != nil {
t.Fatal(err)
}
s := b.Skills[0]
if !bytes.Equal(s.Files["templates/report.pptx"], deck) || len(s.Left) != 0 {
t.Fatalf("the template wasn't carried: %v, left %v", keysOf(s.Files), s.Left)
}
raw, err := json.Marshal(b)
if err != nil {
t.Fatal(err)
}
// another computer, which hasn't the folder the skill came from
os.RemoveAll(filepath.Join(h, "src"))
h2 := sandbox(t)
var in Bundle
json.Unmarshal(raw, &in)
ok(t)(Put(&in))
for _, p := range []string{filepath.Join(SkillPath("pdf"), "templates/report.pptx"), filepath.Join(h2, ".claude/skills/pdf/templates/report.pptx")} {
if got, err := os.ReadFile(p); err != nil || !bytes.Equal(got, deck) {
t.Errorf("%s: %d bytes of %d, %v", p, len(got), len(deck), err)
}
}
}
// What can't go with a backup is what is bigger than all of it may be. The
// small files go first, whichever skill has them, so one skill's big file
// never keeps another's SKILL.md from coming.
func TestCarrySmallFirst(t *testing.T) {
h := sandbox(t)
skill(t, filepath.Join(h, "src/aaa"), "aaa", "First by name")
skill(t, filepath.Join(h, "src/zzz"), "zzz", "Last by name")
write(t, filepath.Join(h, "src/aaa/huge.bin"), strings.Repeat("x", maxCarried+1))
write(t, filepath.Join(h, "src/zzz/notes.md"), strings.Repeat("n", 1024))
// the big ones come to all a backup may have, less a few bytes
for i := range maxCarriedAll / maxCarried {
write(t, filepath.Join(h, "src/aaa/big"+string(rune('a'+i))+".pptx"), strings.Repeat("y", maxCarried-8))
}
ok(t)(InstallSkills(filepath.Join(h, "src"), []string{"aaa", "zzz"}, []string{"claude"}))
b, err := Collect()
if err != nil {
t.Fatal(err)
}
aaa, zzz := b.Skills[0], b.Skills[1]
if _, ok := zzz.Files["notes.md"]; !ok || len(zzz.Left) != 0 {
t.Fatalf("zzz: %v, left %v", keysOf(zzz.Files), zzz.Left)
}
if _, ok := aaa.Files["SKILL.md"]; !ok || !slices.Contains(aaa.Left, "huge.bin") || len(aaa.Left) != 2 {
t.Fatalf("aaa: %v, left %v", keysOf(aaa.Files), aaa.Left)
}
total := 0
for _, s := range b.Skills {
for _, f := range s.Files {
total += len(f)
}
}
if total > maxCarriedAll {
t.Fatalf("carried %d bytes, over %d", total, maxCarriedAll)
}
}
// A file the other computer couldn't carry is one it has: when this
// computer has that file in the skill too, putting the skill keeps it
// rather than setting the folder aside without it.
func TestCarryKeepsWhatWasLeft(t *testing.T) {
h := sandbox(t)
setUpLibrary(t, h)
write(t, filepath.Join(h, "src/pdf/huge.pptx"), strings.Repeat("x", maxCarried+1))
b, err := Collect()
if err != nil {
t.Fatal(err)
}
if !slices.Equal(b.Skills[0].Left, []string{"huge.pptx"}) {
t.Fatalf("left %v", b.Skills[0].Left)
}
// the same library on the other computer, its skill since edited there
raw, _ := json.Marshal(b)
b.Skills[0].Files["SKILL.md"] = append(b.Skills[0].Files["SKILL.md"], "\nEdited elsewhere.\n"...)
edited, _ := json.Marshal(b)
os.RemoveAll(filepath.Join(h, "src"))
h2 := sandbox(t)
var in Bundle
json.Unmarshal(raw, &in)
ok(t)(Put(&in))
here := filepath.Join(SkillPath("pdf"), "huge.pptx")
write(t, here, strings.Repeat("x", maxCarried+1))
var again Bundle
json.Unmarshal(edited, &again)
ok(t)(Put(&again))
if s := read(t, filepath.Join(SkillPath("pdf"), "SKILL.md")); !strings.Contains(s, "Edited elsewhere.") {
t.Fatalf("SKILL.md: %q", s)
}
if fi, err := os.Stat(here); err != nil || fi.Size() != maxCarried+1 {
t.Fatalf("the file this computer had went: %v %v", fi, err)
}
if fi, err := os.Stat(filepath.Join(h2, ".claude/skills/pdf/huge.pptx")); err != nil || fi.Size() != maxCarried+1 {
t.Fatalf("claude's copy: %v %v", fi, err)
}
// a left file's name is checked as the files' are
bad := &Bundle{Skills: []*CarriedSkill{{Name: "x", Files: map[string][]byte{"SKILL.md": nil}, Left: []string{"../../../etc/passwd"}}}}
if _, err := Put(bad); err == nil {
t.Fatal("put a bundle whose left file climbs out")
}
}
// The same template on this computer: every agent gets it whole, the one
// linked to the library, one given copies, and agents in a WSL distro,
// which always get copies.
func TestSkillTemplateReachesEveryAgent(t *testing.T) {
h := wslSandbox(t)
src := filepath.Join(home(), "src", "skills")
skill(t, filepath.Join(src, "pdf"), "pdf", "Read PDFs")
deck := pptx(t, 6<<20)
write(t, filepath.Join(src, "pdf/templates/report.pptx"), string(deck))
ok(t)(SetSkillHow("codex", HowCopy))
ok(t)(InstallSkills(src, []string{"pdf"}, []string{"claude", "codex", wslCodex, wslClaude, wslPi}))
for _, p := range []string{
filepath.Join(home(), ".claude/skills/pdf"), filepath.Join(home(), ".codex/skills/pdf"),
filepath.Join(h, ".codex/skills/pdf"), filepath.Join(h, ".claude/skills/pdf"), filepath.Join(h, ".pi/agent/skills/pdf"),
} {
if got, err := os.ReadFile(filepath.Join(p, "templates/report.pptx")); err != nil || !bytes.Equal(got, deck) {
t.Errorf("%s: %d bytes of %d, %v", p, len(got), len(deck), err)
}
}
}