Skip to content
quarry Artifact [0fa28d0ca6]
Login

Artifact [0fa28d0ca6]

Artifact 0fa28d0ca608112885a2fe6327807e964c71428ff8b09d8df5040aff59d57387:


package oltp

import (
	"sync"
	"testing"
	"time"
)

// race runs fn on n goroutines released together, and returns how
// many returned nil — the count a check-then-write race inflates.
func race(n int, fn func(i int) error) (ok int, errs []error) {
	var start, done sync.WaitGroup
	start.Add(1)
	results := make([]error, n)
	for i := 0; i < n; i++ {
		done.Add(1)
		go func(i int) {
			defer done.Done()
			start.Wait()
			results[i] = fn(i)
		}(i)
	}
	start.Done()
	done.Wait()
	for _, err := range results {
		if err == nil {
			ok++
		} else {
			errs = append(errs, err)
		}
	}
	return ok, errs
}

func TestTwoRedemptionsOfOneInviteAtOnceLetExactlyOneThrough(t *testing.T) {
	db := testDB(t)
	token, err := db.InviteAccount("eve", "[email protected]", false)
	if err != nil {
		t.Fatal(err)
	}
	passwords := []string{"first-password-1", "second-password-2"}
	ok, errs := race(len(passwords), func(i int) error {
		_, err := db.RedeemInvite(token, passwords[i])
		return err
	})
	if ok != 1 {
		t.Fatalf("FAIL: %d of %d concurrent redemptions of one invite succeeded (errors %v)", ok, len(passwords), errs)
	}
	t.Logf("ok: one redemption won, the other was refused: %v", errs)
	signIns := 0
	for _, p := range passwords {
		if _, ok := db.Authenticate("eve", p); ok {
			signIns++
		}
	}
	if signIns != 1 {
		t.Fatalf("FAIL: %d passwords sign eve in after the race, want the winner's only", signIns)
	}
	t.Logf("ok: exactly the winning password signs in")
}

// rounds repeats a race whose window is a few statements wide: one
// round rarely lands both goroutines inside it, hundreds nearly always
// do.
const rounds = 300

func TestTwoAdminsDemotingEachOtherAtOnceLeaveTheRepositoryAnAdmin(t *testing.T) {
	db := testDB(t)
	for _, n := range []string{"alice", "bob"} {
		if err := db.AddAccount(n, "pw12345678", false); err != nil {
			t.Fatal(err)
		}
	}
	// Each demotes the other: alone, each save leaves one admin and
	// the guard passes it.
	pairs := [][2]string{{"alice", "bob"}, {"bob", "alice"}}
	for round := 0; round < rounds; round++ {
		for _, n := range []string{"alice", "bob"} {
			if err := db.Grant(n, "repo", RepoAdmin); err != nil {
				t.Fatal(err)
			}
		}
		ok, errs := race(len(pairs), func(i int) error {
			_, err := db.SetCapsGuarded(pairs[i][0], pairs[i][1], "repo", Write.Caps())
			return err
		})
		if ok != 1 {
			t.Fatalf("FAIL: round %d: %d of 2 crossing demotions succeeded (errors %v)", round, ok, errs)
		}
		if !IsRefusal(errs[0]) {
			t.Fatalf("FAIL: round %d: the losing demotion failed with %v, want the guard's refusal", round, errs[0])
		}
		if before, _, err := db.adminCount("repo", "", ""); err != nil || before != 1 {
			t.Fatalf("FAIL: round %d: the repository has %d admins after the race (%v), want 1", round, before, err)
		}
	}
	t.Logf("ok: in %d rounds one demotion went through, the other was refused, and one admin remained", rounds)
}

func TestTwoOwnersRemovingEachOtherAtOnceLeaveTheTeamAnOwner(t *testing.T) {
	for _, tc := range []struct {
		name  string
		apply func(db *DB, actor, account string) error
	}{
		{"removal", func(db *DB, actor, account string) error {
			_, err := db.RemoveTeamMemberGuarded(actor, "crew", account)
			return err
		}},
		{"demotion", func(db *DB, actor, account string) error {
			_, err := db.SetTeamMemberGuarded(actor, "crew", account, "", false, true)
			return err
		}},
	} {
		t.Run(tc.name, func(t *testing.T) {
			db := testDB(t)
			for _, n := range []string{"alice", "bob"} {
				if err := db.AddAccount(n, "pw12345678", false); err != nil {
					t.Fatal(err)
				}
			}
			if err := db.AddTeam("crew", "Crew", "", "alice"); err != nil {
				t.Fatal(err)
			}
			pairs := [][2]string{{"alice", "bob"}, {"bob", "alice"}}
			for round := 0; round < rounds; round++ {
				for _, n := range []string{"alice", "bob"} {
					if err := db.SetTeamMember("crew", n, "", true, true); err != nil {
						t.Fatal(err)
					}
				}
				ok, errs := race(len(pairs), func(i int) error {
					return tc.apply(db, pairs[i][0], pairs[i][1])
				})
				if ok != 1 {
					t.Fatalf("FAIL: round %d: %d of 2 crossing %ss succeeded (errors %v)", round, ok, tc.name, errs)
				}
				if !IsRefusal(errs[0]) {
					t.Fatalf("FAIL: round %d: the losing %s failed with %v, want the guard's refusal", round, tc.name, errs[0])
				}
				if n, err := db.TeamOwnerCount("crew"); err != nil || n != 1 {
					t.Fatalf("FAIL: round %d: the team has %d owners after the race (%v), want 1", round, n, err)
				}
			}
			t.Logf("ok: in %d rounds one %s went through, the other was refused, and one owner remained", rounds, tc.name)
		})
	}
}

func TestLinkingTheLastAdminToANonAdminLoginIsRefused(t *testing.T) {
	db := testDB(t)
	if err := db.AddAccount("alice", "pw12345678", false); err != nil {
		t.Fatal(err)
	}
	if err := db.Grant("alice", "alpha", RepoAdmin); err != nil {
		t.Fatal(err)
	}
	if _, _, err := db.ReplaceFossilUsers("alpha", []FossilUser{{Login: "al", Caps: "u"}}); err != nil {
		t.Fatal(err)
	}
	// alice is alpha's only admin; the link would replace her string
	// with the login's, which holds no a.
	_, err := db.LinkFossilUser("alice", "alpha", "al", "alice")
	if !IsRefusal(err) {
		t.Fatalf("FAIL: linking the last admin to a login without a: %v, want the guard's refusal", err)
	}
	t.Logf("ok: the link was refused: %v", err)
	if !db.EffectiveCaps("alice", "alpha").HasAny("a") {
		t.Fatal("FAIL: the refused link still took alice's admin")
	}
	if users, _ := db.FossilUsers("alpha"); len(users) != 1 || users[0].Linked != "" {
		t.Fatalf("FAIL: the refused link was recorded: %+v", users)
	}
	t.Logf("ok: alice keeps her admin and the login stays unlinked")
}

func TestAFailedPromotionReleasesOnlyItsOwnClaim(t *testing.T) {
	db := testDB(t)
	id, err := db.LocalTicketNew("alpha", "lou", "", map[string]string{"title": "t"})
	if err != nil {
		t.Fatal(err)
	}
	if err := db.LocalTicketPromoted("alpha", id, "f0551171", ""); err == nil {
		t.Fatal("FAIL: a never-claimed ticket was tombstoned with an empty token")
	}
	t.Log("ok: an empty token tombstones nothing")
	token, ok, err := db.LocalTicketClaim("alpha", id)
	if err != nil || !ok || token == "" {
		t.Fatalf("FAIL: first claim: token=%q ok=%v err=%v", token, ok, err)
	}
	if _, ok, _ := db.LocalTicketClaim("alpha", id); ok {
		t.Fatal("FAIL: a held claim was claimed again")
	}
	if err := db.LocalTicketRelease("alpha", id, "not-the-token"); err != nil {
		t.Fatal(err)
	}
	if _, ok, _ := db.LocalTicketClaim("alpha", id); ok {
		t.Fatal("FAIL: a release with the wrong token freed the claim")
	}
	t.Log("ok: a stranger's release leaves the claim held")
	if err := db.LocalTicketRelease("alpha", id, token); err != nil {
		t.Fatal(err)
	}
	if _, ok, _ := db.LocalTicketClaim("alpha", id); !ok {
		t.Fatal("FAIL: the holder's release did not free the ticket for another promotion")
	}
	t.Log("ok: the holder's release frees the ticket at once")
}

func TestALapsedClaimBelongsToTheNextPromotion(t *testing.T) {
	db := testDB(t)
	id, err := db.LocalTicketNew("alpha", "lou", "", map[string]string{"title": "t"})
	if err != nil {
		t.Fatal(err)
	}
	first, ok, err := db.LocalTicketClaim("alpha", id)
	if err != nil || !ok {
		t.Fatalf("first claim: %v %v", ok, err)
	}
	// The first promotion outruns its lease.
	old := time.Now().UTC().Add(-2 * claimLease).Format("2006-01-02T15:04:05")
	if _, err := db.w.Exec(`UPDATE local_ticket SET claimed = ? WHERE repo = ? AND id = ?`, old, "alpha", id); err != nil {
		t.Fatal(err)
	}
	second, ok, err := db.LocalTicketClaim("alpha", id)
	if err != nil || !ok || second == first {
		t.Fatalf("FAIL: a lapsed claim was not taken over: token=%q ok=%v err=%v", second, ok, err)
	}
	t.Log("ok: the next promotion takes a lapsed claim with a token of its own")
	if err := db.LocalTicketRelease("alpha", id, first); err != nil {
		t.Fatal(err)
	}
	if _, ok, _ := db.LocalTicketClaim("alpha", id); ok {
		t.Fatal("FAIL: the lapsed promotion's release freed the new holder's claim")
	}
	if err := db.LocalTicketPromoted("alpha", id, "f0551171", first); err == nil {
		t.Fatal("FAIL: the lapsed promotion tombstoned a ticket another promotion holds")
	}
	if _, ok := db.LocalTicketRedirect("alpha", id); ok {
		t.Fatal("FAIL: the refused tombstone still recorded a redirect")
	}
	t.Log("ok: the lapsed promotion can neither release nor tombstone the new claim")
	if err := db.LocalTicketPromoted("alpha", id, "f0551171", second); err != nil {
		t.Fatalf("FAIL: the holder's tombstone: %v", err)
	}
	if to, ok := db.LocalTicketRedirect("alpha", id); !ok || to != "f0551171" {
		t.Fatalf("FAIL: redirect = %q %v", to, ok)
	}
	t.Log("ok: the holder's tombstone lands")
}