diff options
| author | Paul Buetow <paul@buetow.org> | 2026-06-11 08:34:41 +0300 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2026-06-11 08:34:41 +0300 |
| commit | e95f3fdf0a66ba05ba2c8fb7e755e107f9cf7991 (patch) | |
| tree | 412c7e21ec9c317beb99ed7fe0d4d93a7dacbe50 /internal/hexailsp/run_test.go | |
| parent | 73dadb573f92dca310036e8793932e94277abd62 (diff) | |
Thread context.Context through blocking I/O entry points
Accept ctx as the first parameter on the blocking I/O entry points and
propagate it to downstream blocking calls so the work is cancellable from
the process entry point:
- appconfig.Load / LoadWithOptions: honor ctx before the blocking file
reads, returning defaults on a cancelled context.
- LSP: lsp.Server.Run(ctx) ties the serve loop to the caller context via a
new watchParentContext bridge (cancels the server context, aborting
in-flight LLM work). Threaded through hexailsp.Run/RunWithConfig/
RunWithFactory and runtimeconfig.Store.Reload.
- MCP: mcp.Server.Run(ctx) stops accepting requests once ctx is cancelled;
threaded through hexaimcp.Run/RunWithFactory/RunBackfill.
- editor: RunEditor/OpenTempAndEdit/OpenFile take ctx and use
exec.CommandContext so a cancelled context kills the editor subprocess;
threaded through hexaicli, hexaiaction and askcli call sites.
Top-level callers (cmd/hexai-lsp-server, cmd/hexai-mcp-server) now build a
signal-cancelled context (SIGINT/SIGTERM) so shutdown tears the run down
cleanly. Updated comments to explain the cancellation flow and added
cancellation tests for the LSP/MCP loops, editor, and config load.
All tests pass with -race; cross-package coverage 86.2%.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Diffstat (limited to 'internal/hexailsp/run_test.go')
| -rw-r--r-- | internal/hexailsp/run_test.go | 19 |
1 files changed, 10 insertions, 9 deletions
diff --git a/internal/hexailsp/run_test.go b/internal/hexailsp/run_test.go index b061f17..fa78436 100644 --- a/internal/hexailsp/run_test.go +++ b/internal/hexailsp/run_test.go @@ -3,6 +3,7 @@ package hexailsp import ( "bytes" + "context" "io" "log" "os" @@ -30,7 +31,7 @@ type fakeServer struct { opts lsp.ServerOptions } -func (f *fakeServer) Run() error { f.ran = true; return nil } +func (f *fakeServer) Run(context.Context) error { f.ran = true; return nil } func TestRunWithFactory_UsesDefaultsAndCallsServer(t *testing.T) { old := os.Getenv("OPENAI_API_KEY") @@ -39,7 +40,7 @@ func TestRunWithFactory_UsesDefaultsAndCallsServer(t *testing.T) { var stderr bytes.Buffer logger := log.New(&stderr, "hexai-lsp-server ", 0) - cfg := appconfig.Load(nil) // defaults + cfg := appconfig.Load(context.Background(), nil) // defaults // Pin provider to openai: the in-code default is now ollama, which would // happily build a client without a key and short-circuit the missing-key // assertion below. Load(nil) returns raw defaults and ignores env vars, @@ -50,7 +51,7 @@ func TestRunWithFactory_UsesDefaultsAndCallsServer(t *testing.T) { gotOpts = opts return &fakeServer{opts: opts} } - if err := RunWithFactory("", "", bytes.NewBuffer(nil), bytes.NewBuffer(nil), logger, cfg, nil, factory); err != nil { + if err := RunWithFactory(context.Background(), "", "", bytes.NewBuffer(nil), bytes.NewBuffer(nil), logger, cfg, nil, factory); err != nil { t.Fatalf("RunWithFactory error: %v", err) } if gotOpts.Config == nil { @@ -82,7 +83,7 @@ func TestRunWithFactory_BuildsClientWhenKeysPresent(t *testing.T) { var stderr bytes.Buffer logger := log.New(&stderr, "hexai-lsp-server ", 0) - cfg := appconfig.Load(nil) // defaults + cfg := appconfig.Load(context.Background(), nil) // defaults // Pin provider to openai (the in-code default is now ollama). Load(nil) // returns raw defaults and ignores env vars, so set this on the struct. cfg.Provider = "openai" @@ -91,7 +92,7 @@ func TestRunWithFactory_BuildsClientWhenKeysPresent(t *testing.T) { got = opts.Client return &fakeServer{opts: opts} } - if err := RunWithFactory("", "", bytes.NewBuffer(nil), bytes.NewBuffer(nil), logger, cfg, nil, factory); err != nil { + if err := RunWithFactory(context.Background(), "", "", bytes.NewBuffer(nil), bytes.NewBuffer(nil), logger, cfg, nil, factory); err != nil { t.Fatalf("RunWithFactory error: %v", err) } if got == nil { @@ -103,7 +104,7 @@ func TestRun_RespectsLogPathFlag(t *testing.T) { tmp := t.TempDir() logFile := filepath.Join(tmp, "hexai-lsp-server.log") // Run with real Run but nil env key so client disabled; ensure no panic and file created - if err := Run(logFile, bytes.NewBuffer(nil), bytes.NewBuffer(nil), bytes.NewBuffer(nil)); err != nil { + if err := Run(context.Background(), logFile, bytes.NewBuffer(nil), bytes.NewBuffer(nil), bytes.NewBuffer(nil)); err != nil { t.Fatalf("Run error: %v", err) } if _, err := os.Stat(logFile); err != nil { @@ -126,7 +127,7 @@ func TestRunWithFactory_NormalizesContextMode_AndSetsPreviewLimit(t *testing.T) gotOpts = opts return &fakeServer{opts: opts} } - if err := RunWithFactory("", "", bytes.NewBuffer(nil), bytes.NewBuffer(nil), logger, cfg, nil, factory); err != nil { + if err := RunWithFactory(context.Background(), "", "", bytes.NewBuffer(nil), bytes.NewBuffer(nil), logger, cfg, nil, factory); err != nil { t.Fatalf("RunWithFactory error: %v", err) } if gotOpts.Config == nil { @@ -155,13 +156,13 @@ func TestRunWithFactory_LogContextFlag(t *testing.T) { } return &fakeServer{opts: opts} } - if err := RunWithFactory("/tmp/some.log", "", bytes.NewBuffer(nil), bytes.NewBuffer(nil), logger, cfg, nil, factory); err != nil { + if err := RunWithFactory(context.Background(), "/tmp/some.log", "", bytes.NewBuffer(nil), bytes.NewBuffer(nil), logger, cfg, nil, factory); err != nil { t.Fatalf("RunWithFactory error: %v", err) } if !got1.LogContext { t.Fatalf("expected LogContext true when logPath is non-empty") } - if err := RunWithFactory("", "", bytes.NewBuffer(nil), bytes.NewBuffer(nil), logger, cfg, nil, factory); err != nil { + if err := RunWithFactory(context.Background(), "", "", bytes.NewBuffer(nil), bytes.NewBuffer(nil), logger, cfg, nil, factory); err != nil { t.Fatalf("RunWithFactory error: %v", err) } if got2.LogContext { |
