mirror of https://github.com/tidwall/tile38.git
283 lines
5.9 KiB
Go
283 lines
5.9 KiB
Go
package tests
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"log"
|
|
"math/rand"
|
|
"os"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/gomodule/redigo/redis"
|
|
"github.com/tidwall/sjson"
|
|
"github.com/tidwall/tile38/core"
|
|
tlog "github.com/tidwall/tile38/internal/log"
|
|
"github.com/tidwall/tile38/internal/server"
|
|
)
|
|
|
|
var errTimeout = errors.New("timeout")
|
|
|
|
func mockCleanup() {
|
|
fmt.Printf("Cleanup: may take some time... ")
|
|
files, _ := ioutil.ReadDir(".")
|
|
for _, file := range files {
|
|
if strings.HasPrefix(file.Name(), "data-mock-") {
|
|
os.RemoveAll(file.Name())
|
|
}
|
|
}
|
|
fmt.Printf("OK\n")
|
|
}
|
|
|
|
type mockServer struct {
|
|
port int
|
|
//join string
|
|
//n *finn.Node
|
|
//m *Machine
|
|
conn redis.Conn
|
|
}
|
|
|
|
func mockOpenServer() (*mockServer, error) {
|
|
rand.Seed(time.Now().UnixNano())
|
|
port := rand.Int()%20000 + 20000
|
|
dir := fmt.Sprintf("data-mock-%d", port)
|
|
fmt.Printf("Starting test server at port %d\n", port)
|
|
logOutput := ioutil.Discard
|
|
if os.Getenv("PRINTLOG") == "1" {
|
|
logOutput = os.Stderr
|
|
}
|
|
core.DevMode = true
|
|
s := &mockServer{port: port}
|
|
tlog.SetOutput(logOutput)
|
|
go func() {
|
|
if err := server.Serve("localhost", port, dir, true); err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
}()
|
|
if err := s.waitForStartup(); err != nil {
|
|
s.Close()
|
|
return nil, err
|
|
}
|
|
return s, nil
|
|
}
|
|
|
|
func (s *mockServer) waitForStartup() error {
|
|
var lerr error
|
|
start := time.Now()
|
|
for {
|
|
if time.Now().Sub(start) > time.Second*5 {
|
|
if lerr != nil {
|
|
return lerr
|
|
}
|
|
return errTimeout
|
|
}
|
|
resp, err := redis.String(s.Do("SET", "please", "allow", "POINT", "33", "-115"))
|
|
if err != nil {
|
|
lerr = err
|
|
} else if resp != "OK" {
|
|
lerr = errors.New("not OK")
|
|
} else {
|
|
resp, err := redis.Int(s.Do("DEL", "please", "allow"))
|
|
if err != nil {
|
|
lerr = err
|
|
} else if resp != 1 {
|
|
lerr = errors.New("not 1")
|
|
} else {
|
|
return nil
|
|
}
|
|
}
|
|
time.Sleep(time.Millisecond * 100)
|
|
}
|
|
}
|
|
|
|
func (mc *mockServer) Close() {
|
|
if mc.conn != nil {
|
|
mc.conn.Close()
|
|
}
|
|
}
|
|
|
|
func (mc *mockServer) ResetConn() {
|
|
if mc.conn != nil {
|
|
mc.conn.Close()
|
|
mc.conn = nil
|
|
}
|
|
}
|
|
|
|
func (s *mockServer) DoPipeline(cmds [][]interface{}) ([]interface{}, error) {
|
|
if s.conn == nil {
|
|
var err error
|
|
s.conn, err = redis.Dial("tcp", fmt.Sprintf(":%d", s.port))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
//defer conn.Close()
|
|
for _, cmd := range cmds {
|
|
if err := s.conn.Send(cmd[0].(string), cmd[1:]...); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
if err := s.conn.Flush(); err != nil {
|
|
return nil, err
|
|
}
|
|
var resps []interface{}
|
|
for i := 0; i < len(cmds); i++ {
|
|
resp, err := s.conn.Receive()
|
|
if err != nil {
|
|
resps = append(resps, err)
|
|
} else {
|
|
resps = append(resps, resp)
|
|
}
|
|
}
|
|
return resps, nil
|
|
}
|
|
func (s *mockServer) Do(commandName string, args ...interface{}) (interface{}, error) {
|
|
resps, err := s.DoPipeline([][]interface{}{
|
|
append([]interface{}{commandName}, args...),
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if len(resps) != 1 {
|
|
return nil, errors.New("invalid number or responses")
|
|
}
|
|
return resps[0], nil
|
|
}
|
|
|
|
func (mc *mockServer) DoBatch(commands ...interface{}) error { //[][]interface{}) error {
|
|
var tag string
|
|
for _, commands := range commands {
|
|
switch commands := commands.(type) {
|
|
case string:
|
|
tag = commands
|
|
case [][]interface{}:
|
|
for i := 0; i < len(commands); i += 2 {
|
|
cmds := commands[i]
|
|
if dur, ok := cmds[0].(time.Duration); ok {
|
|
time.Sleep(dur)
|
|
} else {
|
|
if err := mc.DoExpect(commands[i+1][0], cmds[0].(string), cmds[1:]...); err != nil {
|
|
if tag == "" {
|
|
return fmt.Errorf("batch[%d]: %v", i/2, err)
|
|
} else {
|
|
return fmt.Errorf("batch[%d][%v]: %v", i/2, tag, err)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
tag = ""
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func normalize(v interface{}) interface{} {
|
|
switch v := v.(type) {
|
|
default:
|
|
return v
|
|
case []interface{}:
|
|
for i := 0; i < len(v); i++ {
|
|
v[i] = normalize(v[i])
|
|
}
|
|
case []uint8:
|
|
return string(v)
|
|
}
|
|
return v
|
|
}
|
|
func (mc *mockServer) DoExpect(expect interface{}, commandName string, args ...interface{}) error {
|
|
resp, err := mc.Do(commandName, args...)
|
|
if err != nil {
|
|
if exs, ok := expect.(string); ok {
|
|
if err.Error() == exs {
|
|
return nil
|
|
}
|
|
}
|
|
return err
|
|
}
|
|
if b, ok := resp.([]byte); ok && len(b) > 1 && b[0] == '{' {
|
|
b, err = sjson.DeleteBytes(b, "elapsed")
|
|
if err == nil {
|
|
resp = b
|
|
}
|
|
}
|
|
oresp := resp
|
|
resp = normalize(resp)
|
|
if expect == nil && resp != nil {
|
|
return fmt.Errorf("expected '%v', got '%v'", expect, resp)
|
|
}
|
|
if vv, ok := resp.([]interface{}); ok {
|
|
var ss []string
|
|
for _, v := range vv {
|
|
if v == nil {
|
|
ss = append(ss, "nil")
|
|
} else if s, ok := v.(string); ok {
|
|
ss = append(ss, s)
|
|
} else if b, ok := v.([]uint8); ok {
|
|
if b == nil {
|
|
ss = append(ss, "nil")
|
|
} else {
|
|
ss = append(ss, string(b))
|
|
}
|
|
} else {
|
|
ss = append(ss, fmt.Sprintf("%v", v))
|
|
}
|
|
}
|
|
resp = ss
|
|
}
|
|
if b, ok := resp.([]uint8); ok {
|
|
if b == nil {
|
|
resp = nil
|
|
} else {
|
|
resp = string([]byte(b))
|
|
}
|
|
}
|
|
err = func() (err error) {
|
|
defer func() {
|
|
v := recover()
|
|
if v != nil {
|
|
err = fmt.Errorf("panic '%v'", v)
|
|
}
|
|
}()
|
|
if fn, ok := expect.(func(v, org interface{}) (resp, expect interface{})); ok {
|
|
resp, expect = fn(resp, oresp)
|
|
}
|
|
if fn, ok := expect.(func(v interface{}) (resp, expect interface{})); ok {
|
|
resp, expect = fn(resp)
|
|
}
|
|
return nil
|
|
}()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if fmt.Sprintf("%v", resp) != fmt.Sprintf("%v", expect) {
|
|
return fmt.Errorf("expected '%v', got '%v'", expect, resp)
|
|
}
|
|
return nil
|
|
}
|
|
func round(v float64, decimals int) float64 {
|
|
var pow float64 = 1
|
|
for i := 0; i < decimals; i++ {
|
|
pow *= 10
|
|
}
|
|
return float64(int((v*pow)+0.5)) / pow
|
|
}
|
|
|
|
func exfloat(v float64, decimals int) func(v interface{}) (resp, expect interface{}) {
|
|
ex := round(v, decimals)
|
|
return func(v interface{}) (resp, expect interface{}) {
|
|
var s string
|
|
if b, ok := v.([]uint8); ok {
|
|
s = string(b)
|
|
} else {
|
|
s = fmt.Sprintf("%v", v)
|
|
}
|
|
n, err := strconv.ParseFloat(s, 64)
|
|
if err != nil {
|
|
return v, ex
|
|
}
|
|
return round(n, decimals), ex
|
|
}
|
|
}
|