mirror of
				https://github.com/go-delve/delve.git
				synced 2025-10-31 10:47:27 +08:00 
			
		
		
		
	service/dap: fix noDebug mode to handle requests while running (#2658)
Co-authored-by: Polina Sokolova <polinasok@users.noreply.github.com>
This commit is contained in:
		| @ -156,6 +156,11 @@ func (c *Client) ExpectOutputEventDetachingNoKill(t *testing.T) *dap.OutputEvent | |||||||
| 	return c.ExpectOutputEventRegex(t, `Detaching without terminating target process\n`) | 	return c.ExpectOutputEventRegex(t, `Detaching without terminating target process\n`) | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func (c *Client) ExpectOutputEventTerminating(t *testing.T) *dap.OutputEvent { | ||||||
|  | 	t.Helper() | ||||||
|  | 	return c.ExpectOutputEventRegex(t, `Terminating process [0-9]+\n`) | ||||||
|  | } | ||||||
|  |  | ||||||
| // InitializeRequest sends an 'initialize' request. | // InitializeRequest sends an 'initialize' request. | ||||||
| func (c *Client) InitializeRequest() { | func (c *Client) InitializeRequest() { | ||||||
| 	request := &dap.InitializeRequest{Request: *c.newRequest("initialize")} | 	request := &dap.InitializeRequest{Request: *c.newRequest("initialize")} | ||||||
|  | |||||||
| @ -25,6 +25,7 @@ const ( | |||||||
| 	UnableToGetExceptionInfo   = 2011 | 	UnableToGetExceptionInfo   = 2011 | ||||||
| 	UnableToSetVariable        = 2012 | 	UnableToSetVariable        = 2012 | ||||||
| 	// Add more codes as we support more requests | 	// Add more codes as we support more requests | ||||||
|  | 	NoDebugIsRunning  = 3000 | ||||||
| 	DebuggeeIsRunning = 4000 | 	DebuggeeIsRunning = 4000 | ||||||
| 	DisconnectError   = 5000 | 	DisconnectError   = 5000 | ||||||
| ) | ) | ||||||
|  | |||||||
| @ -411,6 +411,19 @@ func (s *Server) handleRequest(request dap.Message) { | |||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	if s.isNoDebug() { | ||||||
|  | 		switch request := request.(type) { | ||||||
|  | 		case *dap.DisconnectRequest: | ||||||
|  | 			s.onDisconnectRequest(request) | ||||||
|  | 		case *dap.RestartRequest: | ||||||
|  | 			s.sendUnsupportedErrorResponse(request.Request) | ||||||
|  | 		default: | ||||||
|  | 			r := request.(dap.RequestMessage).GetRequest() | ||||||
|  | 			s.sendErrorResponse(*r, NoDebugIsRunning, "noDebug mode", fmt.Sprintf("unable to process '%s' request", r.Command)) | ||||||
|  | 		} | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	// These requests, can be handled regardless of whether the targret is running | 	// These requests, can be handled regardless of whether the targret is running | ||||||
| 	switch request := request.(type) { | 	switch request := request.(type) { | ||||||
| 	case *dap.DisconnectRequest: | 	case *dap.DisconnectRequest: | ||||||
| @ -926,7 +939,7 @@ func (s *Server) onLaunchRequest(request *dap.LaunchRequest) { | |||||||
| 	s.log.Debugf("running program in %s\n", s.config.Debugger.WorkingDir) | 	s.log.Debugf("running program in %s\n", s.config.Debugger.WorkingDir) | ||||||
| 	if noDebug, ok := request.Arguments["noDebug"].(bool); ok && noDebug { | 	if noDebug, ok := request.Arguments["noDebug"].(bool); ok && noDebug { | ||||||
| 		s.mu.Lock() | 		s.mu.Lock() | ||||||
| 		cmd, err := s.startNoDebugProcess(program, targetArgs, s.config.Debugger.WorkingDir) | 		cmd, err := s.newNoDebugProcess(program, targetArgs, s.config.Debugger.WorkingDir) | ||||||
| 		s.mu.Unlock() | 		s.mu.Unlock() | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			s.sendErrorResponse(request.Request, FailedToLaunch, "Failed to launch", err.Error()) | 			s.sendErrorResponse(request.Request, FailedToLaunch, "Failed to launch", err.Error()) | ||||||
| @ -936,20 +949,22 @@ func (s *Server) onLaunchRequest(request *dap.LaunchRequest) { | |||||||
| 		// debug-related requests. | 		// debug-related requests. | ||||||
| 		s.send(&dap.LaunchResponse{Response: *newResponse(request.Request)}) | 		s.send(&dap.LaunchResponse{Response: *newResponse(request.Request)}) | ||||||
|  |  | ||||||
| 		// Then, block until the program terminates or is stopped. | 		// Start the program on a different goroutine, so we can listen for disconnect request. | ||||||
| 		if err := cmd.Wait(); err != nil { | 		go func() { | ||||||
| 			s.log.Debugf("program exited with error: %v", err) | 			if err := cmd.Wait(); err != nil { | ||||||
| 		} | 				s.log.Debugf("program exited with error: %v", err) | ||||||
| 		stopped := false | 			} | ||||||
| 		s.mu.Lock() | 			stopped := false | ||||||
| 		stopped = s.noDebugProcess == nil // if it was stopped, this should be nil. | 			s.mu.Lock() | ||||||
| 		s.noDebugProcess = nil | 			stopped = s.noDebugProcess == nil // if it was stopped, this should be nil | ||||||
| 		s.mu.Unlock() | 			s.noDebugProcess = nil | ||||||
|  | 			s.mu.Unlock() | ||||||
|  |  | ||||||
| 		if !stopped { | 			if !stopped { // process terminated on its own | ||||||
| 			s.logToConsole(proc.ErrProcessExited{Pid: cmd.ProcessState.Pid(), Status: cmd.ProcessState.ExitCode()}.Error()) | 				s.logToConsole(proc.ErrProcessExited{Pid: cmd.ProcessState.Pid(), Status: cmd.ProcessState.ExitCode()}.Error()) | ||||||
| 			s.send(&dap.TerminatedEvent{Event: *newEvent("terminated")}) | 				s.send(&dap.TerminatedEvent{Event: *newEvent("terminated")}) | ||||||
| 		} | 			} | ||||||
|  | 		}() | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| @ -974,9 +989,9 @@ func (s *Server) onLaunchRequest(request *dap.LaunchRequest) { | |||||||
| 	s.send(&dap.LaunchResponse{Response: *newResponse(request.Request)}) | 	s.send(&dap.LaunchResponse{Response: *newResponse(request.Request)}) | ||||||
| } | } | ||||||
|  |  | ||||||
| // startNoDebugProcess is called from onLaunchRequest (run goroutine) and | // newNoDebugProcess is called from onLaunchRequest (run goroutine) and | ||||||
| // requires holding mu lock. | // requires holding mu lock. It prepares process exec.Cmd to be started. | ||||||
| func (s *Server) startNoDebugProcess(program string, targetArgs []string, wd string) (*exec.Cmd, error) { | func (s *Server) newNoDebugProcess(program string, targetArgs []string, wd string) (*exec.Cmd, error) { | ||||||
| 	if s.noDebugProcess != nil { | 	if s.noDebugProcess != nil { | ||||||
| 		return nil, fmt.Errorf("another launch request is in progress") | 		return nil, fmt.Errorf("another launch request is in progress") | ||||||
| 	} | 	} | ||||||
| @ -996,7 +1011,7 @@ func (s *Server) stopNoDebugProcess() { | |||||||
| 		// We already handled termination or there was never a process | 		// We already handled termination or there was never a process | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| 	if s.noDebugProcess.ProcessState.Exited() { | 	if s.noDebugProcess.ProcessState != nil && s.noDebugProcess.ProcessState.Exited() { | ||||||
| 		s.logToConsole(proc.ErrProcessExited{Pid: s.noDebugProcess.ProcessState.Pid(), Status: s.noDebugProcess.ProcessState.ExitCode()}.Error()) | 		s.logToConsole(proc.ErrProcessExited{Pid: s.noDebugProcess.ProcessState.Pid(), Status: s.noDebugProcess.ProcessState.ExitCode()}.Error()) | ||||||
| 	} else { | 	} else { | ||||||
| 		// TODO(hyangah): gracefully terminate the process and its children processes. | 		// TODO(hyangah): gracefully terminate the process and its children processes. | ||||||
| @ -1133,11 +1148,6 @@ func (s *Server) isNoDebug() bool { | |||||||
| } | } | ||||||
|  |  | ||||||
| func (s *Server) onSetBreakpointsRequest(request *dap.SetBreakpointsRequest) { | func (s *Server) onSetBreakpointsRequest(request *dap.SetBreakpointsRequest) { | ||||||
| 	if s.isNoDebug() { |  | ||||||
| 		s.sendErrorResponse(request.Request, UnableToSetBreakpoints, "Unable to set or clear breakpoints", "running in noDebug mode") |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	if request.Arguments.Source.Path == "" { | 	if request.Arguments.Source.Path == "" { | ||||||
| 		s.sendErrorResponse(request.Request, UnableToSetBreakpoints, "Unable to set or clear breakpoints", "empty file path") | 		s.sendErrorResponse(request.Request, UnableToSetBreakpoints, "Unable to set or clear breakpoints", "empty file path") | ||||||
| 		return | 		return | ||||||
| @ -1234,11 +1244,6 @@ func updateBreakpointsResponse(breakpoints []dap.Breakpoint, i int, err error, g | |||||||
| const functionBpPrefix = "functionBreakpoint" | const functionBpPrefix = "functionBreakpoint" | ||||||
|  |  | ||||||
| func (s *Server) onSetFunctionBreakpointsRequest(request *dap.SetFunctionBreakpointsRequest) { | func (s *Server) onSetFunctionBreakpointsRequest(request *dap.SetFunctionBreakpointsRequest) { | ||||||
| 	if s.noDebugProcess != nil { |  | ||||||
| 		s.sendErrorResponse(request.Request, UnableToSetBreakpoints, "Unable to set or clear breakpoints", "running in noDebug mode") |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	// According to the spec, setFunctionBreakpoints "replaces all existing function | 	// According to the spec, setFunctionBreakpoints "replaces all existing function | ||||||
| 	// breakpoints with new function breakpoints." The simplest way is | 	// breakpoints with new function breakpoints." The simplest way is | ||||||
| 	// to clear all and then set all. To maintain state (for hit count conditions) | 	// to clear all and then set all. To maintain state (for hit count conditions) | ||||||
|  | |||||||
| @ -4125,48 +4125,74 @@ func TestLaunchRequestDefaults(t *testing.T) { | |||||||
| 	}) | 	}) | ||||||
| } | } | ||||||
|  |  | ||||||
| func TestLaunchRequestNoDebug_GoodStatus(t *testing.T) { | func TestNoDebug_GoodExitStatus(t *testing.T) { | ||||||
| 	runTest(t, "increment", func(client *daptest.Client, fixture protest.Fixture) { | 	runTest(t, "increment", func(client *daptest.Client, fixture protest.Fixture) { | ||||||
| 		runNoDebugDebugSession(t, client, func() { | 		runNoDebugSession(t, client, func() { | ||||||
| 			client.LaunchRequestWithArgs(map[string]interface{}{ | 			client.LaunchRequestWithArgs(map[string]interface{}{ | ||||||
| 				"noDebug": true, | 				"noDebug": true, "mode": "debug", "program": fixture.Source, "output": "__mybin"}) | ||||||
| 				"mode":    "debug", | 		}, 0) | ||||||
| 				"program": fixture.Source, |  | ||||||
| 				"output":  "__mybin"}) |  | ||||||
| 		}, fixture.Source, []int{8}, 0) |  | ||||||
| 	}) | 	}) | ||||||
| } | } | ||||||
|  |  | ||||||
| func TestLaunchRequestNoDebug_BadStatus(t *testing.T) { | func TestNoDebug_BadExitStatus(t *testing.T) { | ||||||
| 	runTest(t, "issue1101", func(client *daptest.Client, fixture protest.Fixture) { | 	runTest(t, "issue1101", func(client *daptest.Client, fixture protest.Fixture) { | ||||||
| 		runNoDebugDebugSession(t, client, func() { | 		runNoDebugSession(t, client, func() { | ||||||
| 			client.LaunchRequestWithArgs(map[string]interface{}{ | 			client.LaunchRequestWithArgs(map[string]interface{}{ | ||||||
| 				"noDebug": true, | 				"noDebug": true, "mode": "exec", "program": fixture.Path}) | ||||||
| 				"mode":    "debug", | 		}, 2) | ||||||
| 				"program": fixture.Source, |  | ||||||
| 				"output":  "__mybin"}) |  | ||||||
| 		}, fixture.Source, []int{8}, 2) |  | ||||||
| 	}) | 	}) | ||||||
| } | } | ||||||
|  |  | ||||||
| // runNoDebugDebugSession tests the session started with noDebug=true runs uninterrupted | // runNoDebugSession tests the session started with noDebug=true runs | ||||||
| // even when breakpoint is set. | // to completion and logs termination status. | ||||||
| func runNoDebugDebugSession(t *testing.T, client *daptest.Client, cmdRequest func(), source string, breakpoints []int, status int) { | func runNoDebugSession(t *testing.T, client *daptest.Client, launchRequest func(), exitStatus int) { | ||||||
| 	client.InitializeRequest() | 	client.InitializeRequest() | ||||||
| 	client.ExpectInitializeResponseAndCapabilities(t) | 	client.ExpectInitializeResponseAndCapabilities(t) | ||||||
|  |  | ||||||
| 	cmdRequest() | 	launchRequest() | ||||||
| 	// no initialized event. | 	// no initialized event. | ||||||
| 	// noDebug mode applies only to "launch" requests. | 	// noDebug mode applies only to "launch" requests. | ||||||
| 	client.ExpectLaunchResponse(t) | 	client.ExpectLaunchResponse(t) | ||||||
|  |  | ||||||
| 	client.ExpectOutputEventProcessExited(t, status) | 	client.ExpectOutputEventProcessExited(t, exitStatus) | ||||||
| 	client.ExpectTerminatedEvent(t) | 	client.ExpectTerminatedEvent(t) | ||||||
| 	client.DisconnectRequestWithKillOption(true) | 	client.DisconnectRequestWithKillOption(true) | ||||||
| 	client.ExpectDisconnectResponse(t) | 	client.ExpectDisconnectResponse(t) | ||||||
| 	client.ExpectTerminatedEvent(t) | 	client.ExpectTerminatedEvent(t) | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func TestNoDebug_AcceptNoRequestsButDisconnect(t *testing.T) { | ||||||
|  | 	runTest(t, "http_server", func(client *daptest.Client, fixture protest.Fixture) { | ||||||
|  | 		client.InitializeRequest() | ||||||
|  | 		client.ExpectInitializeResponseAndCapabilities(t) | ||||||
|  | 		client.LaunchRequestWithArgs(map[string]interface{}{ | ||||||
|  | 			"noDebug": true, "mode": "exec", "program": fixture.Path}) | ||||||
|  | 		client.ExpectLaunchResponse(t) | ||||||
|  |  | ||||||
|  | 		// Anything other than disconnect should get rejected | ||||||
|  | 		var ExpectNoDebugError = func(cmd string) { | ||||||
|  | 			er := client.ExpectErrorResponse(t) | ||||||
|  | 			if er.Body.Error.Format != fmt.Sprintf("noDebug mode: unable to process '%s' request", cmd) { | ||||||
|  | 				t.Errorf("\ngot %#v\nwant 'noDebug mode: unable to process '%s' request'", er, cmd) | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		client.SetBreakpointsRequest(fixture.Source, []int{8}) | ||||||
|  | 		ExpectNoDebugError("setBreakpoints") | ||||||
|  | 		client.SetFunctionBreakpointsRequest(nil) | ||||||
|  | 		ExpectNoDebugError("setFunctionBreakpoints") | ||||||
|  | 		client.PauseRequest(1) | ||||||
|  | 		ExpectNoDebugError("pause") | ||||||
|  | 		client.RestartRequest() | ||||||
|  | 		client.ExpectUnsupportedCommandErrorResponse(t) | ||||||
|  |  | ||||||
|  | 		// Disconnect request is ok | ||||||
|  | 		client.DisconnectRequestWithKillOption(true) | ||||||
|  | 		client.ExpectOutputEventTerminating(t) | ||||||
|  | 		client.ExpectDisconnectResponse(t) | ||||||
|  | 		client.ExpectTerminatedEvent(t) | ||||||
|  | 	}) | ||||||
|  | } | ||||||
|  |  | ||||||
| func TestLaunchTestRequest(t *testing.T) { | func TestLaunchTestRequest(t *testing.T) { | ||||||
| 	runTest(t, "increment", func(client *daptest.Client, fixture protest.Fixture) { | 	runTest(t, "increment", func(client *daptest.Client, fixture protest.Fixture) { | ||||||
| 		runDebugSession(t, client, "launch", func() { | 		runDebugSession(t, client, "launch", func() { | ||||||
|  | |||||||
		Reference in New Issue
	
	Block a user
	 polinasok
					polinasok