diff --git a/go.mod b/go.mod index 4d459a604b..920e3930e5 100644 --- a/go.mod +++ b/go.mod @@ -64,6 +64,7 @@ require ( go.uber.org/zap v1.27.1 golang.org/x/exp v0.0.0-20250718183923-645b1fa84792 golang.org/x/net v0.50.0 + golang.org/x/sync v0.19.0 gomodules.xyz/jsonpatch/v2 v2.5.0 gonum.org/v1/gonum v0.17.0 google.golang.org/genproto/googleapis/api v0.0.0-20260128011058-8636f8732409 @@ -293,7 +294,6 @@ require ( golang.org/x/crypto/x509roots/fallback v0.0.0-20250406160420-959f8f3db0fb // indirect golang.org/x/mod v0.32.0 // indirect golang.org/x/oauth2 v0.34.0 // indirect - golang.org/x/sync v0.19.0 // indirect golang.org/x/sys v0.41.0 // indirect golang.org/x/term v0.40.0 // indirect golang.org/x/text v0.34.0 // indirect diff --git a/internal/cmd/server_test.go b/internal/cmd/server_test.go index 760f83cacc..7010bbca98 100644 --- a/internal/cmd/server_test.go +++ b/internal/cmd/server_test.go @@ -16,6 +16,7 @@ import ( "testing" "github.com/stretchr/testify/require" + "golang.org/x/sync/errgroup" "github.com/envoyproxy/gateway/internal/envoygateway/config" ) @@ -100,39 +101,44 @@ func testCustomProvider(t *testing.T, genCert bool) (string, string) { func TestCustomProviderCancelWhenStarting(t *testing.T) { _, configPath := testCustomProvider(t, false) - errCh := make(chan error) ctx, cancel := context.WithCancel(t.Context()) - go func() { - errCh <- server(ctx, t.Output(), t.Output(), configPath, testHook, nil) - }() + var g errgroup.Group + + // Use io.Discard to avoid data races (it's thread-safe unlike bytes.Buffer) + g.Go(func() error { + return server(ctx, io.Discard, io.Discard, configPath, testHook, nil) + }) go func() { cancel() }() - err := <-errCh + err := g.Wait() require.NoError(t, err) } func TestCustomProviderFailedToStart(t *testing.T) { _, configPath := testCustomProvider(t, false) - errCh := make(chan error) ctx, cancel := context.WithCancel(t.Context()) - go func() { - errCh <- server(ctx, t.Output(), t.Output(), configPath, testHook, nil) - }() + defer cancel() + var g errgroup.Group + + // Use io.Discard to avoid data races (it's thread-safe unlike bytes.Buffer) + g.Go(func() error { + return server(ctx, io.Discard, io.Discard, configPath, testHook, nil) + }) - err := <-errCh - cancel() + err := g.Wait() require.Error(t, err, "failed to load TLS config") } func TestCustomProviderCancelWhenConfigReload(t *testing.T) { configHome, configPath := testCustomProvider(t, true) - errCh := make(chan error) ctx, cancel := context.WithCancel(t.Context()) + defer cancel() count := atomic.Int32{} + var g errgroup.Group hook := func(c context.Context, cfg *config.Server) error { if count.Add(1) >= 2 { t.Logf("Config reload triggered, cancelling context") @@ -152,12 +158,12 @@ func TestCustomProviderCancelWhenConfigReload(t *testing.T) { }() } - go func() { - errCh <- server(ctx, t.Output(), t.Output(), configPath, hook, startedCallback) - }() + // Use io.Discard to avoid data races (it's thread-safe unlike bytes.Buffer) + g.Go(func() error { + return server(ctx, io.Discard, io.Discard, configPath, hook, startedCallback) + }) - err := <-errCh - cancel() + err := g.Wait() require.NoError(t, err) }