Skip to content

Commit

Permalink
implement testkit
Browse files Browse the repository at this point in the history
  • Loading branch information
ehsannm committed May 1, 2022
1 parent 9cb19d3 commit 2347633
Show file tree
Hide file tree
Showing 8 changed files with 204 additions and 28 deletions.
13 changes: 10 additions & 3 deletions ctx.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ import (
"github.com/clubpay/ronykit/utils"
)

const (
abortIndex = math.MaxInt >> 1
)

type (
ErrHandler func(ctx *Context, err error)
HandlerFunc func(ctx *Context)
Expand All @@ -30,9 +34,12 @@ type Context struct {
index int
}

const (
abortIndex = math.MaxInt >> 1
)
func newContext() *Context {
return &Context{
kv: make(map[string]interface{}),
hdr: make(map[string]string),
}
}

// Context returns a context.Background which can be used a reference context for
// other context aware function calls. You can also replace it with your own context
Expand Down
11 changes: 5 additions & 6 deletions edge.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,16 @@ package ronykit

import (
"fmt"
"github.com/clubpay/ronykit/internal/common"
"os"
"os/signal"
)

var (
errServiceAlreadyRegistered common.ErrFunc = func(v ...interface{}) error {
return fmt.Errorf("service %s already registered", v...)
}
"github.com/clubpay/ronykit/internal/errors"
)

var errServiceAlreadyRegistered errors.ErrFunc = func(v ...interface{}) error {
return fmt.Errorf("service %s already registered", v...)
}

// EdgeServer is the main component of the ronykit. It glues all other components of the
// app to each other.
type EdgeServer struct {
Expand Down
24 changes: 19 additions & 5 deletions envelope.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ type Walker interface {
Walk(f func(k, v string) bool)
}

type EnvelopeHdr map[string]string

// Envelope is an envelope around the messages in RonyKIT. Envelopes are created internally
// by the RonyKIT framework, and provide the abstraction which Bundle implementations could
// take advantage of. For example in std/fasthttp Envelope headers translate from/to http
Expand All @@ -21,20 +23,22 @@ type Envelope struct {
ctx *Context
conn Conn
kvl utils.SpinLock
kv map[string]string
kv EnvelopeHdr
m Message
p *sync.Pool

// outgoing identity the Envelope if it is able to send
outgoing bool
outgoing bool
shouldRelease bool
}

func newEnvelope(ctx *Context, conn Conn, outgoing bool) *Envelope {
e, ok := envelopePool.Get().(*Envelope)
if !ok {
e = &Envelope{
kv: map[string]string{},
p: envelopePool,
kv: EnvelopeHdr{},
p: envelopePool,
shouldRelease: true,
}
}

Expand All @@ -50,6 +54,10 @@ func newEnvelope(ctx *Context, conn Conn, outgoing bool) *Envelope {
}

func (e *Envelope) release() {
if !e.shouldRelease {
return
}

for k := range e.kv {
delete(e.kv, k)
}
Expand Down Expand Up @@ -127,7 +135,7 @@ func (e *Envelope) GetMsg() Message {
// You **MUST NOT** call this function more than once.
func (e *Envelope) Send() {
if e.conn == nil {
panic("BUG!! do not call Send multiple times")
panic("BUG!! do not call Send on nil conn, maybe called multiple times ?!")
}

if !e.outgoing {
Expand All @@ -147,6 +155,12 @@ func (e *Envelope) Send() {
e.release()
}

// DontRelease is used by testkit, you should not use it in your code.
// Caution: internal usage only, DO NOT use in your code.
func (e *Envelope) DontRelease() {
e.shouldRelease = false
}

type (
// Modifier is a function which can modify the outgoing Envelope before sending it to the
// client. Modifier only applies to outgoing envelopes.
Expand Down
40 changes: 40 additions & 0 deletions exmples/simple-rest-server/service_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package main

import (
"reflect"
"testing"

"github.com/clubpay/ronykit"
)

func TestSampleService(t *testing.T) {
err := ronykit.NewTestContext().
SetHandler(echoHandler).
Input(
&echoRequest{
RandomID: 2374,
Ok: false,
},
nil,
).
Expectation(
func(out ...*ronykit.Envelope) error {
if len(out) != 1 {
t.Fatalf("expected 1 envelope, got %d", len(out))
}
out1 := out[0]
res1, ok := out1.GetMsg().(*echoResponse)
if !ok {
t.Fatalf("expected echoResponse, got %v", reflect.TypeOf(out1.GetMsg()))
}
if res1.RandomID != 2374 {
t.Fatalf("got unexpected randomID: %d", res1.RandomID)
}

return nil
},
).Run()
if err != nil {
t.Fatal(err)
}
}
16 changes: 7 additions & 9 deletions internal/common/nop_logger.go
Original file line number Diff line number Diff line change
@@ -1,25 +1,23 @@
package common

import "github.com/clubpay/ronykit"
type NOPLogger struct{}

type nopLogger struct{}

func NewNopLogger() ronykit.Logger {
return nopLogger{}
func NewNopLogger() NOPLogger {
return NOPLogger{}
}

func (n nopLogger) Debug(args ...interface{}) {
func (n NOPLogger) Debug(args ...interface{}) {
return
}

func (n nopLogger) Debugf(format string, args ...interface{}) {
func (n NOPLogger) Debugf(format string, args ...interface{}) {
return
}

func (n nopLogger) Error(args ...interface{}) {
func (n NOPLogger) Error(args ...interface{}) {
return
}

func (n nopLogger) Errorf(format string, args ...interface{}) {
func (n NOPLogger) Errorf(format string, args ...interface{}) {
return
}
2 changes: 1 addition & 1 deletion internal/common/error.go → internal/errors/error.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
package common
package errors

type ErrFunc func(v ...interface{}) error
5 changes: 1 addition & 4 deletions north_bridge.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,10 +67,7 @@ func (n *northBridge) OnMessage(c Conn, msg []byte) {
func (n *northBridge) acquireCtx(c Conn) *Context {
ctx, ok := n.ctxPool.Get().(*Context)
if !ok {
ctx = &Context{
kv: make(map[string]interface{}),
hdr: make(map[string]string),
}
ctx = newContext()
}

ctx.conn = c
Expand Down
121 changes: 121 additions & 0 deletions testkit.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
package ronykit

import (
"context"
"sync"

"github.com/clubpay/ronykit/utils"
)

// TestContext is useful for writing end-to-end tests for your Contract handlers.
type TestContext struct {
ctx *Context
handlers HandlerFuncChain
inMsg Message
inHdr EnvelopeHdr
expectFunc func(...*Envelope) error
}

func NewTestContext() *TestContext {
return &TestContext{}
}

func (testCtx *TestContext) SetHandler(h ...HandlerFunc) *TestContext {
testCtx.handlers = h

return testCtx
}

func (testCtx *TestContext) Input(m Message, hdr EnvelopeHdr) *TestContext {
testCtx.inMsg = m
testCtx.inHdr = hdr

return testCtx
}

func (testCtx *TestContext) Expectation(f func(out ...*Envelope) error) *TestContext {
testCtx.expectFunc = f

return testCtx
}

func (testCtx *TestContext) Run() error {
ctx := newContext()
conn := newTestConn()
ctx.conn = conn
ctx.in = newEnvelope(ctx, conn, false).
SetMsg(testCtx.inMsg).
SetHdrMap(testCtx.inHdr)
ctx.ctx = context.Background()
ctx.wf = func(conn Conn, e *Envelope) error {
e.DontRelease()
tc := conn.(*testConn)
tc.Lock()
tc.out = append(tc.out, e)
tc.Unlock()

return nil
}
ctx.handlers = append(ctx.handlers, testCtx.handlers...)
ctx.Next()

return testCtx.expectFunc(conn.out...)
}

type testConn struct {
sync.Mutex

id uint64
clientIP string
stream bool
kv map[string]string
out []*Envelope
}

var _ Conn = (*testConn)(nil)

func newTestConn() *testConn {
return &testConn{
id: utils.RandomUint64(0),
}
}

func (t *testConn) ConnID() uint64 {
return t.id
}

func (t *testConn) ClientIP() string {
return t.clientIP
}

func (t *testConn) Write(data []byte) (int, error) {
return 0, nil
}

func (t *testConn) Stream() bool {
return t.stream
}

func (t *testConn) Walk(f func(key string, val string) bool) {
t.Lock()
defer t.Unlock()

for k, v := range t.kv {
if !f(k, v) {
return
}
}
}

func (t *testConn) Get(key string) string {
t.Lock()
defer t.Unlock()

return t.kv[key]
}

func (t *testConn) Set(key string, val string) {
t.Lock()
t.kv[key] = val
t.Unlock()
}

0 comments on commit 2347633

Please sign in to comment.