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