wip - better tests

This commit is contained in:
tidwall 2022-09-23 07:30:03 -07:00
parent a824d58419
commit d61f0bc6c8
9 changed files with 305 additions and 125 deletions

View File

@ -5,10 +5,11 @@ cd $(dirname "${BASH_SOURCE[0]}")/..
export CGO_ENABLED=0 export CGO_ENABLED=0
# if [ "$NOMODULES" != "1" ]; then cd tests
# export GO111MODULE=on go test -coverpkg=../internal/server -coverprofile=/tmp/coverage.out
# export GOFLAGS=-mod=vendor go tool cover -html=/tmp/coverage.out -o /tmp/coverage.html
# fi echo "details: file:///tmp/coverage.html"
cd ..
cd tests && go test && cd .. # go test -coverpkg=internal/ \
go test $(go list ./... | grep -v /vendor/ | grep -v /tests) # $(go list ./... | grep -v /vendor/ | grep -v /tests)

View File

@ -2,7 +2,7 @@ package tests
import ( import (
"fmt" "fmt"
"io/ioutil" "io"
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
"sync" "sync"
@ -29,7 +29,7 @@ func fence_roaming_webhook_test(mc *mockServer) error {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if err := func() error { if err := func() error {
// Read the request body // Read the request body
body, err := ioutil.ReadAll(r.Body) body, err := io.ReadAll(r.Body)
if err != nil { if err != nil {
return err return err
} }
@ -115,8 +115,10 @@ func fence_roaming_live_test(mc *mockServer) error {
liveReady.Add(1) liveReady.Add(1)
return goMultiFunc(mc, return goMultiFunc(mc,
func() error { func() error {
sc, err := redis.DialTimeout("tcp", fmt.Sprintf(":%d", mc.port), sc, err := redis.Dial("tcp", fmt.Sprintf(":%d", mc.port),
0, time.Second*5, time.Second*5) redis.DialConnectTimeout(0),
redis.DialReadTimeout(time.Second*5),
redis.DialWriteTimeout(time.Second*5))
if err != nil { if err != nil {
liveReady.Done() liveReady.Done()
return err return err

View File

@ -205,7 +205,7 @@ func fence_channel_message_order_test(mc *mockServer) error {
break loop break loop
} }
case error: case error:
fmt.Printf(err.Error()) fmt.Printf("%s\n", err.Error())
} }
} }
@ -230,10 +230,10 @@ func fence_channel_message_order_test(mc *mockServer) error {
// Fire all setup commands on the base client // Fire all setup commands on the base client
for _, cmd := range []string{ for _, cmd := range []string{
"SET points point POINT 33.412529053733444 -111.93368911743164", "SET points point POINT 33.412529053733444 -111.93368911743164",
fmt.Sprintf(`SETCHAN A WITHIN points FENCE OBJECT {"type":"Polygon","coordinates":[[[-111.95205688476562,33.400491820565236],[-111.92630767822266,33.400491820565236],[-111.92630767822266,33.422272258866045],[-111.95205688476562,33.422272258866045],[-111.95205688476562,33.400491820565236]]]}`), `SETCHAN A WITHIN points FENCE OBJECT {"type":"Polygon","coordinates":[[[-111.95205688476562,33.400491820565236],[-111.92630767822266,33.400491820565236],[-111.92630767822266,33.422272258866045],[-111.95205688476562,33.422272258866045],[-111.95205688476562,33.400491820565236]]]}`,
fmt.Sprintf(`SETCHAN B WITHIN points FENCE OBJECT {"type":"Polygon","coordinates":[[[-111.93952560424803,33.403501285221594],[-111.92630767822266,33.403501285221594],[-111.92630767822266,33.41997983836345],[-111.93952560424803,33.41997983836345],[-111.93952560424803,33.403501285221594]]]}`), `SETCHAN B WITHIN points FENCE OBJECT {"type":"Polygon","coordinates":[[[-111.93952560424803,33.403501285221594],[-111.92630767822266,33.403501285221594],[-111.92630767822266,33.41997983836345],[-111.93952560424803,33.41997983836345],[-111.93952560424803,33.403501285221594]]]}`,
fmt.Sprintf(`SETCHAN C WITHIN points FENCE OBJECT {"type":"Polygon","coordinates":[[[-111.9255781173706,33.40342963251261],[-111.91201686859131,33.40342963251261],[-111.91201686859131,33.41994401881284],[-111.9255781173706,33.41994401881284],[-111.9255781173706,33.40342963251261]]]}`), `SETCHAN C WITHIN points FENCE OBJECT {"type":"Polygon","coordinates":[[[-111.9255781173706,33.40342963251261],[-111.91201686859131,33.40342963251261],[-111.91201686859131,33.41994401881284],[-111.9255781173706,33.41994401881284],[-111.9255781173706,33.40342963251261]]]}`,
fmt.Sprintf(`SETCHAN D WITHIN points FENCE OBJECT {"type":"Polygon","coordinates":[[[-111.92562103271484,33.40063513076968],[-111.90021514892578,33.40063513076968],[-111.90021514892578,33.42212898435788],[-111.92562103271484,33.42212898435788],[-111.92562103271484,33.40063513076968]]]}`), `SETCHAN D WITHIN points FENCE OBJECT {"type":"Polygon","coordinates":[[[-111.92562103271484,33.40063513076968],[-111.90021514892578,33.40063513076968],[-111.90021514892578,33.42212898435788],[-111.92562103271484,33.42212898435788],[-111.92562103271484,33.40063513076968]]]}`,
"SET points point POINT 33.412529053733444 -111.91909790039062", "SET points point POINT 33.412529053733444 -111.91909790039062",
} { } {
if _, err := do(bc, cmd); err != nil { if _, err := do(bc, cmd); err != nil {

View File

@ -4,9 +4,6 @@ import (
"errors" "errors"
"fmt" "fmt"
"math/rand" "math/rand"
"os/exec"
"strconv"
"strings"
"testing" "testing"
"time" "time"
@ -36,18 +33,19 @@ func subTestKeys(t *testing.T, mc *mockServer) {
} }
func keys_BOUNDS_test(mc *mockServer) error { func keys_BOUNDS_test(mc *mockServer) error {
return mc.DoBatch([][]interface{}{ return mc.DoBatch(
{"SET", "mykey", "myid1", "POINT", 33, -115}, {"OK"}, Do("SET", "mykey", "myid1", "POINT", 33, -115).OK(),
{"BOUNDS", "mykey"}, {"[[-115 33] [-115 33]]"}, Do("BOUNDS", "mykey").String("[[-115 33] [-115 33]]"),
{"SET", "mykey", "myid2", "POINT", 34, -112}, {"OK"}, Do("SET", "mykey", "myid2", "POINT", 34, -112).OK(),
{"BOUNDS", "mykey"}, {"[[-115 33] [-112 34]]"}, Do("BOUNDS", "mykey").String("[[-115 33] [-112 34]]"),
{"DEL", "mykey", "myid2"}, {1}, Do("DEL", "mykey", "myid2").String("1"),
{"BOUNDS", "mykey"}, {"[[-115 33] [-115 33]]"}, Do("BOUNDS", "mykey").String("[[-115 33] [-115 33]]"),
{"SET", "mykey", "myid3", "OBJECT", `{"type":"Point","coordinates":[-130,38,10]}`}, {"OK"}, Do("SET", "mykey", "myid3", "OBJECT", `{"type":"Point","coordinates":[-130,38,10]}`).OK(),
{"SET", "mykey", "myid4", "OBJECT", `{"type":"Point","coordinates":[-110,25,-8]}`}, {"OK"}, Do("SET", "mykey", "myid4", "OBJECT", `{"type":"Point","coordinates":[-110,25,-8]}`).OK(),
{"BOUNDS", "mykey"}, {"[[-130 25] [-110 38]]"}, Do("BOUNDS", "mykey").String("[[-130 25] [-110 38]]"),
}) )
} }
func keys_DEL_test(mc *mockServer) error { func keys_DEL_test(mc *mockServer) error {
return mc.DoBatch([][]interface{}{ return mc.DoBatch([][]interface{}{
{"SET", "mykey", "myid", "POINT", 33, -115}, {"OK"}, {"SET", "mykey", "myid", "POINT", 33, -115}, {"OK"},
@ -256,62 +254,6 @@ func keys_TTL_test(mc *mockServer) error {
}) })
} }
type PSAUX struct {
User string
PID int
CPU float64
Mem float64
VSZ int
RSS int
TTY string
Stat string
Start string
Time string
Command string
}
func atoi(s string) int {
n, _ := strconv.ParseInt(s, 10, 64)
return int(n)
}
func atof(s string) float64 {
n, _ := strconv.ParseFloat(s, 64)
return float64(n)
}
func psaux(pid int) PSAUX {
var res []byte
res, err := exec.Command("ps", "aux").CombinedOutput()
if err != nil {
return PSAUX{}
}
pids := strconv.FormatInt(int64(pid), 10)
for _, line := range strings.Split(string(res), "\n") {
var words []string
for _, word := range strings.Split(line, " ") {
if word != "" {
words = append(words, word)
}
if len(words) > 11 {
if words[1] == pids {
return PSAUX{
User: words[0],
PID: atoi(words[1]),
CPU: atof(words[2]),
Mem: atof(words[3]),
VSZ: atoi(words[4]),
RSS: atoi(words[5]),
TTY: words[6],
Stat: words[7],
Start: words[8],
Time: words[9],
Command: words[10],
}
}
}
}
}
return PSAUX{}
}
func keys_SET_EX_test(mc *mockServer) (err error) { func keys_SET_EX_test(mc *mockServer) (err error) {
rand.Seed(time.Now().UnixNano()) rand.Seed(time.Now().UnixNano())

View File

@ -1,7 +1,7 @@
package tests package tests
import ( import (
"io/ioutil" "io"
"net/http" "net/http"
"strings" "strings"
"testing" "testing"
@ -13,7 +13,7 @@ func downloadURLWithStatusCode(t *testing.T, u string) (int, string) {
t.Fatal(err) t.Fatal(err)
} }
defer resp.Body.Close() defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body) body, err := io.ReadAll(resp.Body)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }

227
tests/mock_io_test.go Normal file
View File

@ -0,0 +1,227 @@
package tests
import (
"errors"
"fmt"
"go/ast"
"go/parser"
"go/token"
"os"
"path/filepath"
"runtime"
"strings"
"github.com/tidwall/gjson"
)
type IO struct {
args []any
json bool
out any
}
func Do(args ...any) *IO {
return &IO{args: args}
}
func (cmd *IO) JSON() *IO {
cmd.json = true
return cmd
}
func (cmd *IO) String(s string) *IO {
cmd.out = s
return cmd
}
func (cmd *IO) Custom(fn func(s string) error) *IO {
cmd.out = func(s string) error {
if cmd.json {
if !gjson.Valid(s) {
return errors.New("invalid json")
}
}
return fn(s)
}
return cmd
}
func (cmd *IO) OK() *IO {
return cmd.Custom(func(s string) error {
if cmd.json {
if gjson.Get(s, "ok").Type != gjson.True {
return errors.New("not ok")
}
} else if s != "OK" {
return errors.New("not ok")
}
return nil
})
}
type ioVisitor struct {
fset *token.FileSet
ln int
pos int
got bool
data string
end int
done bool
index int
nidx int
frag string
fpos int
}
func (v *ioVisitor) Visit(n ast.Node) ast.Visitor {
if n == nil || v.done {
return nil
}
if v.got {
if int(n.Pos()) > v.end {
v.done = true
return v
}
if n, ok := n.(*ast.CallExpr); ok {
frag := strings.TrimSpace(v.data[int(n.Pos())-1 : int(n.End())])
if _, ok := n.Fun.(*ast.Ident); ok {
if v.index == v.nidx {
frag = strings.TrimSpace(strings.TrimSuffix(frag, "."))
idx := strings.IndexByte(frag, '(')
if idx != -1 {
frag = frag[idx:]
}
v.frag = frag
v.done = true
v.fpos = int(n.Pos())
return v
}
v.nidx++
}
}
return v
}
if int(n.Pos()) == v.pos {
if n, ok := n.(*ast.CallExpr); ok {
v.end = int(n.Rparen)
v.got = true
return v
}
}
return v
}
func (cmd *IO) deepError(index int, err error) error {
oerr := err
werr := func(err error) error {
return fmt.Errorf("batch[%d]: %v: %v", index, oerr, err)
}
// analyse stack
_, file, ln, ok := runtime.Caller(3)
if !ok {
return werr(errors.New("runtime.Caller failed"))
}
// get the character position from line
bdata, err := os.ReadFile(file)
if err != nil {
return werr(err)
}
data := string(bdata)
var pos int
var iln int
var pln int
for i := 0; i < len(data); i++ {
if data[i] == '\n' {
j := pln
line := data[pln:i]
pln = i + 1
iln++
if iln == ln {
line = strings.TrimSpace(line)
if !strings.HasPrefix(line, "return mc.DoBatch(") {
return oerr
}
for ; j < len(data); j++ {
if data[j] == 'm' {
break
}
}
pos = j + 1
break
}
}
}
if pos == 0 {
return oerr
}
fset := token.NewFileSet()
pfile, err := parser.ParseFile(fset, file, nil, 0)
if err != nil {
return werr(err)
}
v := &ioVisitor{
fset: fset,
ln: ln,
pos: pos,
data: string(data),
index: index,
}
ast.Walk(v, pfile)
if v.fpos == 0 {
return oerr
}
pln = 1
for i := 0; i < len(data); i++ {
if data[i] == '\n' {
if i > v.fpos {
break
}
pln++
}
}
fsig := fmt.Sprintf("%s:%d", filepath.Base(file), pln)
emsg := oerr.Error()
if strings.HasPrefix(emsg, "expected ") &&
strings.Contains(emsg, ", got ") {
emsg = "" +
" EXPECTED: " + strings.Split(emsg, ", got ")[0][9:] + "\n" +
" GOT: " +
strings.Split(emsg, ", got ")[1]
} else {
emsg = "" +
" ERROR: " + emsg
}
return fmt.Errorf("\n%s: entry[%d]\n COMMAND: %s\n%s",
fsig, index+1, v.frag, emsg)
}
func (mc *mockServer) doIOTest(index int, cmd *IO) error {
// switch json mode if desired
if cmd.json {
if !mc.ioJSON {
if _, err := mc.Do("OUTPUT", "json"); err != nil {
return err
}
mc.ioJSON = true
}
} else {
if mc.ioJSON {
if _, err := mc.Do("OUTPUT", "resp"); err != nil {
return err
}
mc.ioJSON = false
}
}
err := mc.DoExpect(cmd.out, cmd.args[0].(string), cmd.args[1:]...)
if err != nil {
return cmd.deepError(index, err)
}
return nil
}

View File

@ -3,11 +3,10 @@ package tests
import ( import (
"errors" "errors"
"fmt" "fmt"
"io/ioutil" "io"
"log" "log"
"math/rand" "math/rand"
"os" "os"
"strconv"
"strings" "strings"
"time" "time"
@ -24,7 +23,7 @@ func mockCleanup(silent bool) {
if !silent { if !silent {
fmt.Printf("Cleanup: may take some time... ") fmt.Printf("Cleanup: may take some time... ")
} }
files, _ := ioutil.ReadDir(".") files, _ := os.ReadDir(".")
for _, file := range files { for _, file := range files {
if strings.HasPrefix(file.Name(), "data-mock-") { if strings.HasPrefix(file.Name(), "data-mock-") {
os.RemoveAll(file.Name()) os.RemoveAll(file.Name())
@ -37,10 +36,8 @@ func mockCleanup(silent bool) {
type mockServer struct { type mockServer struct {
port int port int
//join string
//n *finn.Node
//m *Machine
conn redis.Conn conn redis.Conn
ioJSON bool
} }
func mockOpenServer(silent bool) (*mockServer, error) { func mockOpenServer(silent bool) (*mockServer, error) {
@ -50,7 +47,7 @@ func mockOpenServer(silent bool) (*mockServer, error) {
if !silent { if !silent {
fmt.Printf("Starting test server at port %d\n", port) fmt.Printf("Starting test server at port %d\n", port)
} }
logOutput := ioutil.Discard logOutput := io.Discard
if os.Getenv("PRINTLOG") == "1" { if os.Getenv("PRINTLOG") == "1" {
logOutput = os.Stderr logOutput = os.Stderr
} }
@ -80,7 +77,7 @@ func (s *mockServer) waitForStartup() error {
var lerr error var lerr error
start := time.Now() start := time.Now()
for { for {
if time.Now().Sub(start) > time.Second*5 { if time.Since(start) > time.Second*5 {
if lerr != nil { if lerr != nil {
return lerr return lerr
} }
@ -159,7 +156,28 @@ func (s *mockServer) Do(commandName string, args ...interface{}) (interface{}, e
return resps[0], nil return resps[0], nil
} }
func (mc *mockServer) DoBatch(commands ...interface{}) error { //[][]interface{}) error { func (mc *mockServer) DoBatch(commands ...interface{}) error {
// Probe for I/O tests
if len(commands) > 0 {
if _, ok := commands[0].(*IO); ok {
var cmds []*IO
// If the first is an I/O test then all must be
for _, cmd := range commands {
if cmd, ok := cmd.(*IO); ok {
cmds = append(cmds, cmd)
} else {
return errors.New("DoBatch cannot mix I/O tests with other kinds")
}
}
for i, cmd := range cmds {
if err := mc.doIOTest(i, cmd); err != nil {
return err
}
}
return nil
}
}
var tag string var tag string
for _, commands := range commands { for _, commands := range commands {
switch commands := commands.(type) { switch commands := commands.(type) {
@ -181,6 +199,10 @@ func (mc *mockServer) DoBatch(commands ...interface{}) error { //[][]interface{}
} }
} }
tag = "" tag = ""
case *IO:
return errors.New("DoBatch cannot mix I/O tests with other kinds")
default:
return fmt.Errorf("Unknown command input")
} }
} }
return nil return nil
@ -281,27 +303,3 @@ func (mc *mockServer) DoExpect(expect interface{}, commandName string, args ...i
} }
return nil 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
}
}

View File

@ -30,7 +30,7 @@ func TestAll(t *testing.T) {
mockCleanup(false) mockCleanup(false)
defer mockCleanup(false) defer mockCleanup(false)
ch := make(chan os.Signal) ch := make(chan os.Signal, 1)
signal.Notify(ch, os.Interrupt, syscall.SIGTERM) signal.Notify(ch, os.Interrupt, syscall.SIGTERM)
go func() { go func() {
<-ch <-ch
@ -82,8 +82,9 @@ func runStep(t *testing.T, mc *mockServer, name string, step func(mc *mockServer
} }
return nil return nil
}(); err != nil { }(); err != nil {
fmt.Printf("["+red+"fail"+clear+"]: %s\n", name) fmt.Fprintf(os.Stderr, "["+red+"fail"+clear+"]: %s\n", name)
t.Fatal(err) t.Fatal(err)
// t.Fatal(err)
} }
fmt.Printf("["+green+"ok"+clear+"]: %s\n", name) fmt.Printf("["+green+"ok"+clear+"]: %s\n", name)
}) })
@ -93,7 +94,7 @@ func BenchmarkAll(b *testing.B) {
mockCleanup(true) mockCleanup(true)
defer mockCleanup(true) defer mockCleanup(true)
ch := make(chan os.Signal) ch := make(chan os.Signal, 1)
signal.Notify(ch, os.Interrupt, syscall.SIGTERM) signal.Notify(ch, os.Interrupt, syscall.SIGTERM)
go func() { go func() {
<-ch <-ch

View File

@ -56,6 +56,9 @@ func setup(mc *mockServer, count int, points bool) (err error) {
func timeout_spatial_test(mc *mockServer) (err error) { func timeout_spatial_test(mc *mockServer) (err error) {
err = setup(mc, 10000, true) err = setup(mc, 10000, true)
if err != nil {
return err
}
return mc.DoBatch([][]interface{}{ return mc.DoBatch([][]interface{}{
{"SCAN", "mykey", "WHERE", "foo", -1, 2, "COUNT"}, {"10000"}, {"SCAN", "mykey", "WHERE", "foo", -1, 2, "COUNT"}, {"10000"},
@ -70,6 +73,9 @@ func timeout_spatial_test(mc *mockServer) (err error) {
func timeout_search_test(mc *mockServer) (err error) { func timeout_search_test(mc *mockServer) (err error) {
err = setup(mc, 10000, false) err = setup(mc, 10000, false)
if err != nil {
return err
}
return mc.DoBatch([][]interface{}{ return mc.DoBatch([][]interface{}{
{"SEARCH", "mykey", "MATCH", "val:*", "COUNT"}, {"10000"}, {"SEARCH", "mykey", "MATCH", "val:*", "COUNT"}, {"10000"},
@ -122,6 +128,9 @@ func scriptTimeoutErr(v interface{}) (resp, expect interface{}) {
func timeout_within_scripts_test(mc *mockServer) (err error) { func timeout_within_scripts_test(mc *mockServer) (err error) {
err = setup(mc, 10000, true) err = setup(mc, 10000, true)
if err != nil {
return err
}
script1 := "return tile38.call('timeout', 10, 'SCAN', 'mykey', 'WHERE', 'foo', -1, 2, 'COUNT')" script1 := "return tile38.call('timeout', 10, 'SCAN', 'mykey', 'WHERE', 'foo', -1, 2, 'COUNT')"
script2 := "return tile38.call('timeout', 0.000001, 'SCAN', 'mykey', 'WHERE', 'foo', -1, 2, 'COUNT')" script2 := "return tile38.call('timeout', 0.000001, 'SCAN', 'mykey', 'WHERE', 'foo', -1, 2, 'COUNT')"