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