Files
MyGoNavi/windows_single_instance_windows_test.go
Syngnat 2597d087ad feat(windows): 支持 MSI 主程序严格单实例启动
- 仅对带 MSI 安装标识的 Windows 主程序启用单实例门禁,Portable 与特殊运行模式保持原行为
- 重复启动通过命名事件唤醒首个窗口,并兼容 Wails 运行时尚未就绪的激活请求
- 新增并发抢占、窗口激活与安装模式测试,覆盖同一安装仅保留一个主实例
2026-07-21 10:49:52 +08:00

96 lines
2.7 KiB
Go

//go:build windows
package main
import (
"fmt"
"os"
"sync"
"testing"
"time"
)
func TestAcquireWindowsMSISingleInstanceRejectsAndSignalsSecondLaunch(t *testing.T) {
uniqueID := fmt.Sprintf("test-%d-%d", os.Getpid(), time.Now().UnixNano())
activated := make(chan struct{}, 1)
releasePrimary, isPrimary, err := acquireWindowsMSISingleInstance(uniqueID, func() {
activated <- struct{}{}
})
if err != nil {
t.Fatalf("acquire primary single-instance lock: %v", err)
}
if !isPrimary || releasePrimary == nil {
t.Fatalf("first acquisition = primary %v release %v, want primary with release", isPrimary, releasePrimary != nil)
}
t.Cleanup(releasePrimary)
releaseSecond, isPrimary, err := acquireWindowsMSISingleInstance(uniqueID, nil)
if err != nil {
t.Fatalf("acquire secondary single-instance lock: %v", err)
}
if isPrimary || releaseSecond != nil {
t.Fatalf("second acquisition = primary %v release %v, want rejected secondary", isPrimary, releaseSecond != nil)
}
select {
case <-activated:
case <-time.After(2 * time.Second):
t.Fatal("secondary acquisition did not activate the primary instance")
}
releasePrimary()
releaseReplacement, isPrimary, err := acquireWindowsMSISingleInstance(uniqueID, nil)
if err != nil {
t.Fatalf("reacquire single-instance lock after release: %v", err)
}
if !isPrimary || releaseReplacement == nil {
t.Fatalf("replacement acquisition = primary %v release %v, want new primary", isPrimary, releaseReplacement != nil)
}
releaseReplacement()
}
func TestAcquireWindowsMSISingleInstanceAllowsOnlyOneConcurrentPrimary(t *testing.T) {
uniqueID := fmt.Sprintf("concurrent-test-%d-%d", os.Getpid(), time.Now().UnixNano())
const launchCount = 32
type claimResult struct {
release func()
isPrimary bool
err error
}
start := make(chan struct{})
results := make(chan claimResult, launchCount)
var claims sync.WaitGroup
for range launchCount {
claims.Add(1)
go func() {
defer claims.Done()
<-start
release, isPrimary, err := acquireWindowsMSISingleInstance(uniqueID, nil)
results <- claimResult{release: release, isPrimary: isPrimary, err: err}
}()
}
close(start)
claims.Wait()
close(results)
primaryCount := 0
var releasePrimary func()
for result := range results {
if result.err != nil {
t.Fatalf("concurrent single-instance claim failed: %v", result.err)
}
if result.isPrimary {
primaryCount++
releasePrimary = result.release
} else if result.release != nil {
t.Fatal("secondary concurrent claim unexpectedly returned a release function")
}
}
if primaryCount != 1 || releasePrimary == nil {
t.Fatalf("concurrent primary count = %d release %v, want exactly one primary", primaryCount, releasePrimary != nil)
}
releasePrimary()
}