nanomsg.org/go/mangos/v2@v2.0.9-0.20200203084354-8a092611e461/internal/test/mock.go (about)

     1  // Copyright 2019 The Mangos Authors
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use file except in compliance with the License.
     5  // You may obtain a copy of the license at
     6  //
     7  //    http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package test
    16  
    17  import (
    18  	"nanomsg.org/go/mangos/v2"
    19  	"nanomsg.org/go/mangos/v2/protocol"
    20  	"nanomsg.org/go/mangos/v2/transport"
    21  	"sync"
    22  	"testing"
    23  	"time"
    24  )
    25  
    26  // This file implements a mock transport, useful for testing.
    27  
    28  // ListenerDialer does both Listen and Dial.  (That is, it is both
    29  // a dialer and a listener at once.)
    30  type mockCreator struct {
    31  	pipeQ       chan MockPipe
    32  	closeQ      chan struct{}
    33  	errorQ      chan error
    34  	proto       uint16
    35  	deferClose  bool // sometimes we don't want close to really work yet
    36  	closed      bool
    37  	addr        string
    38  	maxRecvSize int
    39  	lock        sync.Mutex
    40  }
    41  
    42  // mockPipe implements a mocked transport.Pipe
    43  type mockPipe struct {
    44  	lProto     uint16
    45  	rProto     uint16
    46  	closeQ     chan struct{}
    47  	recvQ      chan *mangos.Message
    48  	sendQ      chan *mangos.Message
    49  	recvErrQ   chan error
    50  	sendErrQ   chan error
    51  	deferClose bool
    52  	closed     bool
    53  	lock       sync.Mutex
    54  	initOnce   sync.Once
    55  }
    56  
    57  func (mp *mockPipe) init() {
    58  	mp.initOnce.Do(func() {
    59  		mp.recvQ = make(chan *mangos.Message)
    60  		mp.sendQ = make(chan *mangos.Message)
    61  		mp.closeQ = make(chan struct{})
    62  		mp.recvErrQ = make(chan error, 1)
    63  		mp.sendErrQ = make(chan error, 1)
    64  	})
    65  }
    66  
    67  func (mp *mockPipe) SendQ() <-chan *protocol.Message {
    68  	mp.init()
    69  	return mp.sendQ
    70  }
    71  
    72  func (mp *mockPipe) RecvQ() chan<- *protocol.Message {
    73  	mp.init()
    74  	return mp.recvQ
    75  }
    76  
    77  func (mp *mockPipe) InjectSendError(e error) {
    78  	mp.init()
    79  	select {
    80  	case mp.sendErrQ <- e:
    81  	default:
    82  	}
    83  }
    84  
    85  func (mp *mockPipe) InjectRecvError(e error) {
    86  	mp.init()
    87  	select {
    88  	case mp.recvErrQ <- e:
    89  	default:
    90  	}
    91  }
    92  
    93  func (mp *mockPipe) Send(m *mangos.Message) error {
    94  	mp.init()
    95  	select {
    96  	case <-mp.closeQ:
    97  		return mangos.ErrClosed
    98  	case e := <-mp.sendErrQ:
    99  		return e
   100  	case mp.sendQ <- m:
   101  		return nil
   102  	}
   103  }
   104  
   105  func (mp *mockPipe) Recv() (*mangos.Message, error) {
   106  	mp.init()
   107  	select {
   108  	case <-mp.closeQ:
   109  		return nil, mangos.ErrClosed
   110  	case e := <-mp.recvErrQ:
   111  		return nil, e
   112  	case m := <-mp.recvQ:
   113  		return m, nil
   114  	}
   115  }
   116  
   117  func (mp *mockPipe) GetOption(name string) (interface{}, error) {
   118  	switch name {
   119  	case mangos.OptionRemoteAddr, mangos.OptionLocalAddr:
   120  		return "mock://mock", nil
   121  	}
   122  	return nil, mangos.ErrBadOption
   123  }
   124  
   125  func (mp *mockPipe) Close() error {
   126  	mp.lock.Lock()
   127  	defer mp.lock.Unlock()
   128  	if !mp.closed {
   129  		mp.closed = true
   130  		if !mp.deferClose {
   131  			select {
   132  			case <-mp.closeQ:
   133  			default:
   134  				close(mp.closeQ)
   135  			}
   136  		}
   137  	}
   138  	return nil
   139  }
   140  
   141  func (mp *mockPipe) DeferClose(later bool) {
   142  	mp.lock.Lock()
   143  	defer mp.lock.Unlock()
   144  	mp.deferClose = later
   145  	if !later && mp.closed {
   146  		select {
   147  		case <-mp.closeQ:
   148  		default:
   149  			close(mp.closeQ)
   150  		}
   151  	}
   152  }
   153  
   154  func (mp *mockPipe) MockRecvMsg(d time.Duration) (*protocol.Message, error) {
   155  	select {
   156  	case <-time.After(d):
   157  		return nil, mangos.ErrRecvTimeout
   158  	case m := <-mp.sendQ:
   159  		return m, nil
   160  	case <-mp.closeQ:
   161  		return nil, mangos.ErrClosed
   162  	}
   163  }
   164  
   165  func (mp *mockPipe) MockSendMsg(m *protocol.Message, d time.Duration) error {
   166  	select {
   167  	case <-time.After(d):
   168  		return mangos.ErrSendTimeout
   169  	case mp.recvQ <- m:
   170  		return nil
   171  	case <-mp.closeQ:
   172  		return mangos.ErrClosed
   173  	}
   174  }
   175  
   176  // NewMockPipe creates a mocked transport pipe.
   177  func NewMockPipe(lProto, rProto uint16) MockPipe {
   178  	mp := &mockPipe{
   179  		lProto: lProto,
   180  		rProto: rProto,
   181  	}
   182  	mp.init()
   183  	return mp
   184  }
   185  
   186  // MockPipe is a mocked transport pipe.
   187  type MockPipe interface {
   188  	// SendQ obtains the send queue.  Test code can read from this
   189  	// to get messages sent by the socket.
   190  	SendQ() <-chan *protocol.Message
   191  
   192  	// RecvQ obtains the recv queue.  Test code can write to this
   193  	// to send message to the socket.
   194  	RecvQ() chan<- *protocol.Message
   195  
   196  	// InjectSendError is used to inject an error that will be seen
   197  	// by the next Send() operation.
   198  	InjectSendError(error)
   199  
   200  	// InjectRecvError is used to inject an error that will be seen
   201  	// by the next Recv() operation.
   202  	InjectRecvError(error)
   203  
   204  	// DeferClose defers closing.
   205  	DeferClose(deferring bool)
   206  
   207  	// MockSendMsg lets us inject a message into the queue.
   208  	MockSendMsg(*protocol.Message, time.Duration) error
   209  
   210  	// MockRecvMsg lets us attempt to receive a message.
   211  	MockRecvMsg(time.Duration) (*protocol.Message, error)
   212  
   213  	transport.Pipe
   214  }
   215  
   216  // MockCreator is an abstraction of both dialers and listeners, which
   217  // allows us to test various transport failure conditions.
   218  type MockCreator interface {
   219  	// NewPipe creates a Pipe, but does not add it.  The pipe will
   220  	// use the assigned peer protocol.
   221  	NewPipe(peer uint16) MockPipe
   222  
   223  	// AddPipe adds the given pipe, returning an error if there is
   224  	// no room to do so in the pipeQ.
   225  	AddPipe(pipe MockPipe) error
   226  
   227  	// DeferClose is used to defer close operations.  If Close()
   228  	// is called, and deferring is false, then the close
   229  	// will happen immediately.
   230  	DeferClose(deferring bool)
   231  
   232  	// Close is used to close the creator.
   233  	Close() error
   234  
   235  	// These are methods from transport, for Dialer and Listener.
   236  
   237  	// Dial simulates dialing
   238  	Dial() (transport.Pipe, error)
   239  
   240  	// Listen simulates listening
   241  	Listen() error
   242  
   243  	// Accept simulates accepting.
   244  	Accept() (transport.Pipe, error)
   245  
   246  	// GetOption simulates getting an option.
   247  	GetOption(string) (interface{}, error)
   248  
   249  	// SetOption simulates setting an option.
   250  	SetOption(string, interface{}) error
   251  
   252  	// Address returns the address.
   253  	Address() string
   254  
   255  	// InjectError is used to inject a single error.
   256  	InjectError(error)
   257  }
   258  
   259  func (mc *mockCreator) InjectError(e error) {
   260  	select {
   261  	case mc.errorQ <- e:
   262  	default:
   263  	}
   264  }
   265  
   266  func (mc *mockCreator) getPipe() (transport.Pipe, error) {
   267  	select {
   268  	case mp := <-mc.pipeQ:
   269  		return mp, nil
   270  	case <-mc.closeQ:
   271  		return nil, mangos.ErrClosed
   272  	case e := <-mc.errorQ:
   273  		return nil, e
   274  	}
   275  }
   276  
   277  func (mc *mockCreator) Dial() (transport.Pipe, error) {
   278  	return mc.getPipe()
   279  }
   280  
   281  func (mc *mockCreator) Accept() (transport.Pipe, error) {
   282  	return mc.getPipe()
   283  }
   284  
   285  func (mc *mockCreator) Listen() error {
   286  	select {
   287  	case e := <-mc.errorQ:
   288  		return e
   289  	case <-mc.closeQ:
   290  		return mangos.ErrClosed
   291  	default:
   292  		return nil
   293  	}
   294  }
   295  
   296  func (mc *mockCreator) SetOption(name string, val interface{}) error {
   297  	switch name {
   298  	case "mockError":
   299  		return val.(error)
   300  	case mangos.OptionMaxRecvSize:
   301  		if v, ok := val.(int); ok && v >= 0 {
   302  			// These are magical values used for test validation.
   303  			switch v {
   304  			case 1001:
   305  				return mangos.ErrBadValue
   306  			case 1002:
   307  				return mangos.ErrBadOption
   308  			}
   309  			mc.maxRecvSize = v
   310  			return nil
   311  		}
   312  		return mangos.ErrBadValue
   313  	}
   314  	return mangos.ErrBadOption
   315  }
   316  
   317  func (mc *mockCreator) GetOption(name string) (interface{}, error) {
   318  	switch name {
   319  	case "mock":
   320  		return mc, nil
   321  	case "mockError":
   322  		return nil, mangos.ErrProtoState
   323  	case mangos.OptionMaxRecvSize:
   324  		return mc.maxRecvSize, nil
   325  	}
   326  	return nil, mangos.ErrBadOption
   327  }
   328  
   329  // NewPipe just returns a ready pipe with the local peer set up.
   330  func (mc *mockCreator) NewPipe(peer uint16) MockPipe {
   331  	return NewMockPipe(mc.proto, peer)
   332  }
   333  
   334  // AddPipe adds a pipe.
   335  func (mc *mockCreator) AddPipe(mp MockPipe) error {
   336  	select {
   337  	case mc.pipeQ <- mp:
   338  		return nil
   339  	default:
   340  	}
   341  	return mangos.ErrConnRefused
   342  }
   343  
   344  // DeferClose is used to hold off the close to simulate a the endpoint
   345  // still creating pipes even after close.  It doesn't actually do a close,
   346  // but if this is disabled, and Close() was called previously, then the
   347  // close will happen immediately.
   348  func (mc *mockCreator) DeferClose(b bool) {
   349  	mc.lock.Lock()
   350  	defer mc.lock.Unlock()
   351  	mc.deferClose = b
   352  	if mc.closed && !mc.deferClose {
   353  		select {
   354  		case <-mc.closeQ:
   355  		default:
   356  			close(mc.closeQ)
   357  		}
   358  	}
   359  }
   360  
   361  // Close closes the endpoint, but only if SkipClose is false.
   362  func (mc *mockCreator) Close() error {
   363  	mc.lock.Lock()
   364  	defer mc.lock.Unlock()
   365  	mc.closed = true
   366  	if !mc.deferClose {
   367  		select {
   368  		case <-mc.closeQ:
   369  		default:
   370  			close(mc.closeQ)
   371  		}
   372  	}
   373  	return nil
   374  }
   375  
   376  func (mc *mockCreator) Address() string {
   377  	return mc.addr
   378  }
   379  
   380  type mockTransport struct{}
   381  
   382  func (mockTransport) Scheme() string {
   383  	return "mock"
   384  }
   385  
   386  func (mt mockTransport) newCreator(addr string, sock mangos.Socket) (MockCreator, error) {
   387  	if _, err := transport.StripScheme(mt, addr); err != nil {
   388  		return nil, err
   389  	}
   390  	mc := &mockCreator{
   391  		proto:  sock.Info().Self,
   392  		pipeQ:  make(chan MockPipe, 1),
   393  		closeQ: make(chan struct{}),
   394  		errorQ: make(chan error, 1),
   395  		addr:   addr,
   396  	}
   397  	return mc, nil
   398  }
   399  
   400  func (mt mockTransport) NewListener(addr string, sock mangos.Socket) (transport.Listener, error) {
   401  	return mt.newCreator(addr, sock)
   402  }
   403  
   404  func (mt mockTransport) NewDialer(addr string, sock mangos.Socket) (transport.Dialer, error) {
   405  	return mt.newCreator(addr, sock)
   406  }
   407  
   408  // AddMockTransport registers the mock transport.
   409  func AddMockTransport() {
   410  	transport.RegisterTransport(mockTransport{})
   411  }
   412  
   413  // GetMockListener returns a listener that creates mock pipes.
   414  func GetMockListener(t *testing.T, s mangos.Socket) (mangos.Listener, MockCreator) {
   415  	AddMockTransport()
   416  	l, e := s.NewListener("mock://mock", nil)
   417  	MustSucceed(t, e)
   418  	v, e := l.GetOption("mock")
   419  	MustSucceed(t, e)
   420  	ml, ok := v.(MockCreator)
   421  	MustBeTrue(t, ok)
   422  	return l, ml
   423  }
   424  
   425  // GetMockDialer returns a dialer that creates mock pipes.
   426  func GetMockDialer(t *testing.T, s mangos.Socket) (mangos.Dialer, MockCreator) {
   427  	AddMockTransport()
   428  	d, e := s.NewDialer("mock://mock", nil)
   429  	MustSucceed(t, e)
   430  	v, e := d.GetOption("mock")
   431  	MustSucceed(t, e)
   432  	ml, ok := v.(MockCreator)
   433  	MustBeTrue(t, ok)
   434  	return d, ml
   435  }
   436  
   437  // MockAddPipe simulates adding a pipe.
   438  func MockAddPipe(t *testing.T, s mangos.Socket, c MockCreator, p MockPipe) mangos.Pipe {
   439  	var rv mangos.Pipe
   440  	wg := sync.WaitGroup{}
   441  	wg.Add(1)
   442  	hook := s.SetPipeEventHook(func(ev mangos.PipeEvent, pipe mangos.Pipe) {
   443  		switch ev {
   444  		case mangos.PipeEventAttached:
   445  			rv = pipe
   446  			wg.Done()
   447  		}
   448  	})
   449  	MustSucceed(t, c.AddPipe(p))
   450  	wg.Wait()
   451  	s.SetPipeEventHook(hook)
   452  	return rv
   453  }
   454  
   455  // MockConnect simulates connecting a pipe.
   456  func MockConnect(t *testing.T, s mangos.Socket) (MockPipe, mangos.Pipe) {
   457  	var pipe mangos.Pipe
   458  	wg := sync.WaitGroup{}
   459  	wg.Add(1)
   460  
   461  	l, c := GetMockListener(t, s)
   462  	MustSucceed(t, l.Listen())
   463  
   464  	hook := s.SetPipeEventHook(func(ev mangos.PipeEvent, p mangos.Pipe) {
   465  		switch ev {
   466  		case mangos.PipeEventAttached:
   467  			pipe = p
   468  			wg.Done()
   469  		}
   470  	})
   471  
   472  	mp := c.NewPipe(s.Info().Peer)
   473  	MustSucceed(t, c.AddPipe(mp))
   474  	wg.Wait()
   475  	s.SetPipeEventHook(hook)
   476  	return mp, pipe
   477  }
   478  
   479  // MockMustSendMsg ensures that the pipe sends a message.
   480  func MockMustSendMsg(t *testing.T, p MockPipe, m *mangos.Message, d time.Duration) {
   481  	MustSucceed(t, p.MockSendMsg(m, d))
   482  }
   483  
   484  // MockMustSend ensures that the pipe sends a message with the body given.
   485  func MockMustSend(t *testing.T, p MockPipe, data []byte, d time.Duration) {
   486  	msg := mangos.NewMessage(0)
   487  	msg.Body = append(msg.Body, data...)
   488  	MockMustSendMsg(t, p, msg, d)
   489  }
   490  
   491  // MockMustSendStr ensures that the pipe sends a message with a payload
   492  // containing the given string.
   493  func MockMustSendStr(t *testing.T, p MockPipe, str string, d time.Duration) {
   494  	msg := mangos.NewMessage(0)
   495  	msg.Body = []byte(str)
   496  	MockMustSendMsg(t, p, msg, d)
   497  }
   498  
   499  // MockMustRecvStr ensures that the pipe receives a message with the payload
   500  // equal to the string.
   501  func MockMustRecvStr(t *testing.T, p MockPipe, str string, d time.Duration) {
   502  	m := mangos.NewMessage(0)
   503  	m.Body = append(m.Body, []byte(str)...)
   504  	msg, err := p.MockRecvMsg(d)
   505  	MustSucceed(t, err)
   506  	MustBeTrue(t, string(msg.Body) == str)
   507  }
   508  
   509  // AddrMock returns a generic address for mock sockets.
   510  func AddrMock() string {
   511  	return "mock://mock"
   512  }