@@ -17,11 +17,15 @@ limitations under the License.
1717package main
1818
1919import (
20+ "context"
2021 "fmt"
22+ "io"
2123 "net"
24+ "net/http"
2225 "os"
2326 "reflect"
2427 "testing"
28+ "time"
2529)
2630
2731func TestMain (t * testing.T ) {
@@ -80,3 +84,97 @@ func TestTrapClosedConnErr(t *testing.T) {
8084 }
8185 }
8286}
87+
88+ func TestServeMetrics (t * testing.T ) {
89+ // Open random test port
90+ l , err := net .Listen ("tcp" , "127.0.0.1:0" )
91+ if err != nil {
92+ t .Fatalf ("listen: %v" , err )
93+ }
94+
95+ // Start serveMetrics in background
96+ errCh := make (chan error , 1 )
97+ go func () { errCh <- serveMetrics (l ) }()
98+
99+ // Build URL
100+ url := "http://" + l .Addr ().String () + "/metrics"
101+
102+ // Client timeout for each request
103+ client := & http.Client {Timeout : 500 * time .Millisecond }
104+
105+ // Poll with 3-second deadlines until server is ready
106+ ctx , cancel := context .WithTimeout (context .Background (), 3 * time .Second )
107+ defer cancel ()
108+
109+ done := make (chan struct {})
110+ go func (ctx context.Context ) {
111+ defer close (done )
112+ for {
113+ req , _ := http .NewRequestWithContext (ctx , http .MethodGet , url , nil )
114+ // Execute probe, expecting status 200
115+ resp , err := client .Do (req )
116+ if err == nil {
117+ if resp .StatusCode == http .StatusOK {
118+ resp .Body .Close ()
119+ return
120+ }
121+ resp .Body .Close ()
122+ }
123+ // Abort probe if context deadline is expired or canceled
124+ select {
125+ case <- ctx .Done ():
126+ return
127+ default :
128+ time .Sleep (20 * time .Millisecond )
129+ }
130+ }
131+ }(ctx )
132+
133+ // Wait for readiness or fail on timeout
134+ select {
135+ case <- done :
136+ case <- ctx .Done ():
137+ t .Fatalf ("server not ready: %v" , ctx .Err ())
138+ }
139+
140+ // Perform the request
141+ req , _ := http .NewRequestWithContext (ctx , http .MethodGet , url , nil )
142+ resp , err := client .Do (req )
143+ if err != nil {
144+ t .Fatalf ("Get /metrics: %v" , err )
145+ }
146+ t .Cleanup (func () {
147+ if resp != nil && resp .Body != nil {
148+ _ = resp .Body .Close ()
149+ }
150+ })
151+
152+ // Check for HTTP status 200
153+ if resp .StatusCode != http .StatusOK {
154+ t .Fatalf ("unexpected status: %d" , resp .StatusCode )
155+ }
156+ // Validate response body is non-empty
157+ body , err := io .ReadAll (resp .Body )
158+ if err != nil {
159+ t .Fatalf ("read body: %v" , err )
160+ }
161+ // Validate response body is non-empty
162+ if len (body ) == 0 {
163+ t .Fatalf ("empty metrics body" )
164+ }
165+
166+ // Trigger graceful shutdown
167+ if err := l .Close (); err != nil {
168+ t .Fatalf ("close listener %v" , err )
169+ }
170+
171+ // Fail if errCh exits non-graceful or is not closed after 2 sec
172+ select {
173+ case err := <- errCh :
174+ if err != nil {
175+ t .Fatalf ("serveMetrics error after close: %v" , err )
176+ }
177+ case <- time .After (2 * time .Second ):
178+ t .Fatalf ("serveMetrics did not exit after listener close" )
179+ }
180+ }
0 commit comments