// TestKVClientPrepareAndFlush prepares a sequence of increment // calls and then flushes them and verifies the results. func TestKVClientPrepareAndFlush(t *testing.T) { s := StartTestServer(t) defer s.Stop() kvClient := createTestClient(s.HTTPAddr) kvClient.User = storage.UserRoot replies := []*proto.IncrementResponse{} keys := []proto.Key{} for i := 0; i < 10; i++ { key := proto.Key(fmt.Sprintf("key %02d", i)) keys = append(keys, key) reply := &proto.IncrementResponse{} replies = append(replies, reply) kvClient.Prepare(proto.Increment, proto.IncrementArgs(key, int64(i)), reply) } if err := kvClient.Flush(); err != nil { t.Fatal(err) } for i, reply := range replies { if reply.NewValue != int64(i) { t.Errorf("%d: expected %d; got %d", i, i, reply.NewValue) } } // Now try 2 scans. scan1 := &proto.ScanResponse{} scan2 := &proto.ScanResponse{} kvClient.Prepare(proto.Scan, proto.ScanArgs(proto.Key("key 00"), proto.Key("key 05"), 0), scan1) kvClient.Prepare(proto.Scan, proto.ScanArgs(proto.Key("key 05"), proto.Key("key 10"), 0), scan2) if err := kvClient.Flush(); err != nil { t.Fatal(err) } if len(scan1.Rows) != 5 || len(scan2.Rows) != 5 { t.Errorf("expected scan results to include 5 and 5 rows; got %d and %d", len(scan1.Rows), len(scan2.Rows)) } for i := 0; i < 5; i++ { if key := scan1.Rows[i].Key; !key.Equal(keys[i]) { t.Errorf("expected scan1 key %d to be %q; got %q", i, keys[i], key) } if val := scan1.Rows[i].Value.GetInteger(); val != int64(i) { t.Errorf("expected scan1 result %d to be %d; got %d", i, i, val) } if key := scan2.Rows[i].Key; !key.Equal(keys[i+5]) { t.Errorf("expected scan2 key %d to be %q; got %q", i, keys[i+5], key) } if val := scan2.Rows[i].Value.GetInteger(); val != int64(i+5) { t.Errorf("expected scan2 result %d to be %d; got %d", i, i+5, val) } } }
// concurrentIncrements starts two Goroutines in parallel, both of which // read the integers stored at the other's key and add it onto their own. // It is checked that the outcome is serializable, i.e. exactly one of the // two Goroutines (the later write) sees the previous write by the other. func concurrentIncrements(kvClient *client.KV, t *testing.T) { // wgStart waits for all transactions to line up, wgEnd has the main // function wait for them to finish. var wgStart, wgEnd sync.WaitGroup wgStart.Add(2 + 1) wgEnd.Add(2) for i := 0; i < 2; i++ { go func(i int) { // Read the other key, write key i. readKey := []byte(fmt.Sprintf("value-%d", (i+1)%2)) writeKey := []byte(fmt.Sprintf("value-%d", i)) defer wgEnd.Done() wgStart.Done() // Wait until the other goroutines are running. wgStart.Wait() txnOpts := &client.TransactionOptions{ Name: fmt.Sprintf("test-%d", i), } if err := kvClient.RunTransaction(txnOpts, func(txn *client.KV) error { // Retrieve the other key. gr := &proto.GetResponse{} if err := txn.Call(proto.Get, proto.GetArgs(readKey), gr); err != nil { return err } otherValue := int64(0) if gr.Value != nil && gr.Value.Integer != nil { otherValue = *gr.Value.Integer } pr := &proto.IncrementResponse{} pa := proto.IncrementArgs(writeKey, 1+otherValue) if err := txn.Call(proto.Increment, pa, pr); err != nil { return err } return nil }); err != nil { t.Error(err) } }(i) } // Kick the goroutines loose. wgStart.Done() // Wait for the goroutines to finish. wgEnd.Wait() // Verify that both keys contain something and, more importantly, that // one key actually contains the value of the first writer and not only // its own. total := int64(0) results := []int64(nil) for i := 0; i < 2; i++ { readKey := []byte(fmt.Sprintf("value-%d", i)) gr := &proto.GetResponse{} if err := kvClient.Call(proto.Get, proto.GetArgs(readKey), gr); err != nil { log.Fatal(err) } if gr.Value == nil || gr.Value.Integer == nil { t.Fatalf("unexpected empty key: %v=%v", readKey, gr.Value) } total += *gr.Value.Integer results = append(results, *gr.Value.Integer) } // First writer should have 1, second one 2 if total != 3 { t.Fatalf("got unserializable values %v", results) } }