MXL Fabrics tests
This commit is contained in:
@@ -0,0 +1,293 @@
|
||||
//go:build mxl_integration
|
||||
|
||||
package mxlfabrics_test
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/qvest-digital/go-mxl/fabrics"
|
||||
"github.com/qvest-digital/go-mxl/mxl"
|
||||
)
|
||||
|
||||
const testAudioFlowID = "b3bb5be7-9fe9-4324-a5bb-4c70e1084449"
|
||||
|
||||
const testAudioFlow = `{
|
||||
"description": "MXL Player Fabrics SHM audio integration test",
|
||||
"id": "b3bb5be7-9fe9-4324-a5bb-4c70e1084449",
|
||||
"format": "urn:x-nmos:format:audio",
|
||||
"label": "Fabrics SHM test audio",
|
||||
"tags": {
|
||||
"urn:x-nmos:tag:grouphint/v1.0": [
|
||||
"mxl-player-fabrics-test:Audio"
|
||||
]
|
||||
},
|
||||
"parents": [],
|
||||
"media_type": "audio/float32",
|
||||
"sample_rate": {
|
||||
"numerator": 48000
|
||||
},
|
||||
"channel_count": 2,
|
||||
"bit_depth": 32
|
||||
}`
|
||||
|
||||
type sampleCompletion struct {
|
||||
head uint64
|
||||
count int
|
||||
}
|
||||
|
||||
func TestSampleTransferSHM(t *testing.T) {
|
||||
sourceInstance := newTestDomain(t)
|
||||
targetInstance := newTestDomain(t)
|
||||
|
||||
sourceWriter, _, err := sourceInstance.NewWriter(testAudioFlow)
|
||||
if err != nil {
|
||||
t.Fatalf("create source writer: %v", err)
|
||||
}
|
||||
t.Cleanup(func() {
|
||||
if err := sourceWriter.Close(); err != nil {
|
||||
t.Errorf("close source writer: %v", err)
|
||||
}
|
||||
})
|
||||
|
||||
sourceReader, err := sourceInstance.NewReader(testAudioFlowID)
|
||||
if err != nil {
|
||||
t.Fatalf("create source reader: %v", err)
|
||||
}
|
||||
t.Cleanup(func() {
|
||||
if err := sourceReader.Close(); err != nil {
|
||||
t.Errorf("close source reader: %v", err)
|
||||
}
|
||||
})
|
||||
|
||||
targetWriter, _, err := targetInstance.NewWriter(testAudioFlow)
|
||||
if err != nil {
|
||||
t.Fatalf("create target writer: %v", err)
|
||||
}
|
||||
t.Cleanup(func() {
|
||||
if err := targetWriter.Close(); err != nil {
|
||||
t.Errorf("close target writer: %v", err)
|
||||
}
|
||||
})
|
||||
|
||||
sourceFabrics, err := fabrics.NewInstance(sourceInstance)
|
||||
if err != nil {
|
||||
t.Fatalf("create source Fabrics instance: %v", err)
|
||||
}
|
||||
t.Cleanup(func() {
|
||||
if err := sourceFabrics.Close(); err != nil {
|
||||
t.Errorf("close source Fabrics instance: %v", err)
|
||||
}
|
||||
})
|
||||
|
||||
targetFabrics, err := fabrics.NewInstance(targetInstance)
|
||||
if err != nil {
|
||||
t.Fatalf("create target Fabrics instance: %v", err)
|
||||
}
|
||||
t.Cleanup(func() {
|
||||
if err := targetFabrics.Close(); err != nil {
|
||||
t.Errorf("close target Fabrics instance: %v", err)
|
||||
}
|
||||
})
|
||||
|
||||
target, err := targetFabrics.NewTarget()
|
||||
if err != nil {
|
||||
t.Fatalf("create target: %v", err)
|
||||
}
|
||||
t.Cleanup(func() {
|
||||
if err := target.Close(); err != nil {
|
||||
t.Errorf("close target: %v", err)
|
||||
}
|
||||
})
|
||||
|
||||
targetInfo, err := target.Setup(fabrics.TargetConfig{
|
||||
Interface: requireSHMInterface(t, targetFabrics),
|
||||
Writer: targetWriter,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("set up target: %v", err)
|
||||
}
|
||||
t.Cleanup(func() {
|
||||
if err := targetInfo.Close(); err != nil {
|
||||
t.Errorf("close target info: %v", err)
|
||||
}
|
||||
})
|
||||
|
||||
initiator, err := sourceFabrics.NewInitiator()
|
||||
if err != nil {
|
||||
t.Fatalf("create initiator: %v", err)
|
||||
}
|
||||
t.Cleanup(func() {
|
||||
if err := initiator.Close(); err != nil {
|
||||
t.Errorf("close initiator: %v", err)
|
||||
}
|
||||
})
|
||||
|
||||
if err := initiator.Setup(fabrics.InitiatorConfig{
|
||||
Interface: requireSHMInterface(t, sourceFabrics),
|
||||
Reader: sourceReader,
|
||||
}); err != nil {
|
||||
t.Fatalf("set up initiator: %v", err)
|
||||
}
|
||||
|
||||
if err := initiator.AddTarget(targetInfo); err != nil {
|
||||
t.Fatalf("add target: %v", err)
|
||||
}
|
||||
|
||||
connectDeadline := time.Now().Add(5 * time.Second)
|
||||
for {
|
||||
if time.Now().After(connectDeadline) {
|
||||
t.Fatal("SHM initiator did not become ready")
|
||||
}
|
||||
|
||||
_, _, err := target.ReadSamplesNonBlocking()
|
||||
if err != nil && !errors.Is(err, fabrics.ErrNotReady) {
|
||||
t.Fatalf("progress target during setup: %v", err)
|
||||
}
|
||||
|
||||
err = initiator.MakeProgressNonBlocking()
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
if !errors.Is(err, fabrics.ErrNotReady) {
|
||||
t.Fatalf("progress initiator during setup: %v", err)
|
||||
}
|
||||
|
||||
time.Sleep(time.Millisecond)
|
||||
}
|
||||
|
||||
const sampleCount = 480
|
||||
head := mxl.CurrentIndex(sourceWriter.Config().Common.GrainRate)
|
||||
|
||||
sourceSamples, err := sourceWriter.OpenSamples(head, sampleCount)
|
||||
if err != nil {
|
||||
t.Fatalf("open source samples: %v", err)
|
||||
}
|
||||
for channel := uint64(0); channel < sourceSamples.ChannelCount; channel++ {
|
||||
first, second, err := sourceSamples.ChannelFragments(channel)
|
||||
if err != nil {
|
||||
t.Fatalf("get source channel %d fragments: %v", channel, err)
|
||||
}
|
||||
var position uint64
|
||||
for _, fragment := range [][]byte{first, second} {
|
||||
for offset := range fragment {
|
||||
fragment[offset] = byte((position + head + channel) & 0xff)
|
||||
position++
|
||||
}
|
||||
}
|
||||
}
|
||||
if err := sourceSamples.Commit(); err != nil {
|
||||
t.Fatalf("commit source samples: %v", err)
|
||||
}
|
||||
|
||||
deadline := time.Now().Add(5 * time.Second)
|
||||
received := make(chan sampleCompletion, 1)
|
||||
targetErrors := make(chan error, 1)
|
||||
stopTarget := make(chan struct{})
|
||||
var targetWG sync.WaitGroup
|
||||
|
||||
targetWG.Add(1)
|
||||
go func() {
|
||||
defer targetWG.Done()
|
||||
for {
|
||||
select {
|
||||
case <-stopTarget:
|
||||
return
|
||||
default:
|
||||
}
|
||||
|
||||
receivedHead, receivedCount, err := target.ReadSamplesNonBlocking()
|
||||
switch {
|
||||
case err == nil:
|
||||
received <- sampleCompletion{head: receivedHead, count: receivedCount}
|
||||
return
|
||||
case errors.Is(err, fabrics.ErrNotReady):
|
||||
time.Sleep(time.Millisecond)
|
||||
default:
|
||||
targetErrors <- err
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
defer func() {
|
||||
close(stopTarget)
|
||||
targetWG.Wait()
|
||||
}()
|
||||
|
||||
for {
|
||||
err := initiator.TransferSamples(head, sampleCount)
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
if !errors.Is(err, fabrics.ErrNotReady) {
|
||||
t.Fatalf("transfer samples: %v", err)
|
||||
}
|
||||
if time.Now().After(deadline) {
|
||||
t.Fatal("timed out enqueueing SHM sample transfer")
|
||||
}
|
||||
|
||||
err = initiator.MakeProgressNonBlocking()
|
||||
if err != nil && !errors.Is(err, fabrics.ErrNotReady) {
|
||||
t.Fatalf("make initiator progress while enqueueing: %v", err)
|
||||
}
|
||||
time.Sleep(time.Millisecond)
|
||||
}
|
||||
|
||||
var completion sampleCompletion
|
||||
for {
|
||||
if time.Now().After(deadline) {
|
||||
t.Fatal("SHM sample transfer timed out")
|
||||
}
|
||||
|
||||
err := initiator.MakeProgressNonBlocking()
|
||||
if err != nil && !errors.Is(err, fabrics.ErrNotReady) {
|
||||
t.Fatalf("make initiator progress: %v", err)
|
||||
}
|
||||
|
||||
select {
|
||||
case completion = <-received:
|
||||
goto receivedSamples
|
||||
case err := <-targetErrors:
|
||||
t.Fatalf("read target completion: %v", err)
|
||||
default:
|
||||
time.Sleep(time.Millisecond)
|
||||
}
|
||||
}
|
||||
|
||||
receivedSamples:
|
||||
if completion.head != head {
|
||||
t.Fatalf("received sample head = %d, want %d", completion.head, head)
|
||||
}
|
||||
if completion.count != sampleCount {
|
||||
t.Fatalf("received sample count = %d, want %d", completion.count, sampleCount)
|
||||
}
|
||||
|
||||
targetSamples, err := targetWriter.OpenSamples(head, sampleCount)
|
||||
if err != nil {
|
||||
t.Fatalf("open transferred samples: %v", err)
|
||||
}
|
||||
defer func() {
|
||||
if err := targetSamples.Cancel(); err != nil {
|
||||
t.Errorf("cancel transferred samples: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
for channel := uint64(0); channel < targetSamples.ChannelCount; channel++ {
|
||||
first, second, err := targetSamples.ChannelFragments(channel)
|
||||
if err != nil {
|
||||
t.Fatalf("get target channel %d fragments: %v", channel, err)
|
||||
}
|
||||
var position uint64
|
||||
for _, fragment := range [][]byte{first, second} {
|
||||
for _, got := range fragment {
|
||||
want := byte((position + head + channel) & 0xff)
|
||||
if got != want {
|
||||
t.Fatalf("channel %d sample byte %d = %d, want %d", channel, position, got, want)
|
||||
}
|
||||
position++
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user