diff --git a/.golangci.yml b/.golangci.yml index 53c60e5..b32d856 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -3,9 +3,23 @@ version: "2" linters: default: none enable: + - contextcheck - copyloopvar - errcheck - intrange - mirror - perfsprint - usestdlibvars + - usetesting # Reports uses of functions with replacement inside the testing package. + + settings: + usetesting: + context-background: true + context-todo: true + +issues: + # Maximum issues count per one linter. Set to 0 to disable. Default is 50. + max-issues-per-linter: 0 + + # Maximum count of issues with the same text. Set to 0 to disable. Default is 3. + max-same-issues: 0 diff --git a/client/client_test.go b/client/client_test.go index 0efa757..738bdd4 100644 --- a/client/client_test.go +++ b/client/client_test.go @@ -129,10 +129,10 @@ func TestNewIsLazyAndResolvesTypedGeneratedClient(t *testing.T) { assert.NilError(t, err) assert.Assert(t, greeterConn == echoConn, "all configured points must share one private connection") - reply, err := greeter.Greet(context.Background(), &greeterv0.HelloRequest{Name: "world"}) + reply, err := greeter.Greet(t.Context(), &greeterv0.HelloRequest{Name: "world"}) assert.NilError(t, err) assert.Equal(t, reply.Message, "hello world") - response, err := echo.Echo(context.Background(), &echov1.EchoRequest{Message: "round trip"}) + response, err := echo.Echo(t.Context(), &echov1.EchoRequest{Message: "round trip"}) assert.NilError(t, err) assert.Equal(t, response.Message, "round trip") assert.Equal(t, engine.dials.Load(), int64(1), "the shared lazy connection should dial once") @@ -157,7 +157,7 @@ func TestNewFreezesRegistrations(t *testing.T) { } provider, err := Resolve(client, point) assert.NilError(t, err) - reply, err := provider.Greet(context.Background(), &greeterv0.HelloRequest{Name: "world"}) + reply, err := provider.Greet(t.Context(), &greeterv0.HelloRequest{Name: "world"}) assert.NilError(t, err) assert.Equal(t, reply.Message, "first") assert.Equal(t, engine.dials.Load(), int64(0)) @@ -323,7 +323,7 @@ func TestResolveConcurrentProviderAndMethodCalls(t *testing.T) { results <- err return } - reply, err := provider.Greet(context.Background(), &greeterv0.HelloRequest{Name: "concurrent"}) + reply, err := provider.Greet(t.Context(), &greeterv0.HelloRequest{Name: "concurrent"}) if err != nil { results <- err return @@ -352,7 +352,7 @@ func TestCloseIsIdempotentAndClosesPrivateConnection(t *testing.T) { assert.NilError(t, err) provider, err := Resolve(client, greeterv0.Point) assert.NilError(t, err) - _, err = provider.Greet(context.Background(), &greeterv0.HelloRequest{Name: "close"}) + _, err = provider.Greet(t.Context(), &greeterv0.HelloRequest{Name: "close"}) assert.NilError(t, err) conn := <-engine.conns diff --git a/core_test.go b/core_test.go index 430776b..cf9c238 100644 --- a/core_test.go +++ b/core_test.go @@ -199,7 +199,7 @@ func TestSingleSelection(t *testing.T) { resolvedProvider("org.mobyproject.stock.v1", ExtensionOriginBuiltin, stock), )) assert.NilError(t, err) - assert.NilError(t, got.Call(context.Background()), "the default must stand in when nothing is installed") + assert.NilError(t, got.Call(t.Context()), "the default must stand in when nothing is installed") }) t.Run("executable provider masks the builtin", func(t *testing.T) { @@ -211,7 +211,7 @@ func TestSingleSelection(t *testing.T) { resolvedProvider("org.example.custom.v1", ExtensionOriginExecutable, customC), )) assert.NilError(t, err) - assert.NilError(t, got.Call(context.Background())) + assert.NilError(t, got.Call(t.Context())) assert.Equal(t, called, "custom", "the executable provider must replace the builtin, not conflict with it") }) diff --git a/fanout_test.go b/fanout_test.go index 9db3c08..d9f7ccf 100644 --- a/fanout_test.go +++ b/fanout_test.go @@ -32,7 +32,7 @@ func TestEachBoundsEveryProviderIndependently(t *testing.T) { return nil }) - err := Each(context.Background(), testPoint, resolverOf( + err := Each(t.Context(), testPoint, resolverOf( resolvedProvider("slow", ExtensionOriginExecutable, slow), resolvedProvider("second", ExtensionOriginExecutable, record), ), Policy{Timeout: timeout}, func(ctx context.Context, c caller) error { @@ -50,7 +50,7 @@ func TestEachAbortsOnErrorAndAttributes(t *testing.T) { count := callerFunc(func(context.Context) error { called++; return nil }) veto := callerFunc(func(context.Context) error { called++; return errors.New("not allowed") }) - err := Each(context.Background(), testPoint, resolverOf( + err := Each(t.Context(), testPoint, resolverOf( resolvedProvider("org.example.veto.v1", ExtensionOriginExecutable, veto), resolvedProvider("org.example.after.v1", ExtensionOriginExecutable, count), ), Policy{Action: "vetoed the start"}, func(ctx context.Context, c caller) error { @@ -66,7 +66,7 @@ func TestEachFailOpenSkipsAndContinues(t *testing.T) { count := callerFunc(func(context.Context) error { called++; return nil }) boom := callerFunc(func(context.Context) error { called++; return errors.New("boom") }) - err := Each(context.Background(), testPoint, resolverOf( + err := Each(t.Context(), testPoint, resolverOf( resolvedProvider("org.example.broken.v1", ExtensionOriginExecutable, boom), resolvedProvider("org.example.ok.v1", ExtensionOriginExecutable, count), ), Policy{FailOpen: true}, func(ctx context.Context, c caller) error { @@ -79,7 +79,7 @@ func TestEachFailOpenSkipsAndContinues(t *testing.T) { func TestFoldThreadsValueInOrder(t *testing.T) { noop := callerFunc(func(context.Context) error { return nil }) - out, err := Fold(context.Background(), testPoint, resolverOf( + out, err := Fold(t.Context(), testPoint, resolverOf( resolvedProvider("a", ExtensionOriginExecutable, noop), resolvedProvider("b", ExtensionOriginExecutable, noop), ), Policy{}, "seed", func(_ context.Context, _ caller, acc string) (string, error) { @@ -90,11 +90,11 @@ func TestFoldThreadsValueInOrder(t *testing.T) { } func TestFoldDiscardsPartialValueOnError(t *testing.T) { - out, err := Fold(context.Background(), testPoint, resolverOf( + out, err := Fold(t.Context(), testPoint, resolverOf( resolvedProvider("a", ExtensionOriginExecutable, callerFunc(func(context.Context) error { return nil })), resolvedProvider("b", ExtensionOriginExecutable, callerFunc(func(context.Context) error { return errors.New("no") })), - ), Policy{}, "seed", func(_ context.Context, c caller, acc string) (string, error) { - if err := c.Call(context.Background()); err != nil { + ), Policy{}, "seed", func(ctx context.Context, c caller, acc string) (string, error) { + if err := c.Call(ctx); err != nil { return acc, err } return acc + "+", nil diff --git a/grpcproxy/proxy_test.go b/grpcproxy/proxy_test.go index bc773ad..1e93069 100644 --- a/grpcproxy/proxy_test.go +++ b/grpcproxy/proxy_test.go @@ -48,7 +48,7 @@ var streamerDesc = grpc.ServiceDesc{ } func TestProxyServerStreaming(t *testing.T) { - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + ctx, cancel := context.WithTimeout(t.Context(), 10*time.Second) defer cancel() backendSock := filepath.Join(shortTempDir(t), "backend.sock") @@ -99,7 +99,7 @@ var unaryDesc = grpc.ServiceDesc{ } func TestProxyUnary(t *testing.T) { - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + ctx, cancel := context.WithTimeout(t.Context(), 10*time.Second) defer cancel() clientConn := startProxy(t, "test.Unary", func(s *grpc.Server) { s.RegisterService(&unaryDesc, nil) }) @@ -126,7 +126,7 @@ var statusDesc = grpc.ServiceDesc{ } func TestProxyForwardsBackendStatus(t *testing.T) { - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + ctx, cancel := context.WithTimeout(t.Context(), 10*time.Second) defer cancel() clientConn := startProxy(t, "test.Status", func(s *grpc.Server) { s.RegisterService(&statusDesc, nil) }) @@ -157,7 +157,7 @@ var metaDesc = grpc.ServiceDesc{ } func TestProxyForwardsMetadata(t *testing.T) { - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + ctx, cancel := context.WithTimeout(t.Context(), 10*time.Second) defer cancel() clientConn := startProxy(t, "test.Meta", func(s *grpc.Server) { s.RegisterService(&metaDesc, nil) }) @@ -196,7 +196,7 @@ var collectorDesc = grpc.ServiceDesc{ } func TestProxyClientStreaming(t *testing.T) { - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + ctx, cancel := context.WithTimeout(t.Context(), 10*time.Second) defer cancel() clientConn := startProxy(t, "test.Collector", func(s *grpc.Server) { s.RegisterService(&collectorDesc, nil) }) @@ -249,8 +249,7 @@ func serve(t *testing.T, sock string, register func(*grpc.Server)) grpc.ClientCo func shortTempDir(t *testing.T) string { t.Helper() - // Keep socket paths relative so they fit Windows' AF_UNIX path limit. - dir, err := os.MkdirTemp(".", "m") + dir, err := os.MkdirTemp(".", "m") //nolint:usetesting // Keep socket paths relative so they fit Windows' AF_UNIX path limit. assert.NilError(t, err) t.Cleanup(func() { _ = os.RemoveAll(dir) }) return dir diff --git a/host/dependency_test.go b/host/dependency_test.go index 96fba40..6c9eecf 100644 --- a/host/dependency_test.go +++ b/host/dependency_test.go @@ -32,8 +32,7 @@ func extensionBinaryPath(dir, id string) string { func shortTempDir(t *testing.T) string { t.Helper() - // Keep socket paths relative so they fit Windows' AF_UNIX path limit. - dir, err := os.MkdirTemp(".", "m") + dir, err := os.MkdirTemp(".", "m") //nolint:usetesting // Keep socket paths relative so they fit Windows' AF_UNIX path limit. assert.NilError(t, err) t.Cleanup(func() { _ = os.RemoveAll(dir) }) return dir @@ -57,7 +56,7 @@ func TestOutOfProcessDependency(t *testing.T) { t.Fatalf("build greeterdep extension: %v\n%s", err, out) } - ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + ctx, cancel := context.WithTimeout(t.Context(), 30*time.Second) defer cancel() var calls atomic.Int32 @@ -73,7 +72,7 @@ func TestOutOfProcessDependency(t *testing.T) { host.WithDependencyProviders(greeterpb.ServerPoint), ) assert.NilError(t, err) - defer func() { assert.NilError(t, h.Shutdown(context.Background())) }() + defer func() { assert.NilError(t, h.Shutdown(context.WithoutCancel(ctx))) }() assert.Equal(t, calls.Load(), int32(1), "in-process greeter provider was not called by the out-of-process extension") diff --git a/host/host.go b/host/host.go index 9fc083c..10b48d9 100644 --- a/host/host.go +++ b/host/host.go @@ -315,8 +315,9 @@ func New(ctx context.Context, optionList ...Option) (_ *Host, retErr error) { // also explicitly close loaded resources. defer func() { if retErr != nil { - _ = b.Shutdown(context.Background()) - closeLoaded(context.Background(), loaded) + ctx := context.WithoutCancel(ctx) + _ = b.Shutdown(ctx) + closeLoaded(ctx, loaded) if callback != nil { callback.Stop() } @@ -676,7 +677,7 @@ func loadProcess(ctx context.Context, l launcher.Launcher, bin string, providers owned := true defer func() { if owned { - _ = launched.Close(context.Background()) + _ = launched.Close(context.WithoutCancel(ctx)) } }() diff --git a/host/host_internal_test.go b/host/host_internal_test.go index b538497..8979bfb 100644 --- a/host/host_internal_test.go +++ b/host/host_internal_test.go @@ -31,8 +31,7 @@ var _ PointPolicy = PointPolicyFunc(nil) func shortTempDir(t *testing.T) string { t.Helper() - // Keep socket paths relative so they fit Windows' AF_UNIX path limit. - dir, err := os.MkdirTemp(".", "m") + dir, err := os.MkdirTemp(".", "m") //nolint:usetesting // Keep socket paths relative so they fit Windows' AF_UNIX path limit. assert.NilError(t, err) t.Cleanup(func() { _ = os.RemoveAll(dir) }) return dir @@ -149,7 +148,7 @@ func TestExtensionFromHostedForwardsBrokerConfig(t *testing.T) { b := broker.New() assert.NilError(t, registerExecutableForTest(b, ext)) - assert.NilError(t, b.Init(context.Background(), map[extensions.ExtensionID]extensions.Config{id: want})) + assert.NilError(t, b.Init(t.Context(), map[extensions.ExtensionID]extensions.Config{id: want})) assert.DeepEqual(t, got, want) } @@ -167,8 +166,8 @@ func TestExtensionFromHostedRunsSemanticShutdown(t *testing.T) { b := broker.New() assert.NilError(t, registerExecutableForTest(b, ext)) - assert.NilError(t, b.Init(context.Background(), nil)) - assert.NilError(t, b.Shutdown(context.Background())) + assert.NilError(t, b.Init(t.Context(), nil)) + assert.NilError(t, b.Shutdown(t.Context())) assert.Assert(t, shutdown, "the broker did not run hosted semantic shutdown") } @@ -301,7 +300,7 @@ func TestSinglePointRejectsTwoProviders(t *testing.T) { Single: true, } - _, err := New(context.Background(), + _, err := New(t.Context(), WithRuntimeDir(t.TempDir()), WithExtensions(ext("org.example.one.v1"), ext("org.example.two.v1")), WithClientProviders(singleReg), @@ -310,13 +309,13 @@ func TestSinglePointRejectsTwoProviders(t *testing.T) { assert.ErrorContains(t, err, "org.example.one.v1") assert.ErrorContains(t, err, "org.example.two.v1") - h, err := New(context.Background(), + h, err := New(t.Context(), WithRuntimeDir(t.TempDir()), WithExtensions(ext("org.example.one.v1")), WithClientProviders(singleReg), ) assert.NilError(t, err) - assert.NilError(t, h.Shutdown(context.Background())) + assert.NilError(t, h.Shutdown(t.Context())) } func TestProviderAdmissionPolicy(t *testing.T) { @@ -327,7 +326,7 @@ func TestProviderAdmissionPolicy(t *testing.T) { t.Run("allow keeps provider", func(t *testing.T) { var gotIdentity extensions.ExtensionIdentity var gotPoint extensions.PointID - h, err := New(context.Background(), + h, err := New(t.Context(), WithRuntimeDir(t.TempDir()), WithExtensions(newProviderExtension(id, point)), WithProviderPolicy(PointPolicyFunc(func(identity extensions.ExtensionIdentity, policyPoint extensions.PointID) PointPolicyResult { @@ -337,7 +336,7 @@ func TestProviderAdmissionPolicy(t *testing.T) { })), ) assert.NilError(t, err) - defer func() { assert.NilError(t, h.Shutdown(context.Background())) }() + defer func() { assert.NilError(t, h.Shutdown(context.WithoutCancel(t.Context()))) }() providers := h.Providers(point) assert.Equal(t, len(providers), 1) assert.Equal(t, providers[0].Identity, wantIdentity) @@ -355,7 +354,7 @@ func TestProviderAdmissionPolicy(t *testing.T) { return nil }, }) - h, err := New(context.Background(), + h, err := New(t.Context(), WithRuntimeDir(t.TempDir()), WithExtensions(ext), WithProviderPolicy(PointPolicyFunc(func(extensions.ExtensionIdentity, extensions.PointID) PointPolicyResult { @@ -363,7 +362,7 @@ func TestProviderAdmissionPolicy(t *testing.T) { })), ) assert.NilError(t, err) - defer func() { assert.NilError(t, h.Shutdown(context.Background())) }() + defer func() { assert.NilError(t, h.Shutdown(context.WithoutCancel(t.Context()))) }() assert.Assert(t, initialized) assert.Equal(t, len(h.Providers(point)), 0) _, err = h.Provider(point, id) @@ -372,7 +371,7 @@ func TestProviderAdmissionPolicy(t *testing.T) { t.Run("reject fails with cause and context", func(t *testing.T) { cause := errors.New("provider denied") - _, err := New(context.Background(), + _, err := New(t.Context(), WithRuntimeDir(t.TempDir()), WithExtensions(newProviderExtension(id, point)), WithProviderPolicy(PointPolicyFunc(func(extensions.ExtensionIdentity, extensions.PointID) PointPolicyResult { @@ -386,17 +385,17 @@ func TestProviderAdmissionPolicy(t *testing.T) { }) t.Run("nil policy allows provider", func(t *testing.T) { - h, err := New(context.Background(), + h, err := New(t.Context(), WithRuntimeDir(t.TempDir()), WithExtensions(newProviderExtension(id, point)), ) assert.NilError(t, err) - defer func() { assert.NilError(t, h.Shutdown(context.Background())) }() + defer func() { assert.NilError(t, h.Shutdown(context.WithoutCancel(t.Context()))) }() assert.Equal(t, len(h.Providers(point)), 1) }) t.Run("nil function rejects", func(t *testing.T) { - _, err := New(context.Background(), + _, err := New(t.Context(), WithRuntimeDir(t.TempDir()), WithExtensions(newProviderExtension(id, point)), WithProviderPolicy(PointPolicyFunc(nil)), @@ -405,7 +404,7 @@ func TestProviderAdmissionPolicy(t *testing.T) { }) t.Run("nil rejection cause is replaced", func(t *testing.T) { - _, err := New(context.Background(), + _, err := New(t.Context(), WithRuntimeDir(t.TempDir()), WithExtensions(newProviderExtension(id, point)), WithProviderPolicy(PointPolicyFunc(func(extensions.ExtensionIdentity, extensions.PointID) PointPolicyResult { @@ -416,7 +415,7 @@ func TestProviderAdmissionPolicy(t *testing.T) { }) t.Run("zero result rejects", func(t *testing.T) { - _, err := New(context.Background(), + _, err := New(t.Context(), WithRuntimeDir(t.TempDir()), WithExtensions(newProviderExtension(id, point)), WithProviderPolicy(PointPolicyFunc(func(extensions.ExtensionIdentity, extensions.PointID) PointPolicyResult { @@ -450,7 +449,7 @@ func TestProviderAndPublicationPolicyPoints(t *testing.T) { }, } var policyPoints []extensions.PointID - h, err := New(context.Background(), + h, err := New(t.Context(), WithRuntimeDir(t.TempDir()), WithExtensions(ext), WithPointServers(server), @@ -463,7 +462,7 @@ func TestProviderAndPublicationPolicyPoints(t *testing.T) { })), ) assert.NilError(t, err) - t.Cleanup(func() { assert.NilError(t, h.Shutdown(context.Background())) }) + t.Cleanup(func() { assert.NilError(t, h.Shutdown(context.WithoutCancel(t.Context()))) }) assert.DeepEqual(t, policyPoints, []extensions.PointID{realPoint, servicev0.Point.ID()}) provider, err := h.Provider(realPoint, id) assert.NilError(t, err) @@ -497,7 +496,7 @@ func TestProcessResourceCleanup(t *testing.T) { t.Skip("builds and launches a helper binary") } dir, bin := buildLifecycleExtension(t) - ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + ctx, cancel := context.WithTimeout(t.Context(), 30*time.Second) defer cancel() t.Run("provider policy rejection", func(t *testing.T) { @@ -606,14 +605,14 @@ func TestProcessResourceCleanup(t *testing.T) { shutdown := false t.Cleanup(func() { if !shutdown { - _ = h.Shutdown(context.Background()) + _ = h.Shutdown(context.WithoutCancel(ctx)) } }) assert.Equal(t, len(h.loaded), 1, "only the process-backed extension should own a loaded resource") assertProcessRunning(t, probeFile) - err = h.Shutdown(context.Background()) + err = h.Shutdown(context.WithoutCancel(ctx)) shutdown = true assert.NilError(t, err) assertProcessReleased(t, probeFile) @@ -635,7 +634,7 @@ func TestCloseLoadedErrClosesInReverseOrderAndJoinsErrors(t *testing.T) { }}, } - err := closeLoadedErr(context.Background(), loaded) + err := closeLoadedErr(t.Context(), loaded) assert.DeepEqual(t, closed, []string{"second", "first"}) assert.Assert(t, errors.Is(err, firstErr)) assert.Assert(t, errors.Is(err, secondErr)) @@ -644,7 +643,7 @@ func TestCloseLoadedErrClosesInReverseOrderAndJoinsErrors(t *testing.T) { func TestCloseLoadedSuppressesConstructionCleanupErrors(t *testing.T) { closeErr := errors.New("close failure") var closed []string - closeLoaded(context.Background(), []loadedExtension{ + closeLoaded(t.Context(), []loadedExtension{ {close: func(context.Context) error { closed = append(closed, "first") return closeErr @@ -669,7 +668,7 @@ func TestHostShutdownJoinsSemanticAndResourceErrors(t *testing.T) { return semanticErr }, }))) - assert.NilError(t, b.Init(context.Background(), nil)) + assert.NilError(t, b.Init(t.Context(), nil)) h := &Host{ broker: b, loaded: []loadedExtension{{close: func(context.Context) error { @@ -678,7 +677,7 @@ func TestHostShutdownJoinsSemanticAndResourceErrors(t *testing.T) { }}}, } - err := h.Shutdown(context.Background()) + err := h.Shutdown(t.Context()) assert.DeepEqual(t, order, []string{"semantic", "resource"}) assert.Assert(t, errors.Is(err, semanticErr)) assert.Assert(t, errors.Is(err, resourceErr)) @@ -852,19 +851,19 @@ func TestInProcessPublicationPolicy(t *testing.T) { } t.Run("nil policy keeps provider and drops publication", func(t *testing.T) { - h, err := New(context.Background(), + h, err := New(t.Context(), WithRuntimeDir(t.TempDir()), WithExtensions(ext), WithPointServers(server), ) assert.NilError(t, err) - defer func() { assert.NilError(t, h.Shutdown(context.Background())) }() + defer func() { assert.NilError(t, h.Shutdown(context.WithoutCancel(t.Context()))) }() assert.Equal(t, len(h.Providers(point)), 1) assert.Equal(t, len(h.PublishedServicesForPoint(point)), 0) }) t.Run("allow publishes original provider after ordinary drop", func(t *testing.T) { - h, err := New(context.Background(), + h, err := New(t.Context(), WithRuntimeDir(t.TempDir()), WithExtensions(ext), WithPointServers(server), @@ -876,7 +875,7 @@ func TestInProcessPublicationPolicy(t *testing.T) { })), ) assert.NilError(t, err) - defer func() { assert.NilError(t, h.Shutdown(context.Background())) }() + defer func() { assert.NilError(t, h.Shutdown(context.WithoutCancel(t.Context()))) }() assert.Equal(t, len(h.Providers(point)), 0) assert.DeepEqual(t, h.PublishedServicesForPoint(point), map[extensions.ExtensionID][]string{ id: {"example.API"}, @@ -885,7 +884,7 @@ func TestInProcessPublicationPolicy(t *testing.T) { t.Run("reject fails with cause", func(t *testing.T) { cause := errors.New("publication denied") - h, err := New(context.Background(), + h, err := New(t.Context(), WithRuntimeDir(t.TempDir()), WithExtensions(ext), WithPointServers(server), diff --git a/host/options_test.go b/host/options_test.go index f2aab83..7d7b7f4 100644 --- a/host/options_test.go +++ b/host/options_test.go @@ -15,16 +15,16 @@ import ( func TestNewWithNoOptions(t *testing.T) { t.Parallel() - h, err := host.New(context.Background()) + h, err := host.New(t.Context()) assert.NilError(t, err) - assert.NilError(t, h.Shutdown(context.Background())) + assert.NilError(t, h.Shutdown(t.Context())) } func TestNewRejectsNilOption(t *testing.T) { t.Parallel() var option host.Option - h, err := host.New(context.Background(), option) + h, err := host.New(t.Context(), option) assert.Assert(t, h == nil) if err == nil { t.Fatal("host.New returned nil error for a nil option") @@ -49,9 +49,9 @@ func TestRepeatedExtensionOptionsComposeAndCopyInput(t *testing.T) { firstOption := host.WithExtensions(exts...) exts[0] = second - h, err := host.New(context.Background(), firstOption, host.WithExtensions(second)) + h, err := host.New(t.Context(), firstOption, host.WithExtensions(second)) assert.NilError(t, err) - defer func() { assert.NilError(t, h.Shutdown(context.Background())) }() + defer func() { assert.NilError(t, h.Shutdown(context.WithoutCancel(t.Context()))) }() got, err := h.Provider(point.ID(), "org.example.first.v1") assert.NilError(t, err) diff --git a/host/socket_test.go b/host/socket_test.go index d0186ff..f14a760 100644 --- a/host/socket_test.go +++ b/host/socket_test.go @@ -41,7 +41,7 @@ func TestPointSocketExposure(t *testing.T) { t.Fatalf("build greeter extension: %v\n%s", err, out) } - ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + ctx, cancel := context.WithTimeout(t.Context(), 30*time.Second) defer cancel() var policyIdentity extensions.ExtensionIdentity @@ -59,7 +59,7 @@ func TestPointSocketExposure(t *testing.T) { })), ) assert.NilError(t, err) - defer func() { assert.NilError(t, h.Shutdown(context.Background())) }() + defer func() { assert.NilError(t, h.Shutdown(context.WithoutCancel(ctx))) }() assert.DeepEqual(t, policyIdentity, extensions.ExtensionIdentity{ ID: greeter.ID, Origin: extensions.ExtensionOrigin{ @@ -114,7 +114,7 @@ func TestProcessOfferIsDroppedByDefault(t *testing.T) { t.Fatalf("build exthook extension: %v\n%s", err, out) } - ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + ctx, cancel := context.WithTimeout(t.Context(), 30*time.Second) defer cancel() h, err := host.New(ctx, @@ -123,7 +123,7 @@ func TestProcessOfferIsDroppedByDefault(t *testing.T) { host.WithClientProviders(echopb.ClientPoint), ) assert.NilError(t, err) - defer func() { assert.NilError(t, h.Shutdown(context.Background())) }() + defer func() { assert.NilError(t, h.Shutdown(context.WithoutCancel(ctx))) }() assert.Check(t, h.PublishedServicesForPoint(echov1.Point.ID())[id] == nil) @@ -148,7 +148,7 @@ func TestProcessOfferPolicy(t *testing.T) { t.Fatalf("build exthook extension: %v\n%s", err, out) } - ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + ctx, cancel := context.WithTimeout(t.Context(), 30*time.Second) defer cancel() t.Run("drop", func(t *testing.T) { @@ -162,7 +162,7 @@ func TestProcessOfferPolicy(t *testing.T) { })), ) assert.NilError(t, err) - defer func() { assert.NilError(t, h.Shutdown(context.Background())) }() + defer func() { assert.NilError(t, h.Shutdown(context.WithoutCancel(ctx))) }() assert.Equal(t, policyCalls, 1) assert.Check(t, h.PublishedServicesForPoint(echov1.Point.ID())[id] == nil) @@ -186,7 +186,7 @@ func TestProcessOfferPolicy(t *testing.T) { // TestInProcessPointExposure verifies a published Point can be collected and // registered directly on a gRPC server without a process boundary. func TestInProcessPointExposure(t *testing.T) { - ctx := context.Background() + ctx := t.Context() var policyIdentity extensions.ExtensionIdentity var policyPoints []extensions.PointID h, err := host.New(ctx, @@ -203,7 +203,7 @@ func TestInProcessPointExposure(t *testing.T) { })), ) assert.NilError(t, err) - defer func() { assert.NilError(t, h.Shutdown(context.Background())) }() + defer func() { assert.NilError(t, h.Shutdown(context.WithoutCancel(ctx))) }() assert.DeepEqual(t, policyPoints, []extensions.PointID{greeterv0.Point.ID(), servicev0.Point.ID()}) assert.Equal(t, policyIdentity, extensions.ExtensionIdentity{ ID: greeter.ID, diff --git a/internal/broker/broker_test.go b/internal/broker/broker_test.go index 860f475..8a1d31f 100644 --- a/internal/broker/broker_test.go +++ b/internal/broker/broker_test.go @@ -29,7 +29,7 @@ func registerExecutable(b *Broker, ext extensions.Extension) error { } func TestInitOrdersDependencies(t *testing.T) { - ctx := context.Background() + ctx := t.Context() b := New() var order []extensions.ExtensionID @@ -94,9 +94,9 @@ func TestShutdownOrdersDependenciesInReverse(t *testing.T) { return nil }, }))) - assert.NilError(t, b.Init(context.Background(), nil)) + assert.NilError(t, b.Init(t.Context(), nil)) - err := b.Shutdown(context.Background()) + err := b.Shutdown(t.Context()) assert.NilError(t, err) assert.DeepEqual(t, order, []extensions.ExtensionID{"org.test.dependent.v1", "org.test.dependency.v1"}) } @@ -112,7 +112,7 @@ func TestShutdownSkipsUninitialized(t *testing.T) { }, }))) - assert.NilError(t, b.Shutdown(context.Background())) + assert.NilError(t, b.Shutdown(t.Context())) assert.Check(t, len(shutdown) == 0, "Shutdown ran on an uninitialized extension: %v", shutdown) } @@ -143,10 +143,10 @@ func TestShutdownUnwindsPartialInit(t *testing.T) { Shutdown: shutdownRecorder("org.test.last.v1"), }))) - err := b.Init(context.Background(), nil) + err := b.Init(t.Context(), nil) assert.ErrorContains(t, err, "init failed") - assert.NilError(t, b.Shutdown(context.Background())) + assert.NilError(t, b.Shutdown(t.Context())) assert.DeepEqual(t, order, []extensions.ExtensionID{"org.test.first.v1"}) } @@ -182,7 +182,7 @@ func TestConcurrentAccess(t *testing.T) { ID: "org.test.a.v1", Providers: []extensions.Provider{{Point: "a.point.v1", Impl: pingProvider{}}}, }))) - assert.NilError(t, b.Init(context.Background(), nil)) + assert.NilError(t, b.Init(t.Context(), nil)) var wg sync.WaitGroup for range 20 { @@ -296,7 +296,7 @@ func TestInitFailsForMissingRequiredDependency(t *testing.T) { b := New() assert.NilError(t, registerExecutable(b, extensions.New(extensions.Declaration{ID: "org.test.dependent.v1", Dependencies: []extensions.Dependency{{Point: "missing.point"}}}))) - err := b.Init(context.Background(), nil) + err := b.Init(t.Context(), nil) assert.ErrorContains(t, err, `requires missing point "missing.point"`) } @@ -313,7 +313,7 @@ func TestInitAllowsMissingOptionalDependency(t *testing.T) { })) assert.NilError(t, err) - assert.NilError(t, b.Init(context.Background(), nil)) + assert.NilError(t, b.Init(t.Context(), nil)) assert.Check(t, initialized) } @@ -326,7 +326,7 @@ func TestInitFailsForDependencyCycle(t *testing.T) { assert.NilError(t, registerExecutable(b, extensions.New(ext))) } - err := b.Init(context.Background(), nil) + err := b.Init(t.Context(), nil) assert.ErrorContains(t, err, "extension dependency cycle") } @@ -341,7 +341,7 @@ func TestInitWrapsExtensionError(t *testing.T) { })) assert.NilError(t, err) - err = b.Init(context.Background(), nil) + err = b.Init(t.Context(), nil) assert.ErrorIs(t, err, initErr) } diff --git a/internal/launcher/launcher.go b/internal/launcher/launcher.go index 47b4e85..a67ec27 100644 --- a/internal/launcher/launcher.go +++ b/internal/launcher/launcher.go @@ -123,7 +123,7 @@ func (l Launcher) Launch(ctx context.Context, bin string) (*Launched, error) { return nil, fmt.Errorf("start extension %q: %w", name, err) } stop := func() { - _ = stopProcess(context.Background(), cmd, wait, shutdownTimeout) + _ = stopProcess(context.WithoutCancel(ctx), cmd, wait, shutdownTimeout) _ = lifetime.Close() } startup := sdk.StartupConfig{ diff --git a/internal/launcher/launcher_test.go b/internal/launcher/launcher_test.go index 663cb74..262ff85 100644 --- a/internal/launcher/launcher_test.go +++ b/internal/launcher/launcher_test.go @@ -77,7 +77,7 @@ func TestLaunchedInitializeUsesCallerContext(t *testing.T) { t.Cleanup(func() { assert.NilError(t, conn.Close()) }) launched := &Launched{Conn: conn} - assert.NilError(t, launched.Initialize(context.Background())) + assert.NilError(t, launched.Initialize(t.Context())) assert.Equal(t, <-recorder.hasDeadline, false) } @@ -88,7 +88,7 @@ func TestLogOutputChunksLongRecords(t *testing.T) { logger := logrus.New() logger.SetOutput(io.Discard) logger.AddHook(hook) - ctx := log.WithLogger(context.Background(), logrus.NewEntry(logger)) + ctx := log.WithLogger(t.Context(), logrus.NewEntry(logger)) first := strings.Repeat("a", maxOutputRecordSize-1) + "\r" second := strings.Repeat("b", maxOutputRecordSize) @@ -107,7 +107,7 @@ func TestLogOutputPreservesLines(t *testing.T) { logger := logrus.New() logger.SetOutput(io.Discard) logger.AddHook(hook) - ctx := log.WithLogger(context.Background(), logrus.NewEntry(logger)) + ctx := log.WithLogger(t.Context(), logrus.NewEntry(logger)) logOutput(ctx, "test", strings.NewReader("first\r\nsecond\n\nfinal\r")) @@ -119,7 +119,7 @@ func TestWaitReadyRejectsOversizedAcknowledgement(t *testing.T) { input := strings.Repeat("x", maxOutputRecordSize+1) + "\n" reader := bufio.NewReaderSize(strings.NewReader(input), maxOutputRecordSize) - err := waitReady(context.Background(), io.NopCloser(strings.NewReader("")), reader) + err := waitReady(t.Context(), io.NopCloser(strings.NewReader("")), reader) assert.ErrorContains(t, err, "readiness acknowledgement exceeds 16384 bytes") } @@ -132,8 +132,7 @@ func exeName(name string) string { func shortTempDir(t *testing.T) string { t.Helper() - // Keep socket paths relative so they fit Windows' AF_UNIX path limit. - dir, err := os.MkdirTemp(".", "m") + dir, err := os.MkdirTemp(".", "m") //nolint:usetesting // Keep socket paths relative so they fit Windows' AF_UNIX path limit. assert.NilError(t, err) t.Cleanup(func() { _ = os.RemoveAll(dir) }) return dir @@ -153,7 +152,7 @@ func TestBinaries(t *testing.T) { assert.NilError(t, os.WriteFile(upper, []byte("x"), 0o755)) } - bins, err := Binaries(context.Background(), dir) + bins, err := Binaries(t.Context(), dir) assert.NilError(t, err) want := []string{exe} if runtime.GOOS == "windows" { @@ -161,7 +160,7 @@ func TestBinaries(t *testing.T) { } assert.DeepEqual(t, bins, want) - missing, err := Binaries(context.Background(), filepath.Join(dir, "does-not-exist")) + missing, err := Binaries(t.Context(), filepath.Join(dir, "does-not-exist")) assert.NilError(t, err) assert.Check(t, is.Len(missing, 0)) } @@ -178,14 +177,14 @@ func TestBinariesRefusesWorldWritable(t *testing.T) { assert.NilError(t, os.WriteFile(bad, []byte("x"), 0o755)) assert.NilError(t, os.Chmod(bad, 0o757)) // o+w - bins, err := Binaries(context.Background(), dir) + bins, err := Binaries(t.Context(), dir) assert.NilError(t, err) assert.DeepEqual(t, bins, []string{good}) wwDir := t.TempDir() assert.NilError(t, os.WriteFile(filepath.Join(wwDir, "org.example.x.v1"), []byte("x"), 0o755)) assert.NilError(t, os.Chmod(wwDir, 0o777)) - bins, err = Binaries(context.Background(), wwDir) + bins, err = Binaries(t.Context(), wwDir) assert.NilError(t, err) assert.Check(t, is.Len(bins, 0)) } @@ -205,7 +204,7 @@ func TestBinariesRefusesUntrustedOwner(t *testing.T) { assert.NilError(t, os.WriteFile(bad, []byte("x"), 0o755)) assert.NilError(t, os.Chown(bad, 65534, 65534)) // nobody: not root, not us - bins, err := Binaries(context.Background(), dir) + bins, err := Binaries(t.Context(), dir) assert.NilError(t, err) assert.DeepEqual(t, bins, []string{good}) } @@ -220,14 +219,14 @@ func TestLaunchOutOfProcess(t *testing.T) { out, err := build.CombinedOutput() assert.NilError(t, err, "build extension: %s", out) - ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + ctx, cancel := context.WithTimeout(t.Context(), 30*time.Second) defer cancel() runtimeDir := filepath.Join(shortTempDir(t), strings.Repeat("r", 40)) assert.NilError(t, os.Mkdir(runtimeDir, 0o755)) launched, err := Launcher{RuntimeDir: runtimeDir}.Launch(ctx, bin) assert.NilError(t, err) - defer func() { assert.NilError(t, launched.Close(context.Background())) }() + defer func() { assert.NilError(t, launched.Close(t.Context())) }() assert.Equal(t, launched.ID, extensions.ExtensionID(id)) assert.Equal(t, launched.Path, bin) @@ -255,7 +254,7 @@ func TestStopProcessSignalledExitIsNotAnError(t *testing.T) { assert.NilError(t, err) defer func() { assert.NilError(t, lifetime.Close()) }() - assert.NilError(t, stopProcess(context.Background(), cmd, wait, 5*time.Second)) + assert.NilError(t, stopProcess(t.Context(), cmd, wait, 5*time.Second)) } func TestStopProcessAfterSelfExit(t *testing.T) { @@ -269,7 +268,7 @@ func TestStopProcessAfterSelfExit(t *testing.T) { time.Sleep(500 * time.Millisecond) // let it exit and be reaped done := make(chan error, 1) - go func() { done <- stopProcess(context.Background(), cmd, wait, time.Second) }() + go func() { done <- stopProcess(t.Context(), cmd, wait, time.Second) }() select { case err := <-done: assert.NilError(t, err) diff --git a/internal/launcher/process_unix_test.go b/internal/launcher/process_unix_test.go index 58bcfd6..7727388 100644 --- a/internal/launcher/process_unix_test.go +++ b/internal/launcher/process_unix_test.go @@ -123,7 +123,7 @@ func TestLaunchStartupWriteTimeout(t *testing.T) { }, } started := time.Now() - _, err = launcher.Launch(context.Background(), bin) + _, err = launcher.Launch(t.Context(), bin) assert.ErrorContains(t, err, `write startup config for extension "blocked-extension": context deadline exceeded`) assert.Check(t, time.Since(started) < 5*time.Second, "timed-out launch took %s", time.Since(started)) } @@ -133,7 +133,7 @@ func TestStopProcessKillsProcessGroupDescendant(t *testing.T) { assert.NilError(t, err) defer func() { assert.NilError(t, statusR.Close()) }() - cmd := exec.CommandContext(context.Background(), os.Args[0]) + cmd := exec.CommandContext(t.Context(), os.Args[0]) cmd.Env = launcherHelperEnv("process-group-leader") cmd.ExtraFiles = []*os.File{statusW} lifetime, wait, err := startProcess(cmd) @@ -152,7 +152,7 @@ func TestStopProcessKillsProcessGroupDescendant(t *testing.T) { assert.NilError(t, err, line) defer func() { _ = syscall.Kill(descendantPID, syscall.SIGKILL) }() - assert.NilError(t, stopProcess(context.Background(), cmd, wait, 5*time.Second)) + assert.NilError(t, stopProcess(t.Context(), cmd, wait, 5*time.Second)) assert.NilError(t, lifetime.Close()) assert.NilError(t, statusR.SetReadDeadline(time.Now().Add(5*time.Second))) diff --git a/internal/launcher/shutdown.go b/internal/launcher/shutdown.go index 16d4afb..f7eb567 100644 --- a/internal/launcher/shutdown.go +++ b/internal/launcher/shutdown.go @@ -38,6 +38,7 @@ type processShutdown struct { // Close stops the extension once. The host and broker may both call it during // failure cleanup, so repeated calls are no-ops. func (s *processShutdown) Close(ctx context.Context) error { + // FIXME(thaJeztah): Use singleflight for shutdown, detach it from caller cancellation, and avoid waiting indefinitely for the process after SIGKILL. s.once.Do(func() { s.err = errors.Join( s.conn.Close(), diff --git a/internal/launcher/shutdown_test.go b/internal/launcher/shutdown_test.go index 133dfef..40d1905 100644 --- a/internal/launcher/shutdown_test.go +++ b/internal/launcher/shutdown_test.go @@ -37,8 +37,8 @@ func TestProcessShutdownCloseIsIdempotentAndRetainsError(t *testing.T) { lifetime: lifetime, } - firstErr := shutdown.Close(context.Background()) - canceled, cancel := context.WithCancel(context.Background()) + firstErr := shutdown.Close(t.Context()) + canceled, cancel := context.WithCancel(t.Context()) cancel() secondErr := shutdown.Close(canceled) diff --git a/sdk/sdk_test.go b/sdk/sdk_test.go index 97ab43a..9dcb7cf 100644 --- a/sdk/sdk_test.go +++ b/sdk/sdk_test.go @@ -27,8 +27,7 @@ import ( func shortTempDir(t *testing.T) string { t.Helper() - // Keep socket paths relative so they fit Windows' AF_UNIX path limit. - dir, err := os.MkdirTemp(".", "m") + dir, err := os.MkdirTemp(".", "m") //nolint:usetesting // Keep socket paths relative so they fit Windows' AF_UNIX path limit. assert.NilError(t, err) t.Cleanup(func() { _ = os.RemoveAll(dir) }) return dir @@ -194,12 +193,12 @@ func TestRegisterRejectsUnknownPoint(t *testing.T) { func TestListenRejectsUnsupportedProtocol(t *testing.T) { srv := NewServer() - err := srv.ListenWithIO(context.Background(), strings.NewReader(`{"endpoint":"/tmp/x.sock","protocolVersion":999}`), io.Discard) + err := srv.ListenWithIO(t.Context(), strings.NewReader(`{"endpoint":"/tmp/x.sock","protocolVersion":999}`), io.Discard) assert.ErrorContains(t, err, "unsupported extension protocol version") } func TestListenDeliversConfig(t *testing.T) { - ctx, cancel := context.WithCancel(context.Background()) + ctx, cancel := context.WithCancel(t.Context()) defer cancel() var got extensions.Config ext := extensions.New(extensions.Declaration{ diff --git a/sdk/sdkapi/runtime_test.go b/sdk/sdkapi/runtime_test.go index b6bec30..b27157a 100644 --- a/sdk/sdkapi/runtime_test.go +++ b/sdk/sdkapi/runtime_test.go @@ -80,7 +80,7 @@ func TestDeclarationOfferProtocolRoundTrip(t *testing.T) { assert.NilError(t, err) t.Cleanup(func() { assert.NilError(t, conn.Close()) }) - response, err := sdkapipb.NewClient(conn).Describe(context.Background(), &sdkapi.DescribeRequest{}) + response, err := sdkapipb.NewClient(conn).Describe(t.Context(), &sdkapi.DescribeRequest{}) assert.NilError(t, err) assert.Assert(t, is.DeepEqual(response.Declaration, want)) } diff --git a/servicegrpc/servicegrpc_test.go b/servicegrpc/servicegrpc_test.go index cdacd74..b440b5d 100644 --- a/servicegrpc/servicegrpc_test.go +++ b/servicegrpc/servicegrpc_test.go @@ -61,12 +61,12 @@ func TestGeneratedGreeterPreservesInterceptorFullMethodAndStatus(t *testing.T) { t.Cleanup(func() { assert.NilError(t, conn.Close()) }) client := greeterpb.NewClient(conn) - resp, err := client.Greet(context.Background(), &greeterv0.HelloRequest{Name: "world"}) + resp, err := client.Greet(t.Context(), &greeterv0.HelloRequest{Name: "world"}) assert.NilError(t, err) assert.Equal(t, resp.Message, "hello world") assert.Equal(t, <-intercepted, "/"+serviceName+"/Greet") - _, err = client.Greet(context.Background(), &greeterv0.HelloRequest{Name: "blocked"}) + _, err = client.Greet(t.Context(), &greeterv0.HelloRequest{Name: "blocked"}) assert.Equal(t, status.Code(err), codes.PermissionDenied) assert.Equal(t, status.Convert(err).Message(), "blocked") assert.Equal(t, <-intercepted, "/"+serviceName+"/Greet")