mirror of https://github.com/tidwall/tile38.git
269 lines
5.5 KiB
Go
269 lines
5.5 KiB
Go
|
package tests
|
||
|
|
||
|
import (
|
||
|
"bufio"
|
||
|
"bytes"
|
||
|
"encoding/hex"
|
||
|
"errors"
|
||
|
"fmt"
|
||
|
"io"
|
||
|
"io/ioutil"
|
||
|
"math/rand"
|
||
|
"net"
|
||
|
"os"
|
||
|
"strconv"
|
||
|
"testing"
|
||
|
"time"
|
||
|
|
||
|
"github.com/tidwall/tile38/controller"
|
||
|
)
|
||
|
|
||
|
const port = 21098
|
||
|
|
||
|
func init() {
|
||
|
rand.Seed(time.Now().UnixNano())
|
||
|
}
|
||
|
|
||
|
func uid() string {
|
||
|
b := make([]byte, 8)
|
||
|
if _, err := rand.Read(b); err != nil {
|
||
|
panic("random error: " + err.Error())
|
||
|
}
|
||
|
return hex.EncodeToString(b)
|
||
|
}
|
||
|
func makeKey(size int) string {
|
||
|
s := "key+" + uid()
|
||
|
for len(s) < size {
|
||
|
s += "+" + uid()
|
||
|
}
|
||
|
return s
|
||
|
}
|
||
|
func makeID(size int) string {
|
||
|
s := "key+" + uid()
|
||
|
for len(s) < size {
|
||
|
s += "+" + uid()
|
||
|
}
|
||
|
return s
|
||
|
}
|
||
|
func makeJSON(size int) string {
|
||
|
var buf bytes.Buffer
|
||
|
buf.WriteString(`{"type":"MultiPoint","coordinates":[`)
|
||
|
for i := 0; buf.Len() < size; i++ {
|
||
|
if i > 0 {
|
||
|
buf.WriteString(",")
|
||
|
}
|
||
|
fmt.Fprintf(&buf, "[%f,%f]", rand.Float64()*360-180, rand.Float64()*180-90)
|
||
|
}
|
||
|
|
||
|
buf.WriteString(`]}`)
|
||
|
return buf.String()
|
||
|
}
|
||
|
|
||
|
func TestServer(t *testing.T) {
|
||
|
dir, err := ioutil.TempDir("", "tile38")
|
||
|
if err != nil {
|
||
|
t.Fatal(err)
|
||
|
}
|
||
|
defer os.RemoveAll(dir)
|
||
|
var ln net.Listener
|
||
|
var done = make(chan bool, 2)
|
||
|
var ignoreErrs bool
|
||
|
go func() {
|
||
|
// log.Default = log.New(ioutil.Discard, nil)
|
||
|
err := controller.ListenAndServeEx("localhost", port, dir, &ln)
|
||
|
if err != nil {
|
||
|
if !ignoreErrs {
|
||
|
t.Fatal(err)
|
||
|
}
|
||
|
}
|
||
|
done <- true
|
||
|
}()
|
||
|
defer func() {
|
||
|
ignoreErrs = true
|
||
|
ln.Close()
|
||
|
<-done
|
||
|
}()
|
||
|
time.Sleep(time.Millisecond * 100)
|
||
|
t.Run("PingPong", SubTestPingPong)
|
||
|
t.Run("SetPoint", SubTestSetPoint)
|
||
|
t.Run("Set100KB", SubTestSet100KB)
|
||
|
t.Run("Set1MB", SubTestSet1MB)
|
||
|
t.Run("Set10MB", SubTestSet10MB)
|
||
|
}
|
||
|
|
||
|
func SubTestPingPong(t *testing.T) {
|
||
|
conn, err := net.Dial("tcp", fmt.Sprintf("localhost:%d", port))
|
||
|
if err != nil {
|
||
|
t.Fatal(err)
|
||
|
}
|
||
|
defer conn.Close()
|
||
|
rd := bufio.NewReader(conn)
|
||
|
if _, err := conn.Write(buildCommand("PING")); err != nil {
|
||
|
t.Fatal(err)
|
||
|
}
|
||
|
resp, err := readResponse(rd)
|
||
|
if err != nil {
|
||
|
t.Fatal(err)
|
||
|
}
|
||
|
if resp != "+PONG\r\n" {
|
||
|
t.Fatal("expected pong")
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func SubTestSetPoint(t *testing.T) {
|
||
|
conn, err := net.Dial("tcp", fmt.Sprintf("localhost:%d", port))
|
||
|
if err != nil {
|
||
|
t.Fatal(err)
|
||
|
}
|
||
|
defer conn.Close()
|
||
|
rd := bufio.NewReader(conn)
|
||
|
cmd := buildCommand("SET", makeKey(100), makeID(100), "POINT", "33.5", "-115.5")
|
||
|
if _, err := conn.Write(cmd); err != nil {
|
||
|
t.Fatal(err)
|
||
|
}
|
||
|
resp, err := readResponse(rd)
|
||
|
if err != nil {
|
||
|
t.Fatal(err)
|
||
|
}
|
||
|
if resp != "+OK\r\n" {
|
||
|
t.Fatal("expected pong")
|
||
|
}
|
||
|
}
|
||
|
func testSet(t *testing.T, jsonSize, keyIDSize, frag int) {
|
||
|
conn, err := net.Dial("tcp", fmt.Sprintf("localhost:%d", port))
|
||
|
if err != nil {
|
||
|
t.Fatal(err)
|
||
|
}
|
||
|
defer conn.Close()
|
||
|
rd := bufio.NewReader(conn)
|
||
|
key := makeKey(keyIDSize)
|
||
|
id := makeID(keyIDSize)
|
||
|
json := makeJSON(jsonSize)
|
||
|
cmd := buildCommand("SET", key, id, "OBJECT", json)
|
||
|
if frag == 0 {
|
||
|
if _, err := conn.Write(cmd); err != nil {
|
||
|
t.Fatal(err)
|
||
|
}
|
||
|
} else {
|
||
|
var nn int
|
||
|
olen := len(cmd)
|
||
|
for len(cmd) >= frag {
|
||
|
if n, err := conn.Write(cmd[:frag]); err != nil {
|
||
|
t.Fatal(err)
|
||
|
} else {
|
||
|
nn += int(n)
|
||
|
}
|
||
|
cmd = cmd[frag:]
|
||
|
}
|
||
|
if len(cmd) > 0 {
|
||
|
if n, err := conn.Write(cmd); err != nil {
|
||
|
t.Fatal(err)
|
||
|
} else {
|
||
|
nn += int(n)
|
||
|
}
|
||
|
}
|
||
|
if nn != olen {
|
||
|
t.Fatal("invalid sent amount")
|
||
|
}
|
||
|
}
|
||
|
resp, err := readResponse(rd)
|
||
|
if err != nil {
|
||
|
println(len(resp))
|
||
|
t.Fatal(err)
|
||
|
}
|
||
|
if resp != "+OK\r\n" {
|
||
|
t.Fatal("expected pong")
|
||
|
}
|
||
|
cmd = buildCommand("GET", key, id)
|
||
|
if _, err := conn.Write(cmd); err != nil {
|
||
|
t.Fatal(err)
|
||
|
}
|
||
|
resp, err = readResponse(rd)
|
||
|
if err != nil {
|
||
|
t.Fatal(err)
|
||
|
}
|
||
|
diff := float64(len(json))/float64(len(resp)) - 1.0
|
||
|
if diff > 0.1 {
|
||
|
t.Fatal("too big of a difference")
|
||
|
}
|
||
|
}
|
||
|
func SubTestSet100KB(t *testing.T) {
|
||
|
testSet(t, 100*1024, 100, 1024)
|
||
|
}
|
||
|
func SubTestSet1MB(t *testing.T) {
|
||
|
testSet(t, 1024*1024, 100, 1024)
|
||
|
}
|
||
|
func SubTestSet10MB(t *testing.T) {
|
||
|
testSet(t, 10*1024*1024, 100, 1024)
|
||
|
}
|
||
|
func buildCommand(arg ...string) []byte {
|
||
|
var b []byte
|
||
|
b = append(b, '*')
|
||
|
b = append(b, []byte(strconv.FormatInt(int64(len(arg)), 10))...)
|
||
|
b = append(b, '\r', '\n')
|
||
|
for _, arg := range arg {
|
||
|
b = append(b, '$')
|
||
|
b = append(b, []byte(strconv.FormatInt(int64(len(arg)), 10))...)
|
||
|
b = append(b, '\r', '\n')
|
||
|
b = append(b, []byte(arg)...)
|
||
|
b = append(b, '\r', '\n')
|
||
|
}
|
||
|
return b
|
||
|
}
|
||
|
|
||
|
func readResponse(rd *bufio.Reader) (string, error) {
|
||
|
c, err := rd.ReadByte()
|
||
|
if err != nil {
|
||
|
return "", err
|
||
|
}
|
||
|
var resp []byte
|
||
|
switch c {
|
||
|
default:
|
||
|
return string(resp), errors.New("invalid response")
|
||
|
case '+':
|
||
|
resp, err = readString(rd, []byte{'+'})
|
||
|
case '$':
|
||
|
resp, err = readBulk(rd, []byte{'$'})
|
||
|
}
|
||
|
if err != nil {
|
||
|
return string(resp), err
|
||
|
}
|
||
|
return string(resp), nil
|
||
|
}
|
||
|
|
||
|
func readString(rd *bufio.Reader, b []byte) ([]byte, error) {
|
||
|
line, err := rd.ReadBytes('\n')
|
||
|
if err != nil {
|
||
|
return b, err
|
||
|
}
|
||
|
if len(line) == 1 || line[len(line)-2] != '\r' {
|
||
|
return b, errors.New("invalid response")
|
||
|
}
|
||
|
b = append(b, line...)
|
||
|
return b, nil
|
||
|
}
|
||
|
|
||
|
func readBulk(rd *bufio.Reader, b []byte) ([]byte, error) {
|
||
|
line, err := rd.ReadBytes('\n')
|
||
|
if err != nil {
|
||
|
return b, err
|
||
|
}
|
||
|
if len(line) == 1 || line[len(line)-2] != '\r' {
|
||
|
return b, errors.New("invalid response")
|
||
|
}
|
||
|
b = append(b, line...)
|
||
|
sz, err := strconv.ParseUint(string(line[:len(line)-2]), 10, 64)
|
||
|
if err != nil {
|
||
|
return b, err
|
||
|
}
|
||
|
data := make([]byte, int(sz))
|
||
|
if _, err := io.ReadFull(rd, data); err != nil {
|
||
|
return b, err
|
||
|
}
|
||
|
if len(data) < 2 || line[len(line)-2] != '\r' || line[len(line)-1] != '\n' {
|
||
|
return b, errors.New("invalid response")
|
||
|
}
|
||
|
b = append(b, data...)
|
||
|
return b, nil
|
||
|
}
|