carlos / platform Public

Clone
git clone https://amadan.net/carlos/platform

Plain git — no account needed to clone.

Download

Download this file

1package release
2
3import (
4 "context"
5 "errors"
6 "testing"
7 "time"
8)
9
10func TestSerialBumpFromMissing(t *testing.T) {
11 s := NewDirStore(t.TempDir())
12 ctx := context.Background()
13 doc, etag, err := ReadSerial(ctx, s)
14 if err != nil || doc.Serial != 0 || etag != "" {
15 t.Fatalf("missing serial should read as zero: %+v %q %v", doc, etag, err)
16 }
17 if err := BumpSerial(ctx, s, time.Unix(1754640000, 0).UTC()); err != nil {
18 t.Fatal(err)
19 }
20 doc, etag, err = ReadSerial(ctx, s)
21 if err != nil || doc.Serial != 1 || doc.Schema != 1 || etag == "" {
22 t.Fatalf("first bump: %+v %q %v", doc, etag, err)
23 }
24 if doc.BumpedAt == "" {
25 t.Fatal("bumped_at not stamped")
26 }
27}
28
29func TestSerialBumpIncrements(t *testing.T) {
30 s := NewDirStore(t.TempDir())
31 ctx := context.Background()
32 for i := 0; i < 3; i++ {
33 if err := BumpSerial(ctx, s, time.Unix(1754640000, 0).UTC()); err != nil {
34 t.Fatal(err)
35 }
36 }
37 doc, _, _ := ReadSerial(ctx, s)
38 if doc.Serial != 3 {
39 t.Fatalf("serial = %d, want 3", doc.Serial)
40 }
41}
42
43func TestSerialGarbageObjectStillBumps(t *testing.T) {
44 // A bucket-writer can corrupt the object; a bump must recover, not wedge.
45 s := NewDirStore(t.TempDir())
46 ctx := context.Background()
47 if _, err := s.Put(ctx, SerialKey, []byte("not json"), ""); err != nil {
48 t.Fatal(err)
49 }
50 if err := BumpSerial(ctx, s, time.Unix(1754640000, 0).UTC()); err != nil {
51 t.Fatal(err)
52 }
53 doc, _, err := ReadSerial(ctx, s)
54 if err != nil || doc.Serial != 1 {
55 t.Fatalf("recovery bump: %+v %v", doc, err)
56 }
57}
58
59// serialCancelStore cancels a context at a chosen point and is otherwise the
60// DirStore beneath it, whose Put ignores its context.
61type serialCancelStore struct {
62 Store
63 onGet, onPut func()
64 puts int
65}
66
67func (s *serialCancelStore) Get(ctx context.Context, key string) ([]byte, string, error) {
68 if s.onGet != nil {
69 s.onGet()
70 }
71 return s.Store.Get(ctx, key)
72}
73
74func (s *serialCancelStore) Put(ctx context.Context, key string, body []byte, ifMatch string) (string, error) {
75 s.puts++
76 if s.onPut != nil {
77 s.onPut()
78 return "", ErrCASConflict // a race lost, so BumpSerial retries
79 }
80 return s.Store.Put(ctx, key, body, ifMatch)
81}
82
83// A caller whose context ended while BumpSerial read the serial gets no
84// write, even from a store that ignores its context (db region forward,
85// Astra round 1, P2).
86func TestSerialBumpDoesNotWriteAfterItsContextEnds(t *testing.T) {
87 dir := NewDirStore(t.TempDir())
88 ctx, cancel := context.WithCancel(context.Background())
89 s := &serialCancelStore{Store: dir, onGet: cancel}
90 if err := BumpSerial(ctx, s, time.Now()); !errors.Is(err, context.Canceled) {
91 t.Fatalf("err %v, want the context's", err)
92 }
93 if doc, etag, _ := ReadSerial(context.Background(), dir); doc.Serial != 0 || etag != "" || s.puts != 0 {
94 t.Fatalf("the serial was written after its context ended: %+v, %d puts", doc, s.puts)
95 }
96}
97
98// The same check holds before a retry's write.
99func TestSerialBumpRetryDoesNotWriteAfterItsContextEnds(t *testing.T) {
100 dir := NewDirStore(t.TempDir())
101 ctx, cancel := context.WithCancel(context.Background())
102 s := &serialCancelStore{Store: dir}
103 s.onPut = func() { cancel(); s.onPut = nil }
104 if err := BumpSerial(ctx, s, time.Now()); !errors.Is(err, context.Canceled) {
105 t.Fatalf("err %v, want the context's", err)
106 }
107 if doc, _, _ := ReadSerial(context.Background(), dir); doc.Serial != 0 || s.puts != 1 {
108 t.Fatalf("a retry wrote the serial after its context ended: %+v, %d puts", doc, s.puts)
109 }
110}
111