Merge pull request #3566 from jsternberg/dap-build-integration-tests
tests: add integration tests for `dap build`
This commit is contained in:
+2
-2
@@ -400,7 +400,7 @@ func runBuild(ctx context.Context, dockerCli command.Cli, debugOpts debuggerOpti
|
||||
desktop.PrintBuildDetails(os.Stderr, printer.BuildRefs(), term)
|
||||
}
|
||||
if options.imageIDFile != "" {
|
||||
if err := os.WriteFile(options.imageIDFile, []byte(getImageID(resp.ExporterResponse)), 0644); err != nil {
|
||||
if err := os.WriteFile(options.imageIDFile, []byte(getImageID(resp.ExporterResponse)), 0o644); err != nil {
|
||||
return errors.Wrap(err, "writing image ID file")
|
||||
}
|
||||
}
|
||||
@@ -655,7 +655,7 @@ func writeMetadataFile(filename string, dt any) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return atomicwriter.WriteFile(filename, b, 0644)
|
||||
return atomicwriter.WriteFile(filename, b, 0o644)
|
||||
}
|
||||
|
||||
func decodeExporterResponse(exporterResponse map[string]string) map[string]any {
|
||||
|
||||
+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
|
||||
@@ -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
|
||||
|
||||
+1002
File diff suppressed because it is too large
Load Diff
@@ -34,6 +34,7 @@ func TestIntegration(t *testing.T) {
|
||||
tests = append(tests, dialstdioTests...)
|
||||
tests = append(tests, composeTests...)
|
||||
tests = append(tests, diskusageTests...)
|
||||
tests = append(tests, dapBuildTests...)
|
||||
testIntegration(t, tests...)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,24 +1,26 @@
|
||||
package dap
|
||||
package daptest
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"io"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/buildx/dap/common"
|
||||
"github.com/google/go-dap"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"golang.org/x/sync/errgroup"
|
||||
)
|
||||
|
||||
type Client struct {
|
||||
conn Conn
|
||||
conn common.Conn
|
||||
|
||||
requests map[int]chan<- dap.ResponseMessage
|
||||
requestsMu sync.Mutex
|
||||
|
||||
events map[string]func(dap.EventMessage)
|
||||
events map[string][]func(dap.EventMessage)
|
||||
eventsMu sync.RWMutex
|
||||
|
||||
seq atomic.Int64
|
||||
@@ -26,11 +28,11 @@ type Client struct {
|
||||
cancel context.CancelCauseFunc
|
||||
}
|
||||
|
||||
func NewClient(conn Conn) *Client {
|
||||
func NewClient(conn common.Conn) *Client {
|
||||
c := &Client{
|
||||
conn: conn,
|
||||
requests: make(map[int]chan<- dap.ResponseMessage),
|
||||
events: make(map[string]func(dap.EventMessage)),
|
||||
events: make(map[string][]func(dap.EventMessage)),
|
||||
}
|
||||
|
||||
var ctx context.Context
|
||||
@@ -41,7 +43,7 @@ func NewClient(conn Conn) *Client {
|
||||
for {
|
||||
m, err := conn.RecvMsg(ctx)
|
||||
if err != nil {
|
||||
if errors.Is(err, context.Canceled) {
|
||||
if errors.Is(err, context.Canceled) || errors.Is(err, io.EOF) {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
@@ -83,15 +85,22 @@ func (c *Client) Do(t *testing.T, req dap.RequestMessage) <-chan dap.ResponseMes
|
||||
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
|
||||
}
|
||||
|
||||
// We need to set the channel before we send the message
|
||||
// because it's otherwise possible for us to receive the response
|
||||
// before we've registered the original request.
|
||||
c.requestsMu.Lock()
|
||||
c.requests[req.GetSeq()] = ch
|
||||
c.requestsMu.Unlock()
|
||||
|
||||
if err := c.conn.SendMsg(req); err != nil {
|
||||
assert.NoError(t, err)
|
||||
close(ch)
|
||||
|
||||
c.requestsMu.Lock()
|
||||
delete(c.requests, req.GetSeq())
|
||||
c.requestsMu.Unlock()
|
||||
}
|
||||
return ch
|
||||
}
|
||||
|
||||
@@ -111,15 +120,15 @@ func (c *Client) RegisterEvent(event string, fn func(dap.EventMessage)) {
|
||||
c.eventsMu.Lock()
|
||||
defer c.eventsMu.Unlock()
|
||||
|
||||
c.events[event] = fn
|
||||
c.events[event] = append(c.events[event], fn)
|
||||
}
|
||||
|
||||
func (c *Client) invokeEventCallback(event dap.EventMessage) {
|
||||
c.eventsMu.RLock()
|
||||
fn := c.events[event.GetEvent().Event]
|
||||
fns := c.events[event.GetEvent().Event]
|
||||
c.eventsMu.RUnlock()
|
||||
|
||||
if fn != nil {
|
||||
for _, fn := range fns {
|
||||
fn(event)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
package daptest
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/buildx/dap/common"
|
||||
"github.com/google/go-dap"
|
||||
)
|
||||
|
||||
func LogConn(t *testing.T, prefix string, conn common.Conn) common.Conn {
|
||||
return &loggingConn{
|
||||
Conn: conn,
|
||||
t: t,
|
||||
prefix: prefix,
|
||||
}
|
||||
}
|
||||
|
||||
type loggingConn struct {
|
||||
common.Conn
|
||||
t *testing.T
|
||||
prefix string
|
||||
|
||||
outBuf []byte
|
||||
}
|
||||
|
||||
func (c *loggingConn) SendMsg(m dap.Message) error {
|
||||
c.t.Helper()
|
||||
|
||||
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) {
|
||||
c.t.Helper()
|
||||
|
||||
m, err := c.Conn.RecvMsg(ctx)
|
||||
if err != nil {
|
||||
if !errors.Is(err, context.Canceled) && !errors.Is(err, io.EOF) {
|
||||
c.t.Logf("[%s] recv error: %v", c.prefix, err)
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if e, ok := m.(dap.EventMessage); ok {
|
||||
if drop := c.handleEvent(e); drop {
|
||||
return m, nil
|
||||
}
|
||||
}
|
||||
|
||||
b, _ := json.Marshal(m)
|
||||
c.t.Logf("[%s] recv: %v", c.prefix, string(b))
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func (c *loggingConn) handleEvent(e dap.EventMessage) bool {
|
||||
switch e.GetEvent().Event {
|
||||
case "output":
|
||||
m := e.(*dap.OutputEvent)
|
||||
c.outBuf = append(c.outBuf, []byte(m.Body.Output)...)
|
||||
|
||||
for len(c.outBuf) > 0 {
|
||||
i := bytes.IndexRune(c.outBuf, '\n')
|
||||
if i < 0 {
|
||||
break
|
||||
}
|
||||
|
||||
c.t.Log(string(c.outBuf[:i]))
|
||||
c.outBuf = c.outBuf[i+1:]
|
||||
}
|
||||
return true
|
||||
case "terminated":
|
||||
if len(c.outBuf) > 0 {
|
||||
c.t.Log(string(c.outBuf))
|
||||
c.outBuf = nil
|
||||
}
|
||||
return false
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user