Artifact 49549a67ab1984a4e8de0a5f79872502e78f709b7f4f43c7bb8c4db380c37259:
- File
internal/sync/budget_realfossil_test.go
— part of check-in
[bd2efd6d5a]
at
2026-10-29 00:01:09
on branch feat/receive-size-budget
— feat(sync): per-store artifact size budget on the receive path
HandleOpts.MaxStoredBytes (internal and public) caps how many bytes one pushed artifact may occupy in blob.content as stored: a cfile card's stored form, otherwise the blob.Compress form of the file card's payload (the delta for a delta card). A plain payload whose compression bound fits is not compressed just to measure it.
An artifact over the budget fails the request (h.fail): one error card naming the hash, stored size and limit, plus the file name and check-in when a crosslinked check-in already lists the phantom. The request transaction rolls back, so nothing from the round is stored and FilesReceived is 0. Stock fossil 2.28 prints the error on push. 0 keeps today's behaviour; clone and pull serving are not limited.
Related to #459. (user: [email protected], size: 3039) [annotate] [blame] [check-ins using] [more...]
package sync_test import ( "context" "math/rand" "os" "path/filepath" "strings" "testing" "github.com/danmestas/go-libfossil/internal/auth" libfossil "github.com/danmestas/go-libfossil/internal/fsltype" "github.com/danmestas/go-libfossil/internal/manifest" "github.com/danmestas/go-libfossil/internal/repo" "github.com/danmestas/go-libfossil/internal/sync" "github.com/danmestas/go-libfossil/internal/xfer" "github.com/danmestas/go-libfossil/simio" "github.com/danmestas/go-libfossil/testutil" ) // TestRealFossilPushOverBudget pushes an artifact over the server's // MaxStoredBytes from stock fossil: fossil prints the budget's error, and // the server holds exactly what it held before the push. func TestRealFossilPushOverBudget(t *testing.T) { bin := testutil.RequireFossilBin(t) dir := t.TempDir() const limit = 64 << 10 srv, err := repo.Create(filepath.Join(dir, "server.fossil"), "testuser", simio.CryptoRand{}, "") if err != nil { t.Fatalf("repo.Create: %v", err) } defer srv.Close() if _, _, err := manifest.Checkin(srv, manifest.CheckinOpts{ Comment: "seed", User: "testuser", Parent: libfossil.FslID(0), Files: []manifest.File{{Name: "seed.txt", Content: []byte("seed\n")}}, }); err != nil { t.Fatalf("Checkin: %v", err) } if err := auth.UpdateCaps(srv.DB(), "nobody", "gijoryz"); err != nil { t.Fatalf("grant nobody push: %v", err) } ctx, cancel := context.WithCancel(context.Background()) defer cancel() base := serveRepoWith(ctx, t, srv, func(ctx context.Context, r *repo.Repo, req *xfer.Message) (*xfer.Message, error) { return sync.HandleSyncWithOpts(ctx, r, req, sync.HandleOpts{MaxStoredBytes: limit}) }) clonePath := filepath.Join(dir, "clone.fossil") fossilOK(t, bin, dir, "clone", base, clonePath) work := filepath.Join(dir, "work") if err := os.Mkdir(work, 0o755); err != nil { t.Fatal(err) } fossilOK(t, bin, work, "settings", "autosync", "off", "-R", clonePath) fossilOK(t, bin, work, "open", "--nosync", clonePath) big := make([]byte, 4*limit) rand.New(rand.NewSource(1)).Read(big) if err := os.WriteFile(filepath.Join(work, "big.bin"), big, 0o644); err != nil { t.Fatal(err) } fossilOK(t, bin, work, "add", "big.bin") fossilOK(t, bin, work, "commit", "-m", "adds a big file", "--no-warnings") before := blobDigest(t, srv) out, _ := fossilRun(bin, work, "push", base, "--once") if !strings.Contains(out, "this repository stores at most 65536 bytes per artifact") { t.Fatalf("fossil push did not report the budget refusal:\n%s", out) } if after := blobDigest(t, srv); after != before { t.Fatalf("server store changed across a refused push:\nbefore %s\n after %s", before, after) } } // blobDigest summarises r's blob table, phantoms included. func blobDigest(t *testing.T, r *repo.Repo) string { t.Helper() var d string if err := r.DB().QueryRow(`SELECT count(*) || ':' || coalesce(group_concat(uuid || '/' || size, ','), '') FROM (SELECT uuid, size FROM blob ORDER BY uuid)`).Scan(&d); err != nil { t.Fatal(err) } return d }