Skip to content

Commit

Permalink
feat: error handling (#6)
Browse files Browse the repository at this point in the history
Fixes #3
  • Loading branch information
agaffney authored Mar 23, 2024
1 parent 507c666 commit f31430e
Show file tree
Hide file tree
Showing 2 changed files with 85 additions and 12 deletions.
31 changes: 26 additions & 5 deletions connection.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ type Connection struct {
muxerRecvChan chan *muxer.Segment
doneChan chan any
onceClose sync.Once
errorChan chan error
}

// NewConnection returns a new Connection with the provided conversation entries
Expand All @@ -55,6 +56,7 @@ func NewConnection(
c := &Connection{
conversation: conversation,
doneChan: make(chan any),
errorChan: make(chan error, 1),
}
c.conn, c.mockConn = net.Pipe()
// Start a muxer on the mocked side of the connection
Expand All @@ -76,13 +78,18 @@ func NewConnection(
if !ok {
return
}
panic(fmt.Sprintf("muxer error: %s", err))
c.errorChan <- fmt.Errorf("muxer error: %w", err)
c.Close()
}()
// Start async conversation handler
go c.asyncLoop()
return c
}

func (c *Connection) ErrorChan() <-chan error {
return c.errorChan
}

// Read provides a proxy to the client-side connection's Read function. This is needed to satisfy the net.Conn interface
func (c *Connection) Read(b []byte) (n int, err error) {
return c.conn.Read(b)
Expand Down Expand Up @@ -136,7 +143,18 @@ func (c *Connection) SetWriteDeadline(t time.Time) error {
return c.conn.SetWriteDeadline(t)
}

func (c *Connection) sendError(err error) {
select {
case c.errorChan <- err:
_ = c.Close()
default:
}
}

func (c *Connection) asyncLoop() {
defer func() {
close(c.errorChan)
}()
for _, entry := range c.conversation {
select {
case <-c.doneChan:
Expand All @@ -146,24 +164,27 @@ func (c *Connection) asyncLoop() {
switch entry := entry.(type) {
case ConversationEntryInput:
if err := c.processInputEntry(entry); err != nil {
panic(err.Error())
c.sendError(fmt.Errorf("input error: %w", err))
return
}
case ConversationEntryOutput:
if err := c.processOutputEntry(entry); err != nil {
panic(fmt.Sprintf("output error: %s", err))
c.sendError(fmt.Errorf("output error: %w", err))
return
}
case ConversationEntryClose:
c.Close()
case ConversationEntrySleep:
time.Sleep(entry.Duration)
default:
panic(
fmt.Sprintf(
c.sendError(
fmt.Errorf(
"unknown conversation entry type: %T: %#v",
entry,
entry,
),
)
return
}
}
}
Expand Down
66 changes: 59 additions & 7 deletions mock_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,29 +12,39 @@
// See the License for the specific language governing permissions and
// limitations under the License.

package ouroboros_mock
package ouroboros_mock_test

import (
"fmt"
"testing"
"time"

ouroboros_mock "github.com/blinklabs-io/ouroboros-mock"

ouroboros "github.com/blinklabs-io/gouroboros"
"go.uber.org/goleak"
)

// Basic test of conversation mock functionality
func TestBasic(t *testing.T) {
defer goleak.VerifyNone(t)
mockConn := NewConnection(
ProtocolRoleClient,
[]ConversationEntry{
ConversationEntryHandshakeRequestGeneric,
ConversationEntryHandshakeNtCResponse,
mockConn := ouroboros_mock.NewConnection(
ouroboros_mock.ProtocolRoleClient,
[]ouroboros_mock.ConversationEntry{
ouroboros_mock.ConversationEntryHandshakeRequestGeneric,
ouroboros_mock.ConversationEntryHandshakeNtCResponse,
},
)
// Async mock connection error handler
go func() {
err, ok := <-mockConn.(*ouroboros_mock.Connection).ErrorChan()
if ok {
panic(err)
}
}()
oConn, err := ouroboros.New(
ouroboros.WithConnection(mockConn),
ouroboros.WithNetworkMagic(MockNetworkMagic),
ouroboros.WithNetworkMagic(ouroboros_mock.MockNetworkMagic),
)
if err != nil {
t.Fatalf("unexpected error when creating Ouroboros object: %s", err)
Expand All @@ -50,3 +60,45 @@ func TestBasic(t *testing.T) {
t.Errorf("did not shutdown within timeout")
}
}

func TestError(t *testing.T) {
defer goleak.VerifyNone(t)
expectedErr := "input error: input message protocol ID did not match expected value: expected 999, got 0"
mockConn := ouroboros_mock.NewConnection(
ouroboros_mock.ProtocolRoleClient,
[]ouroboros_mock.ConversationEntry{
ouroboros_mock.ConversationEntryInput{
ProtocolId: 999,
},
},
)
// Async mock connection error handler
asyncErrChan := make(chan error, 1)
go func() {
err := <-mockConn.(*ouroboros_mock.Connection).ErrorChan()
if err == nil {
asyncErrChan <- fmt.Errorf("did not receive expected error")
} else {
if err.Error() != expectedErr {
asyncErrChan <- fmt.Errorf("did not receive expected error\n got: %s\n wanted: %s", err, expectedErr)
}
}
close(asyncErrChan)
}()
_, err := ouroboros.New(
ouroboros.WithConnection(mockConn),
ouroboros.WithNetworkMagic(ouroboros_mock.MockNetworkMagic),
)
if err == nil {
t.Fatalf("did not receive expected error")
}
// Wait for mock connection shutdown
select {
case err, ok := <-asyncErrChan:
if ok {
t.Fatal(err.Error())
}
case <-time.After(2 * time.Second):
t.Fatalf("did not complete within timeout")
}
}

0 comments on commit f31430e

Please sign in to comment.