diff --git a/dap/adapter.go b/dap/adapter.go index ade3139cc..12e8bfae6 100644 --- a/dap/adapter.go +++ b/dap/adapter.go @@ -568,6 +568,9 @@ func newBreakpointMap() *breakpointMap { func (b *breakpointMap) Set(fname string, sbps []dap.SourceBreakpoint) (breakpoints []dap.Breakpoint) { b.mu.Lock() defer b.mu.Unlock() + // explicitly initialize breakpoints so that + // we do not send a null back in the JSON if there are no breakpoints + breakpoints = []dap.Breakpoint{} prev := b.byPath[fname] for _, sbp := range sbps { diff --git a/dap/adapter_test.go b/dap/adapter_test.go index 1017849c1..1e990ada9 100644 --- a/dap/adapter_test.go +++ b/dap/adapter_test.go @@ -4,6 +4,7 @@ import ( "context" "encoding/json" "io" + "path/filepath" "testing" "time" @@ -69,6 +70,70 @@ func TestLaunch(t *testing.T) { eg.Wait() } +// TestSetBreakpoints will test sending a setBreakpoints request with no breakpoints. +// The response should be an empty array instead of null in the JSON. +func TestSetBreakpoints(t *testing.T) { + adapter, conn, client := NewTestAdapter[common.Config](t) + + ctx, cancel := context.WithTimeoutCause(context.Background(), 10*time.Second, context.DeadlineExceeded) + defer cancel() + + eg, _ := errgroup.WithContext(ctx) + eg.Go(func() error { + _, err := adapter.Start(ctx, conn) + assert.NoError(t, err) + return nil + }) + + var ( + initialized = make(chan struct{}) + setBreakpoints <-chan *dap.SetBreakpointsResponse + ) + + client.RegisterEvent("initialized", func(em dap.EventMessage) { + setBreakpoints = DoRequest[*dap.SetBreakpointsResponse](t, client, &dap.SetBreakpointsRequest{ + Request: dap.Request{Command: "setBreakpoints"}, + Arguments: dap.SetBreakpointsArguments{ + Source: dap.Source{Name: "Dockerfile", Path: filepath.Join(t.TempDir(), "Dockerfile")}, + Breakpoints: []dap.SourceBreakpoint{}, + }, + }) + close(initialized) + }) + + eg.Go(func() error { + initializeResp := <-DoRequest[*dap.InitializeResponse](t, client, &dap.InitializeRequest{ + Request: dap.Request{Command: "initialize"}, + }) + assert.True(t, initializeResp.Success) + assert.True(t, initializeResp.Body.SupportsConfigurationDoneRequest) + + launchResp := <-DoRequest[*dap.LaunchResponse](t, client, &dap.LaunchRequest{ + Request: dap.Request{Command: "launch"}, + }) + assert.True(t, launchResp.Success) + + // We should have received the initialized event. + select { + case <-initialized: + default: + assert.Fail(t, "did not receive initialized event") + } + + select { + case setBreakpointsResp := <-setBreakpoints: + assert.True(t, setBreakpointsResp.Success) + assert.Len(t, setBreakpointsResp.Body.Breakpoints, 0) + assert.NotNil(t, setBreakpointsResp.Body.Breakpoints, "breakpoints should be an empty array instead of null in the JSON") + case <-time.After(10 * time.Second): + assert.Fail(t, "did not receive setBreakpoints response") + } + return nil + }) + + eg.Wait() +} + func NewTestAdapter[C LaunchConfig](t *testing.T) (*Adapter[C], Conn, *Client) { t.Helper()