tests: add integration tests for dap build
This adds integration tests for the `dap build` command to test various behavior associated with the command. We start the build and the integration test acts as a dap client to send requests and check that the output is what we expect. Signed-off-by: Jonathan A. Sternberg <jonathan.sternberg@docker.com>
This commit is contained in:
+3
-2
@@ -568,8 +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
|
||||
|
||||
// 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]
|
||||
|
||||
+11
-48
@@ -2,7 +2,6 @@ package dap
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"path/filepath"
|
||||
@@ -10,6 +9,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/docker/buildx/dap/common"
|
||||
"github.com/docker/buildx/util/daptest"
|
||||
"github.com/google/go-dap"
|
||||
"github.com/moby/buildkit/solver/pb"
|
||||
"github.com/stretchr/testify/assert"
|
||||
@@ -36,20 +36,20 @@ func TestLaunch(t *testing.T) {
|
||||
|
||||
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{
|
||||
configurationDone = daptest.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{
|
||||
initializeResp := <-daptest.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{
|
||||
launchResp := <-daptest.DoRequest[*dap.LaunchResponse](t, client, &dap.LaunchRequest{
|
||||
Request: dap.Request{Command: "launch"},
|
||||
})
|
||||
assert.True(t, launchResp.Success)
|
||||
@@ -93,7 +93,7 @@ func TestSetBreakpoints(t *testing.T) {
|
||||
)
|
||||
|
||||
client.RegisterEvent("initialized", func(em dap.EventMessage) {
|
||||
setBreakpoints = DoRequest[*dap.SetBreakpointsResponse](t, client, &dap.SetBreakpointsRequest{
|
||||
setBreakpoints = daptest.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")},
|
||||
@@ -104,13 +104,13 @@ func TestSetBreakpoints(t *testing.T) {
|
||||
})
|
||||
|
||||
eg.Go(func() error {
|
||||
initializeResp := <-DoRequest[*dap.InitializeResponse](t, client, &dap.InitializeRequest{
|
||||
initializeResp := <-daptest.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{
|
||||
launchResp := <-daptest.DoRequest[*dap.LaunchResponse](t, client, &dap.LaunchRequest{
|
||||
Request: dap.Request{Command: "launch"},
|
||||
})
|
||||
assert.True(t, launchResp.Success)
|
||||
@@ -234,66 +234,29 @@ func TestBreakpointMapIntersectVerified(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func NewTestAdapter[C LaunchConfig](t *testing.T) (*Adapter[C], Conn, *Client) {
|
||||
func NewTestAdapter[C LaunchConfig](t *testing.T) (*Adapter[C], Conn, *daptest.Client) {
|
||||
t.Helper()
|
||||
|
||||
rd1, wr1 := io.Pipe()
|
||||
rd2, wr2 := io.Pipe()
|
||||
|
||||
srvConn := logConn(t, "server", NewConn(rd1, wr2))
|
||||
srvConn := daptest.LogConn(t, "server", NewConn(rd1, wr2))
|
||||
t.Cleanup(func() {
|
||||
srvConn.Close()
|
||||
})
|
||||
|
||||
clientConn := logConn(t, "client", NewConn(rd2, wr1))
|
||||
clientConn := daptest.LogConn(t, "client", NewConn(rd2, wr1))
|
||||
t.Cleanup(func() { clientConn.Close() })
|
||||
|
||||
adapter := New[C]()
|
||||
t.Cleanup(func() { adapter.Stop() })
|
||||
|
||||
client := NewClient(clientConn)
|
||||
client := daptest.NewClient(clientConn)
|
||||
t.Cleanup(func() { client.Close() })
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
type breakpointTestContext struct {
|
||||
context.Context
|
||||
messages chan dap.Message
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
package dap
|
||||
@@ -1,135 +0,0 @@
|
||||
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)
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
|
||||
"github.com/google/go-dap"
|
||||
)
|
||||
|
||||
type Conn interface {
|
||||
SendMsg(m dap.Message) error
|
||||
RecvMsg(ctx context.Context) (dap.Message, error)
|
||||
io.Closer
|
||||
}
|
||||
+2
-5
@@ -6,16 +6,13 @@ import (
|
||||
"io"
|
||||
"sync"
|
||||
|
||||
"github.com/docker/buildx/dap/common"
|
||||
"github.com/google/go-dap"
|
||||
"github.com/pkg/errors"
|
||||
"golang.org/x/sync/errgroup"
|
||||
)
|
||||
|
||||
type Conn interface {
|
||||
SendMsg(m dap.Message) error
|
||||
RecvMsg(ctx context.Context) (dap.Message, error)
|
||||
io.Closer
|
||||
}
|
||||
type Conn = common.Conn
|
||||
|
||||
type conn struct {
|
||||
recvCh <-chan dap.Message
|
||||
|
||||
Reference in New Issue
Block a user