mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-01-24 13:32:50 -05:00
core: add test coverage for some of the wayland stack - mostly targeting any race issue detection
This commit is contained in:
314
core/internal/colorpicker/state_test.go
Normal file
314
core/internal/colorpicker/state_test.go
Normal file
@@ -0,0 +1,314 @@
|
||||
package colorpicker
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestSurfaceState_ConcurrentPointerMotion(t *testing.T) {
|
||||
s := NewSurfaceState(FormatHex, false)
|
||||
|
||||
var wg sync.WaitGroup
|
||||
const goroutines = 50
|
||||
const iterations = 100
|
||||
|
||||
for i := 0; i < goroutines; i++ {
|
||||
wg.Add(1)
|
||||
go func(id int) {
|
||||
defer wg.Done()
|
||||
for j := 0; j < iterations; j++ {
|
||||
s.OnPointerMotion(float64(id*10+j), float64(id*10+j))
|
||||
}
|
||||
}(i)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func TestSurfaceState_ConcurrentScaleAccess(t *testing.T) {
|
||||
s := NewSurfaceState(FormatHex, false)
|
||||
|
||||
var wg sync.WaitGroup
|
||||
const goroutines = 30
|
||||
const iterations = 100
|
||||
|
||||
for i := 0; i < goroutines/2; i++ {
|
||||
wg.Add(1)
|
||||
go func(id int) {
|
||||
defer wg.Done()
|
||||
for j := 0; j < iterations; j++ {
|
||||
s.SetScale(int32(id%3 + 1))
|
||||
}
|
||||
}(i)
|
||||
}
|
||||
|
||||
for i := 0; i < goroutines/2; i++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for j := 0; j < iterations; j++ {
|
||||
scale := s.Scale()
|
||||
assert.GreaterOrEqual(t, scale, int32(1))
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func TestSurfaceState_ConcurrentLogicalSize(t *testing.T) {
|
||||
s := NewSurfaceState(FormatHex, false)
|
||||
|
||||
var wg sync.WaitGroup
|
||||
const goroutines = 20
|
||||
const iterations = 100
|
||||
|
||||
for i := 0; i < goroutines/2; i++ {
|
||||
wg.Add(1)
|
||||
go func(id int) {
|
||||
defer wg.Done()
|
||||
for j := 0; j < iterations; j++ {
|
||||
_ = s.OnLayerConfigure(1920+id, 1080+j)
|
||||
}
|
||||
}(i)
|
||||
}
|
||||
|
||||
for i := 0; i < goroutines/2; i++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for j := 0; j < iterations; j++ {
|
||||
w, h := s.LogicalSize()
|
||||
_ = w
|
||||
_ = h
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func TestSurfaceState_ConcurrentIsDone(t *testing.T) {
|
||||
s := NewSurfaceState(FormatHex, false)
|
||||
|
||||
var wg sync.WaitGroup
|
||||
const goroutines = 30
|
||||
const iterations = 100
|
||||
|
||||
for i := 0; i < goroutines/3; i++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for j := 0; j < iterations; j++ {
|
||||
s.OnPointerButton(0x110, 1)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
for i := 0; i < goroutines/3; i++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for j := 0; j < iterations; j++ {
|
||||
s.OnKey(1, 1)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
for i := 0; i < goroutines/3; i++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for j := 0; j < iterations; j++ {
|
||||
picked, cancelled := s.IsDone()
|
||||
_ = picked
|
||||
_ = cancelled
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func TestSurfaceState_ConcurrentIsReady(t *testing.T) {
|
||||
s := NewSurfaceState(FormatHex, false)
|
||||
|
||||
var wg sync.WaitGroup
|
||||
const goroutines = 20
|
||||
const iterations = 100
|
||||
|
||||
for i := 0; i < goroutines; i++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for j := 0; j < iterations; j++ {
|
||||
_ = s.IsReady()
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func TestSurfaceState_ConcurrentSwapBuffers(t *testing.T) {
|
||||
s := NewSurfaceState(FormatHex, false)
|
||||
|
||||
var wg sync.WaitGroup
|
||||
const goroutines = 20
|
||||
const iterations = 100
|
||||
|
||||
for i := 0; i < goroutines; i++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for j := 0; j < iterations; j++ {
|
||||
s.SwapBuffers()
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func TestSurfaceState_ZeroScale(t *testing.T) {
|
||||
s := NewSurfaceState(FormatHex, false)
|
||||
s.SetScale(0)
|
||||
assert.Equal(t, int32(1), s.Scale())
|
||||
}
|
||||
|
||||
func TestSurfaceState_NegativeScale(t *testing.T) {
|
||||
s := NewSurfaceState(FormatHex, false)
|
||||
s.SetScale(-5)
|
||||
assert.Equal(t, int32(1), s.Scale())
|
||||
}
|
||||
|
||||
func TestSurfaceState_ZeroDimensionConfigure(t *testing.T) {
|
||||
s := NewSurfaceState(FormatHex, false)
|
||||
|
||||
err := s.OnLayerConfigure(0, 100)
|
||||
assert.NoError(t, err)
|
||||
|
||||
err = s.OnLayerConfigure(100, 0)
|
||||
assert.NoError(t, err)
|
||||
|
||||
err = s.OnLayerConfigure(-1, 100)
|
||||
assert.NoError(t, err)
|
||||
|
||||
w, h := s.LogicalSize()
|
||||
assert.Equal(t, 0, w)
|
||||
assert.Equal(t, 0, h)
|
||||
}
|
||||
|
||||
func TestSurfaceState_PickColorNilBuffer(t *testing.T) {
|
||||
s := NewSurfaceState(FormatHex, false)
|
||||
color, ok := s.PickColor()
|
||||
assert.False(t, ok)
|
||||
assert.Equal(t, Color{}, color)
|
||||
}
|
||||
|
||||
func TestSurfaceState_RedrawNilBuffer(t *testing.T) {
|
||||
s := NewSurfaceState(FormatHex, false)
|
||||
buf := s.Redraw()
|
||||
assert.Nil(t, buf)
|
||||
}
|
||||
|
||||
func TestSurfaceState_RedrawScreenOnlyNilBuffer(t *testing.T) {
|
||||
s := NewSurfaceState(FormatHex, false)
|
||||
buf := s.RedrawScreenOnly()
|
||||
assert.Nil(t, buf)
|
||||
}
|
||||
|
||||
func TestSurfaceState_FrontRenderBufferNil(t *testing.T) {
|
||||
s := NewSurfaceState(FormatHex, false)
|
||||
buf := s.FrontRenderBuffer()
|
||||
assert.Nil(t, buf)
|
||||
}
|
||||
|
||||
func TestSurfaceState_ScreenBufferNil(t *testing.T) {
|
||||
s := NewSurfaceState(FormatHex, false)
|
||||
buf := s.ScreenBuffer()
|
||||
assert.Nil(t, buf)
|
||||
}
|
||||
|
||||
func TestSurfaceState_DestroyMultipleTimes(t *testing.T) {
|
||||
s := NewSurfaceState(FormatHex, false)
|
||||
s.Destroy()
|
||||
s.Destroy()
|
||||
}
|
||||
|
||||
func TestClamp(t *testing.T) {
|
||||
tests := []struct {
|
||||
v, lo, hi, expected int
|
||||
}{
|
||||
{5, 0, 10, 5},
|
||||
{-5, 0, 10, 0},
|
||||
{15, 0, 10, 10},
|
||||
{0, 0, 10, 0},
|
||||
{10, 0, 10, 10},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
result := clamp(tt.v, tt.lo, tt.hi)
|
||||
assert.Equal(t, tt.expected, result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestClampF(t *testing.T) {
|
||||
tests := []struct {
|
||||
v, lo, hi, expected float64
|
||||
}{
|
||||
{5.0, 0.0, 10.0, 5.0},
|
||||
{-5.0, 0.0, 10.0, 0.0},
|
||||
{15.0, 0.0, 10.0, 10.0},
|
||||
{0.0, 0.0, 10.0, 0.0},
|
||||
{10.0, 0.0, 10.0, 10.0},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
result := clampF(tt.v, tt.lo, tt.hi)
|
||||
assert.InDelta(t, tt.expected, result, 0.001)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAbs(t *testing.T) {
|
||||
tests := []struct {
|
||||
v, expected int
|
||||
}{
|
||||
{5, 5},
|
||||
{-5, 5},
|
||||
{0, 0},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
result := abs(tt.v)
|
||||
assert.Equal(t, tt.expected, result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBlendColors(t *testing.T) {
|
||||
bg := Color{R: 0, G: 0, B: 0, A: 255}
|
||||
fg := Color{R: 255, G: 255, B: 255, A: 255}
|
||||
|
||||
result := blendColors(bg, fg, 0.0)
|
||||
assert.Equal(t, bg.R, result.R)
|
||||
assert.Equal(t, bg.G, result.G)
|
||||
assert.Equal(t, bg.B, result.B)
|
||||
|
||||
result = blendColors(bg, fg, 1.0)
|
||||
assert.Equal(t, fg.R, result.R)
|
||||
assert.Equal(t, fg.G, result.G)
|
||||
assert.Equal(t, fg.B, result.B)
|
||||
|
||||
result = blendColors(bg, fg, 0.5)
|
||||
assert.InDelta(t, 127, int(result.R), 1)
|
||||
assert.InDelta(t, 127, int(result.G), 1)
|
||||
assert.InDelta(t, 127, int(result.B), 1)
|
||||
|
||||
result = blendColors(bg, fg, -1.0)
|
||||
assert.Equal(t, bg.R, result.R)
|
||||
|
||||
result = blendColors(bg, fg, 2.0)
|
||||
assert.Equal(t, fg.R, result.R)
|
||||
}
|
||||
456
core/internal/server/clipboard/manager_test.go
Normal file
456
core/internal/server/clipboard/manager_test.go
Normal file
@@ -0,0 +1,456 @@
|
||||
package clipboard
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestEncodeDecodeEntry_Roundtrip(t *testing.T) {
|
||||
original := Entry{
|
||||
ID: 12345,
|
||||
Data: []byte("hello world"),
|
||||
MimeType: "text/plain;charset=utf-8",
|
||||
Preview: "hello world",
|
||||
Size: 11,
|
||||
Timestamp: time.Now().Truncate(time.Second),
|
||||
IsImage: false,
|
||||
}
|
||||
|
||||
encoded, err := encodeEntry(original)
|
||||
assert.NoError(t, err)
|
||||
|
||||
decoded, err := decodeEntry(encoded)
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.Equal(t, original.ID, decoded.ID)
|
||||
assert.Equal(t, original.Data, decoded.Data)
|
||||
assert.Equal(t, original.MimeType, decoded.MimeType)
|
||||
assert.Equal(t, original.Preview, decoded.Preview)
|
||||
assert.Equal(t, original.Size, decoded.Size)
|
||||
assert.Equal(t, original.Timestamp.Unix(), decoded.Timestamp.Unix())
|
||||
assert.Equal(t, original.IsImage, decoded.IsImage)
|
||||
}
|
||||
|
||||
func TestEncodeDecodeEntry_Image(t *testing.T) {
|
||||
original := Entry{
|
||||
ID: 99999,
|
||||
Data: []byte{0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A},
|
||||
MimeType: "image/png",
|
||||
Preview: "[[ image 8 B png 100x100 ]]",
|
||||
Size: 8,
|
||||
Timestamp: time.Now().Truncate(time.Second),
|
||||
IsImage: true,
|
||||
}
|
||||
|
||||
encoded, err := encodeEntry(original)
|
||||
assert.NoError(t, err)
|
||||
|
||||
decoded, err := decodeEntry(encoded)
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.Equal(t, original.ID, decoded.ID)
|
||||
assert.Equal(t, original.Data, decoded.Data)
|
||||
assert.True(t, decoded.IsImage)
|
||||
assert.Equal(t, original.Preview, decoded.Preview)
|
||||
}
|
||||
|
||||
func TestEncodeDecodeEntry_EmptyData(t *testing.T) {
|
||||
original := Entry{
|
||||
ID: 1,
|
||||
Data: []byte{},
|
||||
MimeType: "text/plain",
|
||||
Preview: "",
|
||||
Size: 0,
|
||||
Timestamp: time.Now().Truncate(time.Second),
|
||||
IsImage: false,
|
||||
}
|
||||
|
||||
encoded, err := encodeEntry(original)
|
||||
assert.NoError(t, err)
|
||||
|
||||
decoded, err := decodeEntry(encoded)
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.Equal(t, original.ID, decoded.ID)
|
||||
assert.Empty(t, decoded.Data)
|
||||
}
|
||||
|
||||
func TestEncodeDecodeEntry_LargeData(t *testing.T) {
|
||||
largeData := make([]byte, 100000)
|
||||
for i := range largeData {
|
||||
largeData[i] = byte(i % 256)
|
||||
}
|
||||
|
||||
original := Entry{
|
||||
ID: 777,
|
||||
Data: largeData,
|
||||
MimeType: "application/octet-stream",
|
||||
Preview: "binary data...",
|
||||
Size: len(largeData),
|
||||
Timestamp: time.Now().Truncate(time.Second),
|
||||
IsImage: false,
|
||||
}
|
||||
|
||||
encoded, err := encodeEntry(original)
|
||||
assert.NoError(t, err)
|
||||
|
||||
decoded, err := decodeEntry(encoded)
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.Equal(t, original.Data, decoded.Data)
|
||||
assert.Equal(t, original.Size, decoded.Size)
|
||||
}
|
||||
|
||||
func TestStateEqual_BothNil(t *testing.T) {
|
||||
assert.False(t, stateEqual(nil, nil))
|
||||
}
|
||||
|
||||
func TestStateEqual_OneNil(t *testing.T) {
|
||||
s := &State{Enabled: true}
|
||||
assert.False(t, stateEqual(s, nil))
|
||||
assert.False(t, stateEqual(nil, s))
|
||||
}
|
||||
|
||||
func TestStateEqual_EnabledDiffers(t *testing.T) {
|
||||
a := &State{Enabled: true, History: []Entry{}}
|
||||
b := &State{Enabled: false, History: []Entry{}}
|
||||
assert.False(t, stateEqual(a, b))
|
||||
}
|
||||
|
||||
func TestStateEqual_HistoryLengthDiffers(t *testing.T) {
|
||||
a := &State{Enabled: true, History: []Entry{{ID: 1}}}
|
||||
b := &State{Enabled: true, History: []Entry{}}
|
||||
assert.False(t, stateEqual(a, b))
|
||||
}
|
||||
|
||||
func TestStateEqual_BothEqual(t *testing.T) {
|
||||
a := &State{Enabled: true, History: []Entry{{ID: 1}, {ID: 2}}}
|
||||
b := &State{Enabled: true, History: []Entry{{ID: 3}, {ID: 4}}}
|
||||
assert.True(t, stateEqual(a, b))
|
||||
}
|
||||
|
||||
func TestManager_ConcurrentSubscriberAccess(t *testing.T) {
|
||||
m := &Manager{
|
||||
subscribers: make(map[string]chan State),
|
||||
dirty: make(chan struct{}, 1),
|
||||
}
|
||||
|
||||
var wg sync.WaitGroup
|
||||
const goroutines = 20
|
||||
|
||||
for i := 0; i < goroutines; i++ {
|
||||
wg.Add(1)
|
||||
go func(id int) {
|
||||
defer wg.Done()
|
||||
subID := string(rune('a' + id))
|
||||
ch := m.Subscribe(subID)
|
||||
assert.NotNil(t, ch)
|
||||
time.Sleep(time.Millisecond)
|
||||
m.Unsubscribe(subID)
|
||||
}(i)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func TestManager_ConcurrentGetState(t *testing.T) {
|
||||
m := &Manager{
|
||||
state: &State{
|
||||
Enabled: true,
|
||||
History: []Entry{{ID: 1}, {ID: 2}},
|
||||
},
|
||||
}
|
||||
|
||||
var wg sync.WaitGroup
|
||||
const goroutines = 50
|
||||
const iterations = 100
|
||||
|
||||
for i := 0; i < goroutines/2; i++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for j := 0; j < iterations; j++ {
|
||||
s := m.GetState()
|
||||
_ = s.Enabled
|
||||
_ = len(s.History)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
for i := 0; i < goroutines/2; i++ {
|
||||
wg.Add(1)
|
||||
go func(i int) {
|
||||
defer wg.Done()
|
||||
for j := 0; j < iterations; j++ {
|
||||
m.stateMutex.Lock()
|
||||
m.state = &State{
|
||||
Enabled: j%2 == 0,
|
||||
History: []Entry{{ID: uint64(j)}},
|
||||
}
|
||||
m.stateMutex.Unlock()
|
||||
}
|
||||
}(i)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func TestManager_ConcurrentConfigAccess(t *testing.T) {
|
||||
m := &Manager{
|
||||
config: DefaultConfig(),
|
||||
}
|
||||
|
||||
var wg sync.WaitGroup
|
||||
const goroutines = 30
|
||||
const iterations = 100
|
||||
|
||||
for i := 0; i < goroutines/2; i++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for j := 0; j < iterations; j++ {
|
||||
cfg := m.getConfig()
|
||||
_ = cfg.MaxHistory
|
||||
_ = cfg.MaxEntrySize
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
for i := 0; i < goroutines/2; i++ {
|
||||
wg.Add(1)
|
||||
go func(i int) {
|
||||
defer wg.Done()
|
||||
for j := 0; j < iterations; j++ {
|
||||
m.configMutex.Lock()
|
||||
m.config.MaxHistory = 50 + j
|
||||
m.config.MaxEntrySize = int64(1024 * j)
|
||||
m.configMutex.Unlock()
|
||||
}
|
||||
}(i)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func TestManager_NotifySubscribersNonBlocking(t *testing.T) {
|
||||
m := &Manager{
|
||||
dirty: make(chan struct{}, 1),
|
||||
}
|
||||
|
||||
for i := 0; i < 10; i++ {
|
||||
m.notifySubscribers()
|
||||
}
|
||||
|
||||
assert.Len(t, m.dirty, 1)
|
||||
}
|
||||
|
||||
func TestManager_ConcurrentOfferAccess(t *testing.T) {
|
||||
m := &Manager{
|
||||
offerMimeTypes: make(map[any][]string),
|
||||
offerRegistry: make(map[uint32]any),
|
||||
}
|
||||
|
||||
var wg sync.WaitGroup
|
||||
const goroutines = 20
|
||||
const iterations = 50
|
||||
|
||||
for i := 0; i < goroutines; i++ {
|
||||
wg.Add(1)
|
||||
go func(id int) {
|
||||
defer wg.Done()
|
||||
key := uint32(id)
|
||||
|
||||
for j := 0; j < iterations; j++ {
|
||||
m.offerMutex.Lock()
|
||||
m.offerRegistry[key] = struct{}{}
|
||||
m.offerMimeTypes[key] = []string{"text/plain"}
|
||||
m.offerMutex.Unlock()
|
||||
|
||||
m.offerMutex.RLock()
|
||||
_ = m.offerRegistry[key]
|
||||
_ = m.offerMimeTypes[key]
|
||||
m.offerMutex.RUnlock()
|
||||
|
||||
m.offerMutex.Lock()
|
||||
delete(m.offerRegistry, key)
|
||||
delete(m.offerMimeTypes, key)
|
||||
m.offerMutex.Unlock()
|
||||
}
|
||||
}(i)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func TestManager_ConcurrentPersistAccess(t *testing.T) {
|
||||
m := &Manager{
|
||||
persistData: make(map[string][]byte),
|
||||
persistMimeTypes: []string{},
|
||||
}
|
||||
|
||||
var wg sync.WaitGroup
|
||||
const goroutines = 20
|
||||
const iterations = 50
|
||||
|
||||
for i := 0; i < goroutines/2; i++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for j := 0; j < iterations; j++ {
|
||||
m.persistMutex.RLock()
|
||||
_ = m.persistData
|
||||
_ = m.persistMimeTypes
|
||||
m.persistMutex.RUnlock()
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
for i := 0; i < goroutines/2; i++ {
|
||||
wg.Add(1)
|
||||
go func(id int) {
|
||||
defer wg.Done()
|
||||
for j := 0; j < iterations; j++ {
|
||||
m.persistMutex.Lock()
|
||||
m.persistMimeTypes = []string{"text/plain", "text/html"}
|
||||
m.persistData = map[string][]byte{
|
||||
"text/plain": []byte("test"),
|
||||
}
|
||||
m.persistMutex.Unlock()
|
||||
}
|
||||
}(i)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func TestManager_ConcurrentOwnerAccess(t *testing.T) {
|
||||
m := &Manager{}
|
||||
|
||||
var wg sync.WaitGroup
|
||||
const goroutines = 30
|
||||
const iterations = 100
|
||||
|
||||
for i := 0; i < goroutines/2; i++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for j := 0; j < iterations; j++ {
|
||||
m.ownerLock.Lock()
|
||||
_ = m.isOwner
|
||||
m.ownerLock.Unlock()
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
for i := 0; i < goroutines/2; i++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for j := 0; j < iterations; j++ {
|
||||
m.ownerLock.Lock()
|
||||
m.isOwner = j%2 == 0
|
||||
m.ownerLock.Unlock()
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func TestItob(t *testing.T) {
|
||||
tests := []struct {
|
||||
input uint64
|
||||
expected []byte
|
||||
}{
|
||||
{0, []byte{0, 0, 0, 0, 0, 0, 0, 0}},
|
||||
{1, []byte{0, 0, 0, 0, 0, 0, 0, 1}},
|
||||
{256, []byte{0, 0, 0, 0, 0, 0, 1, 0}},
|
||||
{0xFFFFFFFFFFFFFFFF, []byte{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
result := itob(tt.input)
|
||||
assert.Equal(t, tt.expected, result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSizeStr(t *testing.T) {
|
||||
tests := []struct {
|
||||
input int
|
||||
expected string
|
||||
}{
|
||||
{0, "0 B"},
|
||||
{100, "100 B"},
|
||||
{1024, "1 KiB"},
|
||||
{2048, "2 KiB"},
|
||||
{1048576, "1 MiB"},
|
||||
{5242880, "5 MiB"},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
result := sizeStr(tt.input)
|
||||
assert.Equal(t, tt.expected, result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSelectMimeType(t *testing.T) {
|
||||
m := &Manager{}
|
||||
|
||||
tests := []struct {
|
||||
mimes []string
|
||||
expected string
|
||||
}{
|
||||
{[]string{"text/plain;charset=utf-8", "text/html"}, "text/plain;charset=utf-8"},
|
||||
{[]string{"text/html", "text/plain"}, "text/plain"},
|
||||
{[]string{"application/json", "image/png"}, "image/png"},
|
||||
{[]string{"application/json", "application/xml"}, ""},
|
||||
{[]string{}, ""},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
result := m.selectMimeType(tt.mimes)
|
||||
assert.Equal(t, tt.expected, result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsImageMimeType(t *testing.T) {
|
||||
m := &Manager{}
|
||||
|
||||
assert.True(t, m.isImageMimeType("image/png"))
|
||||
assert.True(t, m.isImageMimeType("image/jpeg"))
|
||||
assert.True(t, m.isImageMimeType("image/gif"))
|
||||
assert.False(t, m.isImageMimeType("text/plain"))
|
||||
assert.False(t, m.isImageMimeType("application/json"))
|
||||
}
|
||||
|
||||
func TestTextPreview(t *testing.T) {
|
||||
m := &Manager{}
|
||||
|
||||
short := m.textPreview([]byte("hello world"))
|
||||
assert.Equal(t, "hello world", short)
|
||||
|
||||
withWhitespace := m.textPreview([]byte(" hello world "))
|
||||
assert.Equal(t, "hello world", withWhitespace)
|
||||
|
||||
longText := make([]byte, 200)
|
||||
for i := range longText {
|
||||
longText[i] = 'a'
|
||||
}
|
||||
preview := m.textPreview(longText)
|
||||
assert.True(t, len(preview) > 100)
|
||||
assert.Contains(t, preview, "…")
|
||||
}
|
||||
|
||||
func TestDefaultConfig(t *testing.T) {
|
||||
cfg := DefaultConfig()
|
||||
assert.Equal(t, 100, cfg.MaxHistory)
|
||||
assert.Equal(t, int64(5*1024*1024), cfg.MaxEntrySize)
|
||||
assert.Equal(t, 0, cfg.AutoClearDays)
|
||||
assert.False(t, cfg.ClearAtStartup)
|
||||
assert.False(t, cfg.Disabled)
|
||||
assert.False(t, cfg.DisableHistory)
|
||||
assert.False(t, cfg.DisablePersist)
|
||||
}
|
||||
352
core/internal/server/dwl/manager_test.go
Normal file
352
core/internal/server/dwl/manager_test.go
Normal file
@@ -0,0 +1,352 @@
|
||||
package dwl
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestStateChanged_BothNil(t *testing.T) {
|
||||
assert.True(t, stateChanged(nil, nil))
|
||||
}
|
||||
|
||||
func TestStateChanged_OneNil(t *testing.T) {
|
||||
s := &State{TagCount: 9}
|
||||
assert.True(t, stateChanged(s, nil))
|
||||
assert.True(t, stateChanged(nil, s))
|
||||
}
|
||||
|
||||
func TestStateChanged_TagCountDiffers(t *testing.T) {
|
||||
a := &State{TagCount: 9, Outputs: make(map[string]*OutputState), Layouts: []string{}}
|
||||
b := &State{TagCount: 10, Outputs: make(map[string]*OutputState), Layouts: []string{}}
|
||||
assert.True(t, stateChanged(a, b))
|
||||
}
|
||||
|
||||
func TestStateChanged_LayoutLengthDiffers(t *testing.T) {
|
||||
a := &State{TagCount: 9, Layouts: []string{"tile"}, Outputs: make(map[string]*OutputState)}
|
||||
b := &State{TagCount: 9, Layouts: []string{"tile", "monocle"}, Outputs: make(map[string]*OutputState)}
|
||||
assert.True(t, stateChanged(a, b))
|
||||
}
|
||||
|
||||
func TestStateChanged_ActiveOutputDiffers(t *testing.T) {
|
||||
a := &State{TagCount: 9, ActiveOutput: "eDP-1", Outputs: make(map[string]*OutputState), Layouts: []string{}}
|
||||
b := &State{TagCount: 9, ActiveOutput: "HDMI-A-1", Outputs: make(map[string]*OutputState), Layouts: []string{}}
|
||||
assert.True(t, stateChanged(a, b))
|
||||
}
|
||||
|
||||
func TestStateChanged_OutputCountDiffers(t *testing.T) {
|
||||
a := &State{
|
||||
TagCount: 9,
|
||||
Outputs: map[string]*OutputState{"eDP-1": {}},
|
||||
Layouts: []string{},
|
||||
}
|
||||
b := &State{
|
||||
TagCount: 9,
|
||||
Outputs: map[string]*OutputState{},
|
||||
Layouts: []string{},
|
||||
}
|
||||
assert.True(t, stateChanged(a, b))
|
||||
}
|
||||
|
||||
func TestStateChanged_OutputFieldsDiffer(t *testing.T) {
|
||||
a := &State{
|
||||
TagCount: 9,
|
||||
Layouts: []string{},
|
||||
Outputs: map[string]*OutputState{
|
||||
"eDP-1": {Active: 1, Layout: 0, Title: "Firefox"},
|
||||
},
|
||||
}
|
||||
b := &State{
|
||||
TagCount: 9,
|
||||
Layouts: []string{},
|
||||
Outputs: map[string]*OutputState{
|
||||
"eDP-1": {Active: 0, Layout: 0, Title: "Firefox"},
|
||||
},
|
||||
}
|
||||
assert.True(t, stateChanged(a, b))
|
||||
|
||||
b.Outputs["eDP-1"].Active = 1
|
||||
b.Outputs["eDP-1"].Layout = 1
|
||||
assert.True(t, stateChanged(a, b))
|
||||
|
||||
b.Outputs["eDP-1"].Layout = 0
|
||||
b.Outputs["eDP-1"].Title = "Code"
|
||||
assert.True(t, stateChanged(a, b))
|
||||
}
|
||||
|
||||
func TestStateChanged_TagsDiffer(t *testing.T) {
|
||||
a := &State{
|
||||
TagCount: 9,
|
||||
Layouts: []string{},
|
||||
Outputs: map[string]*OutputState{
|
||||
"eDP-1": {Tags: []TagState{{Tag: 1, State: 1, Clients: 2, Focused: 1}}},
|
||||
},
|
||||
}
|
||||
b := &State{
|
||||
TagCount: 9,
|
||||
Layouts: []string{},
|
||||
Outputs: map[string]*OutputState{
|
||||
"eDP-1": {Tags: []TagState{{Tag: 1, State: 2, Clients: 2, Focused: 1}}},
|
||||
},
|
||||
}
|
||||
assert.True(t, stateChanged(a, b))
|
||||
|
||||
b.Outputs["eDP-1"].Tags[0].State = 1
|
||||
b.Outputs["eDP-1"].Tags[0].Clients = 3
|
||||
assert.True(t, stateChanged(a, b))
|
||||
}
|
||||
|
||||
func TestStateChanged_Equal(t *testing.T) {
|
||||
a := &State{
|
||||
TagCount: 9,
|
||||
ActiveOutput: "eDP-1",
|
||||
Layouts: []string{"tile", "monocle"},
|
||||
Outputs: map[string]*OutputState{
|
||||
"eDP-1": {
|
||||
Name: "eDP-1",
|
||||
Active: 1,
|
||||
Layout: 0,
|
||||
LayoutSymbol: "[]=",
|
||||
Title: "Firefox",
|
||||
AppID: "firefox",
|
||||
KbLayout: "us",
|
||||
Keymode: "",
|
||||
Tags: []TagState{{Tag: 1, State: 1, Clients: 2, Focused: 1}},
|
||||
},
|
||||
},
|
||||
}
|
||||
b := &State{
|
||||
TagCount: 9,
|
||||
ActiveOutput: "eDP-1",
|
||||
Layouts: []string{"tile", "monocle"},
|
||||
Outputs: map[string]*OutputState{
|
||||
"eDP-1": {
|
||||
Name: "eDP-1",
|
||||
Active: 1,
|
||||
Layout: 0,
|
||||
LayoutSymbol: "[]=",
|
||||
Title: "Firefox",
|
||||
AppID: "firefox",
|
||||
KbLayout: "us",
|
||||
Keymode: "",
|
||||
Tags: []TagState{{Tag: 1, State: 1, Clients: 2, Focused: 1}},
|
||||
},
|
||||
},
|
||||
}
|
||||
assert.False(t, stateChanged(a, b))
|
||||
}
|
||||
|
||||
func TestManager_ConcurrentGetState(t *testing.T) {
|
||||
m := &Manager{
|
||||
state: &State{
|
||||
TagCount: 9,
|
||||
Layouts: []string{"tile"},
|
||||
Outputs: map[string]*OutputState{"eDP-1": {Name: "eDP-1"}},
|
||||
},
|
||||
}
|
||||
|
||||
var wg sync.WaitGroup
|
||||
const goroutines = 50
|
||||
const iterations = 100
|
||||
|
||||
for i := 0; i < goroutines/2; i++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for j := 0; j < iterations; j++ {
|
||||
s := m.GetState()
|
||||
_ = s.TagCount
|
||||
_ = s.Outputs
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
for i := 0; i < goroutines/2; i++ {
|
||||
wg.Add(1)
|
||||
go func(i int) {
|
||||
defer wg.Done()
|
||||
for j := 0; j < iterations; j++ {
|
||||
m.stateMutex.Lock()
|
||||
m.state = &State{
|
||||
TagCount: uint32(j % 10),
|
||||
Layouts: []string{"tile", "monocle"},
|
||||
Outputs: map[string]*OutputState{"eDP-1": {Active: uint32(j % 2)}},
|
||||
}
|
||||
m.stateMutex.Unlock()
|
||||
}
|
||||
}(i)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func TestManager_ConcurrentSubscriberAccess(t *testing.T) {
|
||||
m := &Manager{
|
||||
stopChan: make(chan struct{}),
|
||||
dirty: make(chan struct{}, 1),
|
||||
}
|
||||
|
||||
var wg sync.WaitGroup
|
||||
const goroutines = 20
|
||||
|
||||
for i := 0; i < goroutines; i++ {
|
||||
wg.Add(1)
|
||||
go func(id int) {
|
||||
defer wg.Done()
|
||||
subID := string(rune('a' + id))
|
||||
ch := m.Subscribe(subID)
|
||||
assert.NotNil(t, ch)
|
||||
time.Sleep(time.Millisecond)
|
||||
m.Unsubscribe(subID)
|
||||
}(i)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func TestManager_SyncmapOutputsConcurrentAccess(t *testing.T) {
|
||||
m := &Manager{}
|
||||
|
||||
var wg sync.WaitGroup
|
||||
const goroutines = 30
|
||||
const iterations = 50
|
||||
|
||||
for i := 0; i < goroutines; i++ {
|
||||
wg.Add(1)
|
||||
go func(id int) {
|
||||
defer wg.Done()
|
||||
key := uint32(id)
|
||||
|
||||
for j := 0; j < iterations; j++ {
|
||||
state := &outputState{
|
||||
id: key,
|
||||
name: "test-output",
|
||||
active: uint32(j % 2),
|
||||
tags: []TagState{{Tag: uint32(j), State: 1}},
|
||||
}
|
||||
m.outputs.Store(key, state)
|
||||
|
||||
if loaded, ok := m.outputs.Load(key); ok {
|
||||
assert.Equal(t, key, loaded.id)
|
||||
}
|
||||
|
||||
m.outputs.Range(func(k uint32, v *outputState) bool {
|
||||
_ = v.name
|
||||
_ = v.active
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
m.outputs.Delete(key)
|
||||
}(i)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func TestManager_NotifySubscribersNonBlocking(t *testing.T) {
|
||||
m := &Manager{
|
||||
dirty: make(chan struct{}, 1),
|
||||
}
|
||||
|
||||
for i := 0; i < 10; i++ {
|
||||
m.notifySubscribers()
|
||||
}
|
||||
|
||||
assert.Len(t, m.dirty, 1)
|
||||
}
|
||||
|
||||
func TestManager_PostQueueFull(t *testing.T) {
|
||||
m := &Manager{
|
||||
cmdq: make(chan cmd, 2),
|
||||
stopChan: make(chan struct{}),
|
||||
}
|
||||
|
||||
m.post(func() {})
|
||||
m.post(func() {})
|
||||
m.post(func() {})
|
||||
m.post(func() {})
|
||||
|
||||
assert.Len(t, m.cmdq, 2)
|
||||
}
|
||||
|
||||
func TestManager_GetStateNilState(t *testing.T) {
|
||||
m := &Manager{}
|
||||
|
||||
s := m.GetState()
|
||||
assert.NotNil(t, s.Outputs)
|
||||
assert.NotNil(t, s.Layouts)
|
||||
assert.Equal(t, uint32(0), s.TagCount)
|
||||
}
|
||||
|
||||
func TestTagState_Fields(t *testing.T) {
|
||||
tag := TagState{
|
||||
Tag: 1,
|
||||
State: 2,
|
||||
Clients: 3,
|
||||
Focused: 1,
|
||||
}
|
||||
|
||||
assert.Equal(t, uint32(1), tag.Tag)
|
||||
assert.Equal(t, uint32(2), tag.State)
|
||||
assert.Equal(t, uint32(3), tag.Clients)
|
||||
assert.Equal(t, uint32(1), tag.Focused)
|
||||
}
|
||||
|
||||
func TestOutputState_Fields(t *testing.T) {
|
||||
out := OutputState{
|
||||
Name: "eDP-1",
|
||||
Active: 1,
|
||||
Tags: []TagState{{Tag: 1}},
|
||||
Layout: 0,
|
||||
LayoutSymbol: "[]=",
|
||||
Title: "Firefox",
|
||||
AppID: "firefox",
|
||||
KbLayout: "us",
|
||||
Keymode: "",
|
||||
}
|
||||
|
||||
assert.Equal(t, "eDP-1", out.Name)
|
||||
assert.Equal(t, uint32(1), out.Active)
|
||||
assert.Len(t, out.Tags, 1)
|
||||
assert.Equal(t, "[]=", out.LayoutSymbol)
|
||||
}
|
||||
|
||||
func TestStateChanged_NewOutputAppears(t *testing.T) {
|
||||
a := &State{
|
||||
TagCount: 9,
|
||||
Layouts: []string{},
|
||||
Outputs: map[string]*OutputState{
|
||||
"eDP-1": {Name: "eDP-1"},
|
||||
},
|
||||
}
|
||||
b := &State{
|
||||
TagCount: 9,
|
||||
Layouts: []string{},
|
||||
Outputs: map[string]*OutputState{
|
||||
"eDP-1": {Name: "eDP-1"},
|
||||
"HDMI-A-1": {Name: "HDMI-A-1"},
|
||||
},
|
||||
}
|
||||
assert.True(t, stateChanged(a, b))
|
||||
}
|
||||
|
||||
func TestStateChanged_TagsLengthDiffers(t *testing.T) {
|
||||
a := &State{
|
||||
TagCount: 9,
|
||||
Layouts: []string{},
|
||||
Outputs: map[string]*OutputState{
|
||||
"eDP-1": {Tags: []TagState{{Tag: 1}}},
|
||||
},
|
||||
}
|
||||
b := &State{
|
||||
TagCount: 9,
|
||||
Layouts: []string{},
|
||||
Outputs: map[string]*OutputState{
|
||||
"eDP-1": {Tags: []TagState{{Tag: 1}, {Tag: 2}}},
|
||||
},
|
||||
}
|
||||
assert.True(t, stateChanged(a, b))
|
||||
}
|
||||
@@ -268,31 +268,36 @@ func (m *Manager) setupOutputControls(outputs []*wlclient.Output, manager *wlr_g
|
||||
}
|
||||
|
||||
func (m *Manager) setupControlHandlers(state *outputState, control *wlr_gamma_control.ZwlrGammaControlV1) {
|
||||
outputID := state.id
|
||||
|
||||
control.SetGammaSizeHandler(func(e wlr_gamma_control.ZwlrGammaControlV1GammaSizeEvent) {
|
||||
if out, ok := m.outputs.Load(state.id); ok {
|
||||
out.rampSize = e.Size
|
||||
out.failed = false
|
||||
out.retryCount = 0
|
||||
}
|
||||
size := e.Size
|
||||
m.post(func() {
|
||||
if out, ok := m.outputs.Load(outputID); ok {
|
||||
out.rampSize = size
|
||||
out.failed = false
|
||||
out.retryCount = 0
|
||||
}
|
||||
m.applyCurrentTemp()
|
||||
})
|
||||
})
|
||||
|
||||
control.SetFailedHandler(func(_ wlr_gamma_control.ZwlrGammaControlV1FailedEvent) {
|
||||
out, ok := m.outputs.Load(state.id)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
out.failed = true
|
||||
out.rampSize = 0
|
||||
out.retryCount++
|
||||
out.lastFailTime = time.Now()
|
||||
m.post(func() {
|
||||
out, ok := m.outputs.Load(outputID)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
out.failed = true
|
||||
out.rampSize = 0
|
||||
out.retryCount++
|
||||
out.lastFailTime = time.Now()
|
||||
|
||||
backoff := time.Duration(300<<uint(min(out.retryCount-1, 4))) * time.Millisecond
|
||||
time.AfterFunc(backoff, func() {
|
||||
m.post(func() {
|
||||
m.recreateOutputControl(out)
|
||||
backoff := time.Duration(300<<uint(min(out.retryCount-1, 4))) * time.Millisecond
|
||||
time.AfterFunc(backoff, func() {
|
||||
m.post(func() {
|
||||
m.recreateOutputControl(out)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -583,7 +588,7 @@ func (m *Manager) schedulerLoop() {
|
||||
m.configMutex.RUnlock()
|
||||
|
||||
if enabled {
|
||||
m.applyCurrentTemp()
|
||||
m.post(func() { m.applyCurrentTemp() })
|
||||
}
|
||||
|
||||
var timer *time.Timer
|
||||
@@ -625,14 +630,14 @@ func (m *Manager) schedulerLoop() {
|
||||
enabled := m.config.Enabled
|
||||
m.configMutex.RUnlock()
|
||||
if enabled {
|
||||
m.applyCurrentTemp()
|
||||
m.post(func() { m.applyCurrentTemp() })
|
||||
}
|
||||
case <-timer.C:
|
||||
m.configMutex.RLock()
|
||||
enabled := m.config.Enabled
|
||||
m.configMutex.RUnlock()
|
||||
if enabled {
|
||||
m.applyCurrentTemp()
|
||||
m.post(func() { m.applyCurrentTemp() })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
386
core/internal/server/wayland/manager_test.go
Normal file
386
core/internal/server/wayland/manager_test.go
Normal file
@@ -0,0 +1,386 @@
|
||||
package wayland
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestManager_ActorSerializesOutputStateAccess(t *testing.T) {
|
||||
m := &Manager{
|
||||
cmdq: make(chan cmd, 128),
|
||||
stopChan: make(chan struct{}),
|
||||
}
|
||||
|
||||
m.wg.Add(1)
|
||||
go m.waylandActor()
|
||||
|
||||
state := &outputState{
|
||||
id: 1,
|
||||
registryName: 100,
|
||||
rampSize: 256,
|
||||
}
|
||||
m.outputs.Store(state.id, state)
|
||||
|
||||
var wg sync.WaitGroup
|
||||
const goroutines = 50
|
||||
const iterations = 100
|
||||
|
||||
for i := 0; i < goroutines; i++ {
|
||||
wg.Add(1)
|
||||
go func(id int) {
|
||||
defer wg.Done()
|
||||
for j := 0; j < iterations; j++ {
|
||||
m.post(func() {
|
||||
if out, ok := m.outputs.Load(state.id); ok {
|
||||
out.rampSize = uint32(j)
|
||||
out.failed = j%2 == 0
|
||||
out.retryCount = j
|
||||
out.lastFailTime = time.Now()
|
||||
}
|
||||
})
|
||||
}
|
||||
}(i)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
|
||||
done := make(chan struct{})
|
||||
m.post(func() { close(done) })
|
||||
<-done
|
||||
|
||||
close(m.stopChan)
|
||||
m.wg.Wait()
|
||||
}
|
||||
|
||||
func TestManager_ConcurrentSubscriberAccess(t *testing.T) {
|
||||
m := &Manager{
|
||||
stopChan: make(chan struct{}),
|
||||
dirty: make(chan struct{}, 1),
|
||||
updateTrigger: make(chan struct{}, 1),
|
||||
}
|
||||
|
||||
var wg sync.WaitGroup
|
||||
const goroutines = 20
|
||||
|
||||
for i := 0; i < goroutines; i++ {
|
||||
wg.Add(1)
|
||||
go func(id int) {
|
||||
defer wg.Done()
|
||||
subID := string(rune('a' + id))
|
||||
ch := m.Subscribe(subID)
|
||||
assert.NotNil(t, ch)
|
||||
time.Sleep(time.Millisecond)
|
||||
m.Unsubscribe(subID)
|
||||
}(i)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func TestManager_ConcurrentGetState(t *testing.T) {
|
||||
m := &Manager{
|
||||
state: &State{
|
||||
CurrentTemp: 5000,
|
||||
IsDay: true,
|
||||
},
|
||||
}
|
||||
|
||||
var wg sync.WaitGroup
|
||||
const goroutines = 50
|
||||
const iterations = 100
|
||||
|
||||
for i := 0; i < goroutines/2; i++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for j := 0; j < iterations; j++ {
|
||||
s := m.GetState()
|
||||
assert.GreaterOrEqual(t, s.CurrentTemp, 0)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
for i := 0; i < goroutines/2; i++ {
|
||||
wg.Add(1)
|
||||
go func(i int) {
|
||||
defer wg.Done()
|
||||
for j := 0; j < iterations; j++ {
|
||||
m.stateMutex.Lock()
|
||||
m.state = &State{
|
||||
CurrentTemp: 4000 + i*100,
|
||||
IsDay: j%2 == 0,
|
||||
}
|
||||
m.stateMutex.Unlock()
|
||||
}
|
||||
}(i)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func TestManager_ConcurrentConfigAccess(t *testing.T) {
|
||||
m := &Manager{
|
||||
config: DefaultConfig(),
|
||||
}
|
||||
|
||||
var wg sync.WaitGroup
|
||||
const goroutines = 30
|
||||
const iterations = 100
|
||||
|
||||
for i := 0; i < goroutines/2; i++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for j := 0; j < iterations; j++ {
|
||||
m.configMutex.RLock()
|
||||
_ = m.config.LowTemp
|
||||
_ = m.config.HighTemp
|
||||
_ = m.config.Enabled
|
||||
m.configMutex.RUnlock()
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
for i := 0; i < goroutines/2; i++ {
|
||||
wg.Add(1)
|
||||
go func(i int) {
|
||||
defer wg.Done()
|
||||
for j := 0; j < iterations; j++ {
|
||||
m.configMutex.Lock()
|
||||
m.config.LowTemp = 3000 + j
|
||||
m.config.HighTemp = 7000 - j
|
||||
m.config.Enabled = j%2 == 0
|
||||
m.configMutex.Unlock()
|
||||
}
|
||||
}(i)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func TestManager_SyncmapOutputsConcurrentAccess(t *testing.T) {
|
||||
m := &Manager{}
|
||||
|
||||
var wg sync.WaitGroup
|
||||
const goroutines = 30
|
||||
const iterations = 50
|
||||
|
||||
for i := 0; i < goroutines; i++ {
|
||||
wg.Add(1)
|
||||
go func(id int) {
|
||||
defer wg.Done()
|
||||
key := uint32(id)
|
||||
|
||||
for j := 0; j < iterations; j++ {
|
||||
state := &outputState{
|
||||
id: key,
|
||||
rampSize: uint32(j),
|
||||
failed: j%2 == 0,
|
||||
}
|
||||
m.outputs.Store(key, state)
|
||||
|
||||
if loaded, ok := m.outputs.Load(key); ok {
|
||||
assert.Equal(t, key, loaded.id)
|
||||
}
|
||||
|
||||
m.outputs.Range(func(k uint32, v *outputState) bool {
|
||||
_ = v.rampSize
|
||||
_ = v.failed
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
m.outputs.Delete(key)
|
||||
}(i)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func TestManager_LocationCacheConcurrentAccess(t *testing.T) {
|
||||
m := &Manager{}
|
||||
|
||||
var wg sync.WaitGroup
|
||||
const goroutines = 20
|
||||
const iterations = 100
|
||||
|
||||
for i := 0; i < goroutines/2; i++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for j := 0; j < iterations; j++ {
|
||||
m.locationMutex.RLock()
|
||||
_ = m.cachedIPLat
|
||||
_ = m.cachedIPLon
|
||||
m.locationMutex.RUnlock()
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
for i := 0; i < goroutines/2; i++ {
|
||||
wg.Add(1)
|
||||
go func(i int) {
|
||||
defer wg.Done()
|
||||
for j := 0; j < iterations; j++ {
|
||||
lat := float64(40 + i)
|
||||
lon := float64(-74 + j)
|
||||
m.locationMutex.Lock()
|
||||
m.cachedIPLat = &lat
|
||||
m.cachedIPLon = &lon
|
||||
m.locationMutex.Unlock()
|
||||
}
|
||||
}(i)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func TestManager_ScheduleConcurrentAccess(t *testing.T) {
|
||||
now := time.Now()
|
||||
m := &Manager{
|
||||
schedule: sunSchedule{
|
||||
times: SunTimes{
|
||||
Dawn: now,
|
||||
Sunrise: now.Add(time.Hour),
|
||||
Sunset: now.Add(12 * time.Hour),
|
||||
Night: now.Add(13 * time.Hour),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
var wg sync.WaitGroup
|
||||
const goroutines = 20
|
||||
const iterations = 100
|
||||
|
||||
for i := 0; i < goroutines/2; i++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for j := 0; j < iterations; j++ {
|
||||
m.scheduleMutex.RLock()
|
||||
_ = m.schedule.times.Dawn
|
||||
_ = m.schedule.times.Sunrise
|
||||
_ = m.schedule.times.Sunset
|
||||
_ = m.schedule.condition
|
||||
m.scheduleMutex.RUnlock()
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
for i := 0; i < goroutines/2; i++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for j := 0; j < iterations; j++ {
|
||||
m.scheduleMutex.Lock()
|
||||
m.schedule.times.Dawn = time.Now()
|
||||
m.schedule.times.Sunrise = time.Now().Add(time.Hour)
|
||||
m.schedule.condition = SunNormal
|
||||
m.scheduleMutex.Unlock()
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func TestInterpolate_EdgeCases(t *testing.T) {
|
||||
now := time.Now()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
now time.Time
|
||||
start time.Time
|
||||
stop time.Time
|
||||
expected float64
|
||||
}{
|
||||
{
|
||||
name: "same start and stop",
|
||||
now: now,
|
||||
start: now,
|
||||
stop: now,
|
||||
expected: 1.0,
|
||||
},
|
||||
{
|
||||
name: "now before start",
|
||||
now: now,
|
||||
start: now.Add(time.Hour),
|
||||
stop: now.Add(2 * time.Hour),
|
||||
expected: 0.0,
|
||||
},
|
||||
{
|
||||
name: "now after stop",
|
||||
now: now.Add(3 * time.Hour),
|
||||
start: now,
|
||||
stop: now.Add(time.Hour),
|
||||
expected: 1.0,
|
||||
},
|
||||
{
|
||||
name: "now at midpoint",
|
||||
now: now.Add(30 * time.Minute),
|
||||
start: now,
|
||||
stop: now.Add(time.Hour),
|
||||
expected: 0.5,
|
||||
},
|
||||
{
|
||||
name: "now equals start",
|
||||
now: now,
|
||||
start: now,
|
||||
stop: now.Add(time.Hour),
|
||||
expected: 0.0,
|
||||
},
|
||||
{
|
||||
name: "now equals stop",
|
||||
now: now.Add(time.Hour),
|
||||
start: now,
|
||||
stop: now.Add(time.Hour),
|
||||
expected: 1.0,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := interpolate(tt.now, tt.start, tt.stop)
|
||||
assert.InDelta(t, tt.expected, result, 0.01)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenerateGammaRamp_ZeroSize(t *testing.T) {
|
||||
ramp := GenerateGammaRamp(0, 5000, 1.0)
|
||||
assert.Empty(t, ramp.Red)
|
||||
assert.Empty(t, ramp.Green)
|
||||
assert.Empty(t, ramp.Blue)
|
||||
}
|
||||
|
||||
func TestGenerateGammaRamp_ValidSizes(t *testing.T) {
|
||||
sizes := []uint32{1, 256, 1024}
|
||||
temps := []int{1000, 4000, 6500, 10000}
|
||||
gammas := []float64{0.5, 1.0, 2.0}
|
||||
|
||||
for _, size := range sizes {
|
||||
for _, temp := range temps {
|
||||
for _, gamma := range gammas {
|
||||
ramp := GenerateGammaRamp(size, temp, gamma)
|
||||
assert.Len(t, ramp.Red, int(size))
|
||||
assert.Len(t, ramp.Green, int(size))
|
||||
assert.Len(t, ramp.Blue, int(size))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestNotifySubscribers_NonBlocking(t *testing.T) {
|
||||
m := &Manager{
|
||||
dirty: make(chan struct{}, 1),
|
||||
}
|
||||
|
||||
for i := 0; i < 10; i++ {
|
||||
m.notifySubscribers()
|
||||
}
|
||||
|
||||
assert.Len(t, m.dirty, 1)
|
||||
}
|
||||
127
core/internal/server/wlcontext/context_test.go
Normal file
127
core/internal/server/wlcontext/context_test.go
Normal file
@@ -0,0 +1,127 @@
|
||||
package wlcontext
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestSharedContext_ConcurrentPostNonBlocking(t *testing.T) {
|
||||
sc := &SharedContext{
|
||||
cmdQueue: make(chan func(), 256),
|
||||
stopChan: make(chan struct{}),
|
||||
}
|
||||
|
||||
var wg sync.WaitGroup
|
||||
const goroutines = 100
|
||||
const iterations = 50
|
||||
|
||||
for i := 0; i < goroutines; i++ {
|
||||
wg.Add(1)
|
||||
go func(id int) {
|
||||
defer wg.Done()
|
||||
for j := 0; j < iterations; j++ {
|
||||
sc.Post(func() {
|
||||
_ = id + j
|
||||
})
|
||||
}
|
||||
}(i)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func TestSharedContext_PostQueueFull(t *testing.T) {
|
||||
sc := &SharedContext{
|
||||
cmdQueue: make(chan func(), 2),
|
||||
stopChan: make(chan struct{}),
|
||||
}
|
||||
|
||||
sc.Post(func() {})
|
||||
sc.Post(func() {})
|
||||
sc.Post(func() {})
|
||||
sc.Post(func() {})
|
||||
|
||||
assert.Len(t, sc.cmdQueue, 2)
|
||||
}
|
||||
|
||||
func TestSharedContext_StartMultipleTimes(t *testing.T) {
|
||||
sc := &SharedContext{
|
||||
cmdQueue: make(chan func(), 256),
|
||||
stopChan: make(chan struct{}),
|
||||
started: false,
|
||||
}
|
||||
|
||||
var wg sync.WaitGroup
|
||||
const goroutines = 10
|
||||
|
||||
for i := 0; i < goroutines; i++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
sc.Start()
|
||||
}()
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
|
||||
assert.True(t, sc.started)
|
||||
}
|
||||
|
||||
func TestSharedContext_DrainCmdQueue(t *testing.T) {
|
||||
sc := &SharedContext{
|
||||
cmdQueue: make(chan func(), 256),
|
||||
stopChan: make(chan struct{}),
|
||||
}
|
||||
|
||||
counter := 0
|
||||
for i := 0; i < 10; i++ {
|
||||
sc.cmdQueue <- func() {
|
||||
counter++
|
||||
}
|
||||
}
|
||||
|
||||
sc.drainCmdQueue()
|
||||
|
||||
assert.Equal(t, 10, counter)
|
||||
assert.Len(t, sc.cmdQueue, 0)
|
||||
}
|
||||
|
||||
func TestSharedContext_DrainCmdQueueEmpty(t *testing.T) {
|
||||
sc := &SharedContext{
|
||||
cmdQueue: make(chan func(), 256),
|
||||
stopChan: make(chan struct{}),
|
||||
}
|
||||
|
||||
sc.drainCmdQueue()
|
||||
|
||||
assert.Len(t, sc.cmdQueue, 0)
|
||||
}
|
||||
|
||||
func TestSharedContext_ConcurrentDrainAndPost(t *testing.T) {
|
||||
sc := &SharedContext{
|
||||
cmdQueue: make(chan func(), 256),
|
||||
stopChan: make(chan struct{}),
|
||||
}
|
||||
|
||||
var wg sync.WaitGroup
|
||||
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for i := 0; i < 100; i++ {
|
||||
sc.Post(func() {})
|
||||
}
|
||||
}()
|
||||
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for i := 0; i < 50; i++ {
|
||||
sc.drainCmdQueue()
|
||||
}
|
||||
}()
|
||||
|
||||
wg.Wait()
|
||||
}
|
||||
400
core/internal/server/wlroutput/manager_test.go
Normal file
400
core/internal/server/wlroutput/manager_test.go
Normal file
@@ -0,0 +1,400 @@
|
||||
package wlroutput
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestStateChanged_BothNil(t *testing.T) {
|
||||
assert.True(t, stateChanged(nil, nil))
|
||||
}
|
||||
|
||||
func TestStateChanged_OneNil(t *testing.T) {
|
||||
s := &State{Serial: 1}
|
||||
assert.True(t, stateChanged(s, nil))
|
||||
assert.True(t, stateChanged(nil, s))
|
||||
}
|
||||
|
||||
func TestStateChanged_SerialDiffers(t *testing.T) {
|
||||
a := &State{Serial: 1, Outputs: []Output{}}
|
||||
b := &State{Serial: 2, Outputs: []Output{}}
|
||||
assert.True(t, stateChanged(a, b))
|
||||
}
|
||||
|
||||
func TestStateChanged_OutputCountDiffers(t *testing.T) {
|
||||
a := &State{Serial: 1, Outputs: []Output{{Name: "eDP-1"}}}
|
||||
b := &State{Serial: 1, Outputs: []Output{}}
|
||||
assert.True(t, stateChanged(a, b))
|
||||
}
|
||||
|
||||
func TestStateChanged_OutputNameDiffers(t *testing.T) {
|
||||
a := &State{Serial: 1, Outputs: []Output{{Name: "eDP-1", Enabled: true}}}
|
||||
b := &State{Serial: 1, Outputs: []Output{{Name: "HDMI-A-1", Enabled: true}}}
|
||||
assert.True(t, stateChanged(a, b))
|
||||
}
|
||||
|
||||
func TestStateChanged_OutputEnabledDiffers(t *testing.T) {
|
||||
a := &State{Serial: 1, Outputs: []Output{{Name: "eDP-1", Enabled: true}}}
|
||||
b := &State{Serial: 1, Outputs: []Output{{Name: "eDP-1", Enabled: false}}}
|
||||
assert.True(t, stateChanged(a, b))
|
||||
}
|
||||
|
||||
func TestStateChanged_OutputPositionDiffers(t *testing.T) {
|
||||
a := &State{Serial: 1, Outputs: []Output{{Name: "eDP-1", X: 0, Y: 0}}}
|
||||
b := &State{Serial: 1, Outputs: []Output{{Name: "eDP-1", X: 1920, Y: 0}}}
|
||||
assert.True(t, stateChanged(a, b))
|
||||
}
|
||||
|
||||
func TestStateChanged_OutputTransformDiffers(t *testing.T) {
|
||||
a := &State{Serial: 1, Outputs: []Output{{Name: "eDP-1", Transform: 0}}}
|
||||
b := &State{Serial: 1, Outputs: []Output{{Name: "eDP-1", Transform: 1}}}
|
||||
assert.True(t, stateChanged(a, b))
|
||||
}
|
||||
|
||||
func TestStateChanged_OutputScaleDiffers(t *testing.T) {
|
||||
a := &State{Serial: 1, Outputs: []Output{{Name: "eDP-1", Scale: 1.0}}}
|
||||
b := &State{Serial: 1, Outputs: []Output{{Name: "eDP-1", Scale: 2.0}}}
|
||||
assert.True(t, stateChanged(a, b))
|
||||
}
|
||||
|
||||
func TestStateChanged_OutputAdaptiveSyncDiffers(t *testing.T) {
|
||||
a := &State{Serial: 1, Outputs: []Output{{Name: "eDP-1", AdaptiveSync: 0}}}
|
||||
b := &State{Serial: 1, Outputs: []Output{{Name: "eDP-1", AdaptiveSync: 1}}}
|
||||
assert.True(t, stateChanged(a, b))
|
||||
}
|
||||
|
||||
func TestStateChanged_CurrentModeNilVsNonNil(t *testing.T) {
|
||||
a := &State{Serial: 1, Outputs: []Output{{Name: "eDP-1", CurrentMode: nil}}}
|
||||
b := &State{Serial: 1, Outputs: []Output{{Name: "eDP-1", CurrentMode: &OutputMode{Width: 1920}}}}
|
||||
assert.True(t, stateChanged(a, b))
|
||||
}
|
||||
|
||||
func TestStateChanged_CurrentModeDiffers(t *testing.T) {
|
||||
a := &State{Serial: 1, Outputs: []Output{{
|
||||
Name: "eDP-1",
|
||||
CurrentMode: &OutputMode{Width: 1920, Height: 1080, Refresh: 60000},
|
||||
}}}
|
||||
b := &State{Serial: 1, Outputs: []Output{{
|
||||
Name: "eDP-1",
|
||||
CurrentMode: &OutputMode{Width: 2560, Height: 1440, Refresh: 60000},
|
||||
}}}
|
||||
assert.True(t, stateChanged(a, b))
|
||||
|
||||
b.Outputs[0].CurrentMode.Width = 1920
|
||||
b.Outputs[0].CurrentMode.Height = 1080
|
||||
b.Outputs[0].CurrentMode.Refresh = 144000
|
||||
assert.True(t, stateChanged(a, b))
|
||||
}
|
||||
|
||||
func TestStateChanged_ModesLengthDiffers(t *testing.T) {
|
||||
a := &State{Serial: 1, Outputs: []Output{{Name: "eDP-1", Modes: []OutputMode{{Width: 1920}}}}}
|
||||
b := &State{Serial: 1, Outputs: []Output{{Name: "eDP-1", Modes: []OutputMode{{Width: 1920}, {Width: 1280}}}}}
|
||||
assert.True(t, stateChanged(a, b))
|
||||
}
|
||||
|
||||
func TestStateChanged_Equal(t *testing.T) {
|
||||
mode := OutputMode{Width: 1920, Height: 1080, Refresh: 60000, Preferred: true}
|
||||
a := &State{
|
||||
Serial: 5,
|
||||
Outputs: []Output{{
|
||||
Name: "eDP-1",
|
||||
Description: "Built-in display",
|
||||
Make: "BOE",
|
||||
Model: "0x0ABC",
|
||||
SerialNumber: "12345",
|
||||
PhysicalWidth: 309,
|
||||
PhysicalHeight: 174,
|
||||
Enabled: true,
|
||||
X: 0,
|
||||
Y: 0,
|
||||
Transform: 0,
|
||||
Scale: 1.0,
|
||||
CurrentMode: &mode,
|
||||
Modes: []OutputMode{mode},
|
||||
AdaptiveSync: 0,
|
||||
}},
|
||||
}
|
||||
b := &State{
|
||||
Serial: 5,
|
||||
Outputs: []Output{{
|
||||
Name: "eDP-1",
|
||||
Description: "Built-in display",
|
||||
Make: "BOE",
|
||||
Model: "0x0ABC",
|
||||
SerialNumber: "12345",
|
||||
PhysicalWidth: 309,
|
||||
PhysicalHeight: 174,
|
||||
Enabled: true,
|
||||
X: 0,
|
||||
Y: 0,
|
||||
Transform: 0,
|
||||
Scale: 1.0,
|
||||
CurrentMode: &mode,
|
||||
Modes: []OutputMode{mode},
|
||||
AdaptiveSync: 0,
|
||||
}},
|
||||
}
|
||||
assert.False(t, stateChanged(a, b))
|
||||
}
|
||||
|
||||
func TestManager_ConcurrentGetState(t *testing.T) {
|
||||
m := &Manager{
|
||||
state: &State{
|
||||
Serial: 1,
|
||||
Outputs: []Output{{Name: "eDP-1", Enabled: true}},
|
||||
},
|
||||
}
|
||||
|
||||
var wg sync.WaitGroup
|
||||
const goroutines = 50
|
||||
const iterations = 100
|
||||
|
||||
for i := 0; i < goroutines/2; i++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for j := 0; j < iterations; j++ {
|
||||
s := m.GetState()
|
||||
_ = s.Serial
|
||||
_ = s.Outputs
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
for i := 0; i < goroutines/2; i++ {
|
||||
wg.Add(1)
|
||||
go func(i int) {
|
||||
defer wg.Done()
|
||||
for j := 0; j < iterations; j++ {
|
||||
m.stateMutex.Lock()
|
||||
m.state = &State{
|
||||
Serial: uint32(j),
|
||||
Outputs: []Output{{Name: "eDP-1", Scale: float64(j % 3)}},
|
||||
}
|
||||
m.stateMutex.Unlock()
|
||||
}
|
||||
}(i)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func TestManager_ConcurrentSubscriberAccess(t *testing.T) {
|
||||
m := &Manager{
|
||||
stopChan: make(chan struct{}),
|
||||
dirty: make(chan struct{}, 1),
|
||||
}
|
||||
|
||||
var wg sync.WaitGroup
|
||||
const goroutines = 20
|
||||
|
||||
for i := 0; i < goroutines; i++ {
|
||||
wg.Add(1)
|
||||
go func(id int) {
|
||||
defer wg.Done()
|
||||
subID := string(rune('a' + id))
|
||||
ch := m.Subscribe(subID)
|
||||
assert.NotNil(t, ch)
|
||||
time.Sleep(time.Millisecond)
|
||||
m.Unsubscribe(subID)
|
||||
}(i)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func TestManager_SyncmapHeadsConcurrentAccess(t *testing.T) {
|
||||
m := &Manager{}
|
||||
|
||||
var wg sync.WaitGroup
|
||||
const goroutines = 30
|
||||
const iterations = 50
|
||||
|
||||
for i := 0; i < goroutines; i++ {
|
||||
wg.Add(1)
|
||||
go func(id int) {
|
||||
defer wg.Done()
|
||||
key := uint32(id)
|
||||
|
||||
for j := 0; j < iterations; j++ {
|
||||
state := &headState{
|
||||
id: key,
|
||||
name: "test-head",
|
||||
enabled: j%2 == 0,
|
||||
scale: float64(j % 3),
|
||||
modeIDs: []uint32{uint32(j)},
|
||||
}
|
||||
m.heads.Store(key, state)
|
||||
|
||||
if loaded, ok := m.heads.Load(key); ok {
|
||||
assert.Equal(t, key, loaded.id)
|
||||
}
|
||||
|
||||
m.heads.Range(func(k uint32, v *headState) bool {
|
||||
_ = v.name
|
||||
_ = v.enabled
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
m.heads.Delete(key)
|
||||
}(i)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func TestManager_SyncmapModesConcurrentAccess(t *testing.T) {
|
||||
m := &Manager{}
|
||||
|
||||
var wg sync.WaitGroup
|
||||
const goroutines = 30
|
||||
const iterations = 50
|
||||
|
||||
for i := 0; i < goroutines; i++ {
|
||||
wg.Add(1)
|
||||
go func(id int) {
|
||||
defer wg.Done()
|
||||
key := uint32(id)
|
||||
|
||||
for j := 0; j < iterations; j++ {
|
||||
state := &modeState{
|
||||
id: key,
|
||||
width: int32(1920 + j),
|
||||
height: int32(1080 + j),
|
||||
refresh: 60000,
|
||||
preferred: j == 0,
|
||||
}
|
||||
m.modes.Store(key, state)
|
||||
|
||||
if loaded, ok := m.modes.Load(key); ok {
|
||||
assert.Equal(t, key, loaded.id)
|
||||
}
|
||||
|
||||
m.modes.Range(func(k uint32, v *modeState) bool {
|
||||
_ = v.width
|
||||
_ = v.height
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
m.modes.Delete(key)
|
||||
}(i)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func TestManager_NotifySubscribersNonBlocking(t *testing.T) {
|
||||
m := &Manager{
|
||||
dirty: make(chan struct{}, 1),
|
||||
}
|
||||
|
||||
for i := 0; i < 10; i++ {
|
||||
m.notifySubscribers()
|
||||
}
|
||||
|
||||
assert.Len(t, m.dirty, 1)
|
||||
}
|
||||
|
||||
func TestManager_PostQueueFull(t *testing.T) {
|
||||
m := &Manager{
|
||||
cmdq: make(chan cmd, 2),
|
||||
stopChan: make(chan struct{}),
|
||||
}
|
||||
|
||||
m.post(func() {})
|
||||
m.post(func() {})
|
||||
m.post(func() {})
|
||||
m.post(func() {})
|
||||
|
||||
assert.Len(t, m.cmdq, 2)
|
||||
}
|
||||
|
||||
func TestManager_GetStateNilState(t *testing.T) {
|
||||
m := &Manager{}
|
||||
|
||||
s := m.GetState()
|
||||
assert.NotNil(t, s.Outputs)
|
||||
assert.Equal(t, uint32(0), s.Serial)
|
||||
}
|
||||
|
||||
func TestManager_FatalErrorChannel(t *testing.T) {
|
||||
m := &Manager{
|
||||
fatalError: make(chan error, 1),
|
||||
}
|
||||
|
||||
ch := m.FatalError()
|
||||
assert.NotNil(t, ch)
|
||||
|
||||
m.fatalError <- assert.AnError
|
||||
err := <-ch
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestOutputMode_Fields(t *testing.T) {
|
||||
mode := OutputMode{
|
||||
Width: 1920,
|
||||
Height: 1080,
|
||||
Refresh: 60000,
|
||||
Preferred: true,
|
||||
ID: 42,
|
||||
}
|
||||
|
||||
assert.Equal(t, int32(1920), mode.Width)
|
||||
assert.Equal(t, int32(1080), mode.Height)
|
||||
assert.Equal(t, int32(60000), mode.Refresh)
|
||||
assert.True(t, mode.Preferred)
|
||||
assert.Equal(t, uint32(42), mode.ID)
|
||||
}
|
||||
|
||||
func TestOutput_Fields(t *testing.T) {
|
||||
out := Output{
|
||||
Name: "eDP-1",
|
||||
Description: "Built-in display",
|
||||
Make: "BOE",
|
||||
Model: "0x0ABC",
|
||||
SerialNumber: "12345",
|
||||
PhysicalWidth: 309,
|
||||
PhysicalHeight: 174,
|
||||
Enabled: true,
|
||||
X: 0,
|
||||
Y: 0,
|
||||
Transform: 0,
|
||||
Scale: 1.5,
|
||||
AdaptiveSync: 1,
|
||||
ID: 1,
|
||||
}
|
||||
|
||||
assert.Equal(t, "eDP-1", out.Name)
|
||||
assert.Equal(t, "Built-in display", out.Description)
|
||||
assert.True(t, out.Enabled)
|
||||
assert.Equal(t, float64(1.5), out.Scale)
|
||||
assert.Equal(t, uint32(1), out.AdaptiveSync)
|
||||
}
|
||||
|
||||
func TestHeadState_ModeIDsSlice(t *testing.T) {
|
||||
head := &headState{
|
||||
id: 1,
|
||||
modeIDs: make([]uint32, 0),
|
||||
}
|
||||
|
||||
head.modeIDs = append(head.modeIDs, 1, 2, 3)
|
||||
assert.Len(t, head.modeIDs, 3)
|
||||
assert.Equal(t, uint32(1), head.modeIDs[0])
|
||||
}
|
||||
|
||||
func TestStateChanged_BothCurrentModeNil(t *testing.T) {
|
||||
a := &State{Serial: 1, Outputs: []Output{{Name: "eDP-1", CurrentMode: nil}}}
|
||||
b := &State{Serial: 1, Outputs: []Output{{Name: "eDP-1", CurrentMode: nil}}}
|
||||
assert.False(t, stateChanged(a, b))
|
||||
}
|
||||
|
||||
func TestStateChanged_IndexOutOfBounds(t *testing.T) {
|
||||
a := &State{Serial: 1, Outputs: []Output{{Name: "eDP-1"}}}
|
||||
b := &State{Serial: 1, Outputs: []Output{{Name: "eDP-1"}, {Name: "HDMI-A-1"}}}
|
||||
assert.True(t, stateChanged(a, b))
|
||||
}
|
||||
Reference in New Issue
Block a user