Skip to content

Commit 8d822eb

Browse files
committed
dap: Fix breakpoint overlap check
The old logic wasn't quite right, and a little hard to follow. Before this, because breakpoints sent from setBreakpoints don't have an end line or column they are just a single point the comparison would fail the check due to the column check. E.g. if I have a breakpoint on line 30. The content lines range from 29-31. Now lets say the end column is at postion 10, but my breakpoint is at L30 column 11 (still within range). The overlap check would fail because 11 > 10 but these shouldn't be compared at all in this case. Signed-off-by: Brian Goff <[email protected]>
1 parent 4826295 commit 8d822eb

File tree

2 files changed

+136
-1
lines changed

2 files changed

+136
-1
lines changed

dap/adapter.go

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -612,7 +612,16 @@ func (b *breakpointMap) Intersect(ctx Context, src *pb.Source, ws string) map[di
612612

613613
func (b *breakpointMap) intersect(ctx Context, src *pb.Source, locs *pb.Locations, ws string) int {
614614
overlaps := func(r *pb.Range, bp *dap.Breakpoint) bool {
615-
return r.Start.Line <= int32(bp.Line) && r.Start.Character <= int32(bp.Column) && r.End.Line >= int32(bp.EndLine) && r.End.Character >= int32(bp.EndColumn)
615+
if bp.Line < int(r.Start.Line) || bp.Line > int(r.End.Line) {
616+
return false
617+
}
618+
if bp.Line == int(r.Start.Line) && bp.Column < int(r.Start.Character) {
619+
return false
620+
}
621+
if bp.Line == int(r.End.Line) && bp.Column > int(r.End.Character) {
622+
return false
623+
}
624+
return true
616625
}
617626

618627
for _, loc := range locs.Locations {

dap/adapter_test.go

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,15 @@ package dap
33
import (
44
"context"
55
"encoding/json"
6+
"fmt"
67
"io"
78
"path/filepath"
89
"testing"
910
"time"
1011

1112
"github.com/docker/buildx/dap/common"
1213
"github.com/google/go-dap"
14+
"github.com/moby/buildkit/solver/pb"
1315
"github.com/stretchr/testify/assert"
1416
"golang.org/x/sync/errgroup"
1517
)
@@ -134,6 +136,104 @@ func TestSetBreakpoints(t *testing.T) {
134136
eg.Wait()
135137
}
136138

139+
func TestBreakpointMapIntersectVerified(t *testing.T) {
140+
t.Parallel()
141+
142+
ws := t.TempDir()
143+
filename := "Dockerfile"
144+
fpath := filepath.Join(ws, filename)
145+
146+
type breakpointCase struct {
147+
desc string
148+
sbp dap.SourceBreakpoint
149+
expectVerified bool
150+
}
151+
152+
docRanges := []*pb.Range{
153+
{Start: &pb.Position{Line: 10, Character: 0}, End: &pb.Position{Line: 10, Character: 10}},
154+
{Start: &pb.Position{Line: 20, Character: 5}, End: &pb.Position{Line: 20, Character: 5}},
155+
{Start: &pb.Position{Line: 30, Character: 0}, End: &pb.Position{Line: 30, Character: 10}},
156+
{Start: &pb.Position{Line: 35, Character: 2}, End: &pb.Position{Line: 35, Character: 7}},
157+
}
158+
159+
breakpointCases := []breakpointCase{
160+
{desc: "inside range 0", sbp: dap.SourceBreakpoint{Line: 10, Column: 5}, expectVerified: true},
161+
{desc: "before range 0", sbp: dap.SourceBreakpoint{Line: 10, Column: -1}},
162+
{desc: "range 1 point", sbp: dap.SourceBreakpoint{Line: 20, Column: 5}, expectVerified: true},
163+
{desc: "before range 1 point", sbp: dap.SourceBreakpoint{Line: 20, Column: 4}},
164+
{desc: "range 2 end", sbp: dap.SourceBreakpoint{Line: 30, Column: 10}, expectVerified: true},
165+
{desc: "after range 2", sbp: dap.SourceBreakpoint{Line: 30, Column: 11}},
166+
{desc: "inside range 3", sbp: dap.SourceBreakpoint{Line: 35, Column: 4}, expectVerified: true},
167+
{desc: "different line", sbp: dap.SourceBreakpoint{Line: 40, Column: 0}},
168+
}
169+
170+
bm := newBreakpointMap()
171+
sbps := make([]dap.SourceBreakpoint, len(breakpointCases))
172+
for i, bc := range breakpointCases {
173+
sbps[i] = bc.sbp
174+
}
175+
bm.Set(fpath, sbps)
176+
177+
srcLocs := make(map[string]*pb.Locations, len(docRanges))
178+
for i, rng := range docRanges {
179+
srcLocs[fmt.Sprintf("doc-%d", i)] = &pb.Locations{
180+
Locations: []*pb.Location{{
181+
SourceIndex: 0,
182+
Ranges: []*pb.Range{rng},
183+
}},
184+
}
185+
}
186+
187+
src := &pb.Source{
188+
Locations: srcLocs,
189+
Infos: []*pb.SourceInfo{
190+
{Filename: filename},
191+
},
192+
}
193+
194+
ctx := newBreakpointTestContext(t)
195+
digests := bm.Intersect(ctx, src, ws)
196+
wantMatches := 0
197+
for _, bc := range breakpointCases {
198+
if bc.expectVerified {
199+
wantMatches++
200+
}
201+
}
202+
assert.Len(t, digests, wantMatches)
203+
204+
expectedEvents := make(map[int]struct{})
205+
for i, bp := range bm.byPath[fpath] {
206+
if breakpointCases[i].expectVerified {
207+
expectedEvents[bp.Id] = struct{}{}
208+
}
209+
}
210+
211+
for len(expectedEvents) > 0 {
212+
select {
213+
case msg := <-ctx.messages:
214+
evt, ok := msg.(*dap.BreakpointEvent)
215+
if !assert.True(t, ok, "expected breakpoint event message") {
216+
continue
217+
}
218+
if _, ok := expectedEvents[evt.Body.Breakpoint.Id]; ok {
219+
delete(expectedEvents, evt.Body.Breakpoint.Id)
220+
assert.True(t, evt.Body.Breakpoint.Verified)
221+
} else {
222+
t.Fatalf("unexpected breakpoint event for id %d", evt.Body.Breakpoint.Id)
223+
}
224+
case <-time.After(time.Second):
225+
t.Fatalf("expected %d more breakpoint events", len(expectedEvents))
226+
}
227+
}
228+
229+
stored := bm.byPath[fpath]
230+
if assert.Len(t, stored, len(breakpointCases)) {
231+
for i, bc := range breakpointCases {
232+
assert.Equal(t, bc.expectVerified, stored[i].Verified, "breakpoint %d (%s) mismatch", i, bc.desc)
233+
}
234+
}
235+
}
236+
137237
func NewTestAdapter[C LaunchConfig](t *testing.T) (*Adapter[C], Conn, *Client) {
138238
t.Helper()
139239

@@ -193,3 +293,29 @@ func (c *loggingConn) RecvMsg(ctx context.Context) (dap.Message, error) {
193293
c.t.Logf("[%s] recv: %v", c.prefix, string(b))
194294
return m, nil
195295
}
296+
297+
type breakpointTestContext struct {
298+
context.Context
299+
messages chan dap.Message
300+
}
301+
302+
func newBreakpointTestContext(t *testing.T) *breakpointTestContext {
303+
t.Helper()
304+
return &breakpointTestContext{
305+
Context: context.Background(),
306+
messages: make(chan dap.Message, 16),
307+
}
308+
}
309+
310+
func (c *breakpointTestContext) C() chan<- dap.Message {
311+
return c.messages
312+
}
313+
314+
func (c *breakpointTestContext) Go(f func(Context)) bool {
315+
go f(c)
316+
return true
317+
}
318+
319+
func (c *breakpointTestContext) Request(dap.RequestMessage) dap.ResponseMessage {
320+
return nil
321+
}

0 commit comments

Comments
 (0)