github.com/livekit/protocol@v1.16.1-0.20240517185851-47e4c6bba773/utils/protoproxy_test.go (about) 1 // Copyright 2023 LiveKit, Inc. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package utils 16 17 import ( 18 "runtime" 19 "sync/atomic" 20 "testing" 21 "time" 22 23 "github.com/stretchr/testify/require" 24 25 "github.com/livekit/protocol/livekit" 26 ) 27 28 func TestProtoProxy(t *testing.T) { 29 t.Run("basics", func(t *testing.T) { 30 numGoRoutines := runtime.NumGoroutine() 31 proxy, numParticipants, freeze := createTestProxy() 32 33 select { 34 case <-proxy.Updated(): 35 t.Fatal("should not have received an update") 36 default: 37 } 38 39 // should not have changed, initial value should persist 40 require.EqualValues(t, 0, proxy.Get().NumParticipants) 41 42 // immediate change 43 proxy.MarkDirty(true) 44 time.Sleep(100 * time.Millisecond) 45 46 require.EqualValues(t, 2, numParticipants.Load()) 47 require.EqualValues(t, 1, proxy.Get().NumParticipants) 48 49 // queued updates 50 proxy.MarkDirty(false) 51 select { 52 case <-proxy.Updated(): 53 // consume previous notification 54 default: 55 } 56 require.EqualValues(t, 1, proxy.Get().NumParticipants) 57 58 // freeze and ensure that updates are not triggered 59 freeze.Store(true) 60 // freezing and consuming the previous notification to ensure counter does not increase in updateFn 61 select { 62 case <-proxy.Updated(): 63 case <-time.After(100 * time.Millisecond): 64 t.Fatal("should have received an update") 65 } 66 // possible that ticker was updated while markDirty queued another update 67 require.GreaterOrEqual(t, int(proxy.Get().NumParticipants), 2) 68 69 // trigger another update, but should not get notification as freeze is in place and the model should not have changed 70 proxy.MarkDirty(false) 71 time.Sleep(100 * time.Millisecond) 72 select { 73 case <-proxy.Updated(): 74 t.Fatal("should not have received an update") 75 default: 76 } 77 require.EqualValues(t, 2, proxy.Get().NumParticipants) 78 79 // ensure we didn't leak 80 proxy.Stop() 81 82 for i := 0; i < 10; i++ { 83 if runtime.NumGoroutine() <= numGoRoutines { 84 break 85 } 86 time.Sleep(100 * time.Millisecond) 87 } 88 require.LessOrEqual(t, runtime.NumGoroutine(), numGoRoutines) 89 }) 90 91 t.Run("await next update after marking dirty", func(t *testing.T) { 92 proxy, _, _ := createTestProxy() 93 require.EqualValues(t, 0, proxy.Get().NumParticipants) 94 <-proxy.MarkDirty(true) 95 require.EqualValues(t, 1, proxy.Get().NumParticipants) 96 }) 97 98 t.Run("await resolves when proxy is stopped", func(t *testing.T) { 99 proxy, _, _ := createTestProxy() 100 done := proxy.MarkDirty(true) 101 proxy.Stop() 102 <-done 103 }) 104 105 t.Run("multiple awaits resolve for one update", func(t *testing.T) { 106 proxy, _, _ := createTestProxy() 107 done0 := proxy.MarkDirty(false) 108 done1 := proxy.MarkDirty(true) 109 <-done0 110 <-done1 111 require.EqualValues(t, 1, proxy.Get().NumParticipants) 112 }) 113 114 t.Run("await resolve when there is no change", func(t *testing.T) { 115 proxy := NewProtoProxy[*livekit.Room](10*time.Millisecond, func() *livekit.Room { return nil }) 116 done := proxy.MarkDirty(true) 117 time.Sleep(100 * time.Millisecond) 118 select { 119 case <-done: 120 default: 121 t.FailNow() 122 } 123 }) 124 } 125 126 func createTestProxy() (*ProtoProxy[*livekit.Room], *atomic.Uint32, *atomic.Bool) { 127 // uses an update func that increments numParticipants each time 128 var numParticipants atomic.Uint32 129 var freeze atomic.Bool 130 return NewProtoProxy[*livekit.Room]( 131 10*time.Millisecond, 132 func() *livekit.Room { 133 if !freeze.Load() { 134 defer numParticipants.Add(1) 135 } 136 return &livekit.Room{ 137 NumParticipants: numParticipants.Load(), 138 } 139 }, 140 ), &numParticipants, &freeze 141 }