Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

enhanced test suite #1355

Draft
wants to merge 5 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 89 additions & 0 deletions integration_tests/commands/tests/aggregator.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package tests

import (
"fmt"
"testing"
"time"

"github.com/stretchr/testify/assert"
)

var allTests []Meta

// SetupCmd struct is used to define a setup command
// Input: Input commands to be executed
// Output: Expected output of the setup command
// Keep the setup tests simple which return "OK" or "integer" or "(nil)"
// For complex setup tests, use the test cases
type SetupCmd struct {
Input []string
Output []interface{}
}

const (
EQUAL = "EQUAL"
JSON = "JSON"
ARRAY = "ARRAY"
)

// Meta struct is used to define a test case
// Name: Name of the test case
// Cmd: Command to be executed
// Setup: Setup commands to be executed
// Input: Input commands to be executed
// Output: Expected output of the test case
// Delays: Delays to be introduced between commands
// CleanupKeys: list of keys to be cleaned up after the test case
type Meta struct {
Name string
Cmd string
Setup []SetupCmd
Input []string
Output []interface{}
Assert []string
Delays []time.Duration
Cleanup []string
}

// RegisterTests appends a Meta slice to the global test list
func RegisterTests(tests []Meta) {
allTests = append(allTests, tests...)
}

// GetAllTests returns all registered test cases
func GetAllTests() []Meta {
return allTests
}

func SwitchAsserts(t *testing.T, kind string, expected, actual interface{}) {
switch kind {
case EQUAL:
assert.Equal(t, expected, actual)
case JSON:
assert.JSONEq(t, expected.(string), actual.(string))
case ARRAY:
assert.ElementsMatch(t, expected, actual)
}
}

func Validate(test *Meta) bool {
// Validate test structure
if len(test.Input) != len(test.Output) {
fmt.Printf("Test %s: mismatch between number of inputs (%d) and outputs (%d)", test.Name, len(test.Input), len(test.Output))
return false
}
if len(test.Delays) > 0 && len(test.Delays) != len(test.Input) {
fmt.Printf("Test %s: mismatch between number of inputs (%d) and delays (%d)", test.Name, len(test.Input), len(test.Delays))
return false
}
if len(test.Setup) > 0 {
for _, setup := range test.Setup {
if len(setup.Input) != len(setup.Output) {
fmt.Printf("Test %s (Setup): mismatch between number of setup inputs (%d) and outputs (%d)", test.Name, len(setup.Input), len(setup.Output))
return false
}
}
}

return true
}
30 changes: 30 additions & 0 deletions integration_tests/commands/tests/get.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package tests

import (
"time"
)

var getTestCases = []Meta{
{
Name: "Get on non-existing key",
Input: []string{"GET k"},
Output: []interface{}{"(nil)"},
},
{
Name: "Get on existing key",
Input: []string{"SET k v", "GET k"},
Output: []interface{}{"OK", "v"},
Cleanup: []string{"k"},
},
{
Name: "Get with expiration",
Input: []string{"SET k v EX 2", "GET k", "GET k"},
Output: []interface{}{"OK", "v", "(nil)"},
Delays: []time.Duration{0, 0, 3 * time.Second},
Cleanup: []string{"k"},
},
}

func init() {
RegisterTests(getTestCases)
}
63 changes: 63 additions & 0 deletions integration_tests/commands/tests/http_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package tests

import (
"log"
"testing"
"time"

"github.com/dicedb/dice/config"
"github.com/dicedb/dice/integration_tests/commands/tests/parsers"
"github.com/dicedb/dice/integration_tests/commands/tests/servers"
"github.com/stretchr/testify/assert"
)

func init() {
parser := config.NewConfigParser()
if err := parser.ParseDefaults(config.DiceConfig); err != nil {
log.Fatalf("failed to load configuration: %v", err)
}
}

func TestHttpCommands(t *testing.T) {
exec := servers.NewHTTPCommandExecutor()
allTests := GetAllTests()

for _, test := range allTests {
t.Run(test.Name, func(t *testing.T) {
if !Validate(&test) {
t.Fatal("Test progression failed...")
}

// Setup commands
if len(test.Setup) > 0 {
for _, setup := range test.Setup {
for idx, cmd := range setup.Input {
output, _ := parsers.HttpCommandExecuter(exec, cmd)

Check failure on line 35 in integration_tests/commands/tests/http_test.go

View workflow job for this annotation

GitHub Actions / build

undefined: parsers.HttpCommandExecuter
assert.Equal(t, setup.Output[idx], output)
}
}
}

for idx, cmd := range test.Input {
if len(test.Delays) > 0 {
time.Sleep(test.Delays[idx])
}
output, _ := parsers.HttpCommandExecuter(exec, cmd)

Check failure on line 45 in integration_tests/commands/tests/http_test.go

View workflow job for this annotation

GitHub Actions / build

undefined: parsers.HttpCommandExecuter
if len(test.Assert) > 0 {
SwitchAsserts(t, test.Assert[idx], test.Output[idx], output)
} else {
assert.Equal(t, test.Output[idx], output)
}
}
if len(test.Cleanup) > 0 {
// join all the keys to be cleaned up
keys := ""
for _, key := range test.Cleanup {
keys += key + " "
}
parsers.HttpCommandExecuter(exec, `DEL `+keys)

Check failure on line 58 in integration_tests/commands/tests/http_test.go

View workflow job for this annotation

GitHub Actions / build

undefined: parsers.HttpCommandExecuter
}
})

}
}
59 changes: 59 additions & 0 deletions integration_tests/commands/tests/main_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package tests

import (
"context"
"os"
"sync"
"testing"
"time"

"github.com/dicedb/dice/integration_tests/commands/tests/servers"
)

func TestMain(m *testing.M) {
ctx, cancel := context.WithCancel(context.Background())
var wg sync.WaitGroup
respOpts := servers.TestServerOptions{
Port: 9738,
}
httpOpts := servers.TestServerOptions{
Port: 8083,
}
wsOpts := servers.TestServerOptions{
Port: 8380,
}

wg.Add(1)
go func() {
defer wg.Done()
servers.RunRespServer(ctx, &wg, respOpts)
}()

wg.Add(1)
go func() {
defer wg.Done()
servers.RunHTTPServer(ctx, &wg, httpOpts)
}()

//TODO: RunWebSocketServer
wg.Add(1)
go func() {
defer wg.Done()
servers.RunWebsocketServer(ctx, &wg, wsOpts)
}()

// Wait for the server to start
time.Sleep(2 * time.Second)

// Run the test suite
exitCode := m.Run()

// Signal all servers to stop
cancel()

// Wait for all goroutines to finish
wg.Wait()

// Exit with the appropriate code
os.Exit(exitCode)
}
12 changes: 12 additions & 0 deletions integration_tests/commands/tests/parsers/common.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package parsers

func ParseResponse(response interface{}) interface{} {
switch response := response.(type) {
case float64:
return int64(response)
case nil:
return "(nil)"
default:
return response
}
}
34 changes: 34 additions & 0 deletions integration_tests/commands/tests/parsers/http.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package parsers

import (
"strings"

"github.com/dicedb/dice/integration_tests/commands/tests/servers"
)

func HTTPCommandExecuter(exec *servers.HTTPCommandExecutor, cmd string) (interface{}, error) {
// convert the command to a HTTPCommand
// cmd starts with Command and Body is values after that
tokens := strings.Split(cmd, " ")
command := tokens[0]
body := make(map[string]interface{})
if len(tokens) > 1 {
// convert the tokens []string to []interface{}
values := make([]interface{}, len(tokens[1:]))
for i, v := range tokens[1:] {
values[i] = v
}
body["values"] = values
} else {
body["values"] = []interface{}{}
}
diceHTTPCmd := servers.HTTPCommand{
Command: strings.ToLower(command),
Body: body,
}
res, err := exec.FireCommand(diceHTTPCmd)
if err != nil {
return nil, err
}
return ParseResponse(res), nil
}
41 changes: 41 additions & 0 deletions integration_tests/commands/tests/parsers/resp.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package parsers

import (
"io"
"log/slog"
"net"
"os"

"github.com/dicedb/dice/internal/clientio"
"github.com/dicedb/dice/testutils"
)

func RespCommandExecuter(conn net.Conn, cmd string) interface{} {
var err error
args := testutils.ParseCommand(cmd)
_, err = conn.Write(clientio.Encode(args, false))
if err != nil {
slog.Error(
"error while firing command",
slog.Any("error", err),
slog.String("command", cmd),
)
os.Exit(1)
}

rp := clientio.NewRESPParser(conn)
v, err := rp.DecodeOne()
if err != nil {
if err == io.EOF {
return nil
}
slog.Error(
"error while firing command",
slog.Any("error", err),
slog.String("command", cmd),
)
os.Exit(1)
}

return v
}
39 changes: 39 additions & 0 deletions integration_tests/commands/tests/parsers/websocket.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package parsers

import (
"encoding/json"
"fmt"

"github.com/gorilla/websocket"
)

func FireWSCommandAndReadResponse(conn *websocket.Conn, cmd string) (interface{}, error) {
err := FireWSCommand(conn, cmd)
if err != nil {
return nil, err
}

// read the response
_, resp, err := conn.ReadMessage()
if err != nil {
return nil, err
}

// marshal to json
var respJSON interface{}
if err = json.Unmarshal(resp, &respJSON); err != nil {
return nil, fmt.Errorf("error unmarshaling response")
}
respJSON = ParseResponse(respJSON)
return respJSON, nil
}

func FireWSCommand(conn *websocket.Conn, cmd string) error {
// send request
err := conn.WriteMessage(websocket.TextMessage, []byte(cmd))
if err != nil {
return err
}

return nil
}
Loading
Loading