Files
buildx/dap/client_test.go
T
Jonathan A. Sternberg 42599a7d49 dap: add debug adapter implementation
Adds a simple implementation of the debug adapter that supports the very
basics of a debug adapter.

It supports the launch request, the configuration done request, the
creation of threads, stopping, resuming, and disconnecting from server.

It does not support custom breakpoints, stack traces, or variable
inspection yet. These are planned to be added in the future.

Signed-off-by: Jonathan A. Sternberg <jonathan.sternberg@docker.com>
2025-06-30 10:51:20 -05:00

136 lines
2.8 KiB
Go

package dap
import (
"context"
"sync"
"sync/atomic"
"testing"
"github.com/google/go-dap"
"github.com/pkg/errors"
"github.com/stretchr/testify/assert"
"golang.org/x/sync/errgroup"
)
type Client struct {
conn Conn
requests map[int]chan<- dap.ResponseMessage
requestsMu sync.Mutex
events map[string]func(dap.EventMessage)
eventsMu sync.RWMutex
seq atomic.Int64
eg *errgroup.Group
cancel context.CancelCauseFunc
}
func NewClient(conn Conn) *Client {
c := &Client{
conn: conn,
requests: make(map[int]chan<- dap.ResponseMessage),
events: make(map[string]func(dap.EventMessage)),
}
var ctx context.Context
ctx, c.cancel = context.WithCancelCause(context.Background())
c.eg, _ = errgroup.WithContext(context.Background())
c.eg.Go(func() error {
for {
m, err := conn.RecvMsg(ctx)
if err != nil {
if errors.Is(err, context.Canceled) {
return nil
}
return err
}
switch m := m.(type) {
case dap.RequestMessage:
// TODO: no reverse requests are currently supported
conn.SendMsg(&dap.Response{
ProtocolMessage: dap.ProtocolMessage{
Seq: c.nextSeq(),
Type: "response",
},
RequestSeq: m.GetRequest().GetSeq(),
Success: false,
Command: m.GetRequest().Command,
Message: "not implemented",
})
case dap.ResponseMessage:
c.requestsMu.Lock()
req := m.GetResponse().GetResponse().RequestSeq
ch := c.requests[req]
delete(c.requests, req)
c.requestsMu.Unlock()
if ch != nil {
ch <- m
}
case dap.EventMessage:
c.invokeEventCallback(m)
}
}
})
return c
}
func (c *Client) Do(t *testing.T, req dap.RequestMessage) <-chan dap.ResponseMessage {
req.GetRequest().Type = "request"
req.GetRequest().Seq = c.nextSeq()
ch := make(chan dap.ResponseMessage, 1)
if err := c.conn.SendMsg(req); err != nil {
assert.NoError(t, err)
close(ch)
return ch
}
c.requestsMu.Lock()
c.requests[req.GetSeq()] = ch
c.requestsMu.Unlock()
return ch
}
func DoRequest[ResponseMessage dap.ResponseMessage, RequestMessage dap.RequestMessage](t *testing.T, c *Client, req RequestMessage) <-chan ResponseMessage {
ch := make(chan ResponseMessage, 1)
go func() {
defer close(ch)
if m := <-c.Do(t, req); m != nil {
ch <- m.(ResponseMessage)
}
}()
return ch
}
func (c *Client) RegisterEvent(event string, fn func(dap.EventMessage)) {
c.eventsMu.Lock()
defer c.eventsMu.Unlock()
c.events[event] = fn
}
func (c *Client) invokeEventCallback(event dap.EventMessage) {
c.eventsMu.RLock()
fn := c.events[event.GetEvent().Event]
c.eventsMu.RUnlock()
if fn != nil {
fn(event)
}
}
func (c *Client) Close() error {
c.cancel(context.Canceled)
return c.eg.Wait()
}
func (c *Client) nextSeq() int {
seq := c.seq.Add(1)
return int(seq)
}