-
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
test on dummy X un/checked with/out reply,refactor
- Loading branch information
Showing
3 changed files
with
704 additions
and
646 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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{}) | ||
} |
Oops, something went wrong.