From 015f1089ea875a153dd2a18ef42c25145d4868f5 Mon Sep 17 00:00:00 2001
From: Ariver <shanghai3168@gmail.com>
Date: Sat, 11 Jul 2026 22:28:32 +0800
Subject: [PATCH] freeze(mas): snapshot tested 2.2.3 build 20260711.1807
---
C1.source/privatevoice.src/internal/audio/recorder_test.go | 472 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 471 insertions(+), 1 deletions(-)
diff --git a/C1.source/privatevoice.src/internal/audio/recorder_test.go b/C1.source/privatevoice.src/internal/audio/recorder_test.go
index 092b043..12b3673 100644
--- a/C1.source/privatevoice.src/internal/audio/recorder_test.go
+++ b/C1.source/privatevoice.src/internal/audio/recorder_test.go
@@ -1,6 +1,25 @@
package audio
-import "testing"
+import (
+ "errors"
+ "os"
+ "sync"
+ "sync/atomic"
+ "testing"
+ "time"
+
+ "github.com/gen2brain/malgo"
+)
+
+func TestMain(m *testing.M) {
+ oldEnsure := ensureMicrophoneAuthorization
+ ensureMicrophoneAuthorization = func() microphoneAuthorization {
+ return microphoneAuthorization{granted: true, state: "granted"}
+ }
+ code := m.Run()
+ ensureMicrophoneAuthorization = oldEnsure
+ os.Exit(code)
+}
func TestReadSamplesSinceReturnsOnlyNewSamples(t *testing.T) {
r := &Recorder{
@@ -57,6 +76,388 @@
}
}
+func TestStartWithoutMicrophonePermissionDoesNotInitializeDevice(t *testing.T) {
+ oldEnsure := ensureMicrophoneAuthorization
+ ensureMicrophoneAuthorization = func() microphoneAuthorization {
+ return microphoneAuthorization{granted: false, state: "denied"}
+ }
+ defer func() {
+ ensureMicrophoneAuthorization = oldEnsure
+ }()
+
+ device := newFakeRecorderDevice()
+ ctx := &fakeRecorderContext{device: device}
+ r := &Recorder{ctx: ctx}
+
+ err := r.Start()
+ if err == nil {
+ t.Fatal("Start returned nil without microphone permission")
+ }
+ if ctx.initEntered.Load() {
+ t.Fatal("initDevice should not run without microphone permission")
+ }
+ if device.startEntered.Load() {
+ t.Fatal("device Start should not run without microphone permission")
+ }
+
+ r.mu.Lock()
+ defer r.mu.Unlock()
+ if r.startRequested || r.isStarting || r.isRecording || r.stopAfterStart || r.device != nil {
+ t.Fatalf("recorder state leaked after microphone denial: startRequested=%t isStarting=%t isRecording=%t stopAfterStart=%t deviceNil=%t",
+ r.startRequested, r.isStarting, r.isRecording, r.stopAfterStart, r.device == nil)
+ }
+}
+
+func TestStopWhileMicrophoneAuthorizationPendingPreventsNativeStart(t *testing.T) {
+ oldEnsure := ensureMicrophoneAuthorization
+ entered := make(chan struct{})
+ allow := make(chan struct{})
+ var enteredOnce sync.Once
+ ensureMicrophoneAuthorization = func() microphoneAuthorization {
+ enteredOnce.Do(func() { close(entered) })
+ <-allow
+ return microphoneAuthorization{granted: true, state: "granted"}
+ }
+ defer func() {
+ ensureMicrophoneAuthorization = oldEnsure
+ }()
+
+ device := newFakeRecorderDevice()
+ ctx := &fakeRecorderContext{device: device}
+ r := &Recorder{ctx: ctx}
+
+ startDone := make(chan error, 1)
+ go func() {
+ startDone <- r.Start()
+ }()
+ select {
+ case <-entered:
+ case <-time.After(time.Second):
+ t.Fatal("microphone authorization was not entered")
+ }
+
+ r.Stop()
+ close(allow)
+ if err := <-startDone; err != nil {
+ t.Fatalf("Start returned error = %v", err)
+ }
+ if ctx.initEntered.Load() {
+ t.Fatal("initDevice should not run after Stop during microphone authorization")
+ }
+ if device.startEntered.Load() {
+ t.Fatal("device Start should not run after Stop during microphone authorization")
+ }
+
+ r.mu.Lock()
+ defer r.mu.Unlock()
+ if r.startRequested || r.isStarting || r.isRecording || r.stopAfterStart || r.device != nil {
+ t.Fatalf("recorder state leaked after pending authorization stop: startRequested=%t isStarting=%t isRecording=%t stopAfterStart=%t deviceNil=%t",
+ r.startRequested, r.isStarting, r.isRecording, r.stopAfterStart, r.device == nil)
+ }
+}
+
+func TestStopAndGetSamplesWhileMicrophoneAuthorizationPendingPreventsNativeStart(t *testing.T) {
+ oldEnsure := ensureMicrophoneAuthorization
+ entered := make(chan struct{})
+ allow := make(chan struct{})
+ var enteredOnce sync.Once
+ ensureMicrophoneAuthorization = func() microphoneAuthorization {
+ enteredOnce.Do(func() { close(entered) })
+ <-allow
+ return microphoneAuthorization{granted: true, state: "granted"}
+ }
+ defer func() {
+ ensureMicrophoneAuthorization = oldEnsure
+ }()
+
+ device := newFakeRecorderDevice()
+ ctx := &fakeRecorderContext{device: device}
+ r := &Recorder{ctx: ctx}
+
+ startDone := make(chan error, 1)
+ go func() {
+ startDone <- r.Start()
+ }()
+ select {
+ case <-entered:
+ case <-time.After(time.Second):
+ t.Fatal("microphone authorization was not entered")
+ }
+
+ samples := r.StopAndGetSamples()
+ if len(samples) != 0 {
+ t.Fatalf("samples while microphone authorization pending = %d, want 0", len(samples))
+ }
+ close(allow)
+ if err := <-startDone; err != nil {
+ t.Fatalf("Start returned error = %v", err)
+ }
+ if ctx.initEntered.Load() {
+ t.Fatal("initDevice should not run after StopAndGetSamples during microphone authorization")
+ }
+ if device.startEntered.Load() {
+ t.Fatal("device Start should not run after StopAndGetSamples during microphone authorization")
+ }
+
+ r.mu.Lock()
+ defer r.mu.Unlock()
+ if r.startRequested || r.isStarting || r.isRecording || r.stopAfterStart || r.device != nil {
+ t.Fatalf("recorder state leaked after pending authorization samples stop: startRequested=%t isStarting=%t isRecording=%t stopAfterStart=%t deviceNil=%t",
+ r.startRequested, r.isStarting, r.isRecording, r.stopAfterStart, r.device == nil)
+ }
+}
+
+func TestCloseWaitsForBlockedStartBeforeFreeingContext(t *testing.T) {
+ device := newFakeRecorderDevice()
+ ctx := &fakeRecorderContext{device: device}
+ r := &Recorder{ctx: ctx}
+
+ startDone := make(chan error, 1)
+ go func() {
+ startDone <- r.Start()
+ }()
+ waitForRecorderTest(t, func() bool { return device.startEntered.Load() })
+
+ closeDone := make(chan struct{})
+ go func() {
+ r.Close()
+ close(closeDone)
+ }()
+
+ select {
+ case <-closeDone:
+ t.Fatal("Close returned while recorder Start was still blocked")
+ case <-time.After(50 * time.Millisecond):
+ }
+ if got := ctx.freeCount.Load(); got != 0 {
+ t.Fatalf("context freed before Start returned = %d, want 0", got)
+ }
+ if got := device.uninitCount.Load(); got != 0 {
+ t.Fatalf("device uninitialized before Start returned = %d, want 0", got)
+ }
+
+ close(device.allowStart)
+ if err := <-startDone; err == nil {
+ t.Fatal("Start returned nil after Close was requested during startup")
+ }
+ waitForRecorderTest(t, func() bool {
+ select {
+ case <-closeDone:
+ return true
+ default:
+ return false
+ }
+ })
+ if got := ctx.freeCount.Load(); got != 1 {
+ t.Fatalf("context free count = %d, want 1", got)
+ }
+ if got := device.stopCount.Load(); got != 1 {
+ t.Fatalf("device stop count = %d, want 1", got)
+ }
+ if got := device.uninitCount.Load(); got != 1 {
+ t.Fatalf("device uninit count = %d, want 1", got)
+ }
+}
+
+func TestStopWhileStartBlockedDefersDeviceCleanup(t *testing.T) {
+ device := newFakeRecorderDevice()
+ ctx := &fakeRecorderContext{device: device}
+ r := &Recorder{ctx: ctx}
+
+ startDone := make(chan error, 1)
+ go func() {
+ startDone <- r.Start()
+ }()
+ waitForRecorderTest(t, func() bool { return device.startEntered.Load() })
+
+ stopDone := make(chan struct{})
+ go func() {
+ r.Stop()
+ close(stopDone)
+ }()
+
+ select {
+ case <-stopDone:
+ case <-time.After(time.Second):
+ t.Fatal("Stop should return while recorder Start is still blocked")
+ }
+ if got := device.stopCount.Load(); got != 0 {
+ t.Fatalf("device stopped before Start returned = %d, want 0", got)
+ }
+ if got := device.uninitCount.Load(); got != 0 {
+ t.Fatalf("device uninitialized before Start returned = %d, want 0", got)
+ }
+
+ close(device.allowStart)
+ if err := <-startDone; err != nil {
+ t.Fatalf("Start returned error = %v", err)
+ }
+ waitForRecorderTest(t, func() bool { return device.uninitCount.Load() == 1 })
+ if got := device.stopCount.Load(); got != 1 {
+ t.Fatalf("device stop count = %d, want 1", got)
+ }
+}
+
+func TestStopAndGetSamplesWhileStartBlockedReturnsBufferedPCM(t *testing.T) {
+ device := newFakeRecorderDevice()
+ ctx := &fakeRecorderContext{device: device}
+ r := &Recorder{ctx: ctx}
+
+ startDone := make(chan error, 1)
+ go func() {
+ startDone <- r.Start()
+ }()
+ waitForRecorderTest(t, func() bool { return device.startEntered.Load() })
+
+ r.onData(pcm16LE(16384, -32768))
+ samples := r.StopAndGetSamples()
+ if len(samples) != 2 {
+ t.Fatalf("samples len = %d, want 2", len(samples))
+ }
+ if samples[0] != 0.5 || samples[1] != -1 {
+ t.Fatalf("samples = %#v, want [0.5 -1]", samples)
+ }
+ if got := device.stopCount.Load(); got != 0 {
+ t.Fatalf("device stopped before Start returned = %d, want 0", got)
+ }
+ if got := device.uninitCount.Load(); got != 0 {
+ t.Fatalf("device uninitialized before Start returned = %d, want 0", got)
+ }
+
+ close(device.allowStart)
+ if err := <-startDone; err != nil {
+ t.Fatalf("Start returned error = %v", err)
+ }
+ if got := device.stopCount.Load(); got != 1 {
+ t.Fatalf("device stop count after Start returned = %d, want 1", got)
+ }
+ if got := device.uninitCount.Load(); got != 1 {
+ t.Fatalf("device uninit count after Start returned = %d, want 1", got)
+ }
+}
+
+func TestStopAndGetSamplesBeforeDevicePublishDefersIntentAndCleansInitializedDevice(t *testing.T) {
+ device := newFakeRecorderDevice()
+ initBlock := make(chan struct{})
+ ctx := &fakeRecorderContext{device: device, initBlock: initBlock}
+ r := &Recorder{ctx: ctx}
+
+ startDone := make(chan error, 1)
+ go func() {
+ startDone <- r.Start()
+ }()
+ waitForRecorderTest(t, func() bool { return ctx.initEntered.Load() })
+
+ samples := r.StopAndGetSamples()
+ if len(samples) != 0 {
+ t.Fatalf("samples len before device publish = %d, want 0", len(samples))
+ }
+ if device.startEntered.Load() {
+ t.Fatal("device Start should not be entered while initDevice is blocked")
+ }
+ if got := device.stopCount.Load(); got != 0 {
+ t.Fatalf("device stopped before it was published = %d, want 0", got)
+ }
+ if got := device.uninitCount.Load(); got != 0 {
+ t.Fatalf("device uninit before initDevice returned = %d, want 0", got)
+ }
+
+ close(initBlock)
+ if err := <-startDone; err != nil {
+ t.Fatalf("Start returned error = %v", err)
+ }
+ if device.startEntered.Load() {
+ t.Fatal("device Start should not run after stop intent arrived before publish")
+ }
+ if got := device.stopCount.Load(); got != 0 {
+ t.Fatalf("device stop count = %d, want 0 for never-started device", got)
+ }
+ if got := device.uninitCount.Load(); got != 1 {
+ t.Fatalf("device uninit count = %d, want 1", got)
+ }
+
+ r.mu.Lock()
+ defer r.mu.Unlock()
+ if r.startRequested || r.isStarting || r.isRecording || r.stopAfterStart || r.device != nil {
+ t.Fatalf("recorder state leaked after pre-publish stop: startRequested=%t isStarting=%t isRecording=%t stopAfterStart=%t deviceNil=%t",
+ r.startRequested, r.isStarting, r.isRecording, r.stopAfterStart, r.device == nil)
+ }
+}
+
+func TestStopBeforeDevicePublishDefersIntentAndCleansInitializedDevice(t *testing.T) {
+ device := newFakeRecorderDevice()
+ initBlock := make(chan struct{})
+ ctx := &fakeRecorderContext{device: device, initBlock: initBlock}
+ r := &Recorder{ctx: ctx}
+
+ startDone := make(chan error, 1)
+ go func() {
+ startDone <- r.Start()
+ }()
+ waitForRecorderTest(t, func() bool { return ctx.initEntered.Load() })
+
+ r.Stop()
+ if device.startEntered.Load() {
+ t.Fatal("device Start should not be entered while initDevice is blocked")
+ }
+ if got := device.stopCount.Load(); got != 0 {
+ t.Fatalf("device stopped before it was published = %d, want 0", got)
+ }
+ if got := device.uninitCount.Load(); got != 0 {
+ t.Fatalf("device uninit before initDevice returned = %d, want 0", got)
+ }
+
+ close(initBlock)
+ if err := <-startDone; err != nil {
+ t.Fatalf("Start returned error = %v", err)
+ }
+ if device.startEntered.Load() {
+ t.Fatal("device Start should not run after stop intent arrived before publish")
+ }
+ if got := device.stopCount.Load(); got != 0 {
+ t.Fatalf("device stop count = %d, want 0 for never-started device", got)
+ }
+ if got := device.uninitCount.Load(); got != 1 {
+ t.Fatalf("device uninit count = %d, want 1", got)
+ }
+}
+
+func TestStopWhileStartBlockedLetsStartErrorCleanup(t *testing.T) {
+ startErr := errors.New("start failed")
+ device := newFakeRecorderDevice()
+ device.startErr = startErr
+ ctx := &fakeRecorderContext{device: device}
+ r := &Recorder{ctx: ctx}
+
+ startDone := make(chan error, 1)
+ go func() {
+ startDone <- r.Start()
+ }()
+ waitForRecorderTest(t, func() bool { return device.startEntered.Load() })
+
+ stopDone := make(chan struct{})
+ go func() {
+ r.Stop()
+ close(stopDone)
+ }()
+
+ select {
+ case <-stopDone:
+ case <-time.After(time.Second):
+ t.Fatal("Stop should return while recorder Start is still blocked")
+ }
+ if got := device.uninitCount.Load(); got != 0 {
+ t.Fatalf("device uninit before Start error returned = %d, want 0", got)
+ }
+ close(device.allowStart)
+ if err := <-startDone; !errors.Is(err, startErr) {
+ t.Fatalf("Start error = %v, want %v", err, startErr)
+ }
+ if got := device.uninitCount.Load(); got != 1 {
+ t.Fatalf("device uninit count = %d, want 1", got)
+ }
+}
+
func pcm16LE(values ...int16) []byte {
buf := make([]byte, len(values)*2)
for i, value := range values {
@@ -65,3 +466,72 @@
}
return buf
}
+
+type fakeRecorderContext struct {
+ device *fakeRecorderDevice
+ initErr error
+ initBlock <-chan struct{}
+ initEntered atomic.Bool
+ freeCount atomic.Int32
+}
+
+func (c *fakeRecorderContext) devices(malgo.DeviceType) ([]malgo.DeviceInfo, error) {
+ return nil, nil
+}
+
+func (c *fakeRecorderContext) initDevice(malgo.DeviceConfig, malgo.DeviceCallbacks) (recorderDevice, error) {
+ c.initEntered.Store(true)
+ if c.initBlock != nil {
+ <-c.initBlock
+ }
+ if c.initErr != nil {
+ return nil, c.initErr
+ }
+ return c.device, nil
+}
+
+func (c *fakeRecorderContext) free() {
+ c.freeCount.Add(1)
+}
+
+type fakeRecorderDevice struct {
+ startEntered atomic.Bool
+ startOnce sync.Once
+ allowStart chan struct{}
+ startErr error
+ stopCount atomic.Int32
+ uninitCount atomic.Int32
+}
+
+func newFakeRecorderDevice() *fakeRecorderDevice {
+ return &fakeRecorderDevice{allowStart: make(chan struct{})}
+}
+
+func (d *fakeRecorderDevice) Start() error {
+ d.startOnce.Do(func() {
+ d.startEntered.Store(true)
+ })
+ <-d.allowStart
+ return d.startErr
+}
+
+func (d *fakeRecorderDevice) Stop() error {
+ d.stopCount.Add(1)
+ return nil
+}
+
+func (d *fakeRecorderDevice) Uninit() {
+ d.uninitCount.Add(1)
+}
+
+func waitForRecorderTest(t *testing.T, cond func() bool) {
+ t.Helper()
+ deadline := time.Now().Add(2 * time.Second)
+ for time.Now().Before(deadline) {
+ if cond() {
+ return
+ }
+ time.Sleep(10 * time.Millisecond)
+ }
+ t.Fatal("condition not met before timeout")
+}
--
Gitblit v1.9.3