Files
livekit/pkg/rtc/participant_data_blob_test.go
Raja Subramanian 1faab0c48e Add support for data blob (a. k. a. async participant attributes) (#4619)
* Async attributes on participant.

How it is different from existing participant attributes?
1. Async attribute can be added one at a time.
2. These are not included in `ParticipantInfo`.
3. Get an attribute bt participant identity and async attribute ID as
   and when needed.

* clean up

* get full definitions, not just ids

* listener OnDataTrackSchema

* name length config

* data blob

* deps

* static check

* Add missing request ID

* Update protocol commit

* Wire up StoreDataBlobResponse

* Pass request ID through in GetDataBlobResponse

* deps

* atomic

* sctp at 1.9.5

* remove proto clone

---------

Co-authored-by: Jacob Gelman <3182119+ladvoc@users.noreply.github.com>
2026-06-24 14:42:37 +05:30

176 lines
4.2 KiB
Go

// Copyright 2026 LiveKit, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package rtc
import (
"fmt"
"sync"
"testing"
"github.com/stretchr/testify/require"
"github.com/livekit/protocol/livekit"
"github.com/livekit/protocol/logger"
)
func newTestDataBlob() *ParticipantDataBlob {
return NewParticipantDataBlob(ParticipantDataBlobParams{
Logger: logger.GetLogger(),
})
}
func genericKey(name string) *livekit.DataBlobKey {
return &livekit.DataBlobKey{
Key: &livekit.DataBlobKey_Generic{
Generic: name,
},
}
}
func TestParticipantDataBlob_AddAndGet(t *testing.T) {
a := newTestDataBlob()
key := genericKey("blob-1")
contents := []byte("definition-bytes")
a.Add(&livekit.DataBlob{Key: key, Contents: contents})
got := a.Get(key)
require.NotNil(t, got)
require.Equal(t, key.String(), got.Key.String())
require.Equal(t, contents, got.Contents)
}
func TestParticipantDataBlob_AddOverwrites(t *testing.T) {
a := newTestDataBlob()
key := genericKey("blob-1")
a.Add(&livekit.DataBlob{Key: key, Contents: []byte("v1")})
a.Add(&livekit.DataBlob{Key: key, Contents: []byte("v2")})
got := a.Get(key)
require.NotNil(t, got)
require.Equal(t, []byte("v2"), got.Contents)
require.Len(t, a.GetAll(), 1)
}
func TestParticipantDataBlob_DistinctKeys(t *testing.T) {
a := newTestDataBlob()
key1 := genericKey("blob-1")
key2 := genericKey("blob-2")
a.Add(&livekit.DataBlob{Key: key1, Contents: []byte("c1")})
a.Add(&livekit.DataBlob{Key: key2, Contents: []byte("c2")})
got1 := a.Get(key1)
require.NotNil(t, got1)
require.Equal(t, []byte("c1"), got1.Contents)
got2 := a.Get(key2)
require.NotNil(t, got2)
require.Equal(t, []byte("c2"), got2.Contents)
require.Len(t, a.GetAll(), 2)
}
func TestParticipantDataBlob_Delete(t *testing.T) {
a := newTestDataBlob()
key := genericKey("blob-1")
a.Add(&livekit.DataBlob{Key: key, Contents: []byte("definition")})
a.Delete(key)
require.Nil(t, a.Get(key))
require.Empty(t, a.GetAll())
// deleting a non-existent key is a no-op
a.Delete(key)
require.Empty(t, a.GetAll())
}
func TestParticipantDataBlob_NilKey(t *testing.T) {
a := newTestDataBlob()
// nil key should be silently ignored, not panic
a.Add(&livekit.DataBlob{Contents: []byte("definition")})
require.Empty(t, a.GetAll())
require.Nil(t, a.Get(nil))
a.Delete(nil)
require.Empty(t, a.GetAll())
}
func TestParticipantDataBlob_GetMissing(t *testing.T) {
a := newTestDataBlob()
require.Nil(t, a.Get(genericKey("missing")))
}
func TestParticipantDataBlob_GetAllContents(t *testing.T) {
a := newTestDataBlob()
key1 := genericKey("blob-1")
key2 := genericKey("blob-2")
a.Add(&livekit.DataBlob{Key: key1, Contents: []byte("def-1")})
a.Add(&livekit.DataBlob{Key: key2, Contents: []byte("def-2")})
all := a.GetAll()
require.Len(t, all, 2)
for _, db := range all {
switch key := db.Key.Key.(type) {
case *livekit.DataBlobKey_Generic:
switch key.Generic {
case "blob-1":
require.Equal(t, []byte("def-1"), db.Contents)
case "blob-2":
require.Equal(t, []byte("def-2"), db.Contents)
default:
require.Fail(t, "unexpected key", key.Generic)
}
default:
require.Fail(t, "unexpected key type", "Generic")
}
}
}
func TestParticipantDataBlob_ConcurrentAccess(t *testing.T) {
a := newTestDataBlob()
const numGoroutines = 16
const opsPerGoroutine = 100
var wg sync.WaitGroup
wg.Add(numGoroutines)
for g := 0; g < numGoroutines; g++ {
go func(g int) {
defer wg.Done()
for i := 0; i < opsPerGoroutine; i++ {
key := genericKey(fmt.Sprintf("blob-%d", g%8))
a.Add(&livekit.DataBlob{Key: key, Contents: []byte("v")})
_ = a.Get(key)
_ = a.GetAll()
if i%3 == 0 {
a.Delete(key)
}
}
}(g)
}
wg.Wait()
}