mirror of
https://github.com/Syngnat/GoNavi.git
synced 2026-08-07 15:13:51 +08:00
- 仅对带 MSI 安装标识的 Windows 主程序启用单实例门禁,Portable 与特殊运行模式保持原行为 - 重复启动通过命名事件唤醒首个窗口,并兼容 Wails 运行时尚未就绪的激活请求 - 新增并发抢占、窗口激活与安装模式测试,覆盖同一安装仅保留一个主实例
96 lines
2.7 KiB
Go
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()
|
|
}
|