mirror of
https://github.com/mochi-mqtt/server.git
synced 2025-10-04 07:46:34 +08:00

On 32-bit systems, `atomic` requires its 64-bit arguments to have 64-bit alignment, but the compiler doesn't help ensure that's the case. In this commit, fields that don't need to hold large numbers have been converted to 32-bit types, which are always aligned correctly on all platforms. For fields that may hold large numeric values, padding has been added to get the necessary alignment, and tests have been added to avoid regressions.
318 lines
7.3 KiB
Go
318 lines
7.3 KiB
Go
package circ
|
|
|
|
import (
|
|
//"fmt"
|
|
"sync/atomic"
|
|
"testing"
|
|
"time"
|
|
"unsafe"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestNewBuffer(t *testing.T) {
|
|
var size int = 16
|
|
var block int = 4
|
|
buf := NewBuffer(size, block)
|
|
|
|
require.NotNil(t, buf.buf)
|
|
require.NotNil(t, buf.rcond)
|
|
require.NotNil(t, buf.wcond)
|
|
require.Equal(t, size, len(buf.buf))
|
|
require.Equal(t, size, buf.size)
|
|
require.Equal(t, block, buf.block)
|
|
}
|
|
|
|
func TestNewBuffer0Size(t *testing.T) {
|
|
buf := NewBuffer(0, 0)
|
|
require.NotNil(t, buf.buf)
|
|
require.Equal(t, DefaultBufferSize, buf.size)
|
|
require.Equal(t, DefaultBlockSize, buf.block)
|
|
}
|
|
|
|
func TestNewBufferUndersize(t *testing.T) {
|
|
buf := NewBuffer(DefaultBlockSize+10, DefaultBlockSize)
|
|
require.NotNil(t, buf.buf)
|
|
require.Equal(t, DefaultBlockSize*2, buf.size)
|
|
require.Equal(t, DefaultBlockSize, buf.block)
|
|
}
|
|
|
|
func TestNewBufferFromSlice(t *testing.T) {
|
|
b := NewBytesPool(256)
|
|
buf := NewBufferFromSlice(DefaultBlockSize, b.Get())
|
|
require.NotNil(t, buf.buf)
|
|
require.Equal(t, 256, cap(buf.buf))
|
|
}
|
|
|
|
func TestNewBufferFromSlice0Size(t *testing.T) {
|
|
b := NewBytesPool(256)
|
|
buf := NewBufferFromSlice(0, b.Get())
|
|
require.NotNil(t, buf.buf)
|
|
require.Equal(t, 256, cap(buf.buf))
|
|
}
|
|
|
|
func TestAtomicAlignment(t *testing.T) {
|
|
var b Buffer
|
|
|
|
offset := unsafe.Offsetof(b.head)
|
|
require.Equalf(t, uintptr(0), offset%8,
|
|
"head requires 64-bit alignment for atomic: offset %d", offset)
|
|
|
|
offset = unsafe.Offsetof(b.tail)
|
|
require.Equalf(t, uintptr(0), offset%8,
|
|
"tail requires 64-bit alignment for atomic: offset %d", offset)
|
|
}
|
|
|
|
func TestGetPos(t *testing.T) {
|
|
buf := NewBuffer(16, 4)
|
|
tail, head := buf.GetPos()
|
|
require.Equal(t, int64(0), tail)
|
|
require.Equal(t, int64(0), head)
|
|
|
|
atomic.StoreInt64(&buf.tail, 3)
|
|
atomic.StoreInt64(&buf.head, 11)
|
|
|
|
tail, head = buf.GetPos()
|
|
require.Equal(t, int64(3), tail)
|
|
require.Equal(t, int64(11), head)
|
|
}
|
|
|
|
func TestGet(t *testing.T) {
|
|
buf := NewBuffer(16, 4)
|
|
require.Equal(t, make([]byte, 16), buf.Get())
|
|
|
|
buf.buf[0] = 1
|
|
buf.buf[15] = 1
|
|
require.Equal(t, []byte{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}, buf.Get())
|
|
}
|
|
|
|
func TestSetPos(t *testing.T) {
|
|
buf := NewBuffer(16, 4)
|
|
require.Equal(t, int64(0), atomic.LoadInt64(&buf.tail))
|
|
require.Equal(t, int64(0), atomic.LoadInt64(&buf.head))
|
|
|
|
buf.SetPos(4, 8)
|
|
require.Equal(t, int64(4), atomic.LoadInt64(&buf.tail))
|
|
require.Equal(t, int64(8), atomic.LoadInt64(&buf.head))
|
|
}
|
|
|
|
func TestSet(t *testing.T) {
|
|
buf := NewBuffer(16, 4)
|
|
err := buf.Set([]byte{1, 1, 1, 1}, 17, 19)
|
|
require.Error(t, err)
|
|
|
|
err = buf.Set([]byte{1, 1, 1, 1}, 4, 8)
|
|
require.NoError(t, err)
|
|
require.Equal(t, []byte{0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0}, buf.buf)
|
|
}
|
|
|
|
func TestIndex(t *testing.T) {
|
|
buf := NewBuffer(1024, 4)
|
|
require.Equal(t, 512, buf.Index(512))
|
|
require.Equal(t, 0, buf.Index(1024))
|
|
require.Equal(t, 6, buf.Index(1030))
|
|
require.Equal(t, 6, buf.Index(61446))
|
|
}
|
|
|
|
func TestAwaitFilled(t *testing.T) {
|
|
tests := []struct {
|
|
tail int64
|
|
head int64
|
|
n int
|
|
await int
|
|
desc string
|
|
}{
|
|
{tail: 0, head: 4, n: 4, await: 1, desc: "OK 0, 4"},
|
|
{tail: 8, head: 11, n: 4, await: 1, desc: "OK 8, 11"},
|
|
{tail: 102, head: 103, n: 4, await: 3, desc: "OK 102, 103"},
|
|
}
|
|
|
|
for i, tt := range tests {
|
|
//fmt.Println(i)
|
|
buf := NewBuffer(16, 4)
|
|
buf.SetPos(tt.tail, tt.head)
|
|
o := make(chan error)
|
|
go func() {
|
|
o <- buf.awaitFilled(4)
|
|
}()
|
|
|
|
time.Sleep(time.Millisecond)
|
|
atomic.AddInt64(&buf.head, int64(tt.await))
|
|
buf.wcond.L.Lock()
|
|
buf.wcond.Broadcast()
|
|
buf.wcond.L.Unlock()
|
|
|
|
require.NoError(t, <-o, "Unexpected Error [i:%d] %s", i, tt.desc)
|
|
}
|
|
}
|
|
|
|
func TestAwaitFilledEnded(t *testing.T) {
|
|
buf := NewBuffer(16, 4)
|
|
o := make(chan error)
|
|
go func() {
|
|
o <- buf.awaitFilled(4)
|
|
}()
|
|
time.Sleep(time.Millisecond)
|
|
atomic.StoreUint32(&buf.done, 1)
|
|
buf.wcond.L.Lock()
|
|
buf.wcond.Broadcast()
|
|
buf.wcond.L.Unlock()
|
|
|
|
require.Error(t, <-o)
|
|
}
|
|
|
|
func TestAwaitEmptyOK(t *testing.T) {
|
|
tests := []struct {
|
|
tail int64
|
|
head int64
|
|
await int
|
|
desc string
|
|
}{
|
|
{tail: 0, head: 0, await: 0, desc: "OK 0, 0"},
|
|
{tail: 0, head: 5, await: 0, desc: "OK 0, 5"},
|
|
{tail: 0, head: 14, await: 3, desc: "OK wrap 0, 14 "},
|
|
{tail: 22, head: 35, await: 2, desc: "OK wrap 0, 14 "},
|
|
{tail: 15, head: 17, await: 7, desc: "OK 15,2"},
|
|
{tail: 0, head: 10, await: 2, desc: "OK 0, 10"},
|
|
{tail: 1, head: 15, await: 4, desc: "OK 2, 14"},
|
|
}
|
|
|
|
for i, tt := range tests {
|
|
buf := NewBuffer(16, 4)
|
|
buf.SetPos(tt.tail, tt.head)
|
|
o := make(chan error)
|
|
go func() {
|
|
o <- buf.awaitEmpty(4)
|
|
}()
|
|
|
|
time.Sleep(time.Millisecond)
|
|
atomic.AddInt64(&buf.tail, int64(tt.await))
|
|
buf.rcond.L.Lock()
|
|
buf.rcond.Broadcast()
|
|
buf.rcond.L.Unlock()
|
|
|
|
require.NoError(t, <-o, "Unexpected Error [i:%d] %s", i, tt.desc)
|
|
}
|
|
}
|
|
|
|
func TestAwaitEmptyEnded(t *testing.T) {
|
|
buf := NewBuffer(16, 4)
|
|
buf.SetPos(1, 15)
|
|
o := make(chan error)
|
|
go func() {
|
|
o <- buf.awaitEmpty(4)
|
|
}()
|
|
time.Sleep(time.Millisecond)
|
|
atomic.StoreUint32(&buf.done, 1)
|
|
buf.rcond.L.Lock()
|
|
buf.rcond.Broadcast()
|
|
buf.rcond.L.Unlock()
|
|
|
|
require.Error(t, <-o)
|
|
}
|
|
|
|
func TestCheckEmpty(t *testing.T) {
|
|
buf := NewBuffer(16, 4)
|
|
|
|
tests := []struct {
|
|
head int64
|
|
tail int64
|
|
want bool
|
|
desc string
|
|
}{
|
|
{tail: 0, head: 0, want: true, desc: "0, 0 true"},
|
|
{tail: 3, head: 4, want: true, desc: "4, 3 true"},
|
|
{tail: 15, head: 17, want: true, desc: "15, 17(1) true"},
|
|
{tail: 1, head: 30, want: false, desc: "1, 30(14) false"},
|
|
{tail: 15, head: 30, want: false, desc: "15, 30(14) false; head has caught up to tail"},
|
|
}
|
|
for i, tt := range tests {
|
|
buf.SetPos(tt.tail, tt.head)
|
|
require.Equal(t, tt.want, buf.checkEmpty(4), "Mismatched bool wanted [i:%d] %s", i, tt.desc)
|
|
}
|
|
}
|
|
|
|
func TestCheckFilled(t *testing.T) {
|
|
buf := NewBuffer(16, 4)
|
|
|
|
tests := []struct {
|
|
head int64
|
|
tail int64
|
|
want bool
|
|
desc string
|
|
}{
|
|
{tail: 0, head: 0, want: false, desc: "0, 0 false"},
|
|
{tail: 0, head: 4, want: true, desc: "0, 4 true"},
|
|
{tail: 14, head: 16, want: false, desc: "14,16 false"},
|
|
{tail: 14, head: 18, want: true, desc: "14,16 true"},
|
|
}
|
|
|
|
for i, tt := range tests {
|
|
buf.SetPos(tt.tail, tt.head)
|
|
require.Equal(t, tt.want, buf.checkFilled(4), "Mismatched bool wanted [i:%d] %s", i, tt.desc)
|
|
}
|
|
|
|
}
|
|
|
|
func TestCommitTail(t *testing.T) {
|
|
tests := []struct {
|
|
tail int64
|
|
head int64
|
|
n int
|
|
next int64
|
|
await int
|
|
desc string
|
|
}{
|
|
{tail: 0, head: 5, n: 4, next: 4, await: 0, desc: "OK 0, 4"},
|
|
{tail: 0, head: 5, n: 6, next: 6, await: 1, desc: "OK 0, 5"},
|
|
}
|
|
|
|
for i, tt := range tests {
|
|
buf := NewBuffer(16, 4)
|
|
buf.SetPos(tt.tail, tt.head)
|
|
go func() {
|
|
buf.CommitTail(tt.n)
|
|
}()
|
|
|
|
time.Sleep(time.Millisecond)
|
|
for j := 0; j < tt.await; j++ {
|
|
atomic.AddInt64(&buf.head, 1)
|
|
buf.wcond.L.Lock()
|
|
buf.wcond.Broadcast()
|
|
buf.wcond.L.Unlock()
|
|
}
|
|
require.Equal(t, tt.next, atomic.LoadInt64(&buf.tail), "Next tail mismatch [i:%d] %s", i, tt.desc)
|
|
}
|
|
}
|
|
|
|
/*
|
|
func TestCommitTailEnded(t *testing.T) {
|
|
buf := NewBuffer(16, 4)
|
|
o := make(chan error)
|
|
go func() {
|
|
o <- buf.CommitTail(5)
|
|
}()
|
|
time.Sleep(time.Millisecond)
|
|
atomic.StoreUint32(&buf.done, 1)
|
|
buf.wcond.L.Lock()
|
|
buf.wcond.Broadcast()
|
|
buf.wcond.L.Unlock()
|
|
|
|
require.Error(t, <-o)
|
|
}
|
|
*/
|
|
func TestCapDelta(t *testing.T) {
|
|
buf := NewBuffer(16, 4)
|
|
|
|
require.Equal(t, 0, buf.CapDelta())
|
|
|
|
buf.SetPos(10, 15)
|
|
require.Equal(t, 5, buf.CapDelta())
|
|
}
|
|
|
|
func TestStop(t *testing.T) {
|
|
buf := NewBuffer(16, 4)
|
|
buf.Stop()
|
|
require.Equal(t, uint32(1), buf.done)
|
|
}
|