package ttslaunch import ( "context" "net" "net/http" "net/http/httptest" "strings" "testing" "time" ) // TestHTTPProbe_AnyResponseMeansRunning 钉住探活判据:**任何** HTTP 回应都算在跑 // (这个服务的根路径回什么状态码不稳定,只要有人在应答就不该再拉第二个实例)。 func TestHTTPProbe_AnyResponseMeansRunning(t *testing.T) { for _, code := range []int{http.StatusOK, http.StatusNotFound, http.StatusMethodNotAllowed} { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(code) })) if err := httpProbe(srv.URL, time.Second)(context.Background()); err != nil { t.Fatalf("状态码 %d 被判成没起: %v", code, err) } srv.Close() } } // TestHTTPProbe_Unreachable 钉住"连不上 ⇒ 没起":先占一个端口再放掉,探它必然被拒。 func TestHTTPProbe_Unreachable(t *testing.T) { addr := closedAddr(t) if err := httpProbe("http://"+addr, 500*time.Millisecond)(context.Background()); err == nil { t.Fatal("没人监听的端口被判成在跑") } } // TestHTTPProbe_TimeoutIsHonored 钉住"短超时"确实生效:服务故意不回话, // 100ms 的探活必须按超时失败,而不是等到服务自己醒。 func TestHTTPProbe_TimeoutIsHonored(t *testing.T) { block := make(chan struct{}) srv := httptest.NewServer(http.HandlerFunc(func(http.ResponseWriter, *http.Request) { <-block })) defer func() { close(block) srv.Close() }() start := time.Now() err := httpProbe(srv.URL, 100*time.Millisecond)(context.Background()) elapsed := time.Since(start) if err == nil { t.Fatal("不回话的服务被判成在跑") } if elapsed > time.Second { t.Fatalf("探活等了 %v, 明显没按 100ms 超时", elapsed) } if !strings.Contains(err.Error(), "probe") { t.Fatalf("错误没标来源: %v", err) } } // closedAddr 返回一个"刚被放掉"的回环地址(探它 = 连接被拒)。 func closedAddr(t *testing.T) string { t.Helper() ln, err := net.Listen("tcp", "127.0.0.1:0") if err != nil { t.Fatalf("listen: %v", err) } addr := ln.Addr().String() if err := ln.Close(); err != nil { t.Fatalf("close: %v", err) } return addr }