Files
buildx/dap/adapter_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

130 lines
2.8 KiB
Go

package dap
import (
"context"
"encoding/json"
"io"
"testing"
"time"
"github.com/google/go-dap"
"github.com/stretchr/testify/assert"
"golang.org/x/sync/errgroup"
)
func TestLaunch(t *testing.T) {
adapter, conn, client := NewTestAdapter[any](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{})
configurationDone <-chan *dap.ConfigurationDoneResponse
)
client.RegisterEvent("initialized", func(em dap.EventMessage) {
// Send configuration done since we don't do any configuration.
configurationDone = DoRequest[*dap.ConfigurationDoneResponse](t, client, &dap.ConfigurationDoneRequest{
Request: dap.Request{Command: "configurationDone"},
})
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 <-configurationDone:
case <-time.After(time.Second):
assert.Fail(t, "did not receive configurationDone response")
}
return nil
})
eg.Wait()
}
func NewTestAdapter[T any](t *testing.T) (*Adapter[T], Conn, *Client) {
t.Helper()
rd1, wr1 := io.Pipe()
rd2, wr2 := io.Pipe()
srvConn := logConn(t, "server", NewConn(rd1, wr2))
t.Cleanup(func() {
srvConn.Close()
})
clientConn := logConn(t, "client", NewConn(rd2, wr1))
t.Cleanup(func() {
clientConn.Close()
})
adapter := New[T](nil)
t.Cleanup(func() { adapter.Stop() })
client := NewClient(clientConn)
return adapter, srvConn, client
}
func logConn(t *testing.T, prefix string, conn Conn) Conn {
return &loggingConn{
Conn: conn,
t: t,
prefix: prefix,
}
}
type loggingConn struct {
Conn
t *testing.T
prefix string
}
func (c *loggingConn) SendMsg(m dap.Message) error {
b, _ := json.Marshal(m)
c.t.Logf("[%s] send: %v", c.prefix, string(b))
err := c.Conn.SendMsg(m)
if err != nil {
c.t.Logf("[%s] send error: %v", c.prefix, err)
}
return err
}
func (c *loggingConn) RecvMsg(ctx context.Context) (dap.Message, error) {
m, err := c.Conn.RecvMsg(ctx)
if err != nil {
c.t.Logf("[%s] recv error: %v", c.prefix, err)
return nil, err
}
b, _ := json.Marshal(m)
c.t.Logf("[%s] recv: %v", c.prefix, string(b))
return m, nil
}