Skip to content

Commit

Permalink
test on dummy X un/checked with/out reply,refactor
Browse files Browse the repository at this point in the history
  • Loading branch information
jezek committed Oct 25, 2018
1 parent a258329 commit 94eccf4
Show file tree
Hide file tree
Showing 3 changed files with 704 additions and 646 deletions.
261 changes: 261 additions & 0 deletions dummyNetConn.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,261 @@
package xgb

import (
"bytes"
"errors"
"io"
"net"
"time"
)

type dAddr struct {
s string
}

func (_ dAddr) Network() string { return "dummy" }
func (a dAddr) String() string { return a.s }

var (
dNCErrNotImplemented = errors.New("command not implemented")
dNCErrClosed = errors.New("server closed")
dNCErrWrite = errors.New("server write failed")
dNCErrRead = errors.New("server read failed")
dNCErrResponse = errors.New("server response error")
)

type dNCIoResult struct {
n int
err error
}
type dNCIo struct {
b []byte
result chan dNCIoResult
}

type dNCCWriteLock struct{}
type dNCCWriteUnlock struct{}
type dNCCWriteError struct{}
type dNCCWriteSuccess struct{}
type dNCCReadLock struct{}
type dNCCReadUnlock struct{}
type dNCCReadError struct{}
type dNCCReadSuccess struct{}

// dummy net.Conn interface. Needs to be constructed via newDummyNetConn([...]) function.
type dNC struct {
reply func([]byte) []byte
addr dAddr
in, out chan dNCIo
control chan interface{}
done chan struct{}
}

// Results running dummy server, satisfying net.Conn interface for test purposes.
// 'name' parameter will be returned via (*dNC).Local/RemoteAddr().String()
// 'reply' parameter function will be runned only on successful (*dNC).Write(b) with 'b' as parameter to 'reply'. The result will be stored in internal buffer and can be retrieved later via (*dNC).Read([...]) method.
// It is users responsibility to stop and clean up resources with (*dNC).Close, if not needed anymore.
// By default, the (*dNC).Write([...]) and (*dNC).Read([...]) methods are unlocked and will not result in error.
//TODO make (*dNC).SetDeadline, (*dNC).SetReadDeadline, (*dNC).SetWriteDeadline work proprely.
func newDummyNetConn(name string, reply func([]byte) []byte) *dNC {

s := &dNC{
reply,
dAddr{name},
make(chan dNCIo), make(chan dNCIo),
make(chan interface{}),
make(chan struct{}),
}

in, out := s.in, chan dNCIo(nil)
buf := &bytes.Buffer{}
errorRead, errorWrite := false, false
lockRead := false

go func() {
defer close(s.done)
for {
select {
case dxsio := <-in:
if errorWrite {
dxsio.result <- dNCIoResult{0, dNCErrWrite}
break
}

response := s.reply(dxsio.b)

buf.Write(response)
dxsio.result <- dNCIoResult{len(dxsio.b), nil}

if !lockRead && buf.Len() > 0 && out == nil {
out = s.out
}
case dxsio := <-out:
if errorRead {
dxsio.result <- dNCIoResult{0, dNCErrRead}
break
}

n, err := buf.Read(dxsio.b)
dxsio.result <- dNCIoResult{n, err}

if buf.Len() == 0 {
out = nil
}
case ci := <-s.control:
if ci == nil {
return
}
switch ci.(type) {
case dNCCWriteLock:
in = nil
case dNCCWriteUnlock:
in = s.in
case dNCCWriteError:
errorWrite = true
case dNCCWriteSuccess:
errorWrite = false
case dNCCReadLock:
out = nil
lockRead = true
case dNCCReadUnlock:
lockRead = false
if buf.Len() > 0 && out == nil {
out = s.out
}
case dNCCReadError:
errorRead = true
case dNCCReadSuccess:
errorRead = false
default:
}
}
}
}()
return s
}

// Shuts down dummy net.Conn server. Every blocking or future method calls will do nothing and result in error.
// Result will be dNCErrClosed if server was allready closed.
// Server can not be unclosed.
func (s *dNC) Close() error {
select {
case s.control <- nil:
<-s.done
return nil
case <-s.done:
}
return dNCErrClosed
}

// Performs a write action to server.
// If not locked by (*dNC).WriteLock, it results in error or success. If locked, this method will block until unlocked, or closed.
//
// This method can be set to result in error or success, via (*dNC).WriteError() or (*dNC).WriteSuccess() methods.
//
// If setted to result in error, the 'reply' function will NOT be called and internal buffer will NOT increasethe.
// Result will be (0, dNCErrWrite).
//
// If setted to result in success, the 'reply' function will be called and its result will be writen to internal buffer.
// If there is something in the internal buffer, the (*dNC).Read([...]) will be unblocked (if not previously locked with (*dNC).ReadLock).
// Result will be (len(b), nil)
//
// If server was closed previously, result will be (0, dNCErrClosed).
func (s *dNC) Write(b []byte) (int, error) {
resChan := make(chan dNCIoResult)
select {
case s.in <- dNCIo{b, resChan}:
res := <-resChan
return res.n, res.err
case <-s.done:
}
return 0, dNCErrClosed
}

// Performs a read action from server.
// If locked by (*dNC).ReadLock(), this method will block until unlocked with (*dNC).ReadUnlock(), or server closes.
//
// If not locked, this method can be setted to result imidiatly in error, will block if internal buffer is empty or will perform an read operation from internal buffer.
//
// If setted to result in error via (*dNC).ReadError(), the result will be (0, dNCErrWrite).
//
// If not locked and not setted to result in error via (*dNC).ReadSuccess(), this method will block until internall buffer is not empty, than it returns the result of the buffer read operation via (*bytes.Buffer).Read([...]).
// If the internal buffer is empty after this method, all follwing (*dNC).Read([...]), requests will block until internall buffer is filled after successful write requests.
//
// If server was closed previously, result will be (0, io.EOF).
func (s *dNC) Read(b []byte) (int, error) {
resChan := make(chan dNCIoResult)
select {
case s.out <- dNCIo{b, resChan}:
res := <-resChan
return res.n, res.err
case <-s.done:
}
return 0, io.EOF
}
func (s *dNC) LocalAddr() net.Addr { return s.addr }
func (s *dNC) RemoteAddr() net.Addr { return s.addr }
func (s *dNC) SetDeadline(t time.Time) error { return dNCErrNotImplemented }
func (s *dNC) SetReadDeadline(t time.Time) error { return dNCErrNotImplemented }
func (s *dNC) SetWriteDeadline(t time.Time) error { return dNCErrNotImplemented }

func (s *dNC) Control(i interface{}) error {
select {
case s.control <- i:
return nil
case <-s.done:
}
return dNCErrClosed
}

// Locks writing. All write requests will be blocked until write is unlocked with (*dNC).WriteUnlock, or server closes.
func (s *dNC) WriteLock() error {
return s.Control(dNCCWriteLock{})
}

// Unlocks writing. All blocked write requests until now will be accepted.
func (s *dNC) WriteUnlock() error {
return s.Control(dNCCWriteUnlock{})
}

// Unlocks writing and makes (*dNC).Write to result (0, dNCErrWrite).
func (s *dNC) WriteError() error {
if err := s.WriteUnlock(); err != nil {
return err
}
return s.Control(dNCCWriteError{})
}

// Unlocks writing and makes (*dNC).Write([...]) not result in error. See (*dNC).Write for details.
func (s *dNC) WriteSuccess() error {
if err := s.WriteUnlock(); err != nil {
return err
}
return s.Control(dNCCWriteSuccess{})
}

// Locks reading. All read requests will be blocked until read is unlocked with (*dNC).ReadUnlock, or server closes.
// (*dNC).Read([...]) wil block even after successful write.
func (s *dNC) ReadLock() error {
return s.Control(dNCCReadLock{})
}

// Unlocks reading. If the internall buffer is not empty, next read will not block.
func (s *dNC) ReadUnlock() error {
return s.Control(dNCCReadUnlock{})
}

// Unlocks read and makes every blocked and following (*dNC).Read([...]) imidiatly result in error. See (*dNC).Read for details.
func (s *dNC) ReadError() error {
if err := s.ReadUnlock(); err != nil {
return err
}
return s.Control(dNCCReadError{})
}

// Unlocks read and makes every blocked and following (*dNC).Read([...]) requests be handled, if according to internal buffer. See (*dNC).Read for details.
func (s *dNC) ReadSuccess() error {
if err := s.ReadUnlock(); err != nil {
return err
}
return s.Control(dNCCReadSuccess{})
}
Loading

0 comments on commit 94eccf4

Please sign in to comment.