add a client to use cubby in go projects
This commit is contained in:
parent
5f9c7813df
commit
699aa30b07
4 changed files with 524 additions and 0 deletions
181
client_test.go
Normal file
181
client_test.go
Normal file
|
|
@ -0,0 +1,181 @@
|
|||
package cubby
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
// startServer launches the cubby server binary as a subprocess for the test.
|
||||
// Returns the socket path. Caller doesn't need to clean up; t.Cleanup handles it.
|
||||
//
|
||||
// We can't import the server's main package, so this test exercises the wire
|
||||
// protocol via a real subprocess. That's actually what we want — it catches
|
||||
// any drift between client and server.
|
||||
func startServer(t *testing.T) string {
|
||||
t.Helper()
|
||||
|
||||
dir := t.TempDir()
|
||||
sock := filepath.Join(dir, "cubby.sock")
|
||||
|
||||
// Build the server binary into the temp dir.
|
||||
bin := filepath.Join(dir, "cubby")
|
||||
if out, err := runCmd(t, "go", "build", "-o", bin, "./cmd/cubby"); err != nil {
|
||||
t.Fatalf("build server: %v\n%s", err, out)
|
||||
}
|
||||
|
||||
// Start the server.
|
||||
srv := startCmd(t, bin, "-socket", sock)
|
||||
t.Cleanup(func() {
|
||||
_ = srv.Process.Signal(os.Interrupt)
|
||||
_, _ = srv.Process.Wait()
|
||||
})
|
||||
|
||||
// Wait for the socket to appear.
|
||||
deadline := time.Now().Add(2 * time.Second)
|
||||
for time.Now().Before(deadline) {
|
||||
if _, err := os.Stat(sock); err == nil {
|
||||
return sock
|
||||
}
|
||||
time.Sleep(20 * time.Millisecond)
|
||||
}
|
||||
t.Fatal("server didn't start in time")
|
||||
return ""
|
||||
}
|
||||
|
||||
func TestBasic(t *testing.T) {
|
||||
sock := startServer(t)
|
||||
c, err := Dial(sock)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer c.Close()
|
||||
|
||||
if err := c.Ping(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := c.Set("k", "v", 0); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
v, ok, err := c.Get("k")
|
||||
if err != nil || !ok || v != "v" {
|
||||
t.Fatalf("Get(k) = %q,%v,%v; want v,true,nil", v, ok, err)
|
||||
}
|
||||
|
||||
// Missing key: ok=false, no error.
|
||||
_, ok, err = c.Get("nope")
|
||||
if err != nil || ok {
|
||||
t.Fatalf("Get(nope) = _,%v,%v; want _,false,nil", ok, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestValueWithSpacesAndNewlines(t *testing.T) {
|
||||
sock := startServer(t)
|
||||
c, err := Dial(sock)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer c.Close()
|
||||
|
||||
tricky := "hello world\nline two\twith tab\nand a quote: \""
|
||||
if err := c.Set("trick", tricky, 0); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
got, ok, err := c.Get("trick")
|
||||
if err != nil || !ok || got != tricky {
|
||||
t.Fatalf("round-trip failed:\n got %q\nwant %q\n err=%v ok=%v", got, tricky, err, ok)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTTL(t *testing.T) {
|
||||
sock := startServer(t)
|
||||
c, err := Dial(sock)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer c.Close()
|
||||
|
||||
if err := c.Set("ephemeral", "soon-gone", time.Second); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// Right away: present.
|
||||
_, ok, _ := c.Get("ephemeral")
|
||||
if !ok {
|
||||
t.Fatal("expected key present immediately after set")
|
||||
}
|
||||
// After TTL: gone. Pad a bit for janitor + clock granularity.
|
||||
time.Sleep(1500 * time.Millisecond)
|
||||
_, ok, _ = c.Get("ephemeral")
|
||||
if ok {
|
||||
t.Fatal("expected key to expire")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDelAndKeys(t *testing.T) {
|
||||
sock := startServer(t)
|
||||
c, err := Dial(sock)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer c.Close()
|
||||
|
||||
for _, k := range []string{"a", "b", "c"} {
|
||||
if err := c.Set(k, k+"-val", 0); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
keys, err := c.Keys()
|
||||
if err != nil || len(keys) != 3 {
|
||||
t.Fatalf("Keys() = %v,%v; want 3 keys", keys, err)
|
||||
}
|
||||
|
||||
existed, err := c.Del("b")
|
||||
if err != nil || !existed {
|
||||
t.Fatalf("Del(b) = %v,%v; want true,nil", existed, err)
|
||||
}
|
||||
existed, err = c.Del("b") // again — already gone
|
||||
if err != nil || existed {
|
||||
t.Fatalf("Del(b) twice = %v,%v; want false,nil", existed, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPoolConcurrent(t *testing.T) {
|
||||
sock := startServer(t)
|
||||
pool := NewPool(sock, 4)
|
||||
defer pool.Close()
|
||||
|
||||
const workers = 16
|
||||
const ops = 50
|
||||
var wg sync.WaitGroup
|
||||
errCh := make(chan error, workers)
|
||||
|
||||
for w := range workers {
|
||||
wg.Add(1)
|
||||
go func(w int) {
|
||||
defer wg.Done()
|
||||
for i := range ops {
|
||||
err := pool.Do(func(c *Client) error {
|
||||
key := "w" + strconv.Itoa(w) + "-" + strconv.Itoa(i)
|
||||
if err := c.Set(key, "x", 0); err != nil {
|
||||
return err
|
||||
}
|
||||
_, _, err := c.Get(key)
|
||||
return err
|
||||
})
|
||||
if err != nil {
|
||||
errCh <- err
|
||||
return
|
||||
}
|
||||
}
|
||||
}(w)
|
||||
}
|
||||
wg.Wait()
|
||||
close(errCh)
|
||||
for err := range errCh {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue