Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 10 additions & 5 deletions commands/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -398,10 +398,6 @@ func runBuild(ctx context.Context, dockerCli command.Cli, debugOpts debuggerOpti
done := timeBuildCommand(mp, attributes)
resp, inputs, retErr := runBuildWithOptions(ctx, dockerCli, opts, dbg, printer)

if err := printer.Wait(); retErr == nil {
retErr = err
}

done(retErr)
if retErr != nil {
return retErr
Expand Down Expand Up @@ -462,12 +458,21 @@ func runBuildWithOptions(ctx context.Context, dockerCli command.Cli, opts *Build
if err := dbg.Start(printer, opts); err != nil {
return nil, nil, err
}
defer dbg.Stop()
defer func() { dbg.Stop(retErr) }()

bh = dbg.Handler()
dockerCli.SetIn(nil)
}

// Ensure messages sent to the printer are flushed before the debugger completes.
// This prevents late messages from not being sent because the connection was
// terminated before completion of the debugger.
defer func() {
if err := printer.Wait(); retErr == nil {
retErr = err
}
}()

in := dockerCli.In()
for {
resp, inputs, err := RunBuild(ctx, dockerCli, opts, in, printer, &bh)
Expand Down
4 changes: 2 additions & 2 deletions commands/dap.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,9 +91,9 @@ func (d *adapterProtocolDebugger) Start(printer *progress.Printer, opts *BuildOp
return nil
}

func (d *adapterProtocolDebugger) Stop() error {
func (d *adapterProtocolDebugger) Stop(retErr error) error {
defer d.conn.Close()
return d.Adapter.Stop()
return d.Adapter.Stop(retErr)
}

func dapAttachCmd() *cobra.Command {
Expand Down
4 changes: 2 additions & 2 deletions commands/debug.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ type debuggerOptions interface {
type debuggerInstance interface {
Start(printer *progress.Printer, opts *BuildOptions) error
Handler() build.Handler
Stop() error
Stop(retErr error) error
Out() io.Writer
}

Expand Down Expand Up @@ -98,7 +98,7 @@ func (d *monitorDebuggerInstance) Handler() build.Handler {
return d.m.Handler()
}

func (d *monitorDebuggerInstance) Stop() error {
func (d *monitorDebuggerInstance) Stop(_ error) error {
return d.m.Close()
}

Expand Down
24 changes: 14 additions & 10 deletions dap/adapter.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ func (d *Adapter[C]) Start(conn Conn) (C, error) {
return resp.Config, resp.Error
}

func (d *Adapter[C]) Stop() error {
func (d *Adapter[C]) Stop(retErr error) error {
if d.eg == nil {
return nil
}
Expand All @@ -94,15 +94,19 @@ func (d *Adapter[C]) Stop() error {
Event: "terminated",
},
}
// TODO: detect exit code from threads
// c.C() <- &dap.ExitedEvent{
// Event: dap.Event{
// Event: "exited",
// },
// Body: dap.ExitedEventBody{
// ExitCode: exitCode,
// },
// }

exitCode := 0
if retErr != nil {
exitCode = 1
}
c.C() <- &dap.ExitedEvent{
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

iiuc then "terminated" should come after "exited"?

Event: dap.Event{
Event: "exited",
},
Body: dap.ExitedEventBody{
ExitCode: exitCode,
},
}
})
d.srv.Stop()

Expand Down
2 changes: 1 addition & 1 deletion dap/adapter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -253,7 +253,7 @@ func NewTestAdapter[C LaunchConfig](t *testing.T) (*Adapter[C], Conn, *daptest.C
client := daptest.NewClient(clientConn)
t.Cleanup(func() { client.Close() })

t.Cleanup(func() { adapter.Stop() })
t.Cleanup(func() { adapter.Stop(nil) })
return adapter, srvConn, client
}

Expand Down
69 changes: 69 additions & 0 deletions tests/dap_build.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"os"
"os/exec"
"path"
"path/filepath"
"runtime"
"slices"
"syscall"
Expand Down Expand Up @@ -85,6 +86,7 @@ var dapBuildTests = []func(t *testing.T, sb integration.Sandbox){
testDapBuildStepNext,
testDapBuildStepOut,
testDapBuildVariables,
testDapBuildExitedEvent,
}

func testDapBuild(t *testing.T, sb integration.Sandbox) {
Expand Down Expand Up @@ -857,6 +859,73 @@ func testDapBuildVariables(t *testing.T, sb integration.Sandbox) {
}
}

func testDapBuildExitedEvent(t *testing.T, sb integration.Sandbox) {
t.Run("success", func(t *testing.T) {
dir := createTestProject(t)
client, done, err := dapBuildCmd(t, sb)
require.NoError(t, err)

ch := make(chan *dap.ExitedEvent, 1)
client.RegisterEvent("exited", func(em dap.EventMessage) {
ch <- em.(*dap.ExitedEvent)
close(ch)
})

// Project should just build normally.
doLaunch(t, client, commands.LaunchConfig{
Dockerfile: path.Join(dir, "Dockerfile"),
ContextPath: dir,
})

select {
case exited := <-ch:
require.Equal(t, 0, exited.Body.ExitCode)
case <-time.After(5 * time.Second):
require.Fail(t, "timeout reached")
}

require.NoError(t, done(true))
})

t.Run("failure", func(t *testing.T) {
dir := createTestProject(t)
client, done, err := dapBuildCmd(t, sb)
require.NoError(t, err)

ch := make(chan *dap.ExitedEvent, 1)
client.RegisterEvent("exited", func(em dap.EventMessage) {
ch <- em.(*dap.ExitedEvent)
close(ch)
})

// Delete foo from the test project so this will fail.
err = os.Remove(filepath.Join(dir, "foo"))
require.NoError(t, err)

interruptCh := pollInterruptEvents(client)
doLaunch(t, client, commands.LaunchConfig{
Dockerfile: path.Join(dir, "Dockerfile"),
ContextPath: dir,
})

// We will hit an interrupt because of the failure.
stopped := waitForInterrupt[*dap.StoppedEvent](t, interruptCh)
require.Equal(t, "exception", stopped.Body.Reason)

// Continue execution which should trigger the exited event.
doNext(t, client, stopped.Body.ThreadId)
select {
case exited := <-ch:
require.NotEqual(t, 0, exited.Body.ExitCode)
case <-time.After(time.Second):
require.Fail(t, "timeout reached")
}

var exitErr *exec.ExitError
require.ErrorAs(t, done(false), &exitErr)
})
}

func doLaunch(t *testing.T, client *daptest.Client, config commands.LaunchConfig, bps ...dap.SourceBreakpoint) {
t.Helper()

Expand Down
Loading