test: add ledger load test commands
This commit is contained in:
@@ -0,0 +1,150 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
mathrand "math/rand"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
basev1 "gl/gen/base/v1"
|
||||
ledgerv1 "gl/gen/ledger/v1"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
type memoryClient struct {
|
||||
mu sync.Mutex
|
||||
balances map[string]int64
|
||||
journals int
|
||||
}
|
||||
|
||||
func newMemoryClient() *memoryClient {
|
||||
return &memoryClient{balances: make(map[string]int64)}
|
||||
}
|
||||
|
||||
func (c *memoryClient) Health(context.Context, *basev1.Empty, ...grpc.CallOption) (*ledgerv1.HealthResponse, error) {
|
||||
return &ledgerv1.HealthResponse{Serving: true, DatabaseReady: true}, nil
|
||||
}
|
||||
|
||||
func (c *memoryClient) AppendJournal(_ context.Context, request *ledgerv1.AppendJournalRequest, _ ...grpc.CallOption) (*ledgerv1.AppendJournalResponse, error) {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
for _, entry := range request.GetEntries() {
|
||||
amount, err := strconv.ParseInt(entry.GetAmount(), 10, 64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c.balances[accountKey(entry.GetAccount())] += amount
|
||||
}
|
||||
c.journals++
|
||||
return &ledgerv1.AppendJournalResponse{Journal: &ledgerv1.Journal{JournalId: fmt.Sprintf("journal-%d", c.journals)}}, nil
|
||||
}
|
||||
|
||||
func (c *memoryClient) GetBalance(_ context.Context, request *ledgerv1.GetBalanceRequest, _ ...grpc.CallOption) (*ledgerv1.GetBalanceResponse, error) {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
return &ledgerv1.GetBalanceResponse{Account: request.GetAccount(), Balance: strconv.FormatInt(c.balances[accountKey(request.GetAccount())], 10)}, nil
|
||||
}
|
||||
|
||||
func accountKey(account *ledgerv1.AccountReference) string {
|
||||
return fmt.Sprintf("%d/%s/%s/%d", account.GetAccountClass(), account.GetOwnerType(), account.GetOwnerId(), account.GetAssetId())
|
||||
}
|
||||
|
||||
func TestResolveAssetIDPromptsWhenMissing(t *testing.T) {
|
||||
var output bytes.Buffer
|
||||
assetID, err := resolveAssetID(0, strings.NewReader("42\n"), &output)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if assetID != 42 || output.String() != "Asset number: " {
|
||||
t.Fatalf("asset=%d prompt=%q", assetID, output.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveAssetIDUsesFlagWithoutPrompt(t *testing.T) {
|
||||
var output bytes.Buffer
|
||||
assetID, err := resolveAssetID(7, strings.NewReader(""), &output)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if assetID != 7 || output.Len() != 0 {
|
||||
t.Fatalf("asset=%d prompt=%q", assetID, output.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestRandomReservationsPreserveSupplyAndMinimum(t *testing.T) {
|
||||
state := &balanceState{balances: [personCount]int64{9595, 45, 45, 45, 45, 45, 45, 45, 45, 45}}
|
||||
random := mathrand.New(mathrand.NewSource(1234))
|
||||
distinctAmounts := make(map[int64]struct{})
|
||||
for range 100_000 {
|
||||
value := state.reserve(random)
|
||||
distinctAmounts[value.Amount] = struct{}{}
|
||||
if value.Sender == value.Receiver || value.Amount <= 0 {
|
||||
t.Fatalf("invalid transfer: %+v", value)
|
||||
}
|
||||
}
|
||||
|
||||
var total int64
|
||||
for index, balance := range state.balances {
|
||||
if balance < minimumBalance {
|
||||
t.Fatalf("person %d balance %d is below minimum", index+1, balance)
|
||||
}
|
||||
total += balance
|
||||
}
|
||||
if total != initialSupply {
|
||||
t.Fatalf("total=%d, want %d", total, initialSupply)
|
||||
}
|
||||
if len(distinctAmounts) < 2 {
|
||||
t.Fatalf("expected random values, got %v", distinctAmounts)
|
||||
}
|
||||
}
|
||||
|
||||
func TestVerifyInitialAndFinalBalances(t *testing.T) {
|
||||
initial := [personCount]int64{10_000}
|
||||
if err := verifyInitial(initial); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
final := [personCount]int64{9595, 45, 45, 45, 45, 45, 45, 45, 45, 45}
|
||||
if err := verifyFinal(final); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
final[9] = 44
|
||||
if err := verifyFinal(final); err == nil {
|
||||
t.Fatal("expected minimum-balance failure")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPersonIDsAreIsolatedByRun(t *testing.T) {
|
||||
first := personIDs("run-a")
|
||||
second := personIDs("run-b")
|
||||
if first[0] == second[0] || first[0] == first[1] {
|
||||
t.Fatalf("IDs are not isolated: %q %q %q", first[0], second[0], first[1])
|
||||
}
|
||||
}
|
||||
|
||||
func TestExecuteFundsRedistributesAndVerifiesConservation(t *testing.T) {
|
||||
ledger := newMemoryClient()
|
||||
var output bytes.Buffer
|
||||
err := execute(context.Background(), ledger, config{
|
||||
RunID: "test-run",
|
||||
AssetID: 17,
|
||||
Transactions: 1000,
|
||||
Concurrency: 4,
|
||||
RequestTimeout: time.Second,
|
||||
RandomSeed: 99,
|
||||
}, &output)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if ledger.journals != 1001 {
|
||||
t.Fatalf("journals=%d, want one funding journal and 1000 transfers", ledger.journals)
|
||||
}
|
||||
if !strings.Contains(output.String(), "PASS: all 10 people have at least 45; total=10000") {
|
||||
t.Fatalf("missing successful verification in output:\n%s", output.String())
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user