feat(wip): test reworks

This commit is contained in:
Toby 2023-07-25 16:11:11 -07:00
parent e48bb98024
commit 1c7cb23389
11 changed files with 899 additions and 331 deletions

View file

@ -0,0 +1,9 @@
with-expecter: true
inpackage: true
dir: .
packages:
github.com/apernet/hysteria/core/client:
interfaces:
udpIO:
config:
mockname: mockUDPIO

131
core/client/mock_udpIO.go Normal file
View file

@ -0,0 +1,131 @@
// Code generated by mockery v2.32.0. DO NOT EDIT.
package client
import (
protocol "github.com/apernet/hysteria/core/internal/protocol"
mock "github.com/stretchr/testify/mock"
)
// mockUDPIO is an autogenerated mock type for the udpIO type
type mockUDPIO struct {
mock.Mock
}
type mockUDPIO_Expecter struct {
mock *mock.Mock
}
func (_m *mockUDPIO) EXPECT() *mockUDPIO_Expecter {
return &mockUDPIO_Expecter{mock: &_m.Mock}
}
// ReceiveMessage provides a mock function with given fields:
func (_m *mockUDPIO) ReceiveMessage() (*protocol.UDPMessage, error) {
ret := _m.Called()
var r0 *protocol.UDPMessage
var r1 error
if rf, ok := ret.Get(0).(func() (*protocol.UDPMessage, error)); ok {
return rf()
}
if rf, ok := ret.Get(0).(func() *protocol.UDPMessage); ok {
r0 = rf()
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*protocol.UDPMessage)
}
}
if rf, ok := ret.Get(1).(func() error); ok {
r1 = rf()
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// mockUDPIO_ReceiveMessage_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ReceiveMessage'
type mockUDPIO_ReceiveMessage_Call struct {
*mock.Call
}
// ReceiveMessage is a helper method to define mock.On call
func (_e *mockUDPIO_Expecter) ReceiveMessage() *mockUDPIO_ReceiveMessage_Call {
return &mockUDPIO_ReceiveMessage_Call{Call: _e.mock.On("ReceiveMessage")}
}
func (_c *mockUDPIO_ReceiveMessage_Call) Run(run func()) *mockUDPIO_ReceiveMessage_Call {
_c.Call.Run(func(args mock.Arguments) {
run()
})
return _c
}
func (_c *mockUDPIO_ReceiveMessage_Call) Return(_a0 *protocol.UDPMessage, _a1 error) *mockUDPIO_ReceiveMessage_Call {
_c.Call.Return(_a0, _a1)
return _c
}
func (_c *mockUDPIO_ReceiveMessage_Call) RunAndReturn(run func() (*protocol.UDPMessage, error)) *mockUDPIO_ReceiveMessage_Call {
_c.Call.Return(run)
return _c
}
// SendMessage provides a mock function with given fields: _a0, _a1
func (_m *mockUDPIO) SendMessage(_a0 []byte, _a1 *protocol.UDPMessage) error {
ret := _m.Called(_a0, _a1)
var r0 error
if rf, ok := ret.Get(0).(func([]byte, *protocol.UDPMessage) error); ok {
r0 = rf(_a0, _a1)
} else {
r0 = ret.Error(0)
}
return r0
}
// mockUDPIO_SendMessage_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SendMessage'
type mockUDPIO_SendMessage_Call struct {
*mock.Call
}
// SendMessage is a helper method to define mock.On call
// - _a0 []byte
// - _a1 *protocol.UDPMessage
func (_e *mockUDPIO_Expecter) SendMessage(_a0 interface{}, _a1 interface{}) *mockUDPIO_SendMessage_Call {
return &mockUDPIO_SendMessage_Call{Call: _e.mock.On("SendMessage", _a0, _a1)}
}
func (_c *mockUDPIO_SendMessage_Call) Run(run func(_a0 []byte, _a1 *protocol.UDPMessage)) *mockUDPIO_SendMessage_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].([]byte), args[1].(*protocol.UDPMessage))
})
return _c
}
func (_c *mockUDPIO_SendMessage_Call) Return(_a0 error) *mockUDPIO_SendMessage_Call {
_c.Call.Return(_a0)
return _c
}
func (_c *mockUDPIO_SendMessage_Call) RunAndReturn(run func([]byte, *protocol.UDPMessage) error) *mockUDPIO_SendMessage_Call {
_c.Call.Return(run)
return _c
}
// newMockUDPIO creates a new instance of mockUDPIO. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
// The first argument is typically a *testing.T value.
func newMockUDPIO(t interface {
mock.TestingT
Cleanup(func())
}) *mockUDPIO {
mock := &mockUDPIO{}
mock.Mock.Test(t)
t.Cleanup(func() { mock.AssertExpectations(t) })
return mock
}

View file

@ -2,108 +2,121 @@ package client
import (
"errors"
"fmt"
io2 "io"
"testing"
"time"
"github.com/magiconair/properties/assert"
"github.com/stretchr/testify/mock"
"go.uber.org/goleak"
coreErrs "github.com/apernet/hysteria/core/errors"
"github.com/apernet/hysteria/core/internal/protocol"
"go.uber.org/goleak"
)
type udpEchoIO struct {
MsgCh chan *protocol.UDPMessage
CloseCh chan struct{}
}
func (io *udpEchoIO) ReceiveMessage() (*protocol.UDPMessage, error) {
select {
case m := <-io.MsgCh:
return m, nil
case <-io.CloseCh:
return nil, errors.New("closed")
}
}
func (io *udpEchoIO) SendMessage(buf []byte, msg *protocol.UDPMessage) error {
nMsg := *msg
nMsg.Data = make([]byte, len(msg.Data))
copy(nMsg.Data, msg.Data)
io.MsgCh <- &nMsg
return nil
}
func (io *udpEchoIO) Close() {
close(io.CloseCh)
}
func TestUDPSessionManager(t *testing.T) {
io := &udpEchoIO{
MsgCh: make(chan *protocol.UDPMessage, 10),
CloseCh: make(chan struct{}),
}
io := newMockUDPIO(t)
receiveCh := make(chan *protocol.UDPMessage, 4)
io.EXPECT().ReceiveMessage().RunAndReturn(func() (*protocol.UDPMessage, error) {
m := <-receiveCh
if m == nil {
return nil, errors.New("closed")
}
return m, nil
})
sm := newUDPSessionManager(io)
rChan := make(chan error, 5)
// Test UDP session IO
udpConn1, err := sm.NewUDP()
assert.Equal(t, err, nil)
udpConn2, err := sm.NewUDP()
assert.Equal(t, err, nil)
for i := 0; i < 5; i++ {
go func(id int) {
conn, err := sm.NewUDP()
if err != nil {
rChan <- err
return
}
defer conn.Close()
addr := fmt.Sprintf("wow.com:%d", id)
for j := 0; j < 2; j++ {
s := fmt.Sprintf("hello %d %d", id, j)
err = conn.Send([]byte(s), addr)
if err != nil {
rChan <- err
return
}
bs, addr, err := conn.Receive()
if err != nil {
rChan <- err
return
}
if string(bs) != s || addr != addr {
rChan <- fmt.Errorf("unexpected message: %s %s", bs, addr)
return
}
}
rChan <- nil // Success
}(i)
msg1 := &protocol.UDPMessage{
SessionID: 1,
PacketID: 0,
FragID: 0,
FragCount: 1,
Addr: "random.site.com:9000",
Data: []byte("hello friend"),
}
io.EXPECT().SendMessage(mock.Anything, msg1).Return(nil).Once()
err = udpConn1.Send(msg1.Data, msg1.Addr)
assert.Equal(t, err, nil)
// Check the results
for i := 0; i < 5; i++ {
err := <-rChan
if err != nil {
t.Fatal(err)
}
msg2 := &protocol.UDPMessage{
SessionID: 2,
PacketID: 0,
FragID: 0,
FragCount: 1,
Addr: "another.site.org:8000",
Data: []byte("mr robot"),
}
io.EXPECT().SendMessage(mock.Anything, msg2).Return(nil).Once()
err = udpConn2.Send(msg2.Data, msg2.Addr)
assert.Equal(t, err, nil)
respMsg1 := &protocol.UDPMessage{
SessionID: 1,
PacketID: 0,
FragID: 0,
FragCount: 1,
Addr: msg1.Addr,
Data: []byte("goodbye captain price"),
}
receiveCh <- respMsg1
data, addr, err := udpConn1.Receive()
assert.Equal(t, err, nil)
assert.Equal(t, data, respMsg1.Data)
assert.Equal(t, addr, respMsg1.Addr)
respMsg2 := &protocol.UDPMessage{
SessionID: 2,
PacketID: 0,
FragID: 0,
FragCount: 1,
Addr: msg2.Addr,
Data: []byte("white rose"),
}
receiveCh <- respMsg2
data, addr, err = udpConn2.Receive()
assert.Equal(t, err, nil)
assert.Equal(t, data, respMsg2.Data)
assert.Equal(t, addr, respMsg2.Addr)
respMsg3 := &protocol.UDPMessage{
SessionID: 55, // Bogus session ID that doesn't exist
PacketID: 0,
FragID: 0,
FragCount: 1,
Addr: "burgerking.com:27017",
Data: []byte("impossible whopper"),
}
receiveCh <- respMsg3
// No test for this, just make sure it doesn't panic
// Test close UDP connection unblocks Receive()
errChan := make(chan error, 1)
go func() {
_, _, err := udpConn1.Receive()
errChan <- err
}()
assert.Equal(t, udpConn1.Close(), nil)
assert.Equal(t, <-errChan, io2.EOF)
// Test close IO unblocks Receive() and blocks new UDP creation
errChan = make(chan error, 1)
go func() {
_, _, err := udpConn2.Receive()
errChan <- err
}()
close(receiveCh)
assert.Equal(t, <-errChan, io2.EOF)
_, err = sm.NewUDP()
assert.Equal(t, err, coreErrs.ClosedError{})
// Leak checks
// Create another UDP session
conn, err := sm.NewUDP()
if err != nil {
t.Fatal(err)
}
io.Close()
time.Sleep(1 * time.Second) // Give some time for the goroutines to exit
_, _, err = conn.Receive()
if err != io2.EOF {
t.Fatal("expected EOF after closing io")
}
_, err = sm.NewUDP()
if !errors.Is(err, coreErrs.ClosedError{}) {
t.Fatal("expected ClosedError after closing io")
}
if sm.Count() != 0 {
t.Error("session count should be 0")
}
time.Sleep(1 * time.Second)
assert.Equal(t, sm.Count(), 0, "session count should be 0")
goleak.VerifyNone(t)
}