返回 DeepSeek-Reasonix
serve_portfile_test.go
根目录 / internal / serve / serve_portfile_test.go
1 package serve
2
3 import (
4 "context"
5 "net"
6 "net/http"
7 "testing"
8 "time"
9
10 "reasonix/internal/config"
11 "reasonix/internal/control"
12 "reasonix/internal/stats"
13 )
14
15 func newListenerTestServer(t *testing.T) *Server {
16 t.Helper()
17 // Server construction starts the process-wide usage projection, even with
18 // a mock controller. Fence it on both sides of this test's home override.
19 closeUsage := func() {
20 ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
21 defer cancel()
22 if err := stats.CloseUsageCatalogs(ctx); err != nil {
23 t.Fatalf("close usage catalog: %v", err)
24 }
25 }
26 closeUsage()
27 t.Setenv("REASONIX_HOME", t.TempDir())
28 t.Cleanup(closeUsage)
29 bc := NewBroadcaster()
30 ctrl := control.New(control.Options{
31 Sink: bc,
32 Label: "listener-test",
33 SessionDir: t.TempDir(),
34 })
35 t.Cleanup(func() { ctrl.Close() })
36 return New(ctrl, bc, config.ServeConfig{})
37 }
38
39 func waitForHTTP(t *testing.T, addr string) {
40 t.Helper()
41 client := &http.Client{Timeout: 2 * time.Second}
42 var lastErr error
43 for range 100 {
44 resp, err := client.Get("http://" + addr + "/assets/logo-wordmark.svg")
45 if err == nil {
46 resp.Body.Close()
47 if resp.StatusCode != http.StatusOK {
48 t.Fatalf("GET logo = %d, want 200", resp.StatusCode)
49 }
50 return
51 }
52 lastErr = err
53 time.Sleep(20 * time.Millisecond)
54 }
55 t.Fatalf("server never came up on %s: %v", addr, lastErr)
56 }
57
58 // RunGracefulListener must serve on the caller-supplied listener so callers
59 // that need the real bound address (--addr 127.0.0.1:0 with --port-file) can
60 // listen first, record ln.Addr(), then hand the listener over.
61 func TestRunGracefulListenerServesOnProvidedListener(t *testing.T) {
62 srv := newListenerTestServer(t)
63
64 ln, err := net.Listen("tcp", "127.0.0.1:0")
65 if err != nil {
66 t.Fatalf("listen: %v", err)
67 }
68 ctx, cancel := context.WithCancel(context.Background())
69 done := make(chan error, 1)
70 go func() { done <- srv.RunGracefulListener(ctx, ln) }()
71
72 waitForHTTP(t, ln.Addr().String())
73
74 cancel()
75 select {
76 case err := <-done:
77 if err != nil {
78 t.Fatalf("RunGracefulListener returned a shutdown error: %v", err)
79 }
80 case <-time.After(15 * time.Second):
81 t.Fatal("RunGracefulListener did not return after ctx cancel")
82 }
83 }
84
85 // RunGraceful must keep its historical contract (bind from the addr string
86 // itself) now that it delegates to RunGracefulListener.
87 func TestRunGracefulStillListensFromAddr(t *testing.T) {
88 srv := newListenerTestServer(t)
89
90 probe, err := net.Listen("tcp", "127.0.0.1:0")
91 if err != nil {
92 t.Fatalf("probe listen: %v", err)
93 }
94 addr := probe.Addr().String()
95 probe.Close()
96
97 ctx, cancel := context.WithCancel(context.Background())
98 done := make(chan error, 1)
99 go func() { done <- srv.RunGraceful(ctx, addr) }()
100
101 waitForHTTP(t, addr)
102
103 cancel()
104 select {
105 case err := <-done:
106 if err != nil {
107 t.Fatalf("RunGraceful returned a shutdown error: %v", err)
108 }
109 case <-time.After(15 * time.Second):
110 t.Fatal("RunGraceful did not return after ctx cancel")
111 }
112 }
113
113 lines GO