tile38/tests/tests_test.go

312 lines
6.3 KiB
Go

package tests
import (
"fmt"
"math/rand"
"os"
"os/signal"
"runtime"
"strings"
"sync"
"syscall"
"testing"
"time"
"github.com/gomodule/redigo/redis"
"github.com/tidwall/limiter"
"go.uber.org/atomic"
)
const (
clear = "\x1b[0m"
bright = "\x1b[1m"
dim = "\x1b[2m"
black = "\x1b[30m"
red = "\x1b[31m"
green = "\x1b[32m"
yellow = "\x1b[33m"
blue = "\x1b[34m"
magenta = "\x1b[35m"
cyan = "\x1b[36m"
white = "\x1b[37m"
)
func TestIntegration(t *testing.T) {
mockCleanup(true)
defer mockCleanup(true)
ch := make(chan os.Signal, 1)
signal.Notify(ch, os.Interrupt, syscall.SIGTERM)
go func() {
<-ch
mockCleanup(false)
os.Exit(1)
}()
regTestGroup("keys", subTestKeys)
regTestGroup("json", subTestJSON)
regTestGroup("search", subTestSearch)
regTestGroup("testcmd", subTestTestCmd)
regTestGroup("client", subTestClient)
regTestGroup("scripts", subTestScripts)
regTestGroup("fence", subTestFence)
regTestGroup("info", subTestInfo)
regTestGroup("timeouts", subTestTimeout)
regTestGroup("metrics", subTestMetrics)
regTestGroup("follower", subTestFollower)
regTestGroup("aof", subTestAOF)
regTestGroup("monitor", subTestMonitor)
runTestGroups(t)
}
var allGroups []*testGroup
func runTestGroups(t *testing.T) {
limit := runtime.NumCPU()
if limit > 16 {
limit = 16
}
l := limiter.New(limit)
// Initialize all stores as "skipped", but they'll be unset if the test is
// not actually skipped.
for _, g := range allGroups {
for _, s := range g.subs {
s.skipped.Store(true)
}
}
for _, g := range allGroups {
func(g *testGroup) {
t.Run(g.name, func(t *testing.T) {
for _, s := range g.subs {
func(s *testGroupSub) {
t.Run(s.name, func(t *testing.T) {
s.skipped.Store(false)
var wg sync.WaitGroup
wg.Add(1)
var err error
go func() {
l.Begin()
defer func() {
l.End()
wg.Done()
}()
err = s.run()
}()
if false {
t.Parallel()
t.Run("bg", func(t *testing.T) {
wg.Wait()
if err != nil {
t.Fatal(err)
}
})
}
})
}(s)
}
})
}(g)
}
done := make(chan bool)
go func() {
defer func() { done <- true }()
for {
finished := true
for _, g := range allGroups {
skipped := true
for _, s := range g.subs {
if !s.skipped.Load() {
skipped = false
break
}
}
if !skipped && !g.printed.Load() {
fmt.Printf(bright+"Testing %s\n"+clear, g.name)
g.printed.Store(true)
}
const frtmp = "[" + magenta + " " + clear + "] %s (running) "
for _, s := range g.subs {
if !s.skipped.Load() && !s.printedName.Load() {
fmt.Printf(frtmp, s.name)
s.printedName.Store(true)
}
if s.done.Load() && !s.printedResult.Load() {
fmt.Printf("\r")
msg := fmt.Sprintf(frtmp, s.name)
fmt.Print(strings.Repeat(" ", len(msg)))
fmt.Printf("\r")
if s.err != nil {
fmt.Printf("["+red+"fail"+clear+"] %s\n", s.name)
} else {
fmt.Printf("["+green+"ok"+clear+"] %s\n", s.name)
}
s.printedResult.Store(true)
}
if !s.skipped.Load() && !s.done.Load() {
finished = false
break
}
}
if !finished {
break
}
}
if finished {
break
}
time.Sleep(time.Second / 4)
}
}()
<-done
var fail bool
for _, g := range allGroups {
for _, s := range g.subs {
if s.err != nil {
t.Errorf("%s/%s/%s\n%s", t.Name(), g.name, s.name, s.err)
fail = true
}
}
}
if fail {
t.Fail()
}
}
type testGroup struct {
name string
subs []*testGroupSub
printed atomic.Bool
}
type testGroupSub struct {
g *testGroup
name string
fn func(mc *mockServer) error
err error
skipped atomic.Bool
done atomic.Bool
printedName atomic.Bool
printedResult atomic.Bool
}
func regTestGroup(name string, fn func(g *testGroup)) {
g := &testGroup{name: name}
allGroups = append(allGroups, g)
fn(g)
}
func (g *testGroup) regSubTest(name string, fn func(mc *mockServer) error) {
s := &testGroupSub{g: g, name: name, fn: fn}
g.subs = append(g.subs, s)
}
func (s *testGroupSub) run() (err error) {
// This all happens in a background routine.
defer func() {
s.err = err
s.done.Store(true)
}()
return func() error {
mc, err := mockOpenServer(MockServerOptions{
Silent: true,
Metrics: true,
})
if err != nil {
return err
}
defer mc.Close()
return s.fn(mc)
}()
}
func BenchmarkAll(b *testing.B) {
mockCleanup(true)
defer mockCleanup(true)
ch := make(chan os.Signal, 1)
signal.Notify(ch, os.Interrupt, syscall.SIGTERM)
go func() {
<-ch
mockCleanup(true)
os.Exit(1)
}()
mc, err := mockOpenServer(MockServerOptions{
Silent: true, Metrics: true,
})
if err != nil {
b.Fatal(err)
}
defer mc.Close()
runSubBenchmark(b, "search", mc, subBenchSearch)
}
func loadBenchmarkPoints(b *testing.B, mc *mockServer) (err error) {
const nPoints = 200000
rand.Seed(time.Now().UnixNano())
// add a bunch of points
for i := 0; i < nPoints; i++ {
val := fmt.Sprintf("val:%d", i)
var resp string
var lat, lon, fval float64
fval = rand.Float64()
lat = rand.Float64()*180 - 90
lon = rand.Float64()*360 - 180
resp, err = redis.String(mc.conn.Do("SET",
"mykey", val,
"FIELD", "foo", fval,
"POINT", lat, lon))
if err != nil {
return
}
if resp != "OK" {
err = fmt.Errorf("expected 'OK', got '%s'", resp)
return
}
}
return
}
func runSubBenchmark(b *testing.B, name string, mc *mockServer, bench func(t *testing.B, mc *mockServer)) {
b.Run(name, func(b *testing.B) {
bench(b, mc)
})
}
func runBenchStep(b *testing.B, mc *mockServer, name string, step func(mc *mockServer) error) {
b.Helper()
b.Run(name, func(b *testing.B) {
b.Helper()
if err := func() error {
// reset the current server
mc.ResetConn()
defer mc.ResetConn()
// clear the database so the test is consistent
if err := mc.DoBatch([][]interface{}{
{"OUTPUT", "resp"}, {"OK"},
{"FLUSHDB"}, {"OK"},
}); err != nil {
return err
}
err := loadBenchmarkPoints(b, mc)
if err != nil {
return err
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
if err := step(mc); err != nil {
return err
}
}
return nil
}(); err != nil {
b.Fatal(err)
}
})
}