From 2d1d4ad406228ef62ab078724cb7d1556e003d01 Mon Sep 17 00:00:00 2001
From: Ariver <shanghai3168@gmail.com>
Date: Mon, 13 Jul 2026 16:03:19 +0800
Subject: [PATCH] chore: point independent project metadata to unified root

---
 C1.source/privatevoice.src/app_live_caption_test.go |  101 ++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 101 insertions(+), 0 deletions(-)

diff --git a/C1.source/privatevoice.src/app_live_caption_test.go b/C1.source/privatevoice.src/app_live_caption_test.go
index 2c1610d..accb99c 100644
--- a/C1.source/privatevoice.src/app_live_caption_test.go
+++ b/C1.source/privatevoice.src/app_live_caption_test.go
@@ -2,8 +2,11 @@
 
 import (
 	"strings"
+	"sync/atomic"
 	"testing"
 	"time"
+
+	"voicesnap/internal/engine"
 )
 
 func TestLiveCaptionDisplayTextFitsOverlayBuffer(t *testing.T) {
@@ -131,6 +134,95 @@
 	}
 }
 
+func TestInitEngineSerializesAndDiscardsStaleEngine(t *testing.T) {
+	first := &closeTrackingTestEngine{name: "first"}
+	second := &closeTrackingTestEngine{name: "second"}
+	firstStarted := make(chan struct{})
+	allowFirst := make(chan struct{})
+	calls := atomic.Int32{}
+
+	a := &App{
+		engineFactory: func(initID uint64) (engine.Engine, engineMetadata, bool, error) {
+			call := calls.Add(1)
+			switch call {
+			case 1:
+				close(firstStarted)
+				<-allowFirst
+				return first, engineMetadata{InitID: initID, ResolvedModelID: "first", HardwareInfo: "first"}, true, nil
+			case 2:
+				return second, engineMetadata{InitID: initID, ResolvedModelID: "second", HardwareInfo: "second"}, true, nil
+			default:
+				t.Fatalf("unexpected engine factory call %d", call)
+				return nil, engineMetadata{}, false, nil
+			}
+		},
+	}
+
+	doneFirst := make(chan struct{})
+	go func() {
+		a.initEngine()
+		close(doneFirst)
+	}()
+
+	select {
+	case <-firstStarted:
+	case <-time.After(time.Second):
+		t.Fatal("first engine init did not start")
+	}
+
+	doneSecond := make(chan struct{})
+	go func() {
+		a.initEngine()
+		close(doneSecond)
+	}()
+
+	waitForCondition(t, time.Second, func() bool {
+		return a.engineInitSeq.Load() == 2
+	})
+	close(allowFirst)
+
+	select {
+	case <-doneFirst:
+	case <-time.After(time.Second):
+		t.Fatal("first engine init did not finish")
+	}
+	select {
+	case <-doneSecond:
+	case <-time.After(time.Second):
+		t.Fatal("second engine init did not finish")
+	}
+
+	a.engineMu.Lock()
+	gotEngine := a.eng
+	gotMeta := a.engineMeta
+	a.engineMu.Unlock()
+
+	if gotEngine != second {
+		t.Fatalf("final engine = %#v, want second", gotEngine)
+	}
+	if !first.closed.Load() {
+		t.Fatal("stale first engine should be closed")
+	}
+	if second.closed.Load() {
+		t.Fatal("current second engine should remain open")
+	}
+	if gotMeta.ResolvedModelID != "second" || gotMeta.InitID != 2 {
+		t.Fatalf("engine metadata = %+v, want second init metadata", gotMeta)
+	}
+}
+
+func waitForCondition(t *testing.T, timeout time.Duration, fn func() bool) {
+	t.Helper()
+	deadline := time.Now().Add(timeout)
+	for time.Now().Before(deadline) {
+		if fn() {
+			return
+		}
+		time.Sleep(10 * time.Millisecond)
+	}
+	t.Fatal("condition was not met before timeout")
+}
+
 func TestShouldStartHoldPreCaptureLocked(t *testing.T) {
 	a := &App{}
 	a.holdPreCaptureEnabled.Store(true)
@@ -189,6 +281,15 @@
 func (plainTestEngine) HardwareInfo() string                { return "test" }
 func (plainTestEngine) Close()                              {}
 
+type closeTrackingTestEngine struct {
+	plainTestEngine
+	name   string
+	closed atomic.Bool
+}
+
+func (e *closeTrackingTestEngine) HardwareInfo() string { return e.name }
+func (e *closeTrackingTestEngine) Close()               { e.closed.Store(true) }
+
 type tailCaptureTestEngine struct {
 	plainTestEngine
 	delay time.Duration

--
Gitblit v1.9.3