100 lines
2.3 KiB
Go
100 lines
2.3 KiB
Go
package modbus
|
|
|
|
import (
|
|
"sync"
|
|
"sync/atomic"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
const testTTL = 2 * time.Second
|
|
|
|
func TestBlockContains(t *testing.T) {
|
|
b := Block{Register: 37107, Count: 16}
|
|
|
|
assert.True(t, b.Contains(37107, 2), "first register")
|
|
assert.True(t, b.Contains(37121, 2), "last register exactly at block end")
|
|
assert.False(t, b.Contains(37106, 2), "before block start")
|
|
assert.False(t, b.Contains(37122, 2), "overruns block end")
|
|
}
|
|
|
|
func TestBlockByteOffset(t *testing.T) {
|
|
b := Block{Register: 37107, Count: 16}
|
|
|
|
assert.Equal(t, 0, b.ByteOffset(37107))
|
|
assert.Equal(t, 12, b.ByteOffset(37113))
|
|
assert.Equal(t, 28, b.ByteOffset(37121))
|
|
}
|
|
|
|
func TestCacheGetMiss(t *testing.T) {
|
|
c := NewCache(testTTL)
|
|
_, ok := c.get("nope")
|
|
assert.False(t, ok)
|
|
}
|
|
|
|
func TestCachePutGet(t *testing.T) {
|
|
c := NewCache(testTTL)
|
|
c.put("k", []byte{1, 2, 3})
|
|
got, ok := c.get("k")
|
|
require.True(t, ok)
|
|
assert.Equal(t, []byte{1, 2, 3}, got)
|
|
}
|
|
|
|
// TestCacheFetchSingleFlight verifies that concurrent fetches for the same key
|
|
// collapse into a single load and leave the cache warm.
|
|
func TestCacheFetchSingleFlight(t *testing.T) {
|
|
c := NewCache(testTTL)
|
|
key := "10.0.0.1::1/3/37107/16"
|
|
want := []byte{0xde, 0xad, 0xbe, 0xef}
|
|
|
|
var calls atomic.Int32
|
|
var once sync.Once
|
|
entered := make(chan struct{})
|
|
release := make(chan struct{})
|
|
load := func() ([]byte, error) {
|
|
calls.Add(1)
|
|
once.Do(func() { close(entered) })
|
|
<-release // hold the flight open while followers pile up
|
|
return want, nil
|
|
}
|
|
|
|
const n = 8
|
|
var wg sync.WaitGroup
|
|
payloads := make([][]byte, n)
|
|
|
|
wg.Go(func() {
|
|
got, _, err := c.Fetch(key, load)
|
|
assert.NoError(t, err)
|
|
payloads[0] = got
|
|
})
|
|
|
|
<-entered // ensure the flight is open before followers join
|
|
|
|
for i := 1; i < n; i++ {
|
|
wg.Go(func() {
|
|
got, _, err := c.Fetch(key, load)
|
|
assert.NoError(t, err)
|
|
payloads[i] = got
|
|
})
|
|
}
|
|
|
|
close(release)
|
|
wg.Wait()
|
|
|
|
assert.Equal(t, int32(1), calls.Load(), "concurrent reads of the same block must share one exchange")
|
|
for i := range n {
|
|
assert.Equal(t, want, payloads[i])
|
|
}
|
|
|
|
// cache is now warm: a subsequent read hits without loading again
|
|
got, ok, err := c.Fetch(key, func() ([]byte, error) {
|
|
t.Fatal("must not load on warm cache")
|
|
return nil, nil
|
|
})
|
|
require.NoError(t, err)
|
|
assert.True(t, ok)
|
|
assert.Equal(t, want, got)
|
|
}
|