package release import ( "bytes" "context" "errors" "io" "os" "runtime" "strings" "testing" ) func TestDirStorePutFromMatchesPutSemantics(t *testing.T) { ctx := context.Background() d := NewDirStore(t.TempDir()) var _ PutStreamer = d etag, err := d.PutFrom(ctx, "a/b.bin", strings.NewReader("hello"), 5, "") if err != nil { t.Fatal(err) } got, getEtag, err := d.Get(ctx, "a/b.bin") if err != nil || string(got) != "hello" || getEtag != etag { t.Fatalf("Get = (%q, %q, %v), want hello with etag %q", got, getEtag, err, etag) } if _, err := d.PutFrom(ctx, "a/b.bin", strings.NewReader("again"), 5, ""); !errors.Is(err, ErrCASConflict) { t.Fatalf("create-only over an existing key = %v, want ErrCASConflict", err) } if _, err := d.PutFrom(ctx, "a/b.bin", strings.NewReader("next!"), 5, etag); err != nil { t.Fatalf("replace with the right etag: %v", err) } if _, err := d.PutFrom(ctx, "a/b.bin", strings.NewReader("short"), 9, ""); err == nil { t.Fatal("a body shorter than size must fail") } } func TestPrefixStorePutFromRebases(t *testing.T) { ctx := context.Background() d := NewDirStore(t.TempDir()) p := NewPrefixStore(d, "fleets/x/") ps, ok := p.(PutStreamer) if !ok { t.Fatal("prefixStore must forward PutFrom (codex R1-8)") } if _, err := ps.PutFrom(ctx, "k", strings.NewReader("v"), 1, ""); err != nil { t.Fatal(err) } if b, _, err := d.Get(ctx, "fleets/x/k"); err != nil || string(b) != "v" { t.Fatalf("rebased object = (%q, %v)", b, err) } } type putOnlyStore struct{ Store } func TestPutObjectFromFallsBackOnlyForSmallBodies(t *testing.T) { ctx := context.Background() s := putOnlyStore{NewDirStore(t.TempDir())} if _, err := PutObjectFrom(ctx, s, "small", bytes.NewReader([]byte("abc")), 3, ""); err != nil { t.Fatalf("small fallback: %v", err) } big := io.NewSectionReader(zeroReaderAt{}, 0, PutFromFallbackMax+1) if _, err := PutObjectFrom(ctx, s, "big", big, PutFromFallbackMax+1, ""); !errors.Is(err, ErrTooLargeForPut) { t.Fatalf("big fallback = %v, want ErrTooLargeForPut", err) } } type zeroReaderAt struct{} func (zeroReaderAt) ReadAt(p []byte, _ int64) (int, error) { for i := range p { p[i] = 0 } return len(p), nil } // Put's CAS gate must not read the existing object into memory: the feed // status write runs Put over whatever sits at the key, and an oversized // object there would otherwise be loaded whole (R2-1). A sparse 256 MiB // file stands in for it; ReadFile would allocate all of it. func TestDirStorePutGateDoesNotLoadExistingObject(t *testing.T) { ctx := context.Background() root := t.TempDir() d := NewDirStore(root) if _, err := d.Put(ctx, "big.json", []byte("x"), ""); err != nil { t.Fatal(err) } p, err := d.path("big.json") if err != nil { t.Fatal(err) } const size = 256 << 20 if err := os.Truncate(p, size); err != nil { t.Fatal(err) } var before, after runtime.MemStats runtime.ReadMemStats(&before) if _, err := d.Put(ctx, "big.json", []byte("y"), ""); !errors.Is(err, ErrCASConflict) { t.Fatalf("create-only over an existing key = %v, want ErrCASConflict", err) } if _, err := d.Put(ctx, "big.json", []byte("y"), "not-the-etag"); !errors.Is(err, ErrCASConflict) { t.Fatalf("replace with a stale etag = %v, want ErrCASConflict", err) } runtime.ReadMemStats(&after) if grew := after.TotalAlloc - before.TotalAlloc; grew > 4<<20 { t.Fatalf("Put's gate allocated %d bytes over a %d-byte object; it must stream", grew, size) } cur, err := hashFile(p) if err != nil { t.Fatal(err) } if _, err := d.Put(ctx, "big.json", []byte("small"), cur); err != nil { t.Fatalf("replace with the right etag: %v", err) } if got, _, err := d.Get(ctx, "big.json"); err != nil || string(got) != "small" { t.Fatalf("Get = (%q, %v), want small", got, err) } }