-
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.
- Loading branch information
Showing
5 changed files
with
321 additions
and
0 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 |
---|---|---|
@@ -1,2 +1,9 @@ | ||
# go-mock-io | ||
Test your socket i/o in pure golang | ||
|
||
I needed a library to help me test working with a serial connection. Unfortunately, I didn't see one readily available in Golang that served my needs. So, I figured, I'll have to make one. | ||
|
||
## Basic concept | ||
|
||
Create a mock io.ReadWriteCloser, and give it certain expectations. When someone writing to the mock stream meets any of those expectations, the stream responds with that expectation's response. | ||
|
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,72 @@ | ||
package mockio | ||
|
||
// Expect provides an interface for the mock io stream to use when matching incoming | ||
// information. | ||
type Expect interface { | ||
Match(b []byte) (response []byte, count int, ok bool) | ||
} | ||
|
||
// ExpectBytes provides a kind of Expect that precisely matches a sequence of bytes to | ||
// respond with another sequence of bytes. | ||
type ExpectBytes struct { | ||
Expect []byte | ||
Respond []byte | ||
} | ||
|
||
// Match implements the Expect interface for ExpectBytes. | ||
func (e *ExpectBytes) Match(b []byte) (response []byte, count int, ok bool) { | ||
l := len(e.Expect) | ||
ok = true | ||
for i := 0; i < len(b); i++ { | ||
if i == l { | ||
break | ||
} | ||
if b[i] != e.Expect[i] { | ||
ok = false | ||
break | ||
} | ||
} | ||
|
||
if ok { | ||
response = append(response, e.Respond...) | ||
count = l | ||
} | ||
return response, count, ok | ||
} | ||
|
||
// NewExpectBytes provides a convenience constructor for ExpectBytes. | ||
func NewExpectBytes(expect []byte, respond []byte) *ExpectBytes { | ||
return &ExpectBytes{ | ||
Expect: expect, | ||
Respond: respond, | ||
} | ||
} | ||
|
||
// ExpectFuncTest describes a test that matches a sequence of bytes written to the mock | ||
// io stream. | ||
// Always start the match from b[0]. Use count to indicate how many bytes in b were | ||
// consumed in the test. Set ok to true if there was a successful match. | ||
type ExpectFuncTest func(b []byte) (count int, ok bool) | ||
|
||
// ExpectFunc provides a kind of Expect that tests a sequence of bytes with a function. | ||
type ExpectFunc struct { | ||
Test ExpectFuncTest | ||
Respond []byte | ||
} | ||
|
||
// Match implements the Expect interface for ExpectFunc. | ||
func (e *ExpectFunc) Match(b []byte) (response []byte, count int, ok bool) { | ||
count, ok = e.Test(b) | ||
if ok { | ||
response = append(response, e.Respond...) | ||
} | ||
return response, count, ok | ||
} | ||
|
||
// NewExpectFunc provides a convenience constructor for ExpectFunc. | ||
func NewExpectFunc(fn ExpectFuncTest, response []byte) *ExpectFunc { | ||
return &ExpectFunc{ | ||
Test: fn, | ||
Respond: response, | ||
} | ||
} |
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,3 @@ | ||
module github.com/tvanriper/go-mock-io | ||
|
||
go 1.18 |
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,83 @@ | ||
/* | ||
Package mockio provides a mock I/O ReadWriteCloser stream for testing your software's i/o stream handling. | ||
Example usage: | ||
func main() { | ||
s := NewMockIO() | ||
s.Expect(NewExpectBytes([]byte{1,2},[]byte{3,4})) | ||
s.Write([]byte{1,2}) | ||
b := make([]byte,2) | ||
n, err := s.Read(b) | ||
if err != nil { | ||
panic(err) | ||
} | ||
fmt.Printf("n: %d, b: %#v", n, b) | ||
} | ||
which should print: | ||
n: 2, b: []byte{0x3, 0x4} | ||
You can use this to test things like serial I/O, or perhaps console I/O, without having | ||
to open a serial port or a console, allowing for automated testing in a controlled fashion. | ||
*/ | ||
package mockio | ||
|
||
import "bytes" | ||
|
||
// MockIO provides a mock I/O ReadWriteCloser to test your software's i/o stream handling. | ||
type MockIO struct { | ||
buffer *bytes.Buffer | ||
holding []byte | ||
Expects []Expect | ||
} | ||
|
||
// NewMockIO constructs a new MockIO. | ||
func NewMockIO() *MockIO { | ||
return &MockIO{ | ||
buffer: bytes.NewBuffer([]byte{}), | ||
holding: []byte{}, | ||
Expects: []Expect{}, | ||
} | ||
} | ||
|
||
// Read implements the Reader interface for the MockIO stream. | ||
// Use this with your software to test that it reads correctly. | ||
func (m *MockIO) Read(data []byte) (n int, err error) { | ||
return m.buffer.Read(data) | ||
} | ||
|
||
// Write implements the Writer interface for the MockIO stream. | ||
// Use this with your software to test that it writes correctly. | ||
func (m *MockIO) Write(data []byte) (n int, err error) { | ||
m.holding = append(m.holding, data...) | ||
respond := []byte{} | ||
for _, test := range m.Expects { | ||
response, count, ok := test.Match(m.holding) | ||
if !ok { | ||
continue | ||
} | ||
respond = append(respond, response...) | ||
m.holding = m.holding[count:] | ||
break | ||
} | ||
m.buffer.Write(respond) | ||
return len(data), nil | ||
} | ||
|
||
// Expect adds a new Expect item to a list of things for the MockIO Write to expect. | ||
func (m *MockIO) Expect(exp Expect) { | ||
m.Expects = append(m.Expects, exp) | ||
} | ||
|
||
// ClearExpectations removes all items stored in MockIO's Expect buffer. | ||
func (m *MockIO) ClearExpectations() { | ||
m.Expects = []Expect{} | ||
} | ||
|
||
// Close implements the Closer interface for the MockIO stream. | ||
func (m *MockIO) Close() (err error) { | ||
m.buffer.Reset() | ||
m.holding = []byte{} | ||
return err | ||
} |
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,156 @@ | ||
package mockio | ||
|
||
import ( | ||
"io" | ||
"log" | ||
"testing" | ||
) | ||
|
||
func MatchBytes(l []byte, r []byte) bool { | ||
ll := len(l) | ||
lr := len(r) | ||
if ll != lr { | ||
return false | ||
} | ||
for i := 0; i < ll; i++ { | ||
if l[i] != r[i] { | ||
return false | ||
} | ||
} | ||
return true | ||
} | ||
|
||
func TestMockIO(t *testing.T) { | ||
m := NewMockIO() | ||
exp := []byte{3, 4} | ||
m.Expect(NewExpectBytes([]byte{1, 2}, exp)) | ||
m.Write([]byte{1, 2}) | ||
b := make([]byte, 2) | ||
n, err := m.Read(b) | ||
if err != nil { | ||
t.Error(err) | ||
} | ||
if n != 2 { | ||
t.Errorf("expected %d, received %d", len(exp), n) | ||
} | ||
|
||
if !MatchBytes(b, exp) { | ||
t.Errorf("expected %#v but received %#v", exp, b) | ||
} | ||
|
||
m.Write([]byte{1, 2, 1, 2}) | ||
|
||
b = make([]byte, 2) | ||
n, err = m.Read(b) | ||
if err != nil { | ||
t.Error(err) | ||
} | ||
if n != 2 { | ||
t.Errorf("expected %d, received %d", len(exp), n) | ||
} | ||
|
||
if !MatchBytes(b, exp) { | ||
t.Errorf("expected %#v but received %#v", exp, b) | ||
} | ||
|
||
l := len(m.holding) | ||
if l != 2 { | ||
t.Errorf("expected 2 bytes holding, but found %d", l) | ||
} | ||
} | ||
|
||
func TestMockIOFunc(t *testing.T) { | ||
m := NewMockIO() | ||
test := func(b []byte) (count int, ok bool) { | ||
if len(b) != 2 { | ||
return 0, false | ||
} | ||
if b[0] != 0 && b[1] != 1 { | ||
return 0, false | ||
} | ||
return 2, true | ||
} | ||
exp := []byte{3, 4} | ||
m.Expect(NewExpectFunc(test, exp)) | ||
m.Write([]byte{0, 1}) | ||
b := make([]byte, 2) | ||
n, err := m.Read(b) | ||
if err != nil { | ||
t.Error(err) | ||
} | ||
if n != 2 { | ||
t.Errorf("expected %d, received %d", len(exp), n) | ||
} | ||
|
||
if !MatchBytes(b, exp) { | ||
t.Errorf("expected %#v but received %#v", exp, b) | ||
} | ||
|
||
log.Printf("received: %#v\n", b) | ||
} | ||
|
||
func TestMockIOFail(t *testing.T) { | ||
m := NewMockIO() | ||
exp := []byte{3, 4} | ||
m.Expect(NewExpectBytes([]byte{1, 2}, exp)) | ||
m.Write([]byte{0, 2}) | ||
b := make([]byte, 2) | ||
n, err := m.Read(b) | ||
if err == nil { | ||
t.Errorf("Expected failure") | ||
} | ||
if err != io.EOF { | ||
t.Errorf("expected EOF but got: %s", err) | ||
} | ||
if n != 0 { | ||
t.Errorf("expected no data, received %d", n) | ||
} | ||
l := len(m.buffer.Bytes()) | ||
if l != 0 { | ||
t.Errorf("expected no bytes in buffer, but found %d: %#v", l, m.buffer.Bytes()) | ||
} | ||
|
||
m.Write([]byte{1, 4}) | ||
b = make([]byte, 2) | ||
n, err = m.Read(b) | ||
if err == nil { | ||
t.Errorf("Expected failure") | ||
} | ||
if err != io.EOF { | ||
t.Errorf("expected EOF but got: %s", err) | ||
} | ||
if n != 0 { | ||
t.Errorf("expected no data, received %d", n) | ||
} | ||
l = len(m.buffer.Bytes()) | ||
if l != 0 { | ||
t.Errorf("expected no bytes in buffer, but found %d: %#v", l, m.buffer.Bytes()) | ||
} | ||
} | ||
|
||
func TestMockClear(t *testing.T) { | ||
m := NewMockIO() | ||
exp := []byte{3, 4} | ||
m.Expect(NewExpectBytes([]byte{1, 2}, exp)) | ||
l := len(m.Expects) | ||
if l != 1 { | ||
t.Errorf("expected 1 item, but found %d", l) | ||
} | ||
m.ClearExpectations() | ||
l = len(m.Expects) | ||
if l != 0 { | ||
t.Errorf("expected 0 items, but found %d", l) | ||
} | ||
|
||
m.Expect(NewExpectBytes([]byte{1, 2}, exp)) | ||
m.Write([]byte{3, 4}) | ||
l = len(m.holding) | ||
if l != 2 { | ||
t.Errorf("holding should have 2 items, but found %d", l) | ||
} | ||
m.Close() | ||
l = len(m.holding) | ||
if l != 0 { | ||
t.Errorf("holding should have no items, but found %d", l) | ||
} | ||
} |