55 "fmt"
66 "io"
77 "log/slog"
8+ "net"
89 "net/http"
910 "testing"
1011 "time"
@@ -16,6 +17,18 @@ import (
1617
1718func nopLogger () * slog.Logger { return slog .New (slog .DiscardHandler ) }
1819
20+ // freeAddr returns a "127.0.0.1:<port>" string using a port that is free at
21+ // the time of the call. There is a small TOCTOU window, but it eliminates
22+ // hardcoded-port flakes in CI.
23+ func freeAddr (t * testing.T ) string {
24+ t .Helper ()
25+ ln , err := net .Listen ("tcp" , "127.0.0.1:0" )
26+ require .NoError (t , err )
27+ addr := ln .Addr ().String ()
28+ ln .Close ()
29+ return addr
30+ }
31+
1932func TestNew (t * testing.T ) {
2033 t .Run ("creates app with defaults" , func (t * testing.T ) {
2134 a , err := app .New ()
@@ -44,10 +57,11 @@ func TestNew(t *testing.T) {
4457func TestAppStartAndShutdown (t * testing.T ) {
4558 t .Run ("starts with health check and h2c by default" , func (t * testing.T ) {
4659 ctx , cancel := context .WithCancel (context .Background ())
60+ addr := freeAddr (t )
4761
4862 a , err := app .New (
4963 app .WithLogger (nopLogger ()),
50- app .WithAddr ("127.0.0.1:18950" ),
64+ app .WithAddr (addr ),
5165 )
5266 require .NoError (t , err )
5367
@@ -57,7 +71,7 @@ func TestAppStartAndShutdown(t *testing.T) {
5771 time .Sleep (100 * time .Millisecond )
5872
5973 // Health check should be on by default at /ping
60- resp , err := http .Get ("http://127.0.0.1:18950 /ping" )
74+ resp , err := http .Get ("http://" + addr + " /ping" )
6175 require .NoError (t , err )
6276 defer resp .Body .Close ()
6377 assert .Equal (t , http .StatusOK , resp .StatusCode )
@@ -74,10 +88,11 @@ func TestAppStartAndShutdown(t *testing.T) {
7488
7589 t .Run ("runs onStart hooks" , func (t * testing.T ) {
7690 ctx , cancel := context .WithCancel (context .Background ())
91+ addr := freeAddr (t )
7792
7893 var hookRan bool
7994 a , err := app .New (
80- app .WithAddr ("127.0.0.1:18951" ),
95+ app .WithAddr (addr ),
8196 app .WithOnStart (func (_ context.Context ) error {
8297 hookRan = true
8398 return nil
@@ -96,10 +111,11 @@ func TestAppStartAndShutdown(t *testing.T) {
96111
97112 t .Run ("runs onStop hooks" , func (t * testing.T ) {
98113 ctx , cancel := context .WithCancel (context .Background ())
114+ addr := freeAddr (t )
99115
100116 var hookRan bool
101117 a , err := app .New (
102- app .WithAddr ("127.0.0.1:18952" ),
118+ app .WithAddr (addr ),
103119 app .WithOnStop (func (_ context.Context ) error {
104120 hookRan = true
105121 return nil
@@ -119,10 +135,11 @@ func TestAppStartAndShutdown(t *testing.T) {
119135 t .Run ("serves custom handler" , func (t * testing.T ) {
120136 ctx , cancel := context .WithCancel (context .Background ())
121137 defer cancel ()
138+ addr := freeAddr (t )
122139
123140 a , err := app .New (
124141 app .WithLogger (nopLogger ()),
125- app .WithAddr ("127.0.0.1:18953" ),
142+ app .WithAddr (addr ),
126143 app .WithHandler ("/hello" , http .HandlerFunc (func (w http.ResponseWriter , _ * http.Request ) {
127144 fmt .Fprint (w , "world" )
128145 })),
@@ -132,7 +149,7 @@ func TestAppStartAndShutdown(t *testing.T) {
132149 go a .Start (ctx )
133150 time .Sleep (100 * time .Millisecond )
134151
135- resp , err := http .Get ("http://127.0.0.1:18953 /hello" )
152+ resp , err := http .Get ("http://" + addr + " /hello" )
136153 require .NoError (t , err )
137154 defer resp .Body .Close ()
138155
@@ -145,6 +162,7 @@ func TestAppStartAndShutdown(t *testing.T) {
145162 t .Run ("applies explicit middleware" , func (t * testing.T ) {
146163 ctx , cancel := context .WithCancel (context .Background ())
147164 defer cancel ()
165+ addr := freeAddr (t )
148166
149167 addHeader := func (next http.Handler ) http.Handler {
150168 return http .HandlerFunc (func (w http.ResponseWriter , r * http.Request ) {
@@ -155,7 +173,7 @@ func TestAppStartAndShutdown(t *testing.T) {
155173
156174 a , err := app .New (
157175 app .WithLogger (nopLogger ()),
158- app .WithAddr ("127.0.0.1:18954" ),
176+ app .WithAddr (addr ),
159177 app .WithHTTPMiddleware (addHeader ),
160178 app .WithHandler ("/test" , http .HandlerFunc (func (w http.ResponseWriter , _ * http.Request ) {
161179 w .WriteHeader (http .StatusOK )
@@ -166,7 +184,7 @@ func TestAppStartAndShutdown(t *testing.T) {
166184 go a .Start (ctx )
167185 time .Sleep (100 * time .Millisecond )
168186
169- resp , err := http .Get ("http://127.0.0.1:18954 /test" )
187+ resp , err := http .Get ("http://" + addr + " /test" )
170188 require .NoError (t , err )
171189 defer resp .Body .Close ()
172190
@@ -176,9 +194,10 @@ func TestAppStartAndShutdown(t *testing.T) {
176194 })
177195
178196 t .Run ("onStart failure returns error and runs cleanup" , func (t * testing.T ) {
197+ addr := freeAddr (t )
179198 var cleanupRan bool
180199 a , err := app .New (
181- app .WithAddr ("127.0.0.1:18955" ),
200+ app .WithAddr (addr ),
182201 app .WithOnStart (func (_ context.Context ) error {
183202 return fmt .Errorf ("migration failed" )
184203 }),
0 commit comments