package spindle
import (
"context"
"sync"
"testing"
"time"
"tangled.org/core/spindle/secrets"
)
type gatedVault struct {
secrets.Manager
entered chan struct{}
gate chan struct{}
enter sync.Once
release sync.Once
}
func newGatedVault(inner secrets.Manager) *gatedVault {
return &gatedVault{Manager: inner, entered: make(chan struct{}), gate: make(chan struct{})}
}
func (g *gatedVault) RemoveAllSecrets(ctx context.Context, repo secrets.RepoIdentifier) error {
g.enter.Do(func() { close(g.entered) })
<-g.gate
return g.Manager.RemoveAllSecrets(ctx, repo)
}
func (g *gatedVault) open() { g.release.Do(func() { close(g.gate) }) }
func (g *gatedVault) waitEntered(t *testing.T) {
t.Helper()
select {
case <-g.entered:
case <-time.After(5 * time.Second):
t.Fatal("the wipe never reached its cleanup")
}
}
func TestAWipeFromARefreshHoldsNoLockWhileItsCleanupRuns(t *testing.T) {
env := newEnsureEnv(t)
env.serve("repo")
env.ensure(t)
vault := newGatedVault(env.s.vault)
env.s.vault = vault
defer vault.open()
env.knot.deleteRepo(testRepoDid)
done := ensureAsync(env)
vault.waitEntered(t)
locked := make(chan struct{})
go func() {
defer env.s.lockRepo(testRepoDid).Unlock()
close(locked)
}()
select {
case <-locked:
case <-time.After(2 * time.Second):
t.Fatal("the repo's lock was held while the wipe cleaned up")
}
if repo, err := env.s.servedRepo(testRepoDid); err != nil || repo != nil {
t.Errorf("the repo is still served while its cleanup runs: %+v, %v", repo, err)
}
vault.open()
if err := <-done; err != nil {
t.Fatalf("ensureRepo: %v", err)
}
}
func TestAWipeStopsTheReposWritersBeforeItsCleanup(t *testing.T) {
env := newEnsureEnv(t)
env.serve("repo")
repo := env.ensure(t)
vault := newGatedVault(env.s.vault)
env.s.vault = vault
defer vault.open()
done := goErr(func() error { return env.s.WipeRepo(context.Background(), testRepoDid, "test") })
vault.waitEntered(t)
wrote := false
if err := env.s.whileServed(repo, func() error {
wrote = true
return nil
}); err != nil {
t.Fatal(err)
}
if wrote {
t.Error("a write landed for a repo whose wipe was cleaning up")
}
if rows := env.rows(t); len(rows) != 0 {
t.Errorf("the repo's rows outlive the start of its cleanup: %+v", rows)
}
vault.open()
if err := <-done; err != nil {
t.Fatalf("WipeRepo: %v", err)
}
}
func TestARefreshWhileAWipeCleansUpDoesNotRegisterTheRepoAgain(t *testing.T) {
env := newEnsureEnv(t)
env.serve("repo")
env.ensure(t)
vault := newGatedVault(env.s.vault)
env.s.vault = vault
defer vault.open()
done := goErr(func() error { return env.s.WipeRepo(context.Background(), testRepoDid, "test") })
vault.waitEntered(t)
repo, err := env.s.ensureRepo(context.Background(), env.k, testRepoDid)
if err != nil || repo != nil {
t.Fatalf("a refresh during the cleanup = %+v, %v, want the repo left unserved", repo, err)
}
if rows := env.rows(t); len(rows) != 0 {
t.Fatalf("a refresh registered the repo again mid-cleanup: %+v", rows)
}
vault.open()
if err := <-done; err != nil {
t.Fatalf("WipeRepo: %v", err)
}
if repo := env.ensure(t); repo == nil {
t.Fatal("a refresh after the wipe is over can't register the repo")
}
}