package roster import ( "context" "database/sql" "path/filepath" "sync" "testing" "time" _ "modernc.org/sqlite" ) type sqliteEvidence struct { db *sql.DB mu sync.Mutex entering chan struct{} } func (s *sqliteEvidence) Load(ctx context.Context) ([]byte, error) { var b []byte e := s.db.QueryRowContext(ctx, "SELECT value FROM evidence WHERE id=1").Scan(&b) if e == sql.ErrNoRows { return nil, ErrNoEvidence } return b, e } func (s *sqliteEvidence) Save(ctx context.Context, b []byte) error { s.mu.Lock() entered := s.entering s.entering = nil s.mu.Unlock() if entered != nil { close(entered) } _, e := s.db.ExecContext(ctx, "INSERT INTO evidence(id,value) VALUES(1,?) ON CONFLICT(id) DO UPDATE SET value=excluded.value", b) return e } func TestLeaseDoesNotWaitForSQLiteWriterPublication(t *testing.T) { f := setup(t) db, e := sql.Open("sqlite", filepath.Join(t.TempDir(), "roster.db")) if e != nil { t.Fatal(e) } defer db.Close() db.SetMaxOpenConns(1) if _, e = db.Exec("CREATE TABLE evidence(id INTEGER PRIMARY KEY,value BLOB NOT NULL)"); e != nil { t.Fatal(e) } disk := &sqliteEvidence{db: db} f.cfg.Durable = disk c := f.cache() d := f.check(c, 1) state(t, d, Member) tx, e := db.BeginTx(context.Background(), nil) if e != nil { t.Fatal(e) } defer tx.Rollback() if _, e = tx.Exec("UPDATE evidence SET value=value WHERE id=1"); e != nil { t.Fatal(e) } before := db.Stats().WaitCount entered := make(chan struct{}) disk.mu.Lock() disk.entering = entered disk.mu.Unlock() f.clock.step(30 * time.Second) refreshed := make(chan error, 1) go func() { refreshed <- c.Refresh(context.Background()) }() <-entered // WaitCount proves Save is waiting on the real one-connection writer pool, // not merely scheduled to enter a fake callback. Publication owns Cache.mu. deadline := time.Now().Add(time.Second) for db.Stats().WaitCount == before && time.Now().Before(deadline) { time.Sleep(time.Millisecond) } if db.Stats().WaitCount == before { _ = tx.Rollback() t.Fatal("publication never waited for writer") } valid := make(chan bool, 1) remaining := make(chan time.Duration, 1) go func() { valid <- d.Lease.Valid() }() go func() { remaining <- d.Lease.Remaining() }() var v bool var left time.Duration timedOut := false select { case v = <-valid: case <-time.After(250 * time.Millisecond): timedOut = true } select { case left = <-remaining: case <-time.After(250 * time.Millisecond): timedOut = true } // Always release the writer before reporting a mutation failure, so neither // the refresh goroutine nor the blocked accessor can leak into another test. _ = tx.Rollback() if e = <-refreshed; e != nil { t.Fatal(e) } if timedOut { t.Fatal("lease accessor blocked behind SQLite writer publication") } if v || left != 0 { t.Fatal("contended lease granted standing") } next := f.check(c, 1) state(t, next, Member) if !next.Lease.Valid() { t.Fatal("retry could not use published lease") } if d.Lease.Valid() { t.Fatal("old lease survived publication version") } } func TestLeaseContentionRefusesWithoutChangingAgeOrStanding(t *testing.T) { f := setup(t) c := f.cache() d := f.check(c, 1) f.clock.step(10 * time.Second) before := d.Lease.Remaining() c.mu.Lock() valid := make(chan bool, 1) left := make(chan time.Duration, 1) go func() { valid <- d.Lease.Valid() }() go func() { left <- d.Lease.Remaining() }() timedOut := false var v bool var n time.Duration select { case v = <-valid: case <-time.After(250 * time.Millisecond): timedOut = true } select { case n = <-left: case <-time.After(250 * time.Millisecond): timedOut = true } c.mu.Unlock() if timedOut { t.Fatal("contended accessor waited") } if v || n != 0 { t.Fatal("contention granted standing") } if !d.Lease.Valid() || d.Lease.Remaining() != before { t.Fatal("contention changed the snapshot or deadline") } f.clock.step(50 * time.Second) if d.Lease.Valid() || d.Lease.Remaining() != 0 { t.Fatal("contention extended age") } } func TestLeaseContentionDoesNotSkipLaterClockFence(t *testing.T) { f := setup(t) c := f.cache() d := f.check(c, 1) f.clock.wallStep(-ClockTolerance - time.Nanosecond) c.mu.Lock() done := make(chan bool, 1) go func() { done <- d.Lease.Valid() }() select { case v := <-done: if v { c.mu.Unlock() t.Fatal("contention granted") } case <-time.After(250 * time.Millisecond): c.mu.Unlock() t.Fatal("contention blocked") } c.mu.Unlock() if d.Lease.Valid() || d.Lease.Remaining() != 0 { t.Fatal("successful lock skipped backward-clock fence") } } func TestUnknownSigningKeyRefused(t *testing.T) { f := setup(t) raw, e := SignProvisioning(f.op, "unprovisioned", f.record) if e != nil { t.Fatal(e) } if _, e = VerifyProvisioning(raw, []Key{{"operator", f.record.Keys[0].Public}}, f.record.Bindings); e != ErrRefused { t.Fatalf("unknown operator key result %v", e) } keys, _ := publicKeys(f.record.Keys) // A valid signature under a real key still refuses when its identifier was // not provisioned. The map guard is explicit rather than verifier-dependent. b, e := encode(f.key, "unprovisioned", PageContext, struct { Version int `json:"version"` }{1}) if e != nil { t.Fatal(e) } if _, e = signedPayload(b, keys, PageContext); e != ErrRefused { t.Fatalf("unknown roster key result %v", e) } }