mirror of
https://github.com/xtaci/smux.git
synced 2026-10-19 16:16:13 +00:00
205 lines
4.7 KiB
Go
205 lines
4.7 KiB
Go
package smux
|
|
|
|
import (
|
|
"bytes"
|
|
"io"
|
|
"sync/atomic"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func newUnitTestStream() *stream {
|
|
cfg := DefaultConfig()
|
|
sess := &Session{
|
|
config: cfg,
|
|
streams: make(map[uint32]*stream),
|
|
chSocketReadError: make(chan struct{}),
|
|
chSocketWriteError: make(chan struct{}),
|
|
chProtoError: make(chan struct{}),
|
|
bucketNotify: make(chan struct{}, 1),
|
|
}
|
|
st := newStream(1, cfg.MaxFrameSize, sess)
|
|
sess.streams[st.id] = st
|
|
return st
|
|
}
|
|
|
|
func TestStreamWaitReadTimeout(t *testing.T) {
|
|
s := newUnitTestStream()
|
|
s.readDeadline.Store(time.Now().Add(20 * time.Millisecond))
|
|
if err := s.waitRead(); err != ErrTimeout {
|
|
t.Fatalf("expected ErrTimeout, got %v", err)
|
|
}
|
|
}
|
|
|
|
func TestStreamWaitReadFinWithBufferedData(t *testing.T) {
|
|
s := newUnitTestStream()
|
|
buf := []byte("abc")
|
|
s.pushBytes(&buf)
|
|
|
|
s.fin()
|
|
if err := s.waitRead(); err != nil {
|
|
t.Fatalf("expected nil after fin with buffered data, got %v", err)
|
|
}
|
|
|
|
readBuf := make([]byte, 3)
|
|
n, err := s.tryReadV1(readBuf)
|
|
if err != nil {
|
|
t.Fatalf("read failed: %v", err)
|
|
}
|
|
if n != 3 {
|
|
t.Fatalf("expected 3 bytes, got %d", n)
|
|
}
|
|
if !bytes.Equal(readBuf, []byte("abc")) {
|
|
t.Fatalf("read mismatch: %q", readBuf)
|
|
}
|
|
}
|
|
|
|
func TestStreamRecycleTokens(t *testing.T) {
|
|
s := newUnitTestStream()
|
|
b1 := []byte("hello")
|
|
b2 := []byte("world!")
|
|
s.pushBytes(&b1)
|
|
s.pushBytes(&b2)
|
|
|
|
n := s.recycleTokens()
|
|
if n != len(b1)+len(b2) {
|
|
t.Fatalf("unexpected recycled bytes: %d", n)
|
|
}
|
|
if s.bufferRing.len() != 0 {
|
|
t.Fatalf("expected empty buffer ring, got %d", s.bufferRing.len())
|
|
}
|
|
}
|
|
|
|
func TestStreamUpdateNotifiesWriter(t *testing.T) {
|
|
s := newUnitTestStream()
|
|
s.update(7, 9)
|
|
|
|
if got := atomic.LoadUint32(&s.peerConsumed); got != 7 {
|
|
t.Fatalf("peerConsumed mismatch: %d", got)
|
|
}
|
|
if got := atomic.LoadUint32(&s.peerWindow); got != 9 {
|
|
t.Fatalf("peerWindow mismatch: %d", got)
|
|
}
|
|
|
|
select {
|
|
case <-s.chUpdate:
|
|
// ok
|
|
default:
|
|
t.Fatal("expected chUpdate notification")
|
|
}
|
|
}
|
|
|
|
func TestStreamSetDeadlineWakesUp(t *testing.T) {
|
|
s := newUnitTestStream()
|
|
if err := s.SetDeadline(time.Now().Add(time.Second)); err != nil {
|
|
t.Fatalf("SetDeadline failed: %v", err)
|
|
}
|
|
|
|
select {
|
|
case <-s.chReaderWakeup:
|
|
// ok
|
|
default:
|
|
t.Fatal("expected reader wakeup")
|
|
}
|
|
|
|
select {
|
|
case <-s.chWriterWakeup:
|
|
// ok
|
|
default:
|
|
t.Fatal("expected writer wakeup")
|
|
}
|
|
}
|
|
|
|
func TestStreamWaitReadClosed(t *testing.T) {
|
|
s := newUnitTestStream()
|
|
close(s.die)
|
|
if err := s.waitRead(); err != io.ErrClosedPipe {
|
|
t.Fatalf("expected io.ErrClosedPipe, got %v", err)
|
|
}
|
|
}
|
|
|
|
func TestSendWindowUpdateTimeout(t *testing.T) {
|
|
cfg := DefaultConfig()
|
|
sess := &Session{
|
|
config: cfg,
|
|
streams: make(map[uint32]*stream),
|
|
chSocketReadError: make(chan struct{}),
|
|
chSocketWriteError: make(chan struct{}),
|
|
chProtoError: make(chan struct{}),
|
|
bucketNotify: make(chan struct{}, 1),
|
|
shaper: nil,
|
|
die: make(chan struct{}),
|
|
}
|
|
st := newStream(1, cfg.MaxFrameSize, sess)
|
|
st.readDeadline.Store(time.Now().Add(-time.Second))
|
|
|
|
if err := st.sendWindowUpdate(1); err != ErrTimeout {
|
|
t.Fatalf("expected ErrTimeout, got %v", err)
|
|
}
|
|
}
|
|
|
|
func TestStopTimer(t *testing.T) {
|
|
stopTimer(nil)
|
|
|
|
timer := time.NewTimer(time.Nanosecond)
|
|
<-timer.C
|
|
stopTimer(timer)
|
|
|
|
active := time.NewTimer(time.Second)
|
|
stopTimer(active)
|
|
}
|
|
|
|
func TestNewBufferRingMinCapacity(t *testing.T) {
|
|
r := newBufferRing(0)
|
|
if len(r.bufs) != 1 {
|
|
t.Fatalf("expected capacity 1, got %d", len(r.bufs))
|
|
}
|
|
}
|
|
|
|
func newUnitTestStreamV2() *stream {
|
|
cfg := DefaultConfig()
|
|
cfg.Version = 2
|
|
sess := &Session{
|
|
config: cfg,
|
|
streams: make(map[uint32]*stream),
|
|
chSocketReadError: make(chan struct{}),
|
|
chSocketWriteError: make(chan struct{}),
|
|
chProtoError: make(chan struct{}),
|
|
bucketNotify: make(chan struct{}, 1),
|
|
die: make(chan struct{}),
|
|
}
|
|
st := newStream(1, cfg.MaxFrameSize, sess)
|
|
sess.streams[st.id] = st
|
|
return st
|
|
}
|
|
|
|
func TestWriteV2ClosedPipe(t *testing.T) {
|
|
s := newUnitTestStreamV2()
|
|
close(s.chWriteClosed)
|
|
|
|
if _, err := s.writeV2([]byte("x")); err != io.ErrClosedPipe {
|
|
t.Fatalf("expected io.ErrClosedPipe, got %v", err)
|
|
}
|
|
}
|
|
|
|
func TestWriteV2ConsumedError(t *testing.T) {
|
|
s := newUnitTestStreamV2()
|
|
atomic.StoreUint32(&s.peerConsumed, 10)
|
|
atomic.StoreUint32(&s.numWritten, 0)
|
|
atomic.StoreUint32(&s.peerWindow, 0)
|
|
|
|
if _, err := s.writeV2([]byte("x")); err != ErrConsumed {
|
|
t.Fatalf("expected ErrConsumed, got %v", err)
|
|
}
|
|
}
|
|
|
|
func TestWriteV2TimeoutWhenWindowZero(t *testing.T) {
|
|
s := newUnitTestStreamV2()
|
|
atomic.StoreUint32(&s.peerWindow, 0)
|
|
s.writeDeadline.Store(time.Now().Add(-time.Second))
|
|
|
|
if _, err := s.writeV2([]byte("data")); err != ErrTimeout {
|
|
t.Fatalf("expected ErrTimeout, got %v", err)
|
|
}
|
|
}
|