service/dap: misc remote attach improvements (#2778)

Co-authored-by: Polina Sokolova <polinasok@users.noreply.github.com>
This commit is contained in:
polinasok
2021-12-07 09:23:26 -08:00
committed by GitHub
parent b8a9ae26f6
commit d0898e4de1
5 changed files with 45 additions and 20 deletions

View File

@ -750,9 +750,9 @@ func TestRemoteDAPClient(t *testing.T) {
cmd.Wait() cmd.Wait()
} }
func closeDAPRemoteMultiClient(t *testing.T, c *daptest.Client) { func closeDAPRemoteMultiClient(t *testing.T, c *daptest.Client, expectStatus string) {
c.DisconnectRequest() c.DisconnectRequest()
c.ExpectOutputEventClosingClient(t) c.ExpectOutputEventClosingClient(t, expectStatus)
c.ExpectDisconnectResponse(t) c.ExpectDisconnectResponse(t)
c.ExpectTerminatedEvent(t) c.ExpectTerminatedEvent(t)
c.Close() c.Close()
@ -787,6 +787,11 @@ func TestRemoteDAPClientMulti(t *testing.T) {
} }
}() }()
// Client 0 connects but with the wrong attach request
dapclient0 := daptest.NewClient(listenAddr)
dapclient0.AttachRequest(map[string]interface{}{"mode": "local"})
dapclient0.ExpectErrorResponse(t)
// Client 1 connects and continues to main.main // Client 1 connects and continues to main.main
dapclient := newDAPRemoteClient(t, listenAddr) dapclient := newDAPRemoteClient(t, listenAddr)
dapclient.SetFunctionBreakpointsRequest([]godap.FunctionBreakpoint{{Name: "main.main"}}) dapclient.SetFunctionBreakpointsRequest([]godap.FunctionBreakpoint{{Name: "main.main"}})
@ -795,7 +800,7 @@ func TestRemoteDAPClientMulti(t *testing.T) {
dapclient.ExpectContinueResponse(t) dapclient.ExpectContinueResponse(t)
dapclient.ExpectStoppedEvent(t) dapclient.ExpectStoppedEvent(t)
dapclient.CheckStopLocation(t, 1, "main.main", 5) dapclient.CheckStopLocation(t, 1, "main.main", 5)
closeDAPRemoteMultiClient(t, dapclient) closeDAPRemoteMultiClient(t, dapclient, "halted")
// Client 2 reconnects at main.main and continues to process exit // Client 2 reconnects at main.main and continues to process exit
dapclient2 := newDAPRemoteClient(t, listenAddr) dapclient2 := newDAPRemoteClient(t, listenAddr)
@ -803,13 +808,13 @@ func TestRemoteDAPClientMulti(t *testing.T) {
dapclient2.ContinueRequest(1) dapclient2.ContinueRequest(1)
dapclient2.ExpectContinueResponse(t) dapclient2.ExpectContinueResponse(t)
dapclient2.ExpectTerminatedEvent(t) dapclient2.ExpectTerminatedEvent(t)
closeDAPRemoteMultiClient(t, dapclient2) closeDAPRemoteMultiClient(t, dapclient2, "exited")
// Attach to exited processs is an error // Attach to exited processs is an error
dapclient3 := daptest.NewClient(listenAddr) dapclient3 := daptest.NewClient(listenAddr)
dapclient3.AttachRequest(map[string]interface{}{"mode": "remote", "stopOnEntry": true}) dapclient3.AttachRequest(map[string]interface{}{"mode": "remote", "stopOnEntry": true})
dapclient3.ExpectErrorResponseWith(t, dap.FailedToAttach, `Process \d+ has exited with status 0`, true) dapclient3.ExpectErrorResponseWith(t, dap.FailedToAttach, `Process \d+ has exited with status 0`, true)
closeDAPRemoteMultiClient(t, dapclient3) closeDAPRemoteMultiClient(t, dapclient3, "exited")
// But rpc clients can still connect and restart // But rpc clients can still connect and restart
rpcclient := rpc2.NewClient(listenAddr) rpcclient := rpc2.NewClient(listenAddr)

View File

@ -178,9 +178,11 @@ func (c *Client) ExpectOutputEventTerminating(t *testing.T) *dap.OutputEvent {
return c.ExpectOutputEventRegex(t, `Terminating process [0-9]+\n`) return c.ExpectOutputEventRegex(t, `Terminating process [0-9]+\n`)
} }
func (c *Client) ExpectOutputEventClosingClient(t *testing.T) *dap.OutputEvent { const ClosingClient = "Closing client session, but leaving multi-client DAP server at .+:[0-9]+ with debuggee %s\n"
func (c *Client) ExpectOutputEventClosingClient(t *testing.T, status string) *dap.OutputEvent {
t.Helper() t.Helper()
return c.ExpectOutputEventRegex(t, `Closing client session, but leaving multi-client DAP server running at .+:[0-9]+\n`) return c.ExpectOutputEventRegex(t, fmt.Sprintf(ClosingClient, status))
} }
func (c *Client) CheckStopLocation(t *testing.T, thread int, name string, line int) { func (c *Client) CheckStopLocation(t *testing.T, thread int, name string, line int) {

View File

@ -491,6 +491,16 @@ func (s *Server) RunWithClient(conn net.Conn) {
go s.runSession(conn) go s.runSession(conn)
} }
func (s *Session) address() string {
if s.config.Listener != nil {
return s.config.Listener.Addr().String()
}
if netconn, ok := s.conn.(net.Conn); ok {
return netconn.LocalAddr().String()
}
return ""
}
// ServeDAPCodec reads and decodes requests from the client // ServeDAPCodec reads and decodes requests from the client
// until it encounters an error or EOF, when it sends // until it encounters an error or EOF, when it sends
// a disconnect signal and returns. // a disconnect signal and returns.
@ -934,8 +944,8 @@ func cleanExeName(name string) string {
func (s *Session) onLaunchRequest(request *dap.LaunchRequest) { func (s *Session) onLaunchRequest(request *dap.LaunchRequest) {
var err error var err error
if s.debugger != nil { if s.debugger != nil {
s.sendShowUserErrorResponse(request.Request, FailedToLaunch, s.sendShowUserErrorResponse(request.Request, FailedToLaunch, "Failed to launch",
"Failed to launch", "debugger already started - use remote attach to connect to a server with an active debug session") fmt.Sprintf("debug session already in progress at %s - use remote attach mode to connect to a server with an active debug session", s.address()))
return return
} }
@ -1178,7 +1188,13 @@ func (s *Session) onDisconnectRequest(request *dap.DisconnectRequest) {
// This is a multi-use server/debugger, so a disconnect request that doesn't // This is a multi-use server/debugger, so a disconnect request that doesn't
// terminate the debuggee should clean up only the client connection and pointer to debugger, // terminate the debuggee should clean up only the client connection and pointer to debugger,
// but not the entire server. // but not the entire server.
s.logToConsole("Closing client session, but leaving multi-client DAP server running at " + s.config.Listener.Addr().String()) status := "halted"
if s.isRunningCmd() {
status = "running"
} else if s, err := s.debugger.State(false); processExited(s, err) {
status = "exited"
}
s.logToConsole(fmt.Sprintf("Closing client session, but leaving multi-client DAP server at %s with debuggee %s", s.config.Listener.Addr().String(), status))
s.send(&dap.DisconnectResponse{Response: *newResponse(request.Request)}) s.send(&dap.DisconnectResponse{Response: *newResponse(request.Request)})
s.send(&dap.TerminatedEvent{Event: *newEvent("terminated")}) s.send(&dap.TerminatedEvent{Event: *newEvent("terminated")})
s.conn.Close() s.conn.Close()
@ -1752,7 +1768,8 @@ func (s *Session) onAttachRequest(request *dap.AttachRequest) {
if s.debugger != nil { if s.debugger != nil {
s.sendShowUserErrorResponse( s.sendShowUserErrorResponse(
request.Request, FailedToAttach, request.Request, FailedToAttach,
"Failed to attach", "debugger already started - use remote mode to connect") "Failed to attach",
fmt.Sprintf("debug session already in progress at %s - use remote mode to connect to a server with an active debug session", s.address()))
return return
} }
if args.ProcessID == 0 { if args.ProcessID == 0 {

View File

@ -6358,10 +6358,10 @@ func TestAttachRemoteToRunningTargetContinueOnEntry(t *testing.T) {
}) })
} }
// TestMultiClient tests that that remote attach doesn't take down // TestAttachRemoteMultiClientDisconnect tests that that remote attach doesn't take down
// the server in multi-client mode unless terminateDebugee is explicitely set. // the server in multi-client mode unless terminateDebugee is explicitely set.
func TestAttachRemoteMultiClient(t *testing.T) { func TestAttachRemoteMultiClientDisconnect(t *testing.T) {
closingClientSessionOnly := "Closing client session, but leaving multi-client DAP server running at" closingClientSessionOnly := fmt.Sprintf(daptest.ClosingClient, "halted")
detachingAndTerminating := "Detaching and terminating target process" detachingAndTerminating := "Detaching and terminating target process"
tests := []struct { tests := []struct {
name string name string
@ -6441,18 +6441,18 @@ func TestLaunchAttachErrorWhenDebugInProgress(t *testing.T) {
// Both launch and attach requests should go through for additional error checking // Both launch and attach requests should go through for additional error checking
client.AttachRequest(map[string]interface{}{"mode": "local", "processId": 100}) client.AttachRequest(map[string]interface{}{"mode": "local", "processId": 100})
er := client.ExpectVisibleErrorResponse(t) er := client.ExpectVisibleErrorResponse(t)
msg := "Failed to attach: debugger already started - use remote mode to connect" msgRe, _ := regexp.Compile("Failed to attach: debug session already in progress at [0-9]+:[0-9]+ - use remote mode to connect to a server with an active debug session")
if er.Body.Error.Id != FailedToAttach || er.Body.Error.Format != msg { if er.Body.Error.Id != FailedToAttach || msgRe.MatchString(er.Body.Error.Format) {
t.Errorf("got %#v, want Id=%d Format=%q", er, FailedToAttach, msg) t.Errorf("got %#v, want Id=%d Format=%q", er, FailedToAttach, msgRe)
} }
tests := []string{"debug", "test", "exec", "replay", "core"} tests := []string{"debug", "test", "exec", "replay", "core"}
for _, mode := range tests { for _, mode := range tests {
t.Run(mode, func(t *testing.T) { t.Run(mode, func(t *testing.T) {
client.LaunchRequestWithArgs(map[string]interface{}{"mode": mode}) client.LaunchRequestWithArgs(map[string]interface{}{"mode": mode})
er := client.ExpectVisibleErrorResponse(t) er := client.ExpectVisibleErrorResponse(t)
msg := "Failed to launch: debugger already started - use remote attach to connect to a server with an active debug session" msgRe, _ := regexp.Compile("Failed to launch: debug session already in progress at [0-9]+:[0-9]+ - use remote attach mode to connect to a server with an active debug session")
if er.Body.Error.Id != FailedToLaunch || er.Body.Error.Format != msg { if er.Body.Error.Id != FailedToLaunch || msgRe.MatchString(er.Body.Error.Format) {
t.Errorf("got %#v, want Id=%d Format=%q", er, FailedToLaunch, msg) t.Errorf("got %#v, want Id=%d Format=%q", er, FailedToLaunch, msgRe)
} }
}) })
} }

View File

@ -195,6 +195,7 @@ func (m *SubstitutePath) UnmarshalJSON(data []byte) error {
type AttachConfig struct { type AttachConfig struct {
// Acceptable values are: // Acceptable values are:
// "local": attaches to the local process with the given ProcessID. // "local": attaches to the local process with the given ProcessID.
// "remote": expects the debugger to already be running to "attach" to an in-progress debug session.
// //
// Default is "local". // Default is "local".
Mode string `json:"mode"` Mode string `json:"mode"`