package core import ( "strings" "testing" ) // VNC 会话生命周期的**前向引用**判据(真实 bug 的守卫)。 // // `sweepSessions` 里调用了 reapLoopback / browserStack / reapBrowser,而这三个 // 助手定义在**更后面**。Lua 的 `local` 只从定义处向后可见 ⇒ 未前向声明时它们是 // **全局 nil**:加载期不报错,只有真正走到"会话过期回收"那条分支才炸: // // attempt to call global 'reapLoopback' (a nil value) // // 症状极具迷惑性:**第一次登录完全正常**(没有过期会话可回收),第二次才 500, // 且已经起来的浏览器栈**不会被回收**(reapBrowser 同样调不到)⇒ 进程与端口泄漏。 // 本文件此前已因同一个坑踩过 12 次,故这里钉住"三者都必须有前向声明"。 func TestSweepSessionsHelpersAreForwardDeclared(t *testing.T) { b, err := readBundledChannels(t) if err != nil { t.Fatal(err) } // ① 必须存在对这三个助手的前向声明 if !strings.Contains(b, "local reapLoopback, browserStack, reapBrowser") { t.Fatal("sweepSessions 用到的三个助手必须**前向声明** —— 否则 " + "`local` 只从定义处向后可见,运行到该分支时是 nil 调用(第一次登录" + "正常、第二次 500,且浏览器栈不会被回收)") } // ② 定义处必须是**赋值**形态(绑定到那个 local),不能又是 `local function` // —— 那会遮蔽前向声明,效果与没声明完全相同。 for _, fn := range []string{"browserStack", "reapBrowser", "reapLoopback"} { if strings.Contains(b, "local function "+fn+"(") { t.Errorf("%s 的定必须是 `%s = function(...)` 形式(绑定前向声明);"+ "再用 `local function` 会另起一个 local,前向声明形同虚设", fn, fn) } if !strings.Contains(b, "\n"+fn+" = function(") { t.Errorf("找不到 %s 的赋值式定义", fn) } } // ③ 声明必须**先于**使用(行号比较):这是前向引用的核心 decl := strings.Index(b, "local reapLoopback, browserStack, reapBrowser") use := strings.Index(b, "\n reapLoopback(id)\n") if decl < 0 || use < 0 { t.Fatalf("找不到声明(%d)或使用(%d)", decl, use) } if decl > use { t.Error("前向声明必须在**使用之前** —— 否则 nil 调用") } } // 浏览器栈的**启动后存活**必须有真实判据(谎报"就绪"比直接报错更坏)。 func TestBrowserStackVerifiesLivenessNotJustPort(t *testing.T) { b, err := readBundledChannels(t) if err != nil { t.Fatal(err) } // CDP 真应答(端口在听 ≠ 服务可用) if !strings.Contains(b, "browserUtil.cdpAlive") { t.Error("必须用 CDP 真应答判活,不能只看端口") } // 进程真存活 if !strings.Contains(b, "/proc/") { t.Error("必须检查 chromium 进程仍存活(/proc/)") } // 失败要把日志真因带出来(而不是一句"启动失败") if !strings.Contains(b, "browserUtil.readTail") { t.Error("启动失败必须带出日志真因") } }