mirror of
https://github.com/gortc/stun.git
synced 2025-10-05 08:36:59 +08:00
504 lines
9.6 KiB
Go
504 lines
9.6 KiB
Go
package stun
|
|
|
|
import (
|
|
"bufio"
|
|
"bytes"
|
|
"errors"
|
|
"io"
|
|
"log"
|
|
"os"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
type TestAgent struct {
|
|
f chan Handler
|
|
}
|
|
|
|
func (n *TestAgent) Close() error {
|
|
close(n.f)
|
|
return nil
|
|
}
|
|
|
|
func (TestAgent) Collect(time.Time) error { return nil }
|
|
|
|
func (TestAgent) Process(m *Message) error { return nil }
|
|
|
|
func (n *TestAgent) Start(id [TransactionIDSize]byte, deadline time.Time, f Handler) error {
|
|
n.f <- f
|
|
return nil
|
|
}
|
|
|
|
func (n *TestAgent) Stop([TransactionIDSize]byte) error {
|
|
return nil
|
|
}
|
|
|
|
type noopConnection struct{}
|
|
|
|
func (noopConnection) Write(b []byte) (int, error) {
|
|
return len(b), nil
|
|
}
|
|
|
|
func (noopConnection) Read(b []byte) (int, error) {
|
|
time.Sleep(time.Millisecond)
|
|
return 0, io.EOF
|
|
}
|
|
|
|
func (noopConnection) Close() error {
|
|
return nil
|
|
}
|
|
|
|
func BenchmarkClient_Do(b *testing.B) {
|
|
b.ReportAllocs()
|
|
agent := &TestAgent{
|
|
f: make(chan Handler, 1000),
|
|
}
|
|
client, err := NewClient(ClientOptions{
|
|
Agent: agent,
|
|
Connection: noopConnection{},
|
|
})
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
defer client.Close()
|
|
go func() {
|
|
e := Event{
|
|
Error: nil,
|
|
Message: nil,
|
|
}
|
|
for f := range agent.f {
|
|
f.HandleEvent(e)
|
|
}
|
|
}()
|
|
m := new(Message)
|
|
m.Encode()
|
|
noopF := func(event Event) {
|
|
// pass
|
|
}
|
|
for i := 0; i < b.N; i++ {
|
|
if err := client.Do(m, time.Time{}, noopF); err != nil {
|
|
b.Fatal(err)
|
|
}
|
|
}
|
|
}
|
|
|
|
type testConnection struct {
|
|
write func([]byte) (int, error)
|
|
b []byte
|
|
stopped bool
|
|
}
|
|
|
|
func (t *testConnection) Write(b []byte) (int, error) {
|
|
return t.write(b)
|
|
}
|
|
|
|
func (t *testConnection) Close() error {
|
|
if t.stopped {
|
|
return errors.New("already stopped")
|
|
}
|
|
t.stopped = true
|
|
return nil
|
|
}
|
|
|
|
func (t *testConnection) Read(b []byte) (int, error) {
|
|
if t.stopped {
|
|
return 0, io.EOF
|
|
}
|
|
return copy(b, t.b), nil
|
|
}
|
|
|
|
func TestClosedOrPanic(t *testing.T) {
|
|
closedOrPanic(nil)
|
|
closedOrPanic(ErrAgentClosed)
|
|
func() {
|
|
defer func() {
|
|
r := recover()
|
|
if r != io.EOF {
|
|
t.Error(r)
|
|
}
|
|
}()
|
|
closedOrPanic(io.EOF)
|
|
}()
|
|
}
|
|
|
|
func TestClient_Do(t *testing.T) {
|
|
response := MustBuild(TransactionID, BindingSuccess)
|
|
response.Encode()
|
|
conn := &testConnection{
|
|
b: response.Raw,
|
|
write: func(bytes []byte) (int, error) {
|
|
return len(bytes), nil
|
|
},
|
|
}
|
|
c, err := NewClient(ClientOptions{
|
|
Connection: conn,
|
|
})
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
defer func() {
|
|
if err := c.Close(); err != nil {
|
|
t.Error(err)
|
|
}
|
|
if err := c.Close(); err == nil {
|
|
t.Error("second close should fail")
|
|
}
|
|
if err := c.Do(MustBuild(TransactionID), time.Time{}, nil); err == nil {
|
|
t.Error("Do after Close should fail")
|
|
}
|
|
}()
|
|
m := MustBuild(
|
|
NewTransactionIDSetter(response.TransactionID),
|
|
)
|
|
d := time.Now().Add(time.Second)
|
|
if err := c.Do(m, d, func(event Event) {
|
|
if event.Error != nil {
|
|
t.Error(event.Error)
|
|
}
|
|
}); err != nil {
|
|
t.Error(err)
|
|
}
|
|
m = MustBuild(TransactionID)
|
|
if err := c.Do(m, d, nil); err != nil {
|
|
t.Error(err)
|
|
}
|
|
}
|
|
|
|
func TestCloseErr_Error(t *testing.T) {
|
|
for id, c := range []struct {
|
|
Err CloseErr
|
|
Out string
|
|
}{
|
|
{CloseErr{}, "failed to close: <nil> (connection), <nil> (agent)"},
|
|
{CloseErr{
|
|
AgentErr: io.ErrUnexpectedEOF,
|
|
}, "failed to close: <nil> (connection), unexpected EOF (agent)"},
|
|
{CloseErr{
|
|
ConnectionErr: io.ErrUnexpectedEOF,
|
|
}, "failed to close: unexpected EOF (connection), <nil> (agent)"},
|
|
} {
|
|
if out := c.Err.Error(); out != c.Out {
|
|
t.Errorf("[%d]: Error(%#v) %q (got) != %q (expected)",
|
|
id, c.Err, out, c.Out,
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestStopErr_Error(t *testing.T) {
|
|
for id, c := range []struct {
|
|
Err StopErr
|
|
Out string
|
|
}{
|
|
{StopErr{}, "error while stopping due to <nil>: <nil>"},
|
|
{StopErr{
|
|
Err: io.ErrUnexpectedEOF,
|
|
}, "error while stopping due to <nil>: unexpected EOF"},
|
|
{StopErr{
|
|
Cause: io.ErrUnexpectedEOF,
|
|
}, "error while stopping due to unexpected EOF: <nil>"},
|
|
} {
|
|
if out := c.Err.Error(); out != c.Out {
|
|
t.Errorf("[%d]: Error(%#v) %q (got) != %q (expected)",
|
|
id, c.Err, out, c.Out,
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
type errorAgent struct {
|
|
startErr error
|
|
stopErr error
|
|
closeErr error
|
|
}
|
|
|
|
func (a errorAgent) Close() error { return a.closeErr }
|
|
|
|
func (errorAgent) Collect(time.Time) error { return nil }
|
|
|
|
func (errorAgent) Process(m *Message) error { return nil }
|
|
|
|
func (a errorAgent) Start(id [TransactionIDSize]byte, deadline time.Time, f Handler) error {
|
|
return a.startErr
|
|
}
|
|
|
|
func (a errorAgent) Stop([TransactionIDSize]byte) error {
|
|
return a.stopErr
|
|
}
|
|
|
|
func TestClientAgentError(t *testing.T) {
|
|
response := MustBuild(TransactionID, BindingSuccess)
|
|
response.Encode()
|
|
conn := &testConnection{
|
|
b: response.Raw,
|
|
write: func(bytes []byte) (int, error) {
|
|
return len(bytes), nil
|
|
},
|
|
}
|
|
c, err := NewClient(ClientOptions{
|
|
Agent: errorAgent{
|
|
startErr: io.ErrUnexpectedEOF,
|
|
},
|
|
Connection: conn,
|
|
})
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
defer func() {
|
|
if err := c.Close(); err != nil {
|
|
t.Error(err)
|
|
}
|
|
}()
|
|
m := MustBuild(NewTransactionIDSetter(response.TransactionID))
|
|
if err := c.Do(m, time.Time{}, nil); err != nil {
|
|
t.Error(err)
|
|
}
|
|
if err := c.Do(m, time.Time{}, func(event Event) {
|
|
if event.Error == nil {
|
|
t.Error("error expected")
|
|
}
|
|
}); err != io.ErrUnexpectedEOF {
|
|
t.Error("error expected")
|
|
}
|
|
}
|
|
|
|
func TestClientConnErr(t *testing.T) {
|
|
conn := &testConnection{
|
|
write: func(bytes []byte) (int, error) {
|
|
return 0, io.ErrClosedPipe
|
|
},
|
|
}
|
|
c, err := NewClient(ClientOptions{
|
|
Connection: conn,
|
|
})
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
defer func() {
|
|
if err := c.Close(); err != nil {
|
|
t.Error(err)
|
|
}
|
|
}()
|
|
m := MustBuild(TransactionID)
|
|
if err := c.Do(m, time.Time{}, nil); err == nil {
|
|
t.Error("error expected")
|
|
}
|
|
if err := c.Do(m, time.Time{}, noopHandler); err == nil {
|
|
t.Error("error expected")
|
|
}
|
|
}
|
|
|
|
func TestClientConnErrStopErr(t *testing.T) {
|
|
conn := &testConnection{
|
|
write: func(bytes []byte) (int, error) {
|
|
return 0, io.ErrClosedPipe
|
|
},
|
|
}
|
|
c, err := NewClient(ClientOptions{
|
|
Connection: conn,
|
|
Agent: errorAgent{
|
|
stopErr: io.ErrUnexpectedEOF,
|
|
},
|
|
})
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
defer func() {
|
|
if err := c.Close(); err != nil {
|
|
t.Error(err)
|
|
}
|
|
}()
|
|
m := MustBuild(TransactionID)
|
|
if err := c.Do(m, time.Time{}, noopHandler); err == nil {
|
|
t.Error("error expected")
|
|
}
|
|
}
|
|
|
|
func TestCallbackWaitHandler_setCallback(t *testing.T) {
|
|
c := callbackWaitHandler{}
|
|
defer func() {
|
|
if err := recover(); err == nil {
|
|
t.Error("should panic")
|
|
}
|
|
}()
|
|
c.setCallback(nil)
|
|
}
|
|
|
|
func TestCallbackWaitHandler_HandleEvent(t *testing.T) {
|
|
c := callbackWaitHandler{}
|
|
defer func() {
|
|
if err := recover(); err == nil {
|
|
t.Error("should panic")
|
|
}
|
|
}()
|
|
c.HandleEvent(Event{})
|
|
}
|
|
|
|
func TestNewClientNoConnection(t *testing.T) {
|
|
c, err := NewClient(ClientOptions{
|
|
Connection: nil,
|
|
})
|
|
if c != nil {
|
|
t.Error("c should be nil")
|
|
}
|
|
if err != ErrNoConnection {
|
|
t.Error("bad error")
|
|
}
|
|
}
|
|
|
|
func TestDial(t *testing.T) {
|
|
c, err := Dial("udp4", "localhost:0")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer func() {
|
|
if err = c.Close(); err != nil {
|
|
t.Error(err)
|
|
}
|
|
}()
|
|
}
|
|
|
|
func TestDialError(t *testing.T) {
|
|
_, err := Dial("bad?network", "?????")
|
|
if err == nil {
|
|
t.Fatal("error expected")
|
|
}
|
|
}
|
|
func TestClientCloseErr(t *testing.T) {
|
|
response := MustBuild(TransactionID, BindingSuccess)
|
|
response.Encode()
|
|
conn := &testConnection{
|
|
b: response.Raw,
|
|
write: func(bytes []byte) (int, error) {
|
|
return len(bytes), nil
|
|
},
|
|
}
|
|
c, err := NewClient(ClientOptions{
|
|
Agent: errorAgent{
|
|
closeErr: io.ErrUnexpectedEOF,
|
|
},
|
|
Connection: conn,
|
|
})
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
defer func() {
|
|
if err, ok := c.Close().(CloseErr); !ok || err.AgentErr != io.ErrUnexpectedEOF {
|
|
t.Error("unexpected close err")
|
|
}
|
|
}()
|
|
}
|
|
|
|
type gcWaitAgent struct {
|
|
gc chan struct{}
|
|
}
|
|
|
|
func (a *gcWaitAgent) Stop(id [TransactionIDSize]byte) error {
|
|
return nil
|
|
}
|
|
|
|
func (a *gcWaitAgent) Close() error {
|
|
close(a.gc)
|
|
return nil
|
|
}
|
|
|
|
func (a *gcWaitAgent) Collect(time.Time) error {
|
|
a.gc <- struct{}{}
|
|
return nil
|
|
}
|
|
|
|
func (a *gcWaitAgent) Process(m *Message) error {
|
|
return nil
|
|
}
|
|
|
|
func (a *gcWaitAgent) Start(id [TransactionIDSize]byte, deadline time.Time, f Handler) error {
|
|
return nil
|
|
}
|
|
|
|
func TestClientGC(t *testing.T) {
|
|
response := MustBuild(TransactionID, BindingSuccess)
|
|
response.Encode()
|
|
conn := &testConnection{
|
|
b: response.Raw,
|
|
write: func(bytes []byte) (int, error) {
|
|
return len(bytes), nil
|
|
},
|
|
}
|
|
agent := &gcWaitAgent{
|
|
gc: make(chan struct{}),
|
|
}
|
|
c, err := NewClient(ClientOptions{
|
|
Agent: agent,
|
|
Connection: conn,
|
|
TimeoutRate: time.Millisecond * 1,
|
|
})
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
defer func() {
|
|
if err = c.Close(); err != nil {
|
|
t.Error(err)
|
|
}
|
|
}()
|
|
select {
|
|
case <-agent.gc:
|
|
case <-time.After(time.Millisecond * 200):
|
|
t.Error("timed out")
|
|
}
|
|
}
|
|
|
|
func TestClientFinalizer(t *testing.T) {
|
|
var buf bytes.Buffer
|
|
log.SetOutput(&buf)
|
|
defer func() {
|
|
log.SetOutput(os.Stderr)
|
|
}()
|
|
clientFinalizer(nil) // should not panic
|
|
clientFinalizer(&Client{})
|
|
conn := &testConnection{
|
|
write: func(bytes []byte) (int, error) {
|
|
return 0, io.ErrClosedPipe
|
|
},
|
|
}
|
|
c, err := NewClient(ClientOptions{
|
|
Connection: conn,
|
|
})
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
if err := c.Close(); err != nil {
|
|
t.Error(err)
|
|
}
|
|
clientFinalizer(c)
|
|
response := MustBuild(TransactionID, BindingSuccess)
|
|
response.Encode()
|
|
conn = &testConnection{
|
|
b: response.Raw,
|
|
write: func(bytes []byte) (int, error) {
|
|
return len(bytes), nil
|
|
},
|
|
}
|
|
c, err = NewClient(ClientOptions{
|
|
Agent: errorAgent{
|
|
closeErr: io.ErrUnexpectedEOF,
|
|
},
|
|
Connection: conn,
|
|
})
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
clientFinalizer(c)
|
|
reader := bufio.NewScanner(&buf)
|
|
var lines int
|
|
for reader.Scan() {
|
|
lines += 1
|
|
t.Log(reader.Text())
|
|
}
|
|
if reader.Err() != nil {
|
|
t.Error(err)
|
|
}
|
|
if lines != 2 {
|
|
t.Error("incorrect count of log lines:", lines)
|
|
}
|
|
}
|