mirror of
https://github.com/black-ant/Ant-Browser.git
synced 2026-07-14 18:48:55 +08:00
channel: master version: 1.1.0 source-ref: master published-at-utc: 2026-03-20T14:14:07Z
180 lines
4.2 KiB
Go
180 lines
4.2 KiB
Go
package backend
|
|
|
|
import (
|
|
"ant-chrome/backend/internal/browser"
|
|
"ant-chrome/backend/internal/config"
|
|
"net"
|
|
"os/exec"
|
|
"reflect"
|
|
goruntime "runtime"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func TestEnsureNewWindowLaunchArgAddsFlagOnce(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
got := ensureNewWindowLaunchArg([]string{"--lang=en-US"})
|
|
want := []string{"--lang=en-US", "--new-window"}
|
|
if !reflect.DeepEqual(got, want) {
|
|
t.Fatalf("ensureNewWindowLaunchArg 结果错误: got=%v want=%v", got, want)
|
|
}
|
|
|
|
got = ensureNewWindowLaunchArg([]string{"--new-window", "--lang=en-US"})
|
|
want = []string{"--new-window", "--lang=en-US"}
|
|
if !reflect.DeepEqual(got, want) {
|
|
t.Fatalf("ensureNewWindowLaunchArg 不应重复追加: got=%v want=%v", got, want)
|
|
}
|
|
}
|
|
|
|
func TestIsBrowserProfileLive(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
ln := mustListenLoopback(t)
|
|
defer ln.Close()
|
|
|
|
profile := &BrowserProfile{
|
|
Running: true,
|
|
DebugPort: listenerPort(t, ln),
|
|
}
|
|
if !isBrowserProfileLive(profile) {
|
|
t.Fatal("期望存活中的调试端口被识别为运行中实例")
|
|
}
|
|
|
|
if isBrowserProfileLive(&BrowserProfile{Running: true, DebugPort: 0}) {
|
|
t.Fatal("debugPort=0 不应被识别为运行中实例")
|
|
}
|
|
}
|
|
|
|
func TestWaitBrowserDebugPortStableKeepsListeningPort(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
ln := mustListenLoopback(t)
|
|
defer ln.Close()
|
|
|
|
if err := waitBrowserDebugPortStable(listenerPort(t, ln), time.Second, 250*time.Millisecond); err != nil {
|
|
t.Fatalf("waitBrowserDebugPortStable 返回错误: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestWaitBrowserDebugPortStableRejectsEphemeralPort(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
ln := mustListenLoopback(t)
|
|
port := listenerPort(t, ln)
|
|
time.AfterFunc(120*time.Millisecond, func() {
|
|
_ = ln.Close()
|
|
})
|
|
|
|
err := waitBrowserDebugPortStable(port, time.Second, 400*time.Millisecond)
|
|
if err == nil {
|
|
t.Fatal("期望短暂就绪后关闭的端口被判定为失败")
|
|
}
|
|
}
|
|
|
|
func TestWaitBrowserProcessKeepsRunningWhileDebugPortAlive(t *testing.T) {
|
|
ln := mustListenLoopback(t)
|
|
port := listenerPort(t, ln)
|
|
|
|
app := NewApp("")
|
|
app.browserMgr = browser.NewManager(config.DefaultConfig(), "")
|
|
app.browserMgr.Profiles = map[string]*BrowserProfile{
|
|
"profile-detached": {
|
|
ProfileId: "profile-detached",
|
|
ProfileName: "Detached Browser",
|
|
Running: true,
|
|
DebugPort: port,
|
|
Pid: 12345,
|
|
},
|
|
}
|
|
app.browserMgr.BrowserProcesses = make(map[string]*exec.Cmd)
|
|
|
|
cmd := shortLivedCommand()
|
|
if err := cmd.Start(); err != nil {
|
|
t.Fatalf("启动短命测试进程失败: %v", err)
|
|
}
|
|
app.browserMgr.BrowserProcesses["profile-detached"] = cmd
|
|
|
|
done := make(chan struct{})
|
|
go func() {
|
|
app.waitBrowserProcess("profile-detached", cmd)
|
|
close(done)
|
|
}()
|
|
|
|
waitForCondition(t, 3*time.Second, func() bool {
|
|
app.browserMgr.Mutex.Lock()
|
|
defer app.browserMgr.Mutex.Unlock()
|
|
|
|
profile := app.browserMgr.Profiles["profile-detached"]
|
|
_, tracked := app.browserMgr.BrowserProcesses["profile-detached"]
|
|
return profile != nil && profile.Running && !tracked
|
|
})
|
|
|
|
_ = ln.Close()
|
|
|
|
waitForCondition(t, 4*time.Second, func() bool {
|
|
app.browserMgr.Mutex.Lock()
|
|
defer app.browserMgr.Mutex.Unlock()
|
|
|
|
profile := app.browserMgr.Profiles["profile-detached"]
|
|
return profile != nil && !profile.Running && profile.DebugPort == 0 && profile.Pid == 0
|
|
})
|
|
|
|
select {
|
|
case <-done:
|
|
case <-time.After(4 * time.Second):
|
|
t.Fatal("waitBrowserProcess 未在调试端口关闭后结束")
|
|
}
|
|
}
|
|
|
|
func mustListenLoopback(t *testing.T) net.Listener {
|
|
t.Helper()
|
|
|
|
ln, err := net.Listen("tcp", "127.0.0.1:0")
|
|
if err != nil {
|
|
t.Fatalf("监听测试端口失败: %v", err)
|
|
}
|
|
|
|
go func() {
|
|
for {
|
|
conn, err := ln.Accept()
|
|
if err != nil {
|
|
return
|
|
}
|
|
_ = conn.Close()
|
|
}
|
|
}()
|
|
|
|
return ln
|
|
}
|
|
|
|
func listenerPort(t *testing.T, ln net.Listener) int {
|
|
t.Helper()
|
|
|
|
tcpAddr, ok := ln.Addr().(*net.TCPAddr)
|
|
if !ok {
|
|
t.Fatalf("解析监听地址失败: %T", ln.Addr())
|
|
}
|
|
return tcpAddr.Port
|
|
}
|
|
|
|
func shortLivedCommand() *exec.Cmd {
|
|
if goruntime.GOOS == "windows" {
|
|
return exec.Command("cmd", "/c", "exit", "0")
|
|
}
|
|
return exec.Command("sh", "-c", "exit 0")
|
|
}
|
|
|
|
func waitForCondition(t *testing.T, timeout time.Duration, check func() bool) {
|
|
t.Helper()
|
|
|
|
deadline := time.Now().Add(timeout)
|
|
for time.Now().Before(deadline) {
|
|
if check() {
|
|
return
|
|
}
|
|
time.Sleep(100 * time.Millisecond)
|
|
}
|
|
t.Fatal("等待条件成立超时")
|
|
}
|