mirror of
				https://github.com/go-delve/delve.git
				synced 2025-10-31 02:36:18 +08:00 
			
		
		
		
	 f8c8b33da3
			
		
	
	f8c8b33da3
	
	
	
		
			
			* Add pprofLabelForThreadNames config The config is a string value that indicates the key of a pprof label whose value should be shown as a goroutine name in the threads view.
		
			
				
	
	
		
			7623 lines
		
	
	
		
			293 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			7623 lines
		
	
	
		
			293 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package dap
 | |
| 
 | |
| import (
 | |
| 	"bufio"
 | |
| 	"bytes"
 | |
| 	"flag"
 | |
| 	"fmt"
 | |
| 	"io"
 | |
| 	"math"
 | |
| 	"math/rand"
 | |
| 	"net"
 | |
| 	"os"
 | |
| 	"os/exec"
 | |
| 	"path/filepath"
 | |
| 	"reflect"
 | |
| 	"regexp"
 | |
| 	"runtime"
 | |
| 	"strconv"
 | |
| 	"strings"
 | |
| 	"testing"
 | |
| 	"time"
 | |
| 
 | |
| 	"github.com/go-delve/delve/pkg/goversion"
 | |
| 	"github.com/go-delve/delve/pkg/logflags"
 | |
| 	"github.com/go-delve/delve/pkg/proc"
 | |
| 	protest "github.com/go-delve/delve/pkg/proc/test"
 | |
| 	"github.com/go-delve/delve/service"
 | |
| 	"github.com/go-delve/delve/service/api"
 | |
| 	"github.com/go-delve/delve/service/dap/daptest"
 | |
| 	"github.com/go-delve/delve/service/debugger"
 | |
| 	"github.com/google/go-dap"
 | |
| )
 | |
| 
 | |
| const (
 | |
| 	stopOnEntry bool = true
 | |
| 	hasChildren bool = true
 | |
| 	noChildren  bool = false
 | |
| )
 | |
| 
 | |
| const (
 | |
| 	localsScope  = 1000
 | |
| 	globalsScope = 1001
 | |
| )
 | |
| 
 | |
| var testBackend string
 | |
| 
 | |
| func TestMain(m *testing.M) {
 | |
| 	logOutputVal := ""
 | |
| 	if _, isTeamCityTest := os.LookupEnv("TEAMCITY_VERSION"); isTeamCityTest {
 | |
| 		logOutputVal = "debugger,dap"
 | |
| 	}
 | |
| 	var logOutput string
 | |
| 	flag.StringVar(&logOutput, "log-output", logOutputVal, "configures log output")
 | |
| 	flag.Parse()
 | |
| 	logflags.Setup(logOutput != "", logOutput, "")
 | |
| 	protest.DefaultTestBackend(&testBackend)
 | |
| 	os.Exit(protest.RunTestsWithFixtures(m))
 | |
| }
 | |
| 
 | |
| // name is for _fixtures/<name>.go
 | |
| func runTest(t *testing.T, name string, test func(c *daptest.Client, f protest.Fixture)) {
 | |
| 	runTestBuildFlags(t, name, test, protest.AllNonOptimized, false)
 | |
| }
 | |
| 
 | |
| // name is for _fixtures/<name>.go
 | |
| func runTestBuildFlags(t *testing.T, name string, test func(c *daptest.Client, f protest.Fixture), buildFlags protest.BuildFlags, defaultDebugInfoDirs bool) {
 | |
| 	fixture := protest.BuildFixture(name, buildFlags)
 | |
| 
 | |
| 	// Start the DAP server.
 | |
| 	serverStopped := make(chan struct{})
 | |
| 	client := startDAPServerWithClient(t, defaultDebugInfoDirs, serverStopped)
 | |
| 	defer client.Close()
 | |
| 
 | |
| 	test(client, fixture)
 | |
| 	<-serverStopped
 | |
| }
 | |
| 
 | |
| func startDAPServerWithClient(t *testing.T, defaultDebugInfoDirs bool, serverStopped chan struct{}) *daptest.Client {
 | |
| 	server, _ := startDAPServer(t, defaultDebugInfoDirs, serverStopped)
 | |
| 	client := daptest.NewClient(server.config.Listener.Addr().String())
 | |
| 	return client
 | |
| }
 | |
| 
 | |
| // Starts an empty server and a stripped down config just to establish a client connection.
 | |
| // To mock a server created by dap.NewServer(config) or serving dap.NewSession(conn, config, debugger)
 | |
| // set those arg fields manually after the server creation.
 | |
| func startDAPServer(t *testing.T, defaultDebugInfoDirs bool, serverStopped chan struct{}) (server *Server, forceStop chan struct{}) {
 | |
| 	// Start the DAP server.
 | |
| 	listener, err := net.Listen("tcp", ":0")
 | |
| 	if err != nil {
 | |
| 		t.Fatal(err)
 | |
| 	}
 | |
| 	debugInfoDirs := []string{}
 | |
| 	if defaultDebugInfoDirs {
 | |
| 		debugInfoDirs = []string{"/usr/lib/debug/.build-id"}
 | |
| 	}
 | |
| 	disconnectChan := make(chan struct{})
 | |
| 	server = NewServer(&service.Config{
 | |
| 		Listener:       listener,
 | |
| 		DisconnectChan: disconnectChan,
 | |
| 		Debugger:       debugger.Config{DebugInfoDirectories: debugInfoDirs},
 | |
| 	})
 | |
| 	server.Run()
 | |
| 	// Give server time to start listening for clients
 | |
| 	time.Sleep(100 * time.Millisecond)
 | |
| 
 | |
| 	// Run a goroutine that stops the server when disconnectChan is signaled.
 | |
| 	// This helps us test that certain events cause the server to stop as
 | |
| 	// expected.
 | |
| 	forceStop = make(chan struct{})
 | |
| 	go func() {
 | |
| 		defer func() {
 | |
| 			if serverStopped != nil {
 | |
| 				close(serverStopped)
 | |
| 			}
 | |
| 		}()
 | |
| 		select {
 | |
| 		case <-disconnectChan:
 | |
| 			t.Log("server stop triggered internally")
 | |
| 		case <-forceStop:
 | |
| 			t.Log("server stop triggered externally")
 | |
| 		}
 | |
| 		server.Stop()
 | |
| 	}()
 | |
| 
 | |
| 	return server, forceStop
 | |
| }
 | |
| 
 | |
| func verifyServerStopped(t *testing.T, server *Server) {
 | |
| 	t.Helper()
 | |
| 	if server.listener != nil {
 | |
| 		if server.listener.Close() == nil {
 | |
| 			t.Error("server should have closed listener after shutdown")
 | |
| 		}
 | |
| 	}
 | |
| 	verifySessionStopped(t, server.session)
 | |
| }
 | |
| 
 | |
| func verifySessionStopped(t *testing.T, session *Session) {
 | |
| 	t.Helper()
 | |
| 	if session == nil {
 | |
| 		return
 | |
| 	}
 | |
| 	if session.conn == nil {
 | |
| 		t.Error("session must always have a set connection")
 | |
| 	}
 | |
| 	verifyConnStopped(t, session.conn)
 | |
| 	if session.debugger != nil {
 | |
| 		t.Error("session should have no pointer to debugger after shutdown")
 | |
| 	}
 | |
| 	if session.binaryToRemove != "" {
 | |
| 		t.Error("session should have no binary to remove after shutdown")
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func verifyConnStopped(t *testing.T, conn io.ReadWriteCloser) {
 | |
| 	t.Helper()
 | |
| 	if conn.Close() == nil {
 | |
| 		t.Error("client connection should be closed after shutdown")
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func TestStopNoClient(t *testing.T) {
 | |
| 	for name, triggerStop := range map[string]func(s *Server, forceStop chan struct{}){
 | |
| 		"force":        func(s *Server, forceStop chan struct{}) { close(forceStop) },
 | |
| 		"accept error": func(s *Server, forceStop chan struct{}) { s.config.Listener.Close() },
 | |
| 	} {
 | |
| 		t.Run(name, func(t *testing.T) {
 | |
| 			serverStopped := make(chan struct{})
 | |
| 			server, forceStop := startDAPServer(t, false, serverStopped)
 | |
| 			triggerStop(server, forceStop)
 | |
| 			<-serverStopped
 | |
| 			verifyServerStopped(t, server)
 | |
| 		})
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func TestStopNoTarget(t *testing.T) {
 | |
| 	for name, triggerStop := range map[string]func(c *daptest.Client, forceStop chan struct{}){
 | |
| 		"force":      func(c *daptest.Client, forceStop chan struct{}) { close(forceStop) },
 | |
| 		"read error": func(c *daptest.Client, forceStop chan struct{}) { c.Close() },
 | |
| 		"disconnect": func(c *daptest.Client, forceStop chan struct{}) { c.DisconnectRequest() },
 | |
| 	} {
 | |
| 		t.Run(name, func(t *testing.T) {
 | |
| 			serverStopped := make(chan struct{})
 | |
| 			server, forceStop := startDAPServer(t, false, serverStopped)
 | |
| 			client := daptest.NewClient(server.config.Listener.Addr().String())
 | |
| 			defer client.Close()
 | |
| 
 | |
| 			client.InitializeRequest()
 | |
| 			client.ExpectInitializeResponseAndCapabilities(t)
 | |
| 			triggerStop(client, forceStop)
 | |
| 			<-serverStopped
 | |
| 			verifyServerStopped(t, server)
 | |
| 		})
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func TestStopWithTarget(t *testing.T) {
 | |
| 	for name, triggerStop := range map[string]func(c *daptest.Client, forceStop chan struct{}){
 | |
| 		"force":                  func(c *daptest.Client, forceStop chan struct{}) { close(forceStop) },
 | |
| 		"read error":             func(c *daptest.Client, forceStop chan struct{}) { c.Close() },
 | |
| 		"disconnect before exit": func(c *daptest.Client, forceStop chan struct{}) { c.DisconnectRequest() },
 | |
| 		"disconnect after  exit": func(c *daptest.Client, forceStop chan struct{}) {
 | |
| 			c.ContinueRequest(1)
 | |
| 			c.ExpectContinueResponse(t)
 | |
| 			c.ExpectTerminatedEvent(t)
 | |
| 			c.DisconnectRequest()
 | |
| 		},
 | |
| 	} {
 | |
| 		t.Run(name, func(t *testing.T) {
 | |
| 			serverStopped := make(chan struct{})
 | |
| 			server, forceStop := startDAPServer(t, false, serverStopped)
 | |
| 			client := daptest.NewClient(server.config.Listener.Addr().String())
 | |
| 			defer client.Close()
 | |
| 
 | |
| 			client.InitializeRequest()
 | |
| 			client.ExpectInitializeResponseAndCapabilities(t)
 | |
| 			fixture := protest.BuildFixture("increment", protest.AllNonOptimized)
 | |
| 			client.LaunchRequest("debug", fixture.Source, stopOnEntry)
 | |
| 			client.ExpectInitializedEvent(t)
 | |
| 			client.ExpectLaunchResponse(t)
 | |
| 			triggerStop(client, forceStop)
 | |
| 			<-serverStopped
 | |
| 			verifyServerStopped(t, server)
 | |
| 		})
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func TestSessionStop(t *testing.T) {
 | |
| 	verifySessionState := func(t *testing.T, s *Session, binaryToRemoveSet bool, debuggerSet bool, disconnectChanSet bool) {
 | |
| 		t.Helper()
 | |
| 		if binaryToRemoveSet && s.binaryToRemove == "" || !binaryToRemoveSet && s.binaryToRemove != "" {
 | |
| 			t.Errorf("binaryToRemove: got %s, want set=%v", s.binaryToRemove, binaryToRemoveSet)
 | |
| 		}
 | |
| 		if debuggerSet && s.debugger == nil || !debuggerSet && s.debugger != nil {
 | |
| 			t.Errorf("debugger: got %v, want set=%v", s.debugger, debuggerSet)
 | |
| 		}
 | |
| 		if disconnectChanSet && s.config.DisconnectChan == nil || !disconnectChanSet && s.config.DisconnectChan != nil {
 | |
| 			t.Errorf("disconnectChan: got %v, want set=%v", s.config.DisconnectChan, disconnectChanSet)
 | |
| 		}
 | |
| 	}
 | |
| 	for name, stopSession := range map[string]func(s *Session, c *daptest.Client, serveDone chan struct{}){
 | |
| 		"force": func(s *Session, c *daptest.Client, serveDone chan struct{}) {
 | |
| 			s.Close()
 | |
| 			<-serveDone
 | |
| 			verifySessionState(t, s, false /*binaryToRemoveSet*/, false /*debuggerSet*/, false /*disconnectChanSet*/)
 | |
| 		},
 | |
| 		"read error": func(s *Session, c *daptest.Client, serveDone chan struct{}) {
 | |
| 			c.Close()
 | |
| 			<-serveDone
 | |
| 			verifyConnStopped(t, s.conn)
 | |
| 			verifySessionState(t, s, true /*binaryToRemoveSet*/, true /*debuggerSet*/, false /*disconnectChanSet*/)
 | |
| 			s.Close()
 | |
| 		},
 | |
| 		"disconnect before exit": func(s *Session, c *daptest.Client, serveDone chan struct{}) {
 | |
| 			c.DisconnectRequest()
 | |
| 			<-serveDone
 | |
| 			verifyConnStopped(t, s.conn)
 | |
| 			verifySessionState(t, s, true /*binaryToRemoveSet*/, false /*debuggerSet*/, false /*disconnectChanSet*/)
 | |
| 			s.Close()
 | |
| 		},
 | |
| 		"disconnect after exit": func(s *Session, c *daptest.Client, serveDone chan struct{}) {
 | |
| 			c.ContinueRequest(1)
 | |
| 			c.ExpectContinueResponse(t)
 | |
| 			c.ExpectTerminatedEvent(t)
 | |
| 			c.DisconnectRequest()
 | |
| 			<-serveDone
 | |
| 			verifyConnStopped(t, s.conn)
 | |
| 			verifySessionState(t, s, true /*binaryToRemoveSet*/, false /*debuggerSet*/, false /*disconnectChanSet*/)
 | |
| 			s.Close()
 | |
| 		},
 | |
| 	} {
 | |
| 		t.Run(name, func(t *testing.T) {
 | |
| 			listener, err := net.Listen("tcp", ":0")
 | |
| 			if err != nil {
 | |
| 				t.Fatalf("cannot setup listener required for testing: %v", err)
 | |
| 			}
 | |
| 			defer listener.Close()
 | |
| 			acceptDone := make(chan struct{})
 | |
| 			var conn net.Conn
 | |
| 			go func() {
 | |
| 				conn, err = listener.Accept()
 | |
| 				close(acceptDone)
 | |
| 			}()
 | |
| 			time.Sleep(10 * time.Millisecond) // give time to start listening
 | |
| 			client := daptest.NewClient(listener.Addr().String())
 | |
| 			defer client.Close()
 | |
| 			<-acceptDone
 | |
| 			if err != nil {
 | |
| 				t.Fatalf("cannot accept client required for testing: %v", err)
 | |
| 			}
 | |
| 			session := NewSession(conn, &Config{
 | |
| 				Config:        &service.Config{DisconnectChan: make(chan struct{})},
 | |
| 				StopTriggered: make(chan struct{}),
 | |
| 			}, nil)
 | |
| 			serveDAPCodecDone := make(chan struct{})
 | |
| 			go func() {
 | |
| 				session.ServeDAPCodec()
 | |
| 				close(serveDAPCodecDone)
 | |
| 			}()
 | |
| 			time.Sleep(10 * time.Millisecond) // give time to start reading
 | |
| 			client.InitializeRequest()
 | |
| 			client.ExpectInitializeResponseAndCapabilities(t)
 | |
| 			fixture := protest.BuildFixture("increment", protest.AllNonOptimized)
 | |
| 			client.LaunchRequest("debug", fixture.Source, stopOnEntry)
 | |
| 			client.ExpectInitializedEvent(t)
 | |
| 			client.ExpectLaunchResponse(t)
 | |
| 			stopSession(session, client, serveDAPCodecDone)
 | |
| 			verifySessionStopped(t, session)
 | |
| 		})
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func TestForceStopWhileStopping(t *testing.T) {
 | |
| 	serverStopped := make(chan struct{})
 | |
| 	server, forceStop := startDAPServer(t, false, serverStopped)
 | |
| 	client := daptest.NewClient(server.config.Listener.Addr().String())
 | |
| 
 | |
| 	client.InitializeRequest()
 | |
| 	client.ExpectInitializeResponseAndCapabilities(t)
 | |
| 	fixture := protest.BuildFixture("increment", protest.AllNonOptimized)
 | |
| 	client.LaunchRequest("exec", fixture.Path, stopOnEntry)
 | |
| 	client.ExpectInitializedEvent(t)
 | |
| 	client.Close() // depending on timing may trigger Stop()
 | |
| 	time.Sleep(time.Microsecond)
 | |
| 	close(forceStop) // depending on timing may trigger Stop()
 | |
| 	<-serverStopped
 | |
| 	verifyServerStopped(t, server)
 | |
| }
 | |
| 
 | |
| // TestLaunchStopOnEntry emulates the message exchange that can be observed with
 | |
| // VS Code for the most basic launch debug session with "stopOnEntry" enabled:
 | |
| //
 | |
| //	User selects "Start Debugging":  1 >> initialize
 | |
| //	                              :  1 << initialize
 | |
| //	                              :  2 >> launch
 | |
| //	                              :    << initialized event
 | |
| //	                              :  2 << launch
 | |
| //	                              :  3 >> setBreakpoints (empty)
 | |
| //	                              :  3 << setBreakpoints
 | |
| //	                              :  4 >> setExceptionBreakpoints (empty)
 | |
| //	                              :  4 << setExceptionBreakpoints
 | |
| //	                              :  5 >> configurationDone
 | |
| //	Program stops upon launching  :    << stopped event
 | |
| //	                              :  5 << configurationDone
 | |
| //	                              :  6 >> threads
 | |
| //	                              :  6 << threads (Dummy)
 | |
| //	                              :  7 >> threads
 | |
| //	                              :  7 << threads (Dummy)
 | |
| //	                              :  8 >> stackTrace
 | |
| //	                              :  8 << error (Unable to produce stack trace)
 | |
| //	                              :  9 >> stackTrace
 | |
| //	                              :  9 << error (Unable to produce stack trace)
 | |
| //	User evaluates bad expression : 10 >> evaluate
 | |
| //	                              : 10 << error (unable to find function context)
 | |
| //	User evaluates good expression: 11 >> evaluate
 | |
| //	                              : 11 << evaluate
 | |
| //	User selects "Continue"       : 12 >> continue
 | |
| //	                              : 12 << continue
 | |
| //	Program runs to completion    :    << terminated event
 | |
| //	                              : 13 >> disconnect
 | |
| //	                              :    << output event (Process exited)
 | |
| //	                              :    << output event (Detaching)
 | |
| //	                              : 13 << disconnect
 | |
| //
 | |
| // This test exhaustively tests Seq and RequestSeq on all messages from the
 | |
| // server. Other tests do not necessarily need to repeat all these checks.
 | |
| func TestLaunchStopOnEntry(t *testing.T) {
 | |
| 	runTest(t, "increment", func(client *daptest.Client, fixture protest.Fixture) {
 | |
| 		// 1 >> initialize, << initialize
 | |
| 		client.InitializeRequest()
 | |
| 		initResp := client.ExpectInitializeResponseAndCapabilities(t)
 | |
| 		if initResp.Seq != 0 || initResp.RequestSeq != 1 {
 | |
| 			t.Errorf("\ngot %#v\nwant Seq=0, RequestSeq=1", initResp)
 | |
| 		}
 | |
| 
 | |
| 		// 2 >> launch, << initialized, << launch
 | |
| 		client.LaunchRequest("exec", fixture.Path, stopOnEntry)
 | |
| 		initEvent := client.ExpectInitializedEvent(t)
 | |
| 		if initEvent.Seq != 0 {
 | |
| 			t.Errorf("\ngot %#v\nwant Seq=0", initEvent)
 | |
| 		}
 | |
| 		launchResp := client.ExpectLaunchResponse(t)
 | |
| 		if launchResp.Seq != 0 || launchResp.RequestSeq != 2 {
 | |
| 			t.Errorf("\ngot %#v\nwant Seq=0, RequestSeq=2", launchResp)
 | |
| 		}
 | |
| 
 | |
| 		// 3 >> setBreakpoints, << setBreakpoints
 | |
| 		client.SetBreakpointsRequest(fixture.Source, nil)
 | |
| 		sbpResp := client.ExpectSetBreakpointsResponse(t)
 | |
| 		if sbpResp.Seq != 0 || sbpResp.RequestSeq != 3 || len(sbpResp.Body.Breakpoints) != 0 {
 | |
| 			t.Errorf("\ngot %#v\nwant Seq=0, RequestSeq=3, len(Breakpoints)=0", sbpResp)
 | |
| 		}
 | |
| 
 | |
| 		// 4 >> setExceptionBreakpoints, << setExceptionBreakpoints
 | |
| 		client.SetExceptionBreakpointsRequest()
 | |
| 		sebpResp := client.ExpectSetExceptionBreakpointsResponse(t)
 | |
| 		if sebpResp.Seq != 0 || sebpResp.RequestSeq != 4 {
 | |
| 			t.Errorf("\ngot %#v\nwant Seq=0, RequestSeq=4", sebpResp)
 | |
| 		}
 | |
| 
 | |
| 		// 5 >> configurationDone, << stopped, << configurationDone
 | |
| 		client.ConfigurationDoneRequest()
 | |
| 		stopEvent := client.ExpectStoppedEvent(t)
 | |
| 		if stopEvent.Seq != 0 ||
 | |
| 			stopEvent.Body.Reason != "entry" ||
 | |
| 			stopEvent.Body.ThreadId != 1 ||
 | |
| 			!stopEvent.Body.AllThreadsStopped {
 | |
| 			t.Errorf("\ngot %#v\nwant Seq=0, Body={Reason=\"entry\", ThreadId=1, AllThreadsStopped=true}", stopEvent)
 | |
| 		}
 | |
| 		cdResp := client.ExpectConfigurationDoneResponse(t)
 | |
| 		if cdResp.Seq != 0 || cdResp.RequestSeq != 5 {
 | |
| 			t.Errorf("\ngot %#v\nwant Seq=0, RequestSeq=5", cdResp)
 | |
| 		}
 | |
| 
 | |
| 		// 6 >> threads, << threads
 | |
| 		client.ThreadsRequest()
 | |
| 		tResp := client.ExpectThreadsResponse(t)
 | |
| 		if tResp.Seq != 0 || tResp.RequestSeq != 6 || len(tResp.Body.Threads) != 1 {
 | |
| 			t.Errorf("\ngot %#v\nwant Seq=0, RequestSeq=6 len(Threads)=1", tResp)
 | |
| 		}
 | |
| 		if tResp.Body.Threads[0].Id != 1 || tResp.Body.Threads[0].Name != "Dummy" {
 | |
| 			t.Errorf("\ngot %#v\nwant Id=1, Name=\"Dummy\"", tResp)
 | |
| 		}
 | |
| 
 | |
| 		// 7 >> threads, << threads
 | |
| 		client.ThreadsRequest()
 | |
| 		tResp = client.ExpectThreadsResponse(t)
 | |
| 		if tResp.Seq != 0 || tResp.RequestSeq != 7 || len(tResp.Body.Threads) != 1 {
 | |
| 			t.Errorf("\ngot %#v\nwant Seq=0, RequestSeq=7 len(Threads)=1", tResp)
 | |
| 		}
 | |
| 
 | |
| 		// 8 >> stackTrace, << error
 | |
| 		client.StackTraceRequest(1, 0, 20)
 | |
| 		stResp := client.ExpectInvisibleErrorResponse(t)
 | |
| 		if stResp.Seq != 0 || stResp.RequestSeq != 8 || !checkErrorMessageFormat(stResp.Body.Error, "Unable to produce stack trace: unknown goroutine 1") {
 | |
| 			t.Errorf("\ngot %#v\nwant Seq=0, RequestSeq=8 Format=\"Unable to produce stack trace: unknown goroutine 1\"", stResp)
 | |
| 		}
 | |
| 
 | |
| 		// 9 >> stackTrace, << error
 | |
| 		client.StackTraceRequest(1, 0, 20)
 | |
| 		stResp = client.ExpectInvisibleErrorResponse(t)
 | |
| 		if stResp.Seq != 0 || stResp.RequestSeq != 9 || !checkErrorMessageId(stResp.Body.Error, UnableToProduceStackTrace) {
 | |
| 			t.Errorf("\ngot %#v\nwant Seq=0, RequestSeq=9 Id=%d", stResp, UnableToProduceStackTrace)
 | |
| 		}
 | |
| 
 | |
| 		// 10 >> evaluate, << error
 | |
| 		client.EvaluateRequest("foo", 0 /*no frame specified*/, "repl")
 | |
| 		erResp := client.ExpectInvisibleErrorResponse(t)
 | |
| 		if erResp.Seq != 0 || erResp.RequestSeq != 10 || !checkErrorMessageId(erResp.Body.Error, UnableToEvaluateExpression) {
 | |
| 			t.Errorf("\ngot %#v\nwant Seq=0, RequestSeq=10 Id=%d", erResp, UnableToEvaluateExpression)
 | |
| 		}
 | |
| 
 | |
| 		// 11 >> evaluate, << evaluate
 | |
| 		client.EvaluateRequest("1+1", 0 /*no frame specified*/, "repl")
 | |
| 		evResp := client.ExpectEvaluateResponse(t)
 | |
| 		if evResp.Seq != 0 || evResp.RequestSeq != 11 || evResp.Body.Result != "2" {
 | |
| 			t.Errorf("\ngot %#v\nwant Seq=0, RequestSeq=10 Result=2", evResp)
 | |
| 		}
 | |
| 
 | |
| 		// 12 >> continue, << continue, << terminated
 | |
| 		client.ContinueRequest(1)
 | |
| 		contResp := client.ExpectContinueResponse(t)
 | |
| 		if contResp.Seq != 0 || contResp.RequestSeq != 12 || !contResp.Body.AllThreadsContinued {
 | |
| 			t.Errorf("\ngot %#v\nwant Seq=0, RequestSeq=12 Body.AllThreadsContinued=true", contResp)
 | |
| 		}
 | |
| 		termEvent := client.ExpectTerminatedEvent(t)
 | |
| 		if termEvent.Seq != 0 {
 | |
| 			t.Errorf("\ngot %#v\nwant Seq=0", termEvent)
 | |
| 		}
 | |
| 
 | |
| 		// 13 >> disconnect, << disconnect
 | |
| 		client.DisconnectRequest()
 | |
| 		oep := client.ExpectOutputEventProcessExited(t, 0)
 | |
| 		if oep.Seq != 0 || oep.Body.Category != "console" {
 | |
| 			t.Errorf("\ngot %#v\nwant Seq=0 Category='console'", oep)
 | |
| 		}
 | |
| 		oed := client.ExpectOutputEventDetaching(t)
 | |
| 		if oed.Seq != 0 || oed.Body.Category != "console" {
 | |
| 			t.Errorf("\ngot %#v\nwant Seq=0 Category='console'", oed)
 | |
| 		}
 | |
| 		dResp := client.ExpectDisconnectResponse(t)
 | |
| 		if dResp.Seq != 0 || dResp.RequestSeq != 13 {
 | |
| 			t.Errorf("\ngot %#v\nwant Seq=0, RequestSeq=13", dResp)
 | |
| 		}
 | |
| 		client.ExpectTerminatedEvent(t)
 | |
| 	})
 | |
| }
 | |
| 
 | |
| // TestAttachStopOnEntry is like TestLaunchStopOnEntry, but with attach request.
 | |
| func TestAttachStopOnEntry(t *testing.T) {
 | |
| 	runTest(t, "loopprog", func(client *daptest.Client, fixture protest.Fixture) {
 | |
| 		// Start the program to attach to
 | |
| 		cmd := exec.Command(fixture.Path)
 | |
| 		stdout, err := cmd.StdoutPipe()
 | |
| 		if err != nil {
 | |
| 			t.Fatal(err)
 | |
| 		}
 | |
| 		cmd.Stderr = os.Stderr
 | |
| 		if err := cmd.Start(); err != nil {
 | |
| 			t.Fatal(err)
 | |
| 		}
 | |
| 		// Wait for output.
 | |
| 		// This will give the target process time to initialize the runtime before we attach,
 | |
| 		// so we can rely on having goroutines when they are requested on attach.
 | |
| 		scanOut := bufio.NewScanner(stdout)
 | |
| 		scanOut.Scan()
 | |
| 		if scanOut.Text() != "past main" {
 | |
| 			t.Errorf("expected loopprog.go to output \"past main\"")
 | |
| 		}
 | |
| 
 | |
| 		// 1 >> initialize, << initialize
 | |
| 		client.InitializeRequest()
 | |
| 		initResp := client.ExpectInitializeResponseAndCapabilities(t)
 | |
| 		if initResp.Seq != 0 || initResp.RequestSeq != 1 {
 | |
| 			t.Errorf("\ngot %#v\nwant Seq=0, RequestSeq=1", initResp)
 | |
| 		}
 | |
| 
 | |
| 		// 2 >> attach, << initialized, << attach
 | |
| 		client.AttachRequest(
 | |
| 			map[string]interface{}{"mode": "local", "processId": cmd.Process.Pid, "stopOnEntry": true, "backend": "default"})
 | |
| 		client.ExpectCapabilitiesEventSupportTerminateDebuggee(t)
 | |
| 		initEvent := client.ExpectInitializedEvent(t)
 | |
| 		if initEvent.Seq != 0 {
 | |
| 			t.Errorf("\ngot %#v\nwant Seq=0", initEvent)
 | |
| 		}
 | |
| 		attachResp := client.ExpectAttachResponse(t)
 | |
| 		if attachResp.Seq != 0 || attachResp.RequestSeq != 2 {
 | |
| 			t.Errorf("\ngot %#v\nwant Seq=0, RequestSeq=2", attachResp)
 | |
| 		}
 | |
| 
 | |
| 		// 3 >> setBreakpoints, << setBreakpoints
 | |
| 		client.SetBreakpointsRequest(fixture.Source, nil)
 | |
| 		sbpResp := client.ExpectSetBreakpointsResponse(t)
 | |
| 		if sbpResp.Seq != 0 || sbpResp.RequestSeq != 3 || len(sbpResp.Body.Breakpoints) != 0 {
 | |
| 			t.Errorf("\ngot %#v\nwant Seq=0, RequestSeq=3, len(Breakpoints)=0", sbpResp)
 | |
| 		}
 | |
| 
 | |
| 		// 4 >> setExceptionBreakpoints, << setExceptionBreakpoints
 | |
| 		client.SetExceptionBreakpointsRequest()
 | |
| 		sebpResp := client.ExpectSetExceptionBreakpointsResponse(t)
 | |
| 		if sebpResp.Seq != 0 || sebpResp.RequestSeq != 4 {
 | |
| 			t.Errorf("\ngot %#v\nwant Seq=0, RequestSeq=4", sebpResp)
 | |
| 		}
 | |
| 
 | |
| 		// 5 >> configurationDone, << stopped, << configurationDone
 | |
| 		client.ConfigurationDoneRequest()
 | |
| 		stopEvent := client.ExpectStoppedEvent(t)
 | |
| 		if stopEvent.Seq != 0 ||
 | |
| 			stopEvent.Body.Reason != "entry" ||
 | |
| 			stopEvent.Body.ThreadId != 1 ||
 | |
| 			!stopEvent.Body.AllThreadsStopped {
 | |
| 			t.Errorf("\ngot %#v\nwant Seq=0, Body={Reason=\"entry\", ThreadId=1, AllThreadsStopped=true}", stopEvent)
 | |
| 		}
 | |
| 		cdResp := client.ExpectConfigurationDoneResponse(t)
 | |
| 		if cdResp.Seq != 0 || cdResp.RequestSeq != 5 {
 | |
| 			t.Errorf("\ngot %#v\nwant Seq=0, RequestSeq=5", cdResp)
 | |
| 		}
 | |
| 
 | |
| 		// 6 >> threads, << threads
 | |
| 		client.ThreadsRequest()
 | |
| 		tResp := client.ExpectThreadsResponse(t)
 | |
| 		// Expect main goroutine plus runtime at this point.
 | |
| 		if tResp.Seq != 0 || tResp.RequestSeq != 6 || len(tResp.Body.Threads) < 2 {
 | |
| 			t.Errorf("\ngot %#v\nwant Seq=0, RequestSeq=6 len(Threads)>1", tResp)
 | |
| 		}
 | |
| 
 | |
| 		// 7 >> threads, << threads
 | |
| 		client.ThreadsRequest()
 | |
| 		client.ExpectThreadsResponse(t)
 | |
| 
 | |
| 		// 8 >> stackTrace, << response
 | |
| 		client.StackTraceRequest(1, 0, 20)
 | |
| 		client.ExpectStackTraceResponse(t)
 | |
| 
 | |
| 		// 9 >> stackTrace, << response
 | |
| 		client.StackTraceRequest(1, 0, 20)
 | |
| 		client.ExpectStackTraceResponse(t)
 | |
| 
 | |
| 		// 10 >> evaluate, << error
 | |
| 		client.EvaluateRequest("foo", 0 /*no frame specified*/, "repl")
 | |
| 		erResp := client.ExpectInvisibleErrorResponse(t)
 | |
| 		if erResp.Seq != 0 || erResp.RequestSeq != 10 || !checkErrorMessageId(erResp.Body.Error, UnableToEvaluateExpression) {
 | |
| 			t.Errorf("\ngot %#v\nwant Seq=0, RequestSeq=10 Id=%d", erResp, UnableToEvaluateExpression)
 | |
| 		}
 | |
| 
 | |
| 		// 11 >> evaluate, << evaluate
 | |
| 		client.EvaluateRequest("1+1", 0 /*no frame specified*/, "repl")
 | |
| 		evResp := client.ExpectEvaluateResponse(t)
 | |
| 		if evResp.Seq != 0 || evResp.RequestSeq != 11 || evResp.Body.Result != "2" {
 | |
| 			t.Errorf("\ngot %#v\nwant Seq=0, RequestSeq=10 Result=2", evResp)
 | |
| 		}
 | |
| 
 | |
| 		// 12 >> continue, << continue
 | |
| 		client.ContinueRequest(1)
 | |
| 		cResp := client.ExpectContinueResponse(t)
 | |
| 		if cResp.Seq != 0 || cResp.RequestSeq != 12 {
 | |
| 			t.Errorf("\ngot %#v\nwant Seq=0, RequestSeq=12", cResp)
 | |
| 		}
 | |
| 
 | |
| 		// TODO(polina): once https://github.com/go-delve/delve/issues/2259 is
 | |
| 		// fixed, test with kill=false.
 | |
| 
 | |
| 		// 13 >> disconnect, << disconnect
 | |
| 		client.DisconnectRequestWithKillOption(true)
 | |
| 
 | |
| 		// Disconnect consists of Halt + Detach.
 | |
| 		// Halt interrupts command in progress, which triggers
 | |
| 		// a stopped event in parallel with the disconnect
 | |
| 		// sequence. It might arrive before or during the sequence
 | |
| 		// or never if the server exits before it is sent.
 | |
| 		msg := expectMessageFilterStopped(t, client)
 | |
| 		client.CheckOutputEvent(t, msg)
 | |
| 		msg = expectMessageFilterStopped(t, client)
 | |
| 		client.CheckDisconnectResponse(t, msg)
 | |
| 		client.ExpectTerminatedEvent(t)
 | |
| 
 | |
| 		// If this call to KeepAlive isn't here there's a chance that stdout will
 | |
| 		// be garbage collected (since it is no longer alive long before this
 | |
| 		// point), when that happens, on unix-like OSes, the read end of the pipe
 | |
| 		// will be closed by the finalizer and the target process will die by
 | |
| 		// SIGPIPE, which the rest of this test does not expect.
 | |
| 		runtime.KeepAlive(stdout)
 | |
| 	})
 | |
| }
 | |
| 
 | |
| // Like the test above, except the program is configured to continue on entry.
 | |
| func TestContinueOnEntry(t *testing.T) {
 | |
| 	runTest(t, "increment", func(client *daptest.Client, fixture protest.Fixture) {
 | |
| 		// 1 >> initialize, << initialize
 | |
| 		client.InitializeRequest()
 | |
| 		client.ExpectInitializeResponseAndCapabilities(t)
 | |
| 
 | |
| 		// 2 >> launch, << initialized, << launch
 | |
| 		client.LaunchRequest("exec", fixture.Path, !stopOnEntry)
 | |
| 		client.ExpectInitializedEvent(t)
 | |
| 		client.ExpectLaunchResponse(t)
 | |
| 
 | |
| 		// 3 >> setBreakpoints, << setBreakpoints
 | |
| 		client.SetBreakpointsRequest(fixture.Source, nil)
 | |
| 		client.ExpectSetBreakpointsResponse(t)
 | |
| 
 | |
| 		// 4 >> setExceptionBreakpoints, << setExceptionBreakpoints
 | |
| 		client.SetExceptionBreakpointsRequest()
 | |
| 		client.ExpectSetExceptionBreakpointsResponse(t)
 | |
| 
 | |
| 		// 5 >> configurationDone, << configurationDone
 | |
| 		client.ConfigurationDoneRequest()
 | |
| 		client.ExpectConfigurationDoneResponse(t)
 | |
| 		// "Continue" happens behind the scenes on another goroutine
 | |
| 
 | |
| 		client.ExpectTerminatedEvent(t)
 | |
| 
 | |
| 		// 6 >> threads, << threads
 | |
| 		client.ThreadsRequest()
 | |
| 		tResp := client.ExpectThreadsResponse(t)
 | |
| 		if tResp.Seq != 0 || tResp.RequestSeq != 6 || len(tResp.Body.Threads) != 1 {
 | |
| 			t.Errorf("\ngot %#v\nwant Seq=0, RequestSeq=6 len(Threads)=1", tResp)
 | |
| 		}
 | |
| 		if tResp.Body.Threads[0].Id != 1 || tResp.Body.Threads[0].Name != "Dummy" {
 | |
| 			t.Errorf("\ngot %#v\nwant Id=1, Name=\"Dummy\"", tResp)
 | |
| 		}
 | |
| 
 | |
| 		// 7 >> disconnect, << disconnect
 | |
| 		client.DisconnectRequest()
 | |
| 		client.ExpectOutputEventProcessExited(t, 0)
 | |
| 		client.ExpectOutputEventDetaching(t)
 | |
| 		dResp := client.ExpectDisconnectResponse(t)
 | |
| 		if dResp.Seq != 0 || dResp.RequestSeq != 7 {
 | |
| 			t.Errorf("\ngot %#v\nwant Seq=0, RequestSeq=7", dResp)
 | |
| 		}
 | |
| 		client.ExpectTerminatedEvent(t)
 | |
| 	})
 | |
| }
 | |
| 
 | |
| // TestPreSetBreakpoint corresponds to a debug session that is configured to
 | |
| // continue on entry with a pre-set breakpoint.
 | |
| func TestPreSetBreakpoint(t *testing.T) {
 | |
| 	runTest(t, "increment", func(client *daptest.Client, fixture protest.Fixture) {
 | |
| 		client.InitializeRequest()
 | |
| 		client.ExpectInitializeResponseAndCapabilities(t)
 | |
| 
 | |
| 		client.LaunchRequest("exec", fixture.Path, !stopOnEntry)
 | |
| 		client.ExpectInitializedEvent(t)
 | |
| 		client.ExpectLaunchResponse(t)
 | |
| 
 | |
| 		client.SetBreakpointsRequest(fixture.Source, []int{8})
 | |
| 		sResp := client.ExpectSetBreakpointsResponse(t)
 | |
| 		if len(sResp.Body.Breakpoints) != 1 {
 | |
| 			t.Errorf("got %#v, want len(Breakpoints)=1", sResp)
 | |
| 		}
 | |
| 		bkpt0 := sResp.Body.Breakpoints[0]
 | |
| 		if !bkpt0.Verified || bkpt0.Line != 8 || bkpt0.Id != 1 || bkpt0.Source.Name != filepath.Base(fixture.Source) || bkpt0.Source.Path != fixture.Source {
 | |
| 			t.Errorf("got breakpoints[0] = %#v, want Verified=true, Line=8, Id=1, Path=%q", bkpt0, fixture.Source)
 | |
| 		}
 | |
| 
 | |
| 		client.SetExceptionBreakpointsRequest()
 | |
| 		client.ExpectSetExceptionBreakpointsResponse(t)
 | |
| 
 | |
| 		client.ConfigurationDoneRequest()
 | |
| 		client.ExpectConfigurationDoneResponse(t)
 | |
| 		// This triggers "continue" on a separate goroutine
 | |
| 
 | |
| 		client.ThreadsRequest()
 | |
| 		// Since we are in async mode while running, we might receive messages in either order.
 | |
| 		for i := 0; i < 2; i++ {
 | |
| 			msg := client.ExpectMessage(t)
 | |
| 			switch m := msg.(type) {
 | |
| 			case *dap.ThreadsResponse:
 | |
| 				// If the thread request arrived while the program was running, we expect to get the dummy response
 | |
| 				// with a single goroutine "Current".
 | |
| 				// If the thread request arrived after the stop, we should get the goroutine stopped at main.Increment.
 | |
| 				if (len(m.Body.Threads) != 1 || m.Body.Threads[0].Id != -1 || m.Body.Threads[0].Name != "Current") &&
 | |
| 					(len(m.Body.Threads) < 1 || m.Body.Threads[0].Id != 1 || !strings.HasPrefix(m.Body.Threads[0].Name, "* [Go 1] main.Increment")) {
 | |
| 					t.Errorf("\ngot  %#v\nwant Id=-1, Name=\"Current\" or Id=1, Name=\"* [Go 1] main.Increment ...\"", m.Body.Threads)
 | |
| 				}
 | |
| 			case *dap.StoppedEvent:
 | |
| 				if m.Body.Reason != "breakpoint" || m.Body.ThreadId != 1 || !m.Body.AllThreadsStopped {
 | |
| 					t.Errorf("got %#v, want Body={Reason=\"breakpoint\", ThreadId=1, AllThreadsStopped=true}", m)
 | |
| 				}
 | |
| 			default:
 | |
| 				t.Fatalf("got %#v, want ThreadsResponse or StoppedEvent", m)
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		// Threads-StackTrace-Scopes-Variables request waterfall is
 | |
| 		// triggered on stop event.
 | |
| 		client.ThreadsRequest()
 | |
| 		tResp := client.ExpectThreadsResponse(t)
 | |
| 		if len(tResp.Body.Threads) < 2 { // 1 main + runtime
 | |
| 			t.Errorf("\ngot  %#v\nwant len(Threads)>1", tResp.Body.Threads)
 | |
| 		}
 | |
| 		reMain := regexp.MustCompile(`\* \[Go 1\] main.Increment \(Thread [0-9]+\)`)
 | |
| 		wantMain := dap.Thread{Id: 1, Name: "* [Go 1] main.Increment (Thread ...)"}
 | |
| 		wantRuntime := dap.Thread{Id: 2, Name: "[Go 2] runtime.gopark"}
 | |
| 		for _, got := range tResp.Body.Threads {
 | |
| 			if got.Id != 1 && !reMain.MatchString(got.Name) && !(strings.Contains(got.Name, "runtime.") || strings.Contains(got.Name, "runtime/")) {
 | |
| 				t.Errorf("\ngot  %#v\nwant []dap.Thread{%#v, %#v, ...}", tResp.Body.Threads, wantMain, wantRuntime)
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		client.StackTraceRequest(1, 0, 20)
 | |
| 		stResp := client.ExpectStackTraceResponse(t)
 | |
| 
 | |
| 		if stResp.Body.TotalFrames != 6 {
 | |
| 			t.Errorf("\ngot %#v\nwant TotalFrames=6", stResp.Body.TotalFrames)
 | |
| 		}
 | |
| 		if len(stResp.Body.StackFrames) != 6 {
 | |
| 			t.Errorf("\ngot %#v\nwant len(StackFrames)=6", stResp.Body.StackFrames)
 | |
| 		} else {
 | |
| 			checkFrame := func(got dap.StackFrame, id int, name string, sourceName string, line int) {
 | |
| 				t.Helper()
 | |
| 				if got.Id != id || got.Name != name {
 | |
| 					t.Errorf("\ngot  %#v\nwant Id=%d Name=%s", got, id, name)
 | |
| 				}
 | |
| 				if (sourceName != "" && (got.Source == nil || got.Source.Name != sourceName)) || (line > 0 && got.Line != line) {
 | |
| 					t.Errorf("\ngot  %#v\nwant Source.Name=%s Line=%d", got, sourceName, line)
 | |
| 				}
 | |
| 			}
 | |
| 			checkFrame(stResp.Body.StackFrames[0], 1000, "main.Increment", "increment.go", 8)
 | |
| 			checkFrame(stResp.Body.StackFrames[1], 1001, "main.Increment", "increment.go", 11)
 | |
| 			checkFrame(stResp.Body.StackFrames[2], 1002, "main.Increment", "increment.go", 11)
 | |
| 			checkFrame(stResp.Body.StackFrames[3], 1003, "main.main", "increment.go", 17)
 | |
| 			checkFrame(stResp.Body.StackFrames[4], 1004, "runtime.main", "proc.go", -1)
 | |
| 			checkFrame(stResp.Body.StackFrames[5], 1005, "runtime.goexit", "", -1)
 | |
| 		}
 | |
| 
 | |
| 		client.ScopesRequest(1000)
 | |
| 		scopes := client.ExpectScopesResponse(t)
 | |
| 		if len(scopes.Body.Scopes) > 1 {
 | |
| 			t.Errorf("\ngot  %#v\nwant len(Scopes)=1 (Locals)", scopes)
 | |
| 		}
 | |
| 		checkScope(t, scopes, 0, "Locals", localsScope)
 | |
| 
 | |
| 		client.VariablesRequest(localsScope)
 | |
| 		args := client.ExpectVariablesResponse(t)
 | |
| 		checkChildren(t, args, "Locals", 2)
 | |
| 		checkVarExact(t, args, 0, "y", "y", "0 = 0x0", "uint", noChildren)
 | |
| 		checkVarExact(t, args, 1, "~r1", "", "0 = 0x0", "uint", noChildren)
 | |
| 
 | |
| 		client.ContinueRequest(1)
 | |
| 		ctResp := client.ExpectContinueResponse(t)
 | |
| 		if !ctResp.Body.AllThreadsContinued {
 | |
| 			t.Errorf("\ngot  %#v\nwant AllThreadsContinued=true", ctResp.Body)
 | |
| 		}
 | |
| 		// "Continue" is triggered after the response is sent
 | |
| 
 | |
| 		client.ExpectTerminatedEvent(t)
 | |
| 
 | |
| 		// Pause request after termination should result in an error.
 | |
| 		// But in certain cases this request actually succeeds.
 | |
| 		client.PauseRequest(1)
 | |
| 		switch r := client.ExpectMessage(t).(type) {
 | |
| 		case *dap.ErrorResponse:
 | |
| 			if r.Message != "Unable to halt execution" {
 | |
| 				t.Errorf("\ngot  %#v\nwant Message='Unable to halt execution'", r)
 | |
| 			}
 | |
| 		case *dap.PauseResponse:
 | |
| 		default:
 | |
| 			t.Fatalf("Unexpected response type: expect error or pause, got %#v", r)
 | |
| 		}
 | |
| 
 | |
| 		client.DisconnectRequest()
 | |
| 		client.ExpectOutputEventProcessExited(t, 0)
 | |
| 		client.ExpectOutputEventDetaching(t)
 | |
| 		client.ExpectDisconnectResponse(t)
 | |
| 		client.ExpectTerminatedEvent(t)
 | |
| 	})
 | |
| }
 | |
| 
 | |
| // checkStackFramesExact is a helper for verifying the values within StackTraceResponse.
 | |
| //
 | |
| //	wantStartName - name of the first returned frame (ignored if "")
 | |
| //	wantStartLine - file line of the first returned frame (ignored if <0).
 | |
| //	wantStartID - id of the first frame returned (ignored if wantFrames is 0).
 | |
| //	wantFrames - number of frames returned (length of StackTraceResponse.Body.StackFrames array).
 | |
| //	wantTotalFrames - total number of stack frames available (StackTraceResponse.Body.TotalFrames).
 | |
| func checkStackFramesExact(t *testing.T, got *dap.StackTraceResponse,
 | |
| 	wantStartName string, wantStartLine interface{}, wantStartID, wantFrames, wantTotalFrames int,
 | |
| ) {
 | |
| 	t.Helper()
 | |
| 	checkStackFramesNamed("", t, got, wantStartName, wantStartLine, wantStartID, wantFrames, wantTotalFrames, true)
 | |
| }
 | |
| 
 | |
| func TestFilterGoroutines(t *testing.T) {
 | |
| 	tt := []struct {
 | |
| 		name    string
 | |
| 		filter  string
 | |
| 		want    []string
 | |
| 		wantLen int
 | |
| 		wantErr bool
 | |
| 	}{
 | |
| 		{
 | |
| 			name:    "user goroutines",
 | |
| 			filter:  "-with user",
 | |
| 			want:    []string{"main.main", "main.agoroutine"},
 | |
| 			wantLen: 11,
 | |
| 		},
 | |
| 		{
 | |
| 			name:    "filter by user loc",
 | |
| 			filter:  "-with userloc main.main",
 | |
| 			want:    []string{"main.main"},
 | |
| 			wantLen: 1,
 | |
| 		},
 | |
| 		{
 | |
| 			name:    "multiple filters",
 | |
| 			filter:  "-with user -with userloc main.agoroutine",
 | |
| 			want:    []string{"main.agoroutine"},
 | |
| 			wantLen: 10,
 | |
| 		},
 | |
| 		{
 | |
| 			name:   "system goroutines",
 | |
| 			filter: "-without user",
 | |
| 			want:   []string{"runtime."},
 | |
| 		},
 | |
| 		// Filters that should return all goroutines.
 | |
| 		{
 | |
| 			name:    "empty filter string",
 | |
| 			filter:  "",
 | |
| 			want:    []string{"main.main", "main.agoroutine", "runtime."},
 | |
| 			wantLen: -1,
 | |
| 		},
 | |
| 		{
 | |
| 			name:    "bad filter string",
 | |
| 			filter:  "not parsable to filters",
 | |
| 			want:    []string{"main.main", "main.agoroutine", "runtime."},
 | |
| 			wantLen: -1,
 | |
| 			wantErr: true,
 | |
| 		},
 | |
| 		// Filters that should produce none.
 | |
| 		{
 | |
| 			name:    "no match to user loc",
 | |
| 			filter:  "-with userloc main.NotAUserFrame",
 | |
| 			want:    []string{"Dummy"},
 | |
| 			wantLen: 1,
 | |
| 		},
 | |
| 		{
 | |
| 			name:    "no match to user and not user",
 | |
| 			filter:  "-with user -without user",
 | |
| 			want:    []string{"Dummy"},
 | |
| 			wantLen: 1,
 | |
| 		},
 | |
| 	}
 | |
| 	runTest(t, "goroutinestackprog", func(client *daptest.Client, fixture protest.Fixture) {
 | |
| 		runDebugSessionWithBPs(t, client, "launch",
 | |
| 			// Launch
 | |
| 			func() {
 | |
| 				client.LaunchRequestWithArgs(map[string]interface{}{
 | |
| 					"mode":        "exec",
 | |
| 					"program":     fixture.Path,
 | |
| 					"stopOnEntry": !stopOnEntry,
 | |
| 				})
 | |
| 			},
 | |
| 			// Set breakpoints
 | |
| 			fixture.Source, []int{30},
 | |
| 			[]onBreakpoint{{
 | |
| 				// Stop at line 30
 | |
| 				execute: func() {
 | |
| 					for _, tc := range tt {
 | |
| 						command := fmt.Sprintf("dlv config goroutineFilters %s", tc.filter)
 | |
| 						client.EvaluateRequest(command, 1000, "repl")
 | |
| 						client.ExpectInvalidatedEvent(t)
 | |
| 						client.ExpectEvaluateResponse(t)
 | |
| 
 | |
| 						client.ThreadsRequest()
 | |
| 						if tc.wantErr {
 | |
| 							client.ExpectOutputEvent(t)
 | |
| 						}
 | |
| 						tr := client.ExpectThreadsResponse(t)
 | |
| 						if tc.wantLen > 0 && len(tr.Body.Threads) != tc.wantLen {
 | |
| 							t.Errorf("got Threads=%#v, want Len=%d\n", tr.Body.Threads, tc.wantLen)
 | |
| 						}
 | |
| 						for i, frame := range tr.Body.Threads {
 | |
| 							var found bool
 | |
| 							for _, wantName := range tc.want {
 | |
| 								if strings.Contains(frame.Name, wantName) {
 | |
| 									found = true
 | |
| 									break
 | |
| 								}
 | |
| 							}
 | |
| 							if !found {
 | |
| 								t.Errorf("got Threads[%d]=%#v, want Name=%v\n", i, frame, tc.want)
 | |
| 							}
 | |
| 						}
 | |
| 					}
 | |
| 				},
 | |
| 				disconnect: false,
 | |
| 			}})
 | |
| 	})
 | |
| }
 | |
| 
 | |
| func checkStackFramesHasMore(t *testing.T, got *dap.StackTraceResponse,
 | |
| 	wantStartName string, wantStartLine, wantStartID, wantFrames, wantTotalFrames int,
 | |
| ) {
 | |
| 	t.Helper()
 | |
| 	checkStackFramesNamed("", t, got, wantStartName, wantStartLine, wantStartID, wantFrames, wantTotalFrames, false)
 | |
| }
 | |
| 
 | |
| func checkStackFramesNamed(testName string, t *testing.T, got *dap.StackTraceResponse,
 | |
| 	wantStartName string, wantStartLine interface{}, wantStartID, wantFrames, wantTotalFrames int, totalExact bool,
 | |
| ) {
 | |
| 	t.Helper()
 | |
| 	if totalExact && got.Body.TotalFrames != wantTotalFrames {
 | |
| 		t.Errorf("%s\ngot  %#v\nwant TotalFrames=%d", testName, got.Body.TotalFrames, wantTotalFrames)
 | |
| 	} else if !totalExact && got.Body.TotalFrames < wantTotalFrames {
 | |
| 		t.Errorf("%s\ngot  %#v\nwant TotalFrames>=%d", testName, got.Body.TotalFrames, wantTotalFrames)
 | |
| 	}
 | |
| 
 | |
| 	if len(got.Body.StackFrames) != wantFrames {
 | |
| 		t.Errorf("%s\ngot  len(StackFrames)=%d\nwant %d", testName, len(got.Body.StackFrames), wantFrames)
 | |
| 	} else {
 | |
| 		// Verify that frame ids are consecutive numbers starting at wantStartID
 | |
| 		for i := 0; i < wantFrames; i++ {
 | |
| 			if got.Body.StackFrames[i].Id != wantStartID+i {
 | |
| 				t.Errorf("%s\ngot  %#v\nwant Id=%d", testName, got.Body.StackFrames[i], wantStartID+i)
 | |
| 			}
 | |
| 		}
 | |
| 		// Verify the name and line corresponding to the first returned frame (if any).
 | |
| 		// This is useful when the first frame is the frame corresponding to the breakpoint at
 | |
| 		// a predefined line.
 | |
| 
 | |
| 		startLineOk := true
 | |
| 
 | |
| 		switch wantStartLine := wantStartLine.(type) {
 | |
| 		case int:
 | |
| 			if wantStartLine > 0 {
 | |
| 				startLineOk = got.Body.StackFrames[0].Line == wantStartLine
 | |
| 			}
 | |
| 		case []int:
 | |
| 			startLineOk = false
 | |
| 			for _, ln := range wantStartLine {
 | |
| 				if got.Body.StackFrames[0].Line == ln {
 | |
| 					startLineOk = true
 | |
| 					break
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		if wantFrames > 0 && !startLineOk {
 | |
| 			t.Errorf("%s\ngot  Line=%d\nwant %d", testName, got.Body.StackFrames[0].Line, wantStartLine)
 | |
| 		}
 | |
| 		if wantFrames > 0 && wantStartName != "" && got.Body.StackFrames[0].Name != wantStartName {
 | |
| 			t.Errorf("%s\ngot  Name=%s\nwant %s", testName, got.Body.StackFrames[0].Name, wantStartName)
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // checkScope is a helper for verifying the values within a ScopesResponse.
 | |
| //
 | |
| //	i - index of the scope within ScopesResponse.Body.Scopes array
 | |
| //	name - name of the scope
 | |
| //	varRef - reference to retrieve variables of this scope. If varRef is negative, the reference is not checked.
 | |
| func checkScope(t *testing.T, got *dap.ScopesResponse, i int, name string, varRef int) {
 | |
| 	t.Helper()
 | |
| 	if len(got.Body.Scopes) <= i {
 | |
| 		t.Errorf("\ngot  %d\nwant len(Scopes)>%d", len(got.Body.Scopes), i)
 | |
| 	}
 | |
| 	goti := got.Body.Scopes[i]
 | |
| 	if goti.Name != name || (varRef >= 0 && goti.VariablesReference != varRef) || goti.Expensive {
 | |
| 		t.Errorf("\ngot  %#v\nwant Name=%q VariablesReference=%d Expensive=false", goti, name, varRef)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // checkChildren is a helper for verifying the number of variables within a VariablesResponse.
 | |
| //
 | |
| //	parentName - pseudoname of the enclosing variable or scope (used for error message only)
 | |
| //	numChildren - number of variables/fields/elements of this variable
 | |
| func checkChildren(t *testing.T, got *dap.VariablesResponse, parentName string, numChildren int) {
 | |
| 	t.Helper()
 | |
| 	if got.Body.Variables == nil {
 | |
| 		t.Errorf("\ngot  %s children=%#v want []", parentName, got.Body.Variables)
 | |
| 	}
 | |
| 	if len(got.Body.Variables) != numChildren {
 | |
| 		t.Errorf("\ngot  len(%s)=%d (children=%#v)\nwant len=%d", parentName, len(got.Body.Variables), got.Body.Variables, numChildren)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // checkVar is a helper for verifying the values within a VariablesResponse.
 | |
| //
 | |
| //	i - index of the variable within VariablesResponse.Body.Variables array (-1 will search all vars for a match)
 | |
| //	name - name of the variable
 | |
| //	evalName - fully qualified variable name or alternative expression to load this variable
 | |
| //	value - the value of the variable
 | |
| //	useExactMatch - true if name, evalName and value are to be compared to exactly, false if to be used as regex
 | |
| //	hasRef - true if the variable should have children and therefore a non-0 variable reference
 | |
| //	ref - reference to retrieve children of this variable (0 if none)
 | |
| func checkVar(t *testing.T, got *dap.VariablesResponse, i int, name, evalName, value, typ string, useExactMatch, hasRef bool, indexed, named int) (ref int) {
 | |
| 	t.Helper()
 | |
| 	if len(got.Body.Variables) <= i {
 | |
| 		t.Errorf("\ngot  len=%d (children=%#v)\nwant len>%d", len(got.Body.Variables), got.Body.Variables, i)
 | |
| 		return
 | |
| 	}
 | |
| 	if i < 0 {
 | |
| 		for vi, v := range got.Body.Variables {
 | |
| 			if v.Name == name {
 | |
| 				i = vi
 | |
| 				break
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 	if i < 0 {
 | |
| 		t.Errorf("\ngot  %#v\nwant Variables[i].Name=%q (not found)", got, name)
 | |
| 		return 0
 | |
| 	}
 | |
| 
 | |
| 	goti := got.Body.Variables[i]
 | |
| 	matchedName := false
 | |
| 	if useExactMatch {
 | |
| 		if strings.HasPrefix(name, "~r") {
 | |
| 			matchedName = strings.HasPrefix(goti.Name, "~r")
 | |
| 		} else {
 | |
| 			matchedName = (goti.Name == name)
 | |
| 		}
 | |
| 	} else {
 | |
| 		matchedName, _ = regexp.MatchString(name, goti.Name)
 | |
| 	}
 | |
| 	if !matchedName || (goti.VariablesReference > 0) != hasRef {
 | |
| 		t.Errorf("\ngot  %#v\nwant Name=%q hasRef=%t", goti, name, hasRef)
 | |
| 	}
 | |
| 	matchedEvalName := false
 | |
| 	if useExactMatch {
 | |
| 		matchedEvalName = (goti.EvaluateName == evalName)
 | |
| 	} else {
 | |
| 		matchedEvalName, _ = regexp.MatchString(evalName, goti.EvaluateName)
 | |
| 	}
 | |
| 	if !matchedEvalName {
 | |
| 		t.Errorf("\ngot  %q\nwant EvaluateName=%q", goti.EvaluateName, evalName)
 | |
| 	}
 | |
| 	matchedValue := false
 | |
| 	if useExactMatch {
 | |
| 		matchedValue = (goti.Value == value)
 | |
| 	} else {
 | |
| 		matchedValue, _ = regexp.MatchString(value, goti.Value)
 | |
| 	}
 | |
| 	if !matchedValue {
 | |
| 		t.Errorf("\ngot  %s=%q\nwant %q", name, goti.Value, value)
 | |
| 	}
 | |
| 	matchedType := false
 | |
| 	if useExactMatch {
 | |
| 		matchedType = (goti.Type == typ)
 | |
| 	} else {
 | |
| 		matchedType, _ = regexp.MatchString(typ, goti.Type)
 | |
| 	}
 | |
| 	if !matchedType {
 | |
| 		t.Errorf("\ngot  %s=%q\nwant %q", name, goti.Type, typ)
 | |
| 	}
 | |
| 	if indexed >= 0 && goti.IndexedVariables != indexed {
 | |
| 		t.Errorf("\ngot  %s=%d indexed\nwant %d indexed", name, goti.IndexedVariables, indexed)
 | |
| 	}
 | |
| 	if named >= 0 && goti.NamedVariables != named {
 | |
| 		t.Errorf("\ngot  %s=%d named\nwant %d named", name, goti.NamedVariables, named)
 | |
| 	}
 | |
| 	return goti.VariablesReference
 | |
| }
 | |
| 
 | |
| // checkVarExact is a helper like checkVar that matches value exactly.
 | |
| func checkVarExact(t *testing.T, got *dap.VariablesResponse, i int, name, evalName, value, typ string, hasRef bool) (ref int) {
 | |
| 	t.Helper()
 | |
| 	return checkVarExactIndexed(t, got, i, name, evalName, value, typ, hasRef, -1, -1)
 | |
| }
 | |
| 
 | |
| // checkVarExact is a helper like checkVar that matches value exactly.
 | |
| func checkVarExactIndexed(t *testing.T, got *dap.VariablesResponse, i int, name, evalName, value, typ string, hasRef bool, indexed, named int) (ref int) {
 | |
| 	t.Helper()
 | |
| 	return checkVar(t, got, i, name, evalName, value, typ, true, hasRef, indexed, named)
 | |
| }
 | |
| 
 | |
| // checkVarRegex is a helper like checkVar that treats value, evalName or name as a regex.
 | |
| func checkVarRegex(t *testing.T, got *dap.VariablesResponse, i int, name, evalName, value, typ string, hasRef bool) (ref int) {
 | |
| 	t.Helper()
 | |
| 	return checkVarRegexIndexed(t, got, i, name, evalName, value, typ, hasRef, -1, -1)
 | |
| }
 | |
| 
 | |
| // checkVarRegex is a helper like checkVar that treats value, evalName or name as a regex.
 | |
| func checkVarRegexIndexed(t *testing.T, got *dap.VariablesResponse, i int, name, evalName, value, typ string, hasRef bool, indexed, named int) (ref int) {
 | |
| 	t.Helper()
 | |
| 	return checkVar(t, got, i, name, evalName, value, typ, false, hasRef, indexed, named)
 | |
| }
 | |
| 
 | |
| func expectMessageFilterStopped(t *testing.T, client *daptest.Client) dap.Message {
 | |
| 	msg := client.ExpectMessage(t)
 | |
| 	if _, isStopped := msg.(*dap.StoppedEvent); isStopped {
 | |
| 		msg = client.ExpectMessage(t)
 | |
| 	}
 | |
| 	return msg
 | |
| }
 | |
| 
 | |
| // validateEvaluateName issues an evaluate request with evaluateName of a variable and
 | |
| // confirms that it succeeds and returns the same variable record as the original.
 | |
| func validateEvaluateName(t *testing.T, client *daptest.Client, got *dap.VariablesResponse, i int) {
 | |
| 	t.Helper()
 | |
| 	original := got.Body.Variables[i]
 | |
| 	client.EvaluateRequest(original.EvaluateName, 1000, "this context will be ignored")
 | |
| 	validated := client.ExpectEvaluateResponse(t)
 | |
| 	if original.VariablesReference == 0 && validated.Body.VariablesReference != 0 ||
 | |
| 		original.VariablesReference != 0 && validated.Body.VariablesReference == 0 {
 | |
| 		t.Errorf("\ngot  varref=%d\nwant %d", validated.Body.VariablesReference, original.VariablesReference)
 | |
| 	}
 | |
| 	// The variable might not be fully loaded, and when we reload it with an expression
 | |
| 	// more of the subvalues might be revealed, so we must match the loaded prefix only.
 | |
| 	if strings.Contains(original.Value, "...") {
 | |
| 		origLoaded := strings.Split(original.Value, "...")[0]
 | |
| 		if !strings.HasPrefix(validated.Body.Result, origLoaded) {
 | |
| 			t.Errorf("\ngot  value=%q\nwant %q", validated.Body.Result, original.Value)
 | |
| 		}
 | |
| 	} else if original.Value != validated.Body.Result {
 | |
| 		t.Errorf("\ngot  value=%q\nwant %q", validated.Body.Result, original.Value)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // TestStackTraceRequest executes to a breakpoint and tests different
 | |
| // good and bad configurations of 'stackTrace' requests.
 | |
| func TestStackTraceRequest(t *testing.T) {
 | |
| 	runTest(t, "increment", func(client *daptest.Client, fixture protest.Fixture) {
 | |
| 		var stResp *dap.StackTraceResponse
 | |
| 		runDebugSessionWithBPs(t, client, "launch",
 | |
| 			// Launch
 | |
| 			func() {
 | |
| 				client.LaunchRequest("exec", fixture.Path, !stopOnEntry)
 | |
| 			},
 | |
| 			// Set breakpoints
 | |
| 			fixture.Source, []int{8, 18},
 | |
| 			[]onBreakpoint{{
 | |
| 				// Stop at line 8
 | |
| 				execute: func() {
 | |
| 					// Even though the stack frames do not change,
 | |
| 					// repeated requests at the same breakpoint
 | |
| 					// would assign next block of unique ids to them each time.
 | |
| 					const NumFrames = 6
 | |
| 					reqIndex := 0
 | |
| 					frameID := func() int {
 | |
| 						return startHandle + reqIndex
 | |
| 					}
 | |
| 
 | |
| 					tests := map[string]struct {
 | |
| 						startFrame          int
 | |
| 						levels              int
 | |
| 						wantStartName       string
 | |
| 						wantStartLine       int
 | |
| 						wantStartFrame      int
 | |
| 						wantFramesReturned  int
 | |
| 						wantFramesAvailable int
 | |
| 						exact               bool
 | |
| 					}{
 | |
| 						"all frame levels from 0 to NumFrames":    {0, NumFrames, "main.Increment", 8, 0, NumFrames, NumFrames, true},
 | |
| 						"subset of frames from 1 to -1":           {1, NumFrames - 1, "main.Increment", 11, 1, NumFrames - 1, NumFrames, true},
 | |
| 						"load stack in pages: first half":         {0, NumFrames / 2, "main.Increment", 8, 0, NumFrames / 2, NumFrames, false},
 | |
| 						"load stack in pages: second half":        {NumFrames / 2, NumFrames, "main.main", 17, NumFrames / 2, NumFrames / 2, NumFrames, true},
 | |
| 						"zero levels means all levels":            {0, 0, "main.Increment", 8, 0, NumFrames, NumFrames, true},
 | |
| 						"zero levels means all remaining levels":  {NumFrames / 2, 0, "main.main", 17, NumFrames / 2, NumFrames / 2, NumFrames, true},
 | |
| 						"negative levels treated as 0 (all)":      {0, -10, "main.Increment", 8, 0, NumFrames, NumFrames, true},
 | |
| 						"OOB levels is capped at available len":   {0, NumFrames + 1, "main.Increment", 8, 0, NumFrames, NumFrames, true},
 | |
| 						"OOB levels is capped at available len 1": {1, NumFrames + 1, "main.Increment", 11, 1, NumFrames - 1, NumFrames, true},
 | |
| 						"negative startFrame treated as 0":        {-10, 0, "main.Increment", 8, 0, NumFrames, NumFrames, true},
 | |
| 						"OOB startFrame returns empty trace":      {NumFrames, 0, "main.Increment", -1, -1, 0, NumFrames, true},
 | |
| 					}
 | |
| 					for name, tc := range tests {
 | |
| 						client.StackTraceRequest(1, tc.startFrame, tc.levels)
 | |
| 						stResp = client.ExpectStackTraceResponse(t)
 | |
| 						checkStackFramesNamed(name, t, stResp,
 | |
| 							tc.wantStartName, tc.wantStartLine, frameID(), tc.wantFramesReturned, tc.wantFramesAvailable, tc.exact)
 | |
| 						reqIndex += len(stResp.Body.StackFrames)
 | |
| 					}
 | |
| 				},
 | |
| 				disconnect: false,
 | |
| 			}, {
 | |
| 				// Stop at line 18
 | |
| 				execute: func() {
 | |
| 					// Frame ids get reset at each breakpoint.
 | |
| 					client.StackTraceRequest(1, 0, 0)
 | |
| 					stResp = client.ExpectStackTraceResponse(t)
 | |
| 					checkStackFramesExact(t, stResp, "main.main", 18, startHandle, 3, 3)
 | |
| 				},
 | |
| 				disconnect: false,
 | |
| 			}})
 | |
| 	})
 | |
| 	runTest(t, "increment", func(client *daptest.Client, fixture protest.Fixture) {
 | |
| 		var stResp *dap.StackTraceResponse
 | |
| 		runDebugSessionWithBPs(t, client, "launch",
 | |
| 			// Launch
 | |
| 			func() {
 | |
| 				client.LaunchRequest("exec", fixture.Path, !stopOnEntry)
 | |
| 			},
 | |
| 			// Set breakpoints
 | |
| 			fixture.Source, []int{8, 18},
 | |
| 			[]onBreakpoint{{
 | |
| 				// Stop at line 8
 | |
| 				execute: func() {
 | |
| 					// Even though the stack frames do not change,
 | |
| 					// repeated requests at the same breakpoint
 | |
| 					// would assign next block of unique ids to them each time.
 | |
| 					const NumFrames = 6
 | |
| 
 | |
| 					var frames []dap.StackFrame
 | |
| 
 | |
| 					for start, levels := 0, 1; start < NumFrames; {
 | |
| 						client.StackTraceRequest(1, start, levels)
 | |
| 						stResp = client.ExpectStackTraceResponse(t)
 | |
| 						frames = append(frames, stResp.Body.StackFrames...)
 | |
| 						if stResp.Body.TotalFrames < NumFrames {
 | |
| 							t.Errorf("got  %#v\nwant TotalFrames>=%d\n", stResp.Body.TotalFrames, NumFrames)
 | |
| 						}
 | |
| 
 | |
| 						if len(stResp.Body.StackFrames) < levels {
 | |
| 							t.Errorf("got  len(StackFrames)=%d\nwant >=%d\n", len(stResp.Body.StackFrames), levels)
 | |
| 						}
 | |
| 
 | |
| 						start += len(stResp.Body.StackFrames)
 | |
| 					}
 | |
| 
 | |
| 					// TODO check all the frames.
 | |
| 					want := []struct {
 | |
| 						wantName string
 | |
| 						wantLine int
 | |
| 					}{
 | |
| 						{"main.Increment", 8},
 | |
| 						{"main.Increment", 11},
 | |
| 						{"main.Increment", 11},
 | |
| 						{"main.main", 17},
 | |
| 						{"runtime.main", 0},
 | |
| 						{"runtime.goexit", 0},
 | |
| 					}
 | |
| 					for i, frame := range frames {
 | |
| 						frameId := startHandle + i
 | |
| 						if frame.Id != frameId {
 | |
| 							t.Errorf("got  %#v\nwant Id=%d\n", frame, frameId)
 | |
| 						}
 | |
| 
 | |
| 						// Verify the name and line corresponding to the first returned frame (if any).
 | |
| 						// This is useful when the first frame is the frame corresponding to the breakpoint at
 | |
| 						// a predefined line. Line values < 0 are a signal to skip the check (which can be useful
 | |
| 						// for frames in the third-party code, where we do not control the lines).
 | |
| 						if want[i].wantLine > 0 && frame.Line != want[i].wantLine {
 | |
| 							t.Errorf("got  Line=%d\nwant %d\n", frame.Line, want[i].wantLine)
 | |
| 						}
 | |
| 						if want[i].wantName != "" && frame.Name != want[i].wantName {
 | |
| 							t.Errorf("got  Name=%s\nwant %s\n", frame.Name, want[i].wantName)
 | |
| 						}
 | |
| 					}
 | |
| 				},
 | |
| 				disconnect: false,
 | |
| 			}, {
 | |
| 				// Stop at line 18
 | |
| 				execute: func() {
 | |
| 					// Frame ids get reset at each breakpoint.
 | |
| 					client.StackTraceRequest(1, 0, 0)
 | |
| 					stResp = client.ExpectStackTraceResponse(t)
 | |
| 					checkStackFramesExact(t, stResp, "main.main", 18, startHandle, 3, 3)
 | |
| 				},
 | |
| 				disconnect: false,
 | |
| 			}})
 | |
| 	})
 | |
| }
 | |
| 
 | |
| func TestFunctionNameFormattingInStackTrace(t *testing.T) {
 | |
| 	runTest(t, "consts", func(client *daptest.Client, fixture protest.Fixture) {
 | |
| 		runDebugSessionWithBPs(t, client, "launch",
 | |
| 			// Launch
 | |
| 			func() {
 | |
| 				client.LaunchRequestWithArgs(map[string]interface{}{
 | |
| 					"mode": "exec", "program": fixture.Path,
 | |
| 				})
 | |
| 			},
 | |
| 			// Breakpoints are set within the program
 | |
| 			fixture.Source, []int{},
 | |
| 			[]onBreakpoint{{
 | |
| 				// Stop at line 36
 | |
| 				execute: func() {
 | |
| 					if runtime.GOARCH == "386" && goversion.VersionAfterOrEqual(runtime.Version(), 1, 18) && !goversion.VersionAfterOrEqual(runtime.Version(), 1, 21) {
 | |
| 						client.StepInRequest(1)
 | |
| 						client.ExpectStepInResponse(t)
 | |
| 						client.ExpectStoppedEvent(t)
 | |
| 					}
 | |
| 					client.StackTraceRequest(1, 0, 20)
 | |
| 					stack := client.ExpectStackTraceResponse(t)
 | |
| 					checkStackFramesExact(t, stack, "main.main", 36, 1000, 3, 3)
 | |
| 
 | |
| 					// Step into pkg.AnotherMethod()
 | |
| 					client.StepInRequest(1)
 | |
| 					client.ExpectStepInResponse(t)
 | |
| 					client.ExpectStoppedEvent(t)
 | |
| 
 | |
| 					client.StackTraceRequest(1, 0, 20)
 | |
| 					stack = client.ExpectStackTraceResponse(t)
 | |
| 					checkStackFramesExact(t, stack, "pkg.(*SomeType).AnotherMethod", 13, 1000, 4, 4)
 | |
| 				},
 | |
| 				disconnect: true,
 | |
| 			}})
 | |
| 	})
 | |
| }
 | |
| 
 | |
| func Test_fnName(t *testing.T) {
 | |
| 	tests := []struct {
 | |
| 		symbol string
 | |
| 		want   string
 | |
| 	}{
 | |
| 		{
 | |
| 			symbol: "pkg.functionName",
 | |
| 			want:   "pkg.functionName",
 | |
| 		},
 | |
| 		{
 | |
| 			symbol: "github.com/some/long/package/path/pkg.(*SomeType).Method",
 | |
| 			want:   "pkg.(*SomeType).Method",
 | |
| 		},
 | |
| 		{
 | |
| 			symbol: "github.com/some/path/pkg.typeparametric[go.shape.struct { example.com/blah/otherpkg.x int }]",
 | |
| 			want:   "pkg.typeparametric[go.shape.struct { example.com/blah/otherpkg.x int }]",
 | |
| 		},
 | |
| 	}
 | |
| 	for _, tt := range tests {
 | |
| 		t.Run(tt.symbol, func(t *testing.T) {
 | |
| 			loc := proc.Location{Fn: &proc.Function{Name: tt.symbol}}
 | |
| 			if got := fnName(&loc); got != tt.want {
 | |
| 				t.Errorf("fnName() = %v, want %v", got, tt.want)
 | |
| 			}
 | |
| 		})
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func TestSelectedThreadsRequest(t *testing.T) {
 | |
| 	runTest(t, "goroutinestackprog", func(client *daptest.Client, fixture protest.Fixture) {
 | |
| 		runDebugSessionWithBPs(t, client, "launch",
 | |
| 			// Launch
 | |
| 			func() {
 | |
| 				client.LaunchRequest("exec", fixture.Path, !stopOnEntry)
 | |
| 			},
 | |
| 			// Set breakpoints
 | |
| 			fixture.Source, []int{20},
 | |
| 			[]onBreakpoint{{
 | |
| 				execute: func() {
 | |
| 					checkStop(t, client, 1, "main.main", 20)
 | |
| 
 | |
| 					defaultMaxGoroutines := maxGoroutines
 | |
| 					defer func() { maxGoroutines = defaultMaxGoroutines }()
 | |
| 
 | |
| 					maxGoroutines = 1
 | |
| 					client.SetBreakpointsRequest(fixture.Source, []int{8})
 | |
| 					client.ExpectSetBreakpointsResponse(t)
 | |
| 
 | |
| 					client.ContinueRequest(1)
 | |
| 					client.ExpectContinueResponse(t)
 | |
| 
 | |
| 					se := client.ExpectStoppedEvent(t)
 | |
| 					if se.Body.Reason != "breakpoint" || se.Body.ThreadId == 1 {
 | |
| 						t.Errorf("got %#v, want Reason=%q, ThreadId!=1", se, "breakpoint")
 | |
| 					}
 | |
| 
 | |
| 					client.ThreadsRequest()
 | |
| 					oe := client.ExpectOutputEvent(t)
 | |
| 					if !strings.HasPrefix(oe.Body.Output, "Too many goroutines") {
 | |
| 						t.Errorf("got %#v, expected Output=\"Too many goroutines...\"\n", oe)
 | |
| 					}
 | |
| 					tr := client.ExpectThreadsResponse(t)
 | |
| 
 | |
| 					if len(tr.Body.Threads) != 2 {
 | |
| 						t.Errorf("got %d threads, expected 2\n", len(tr.Body.Threads))
 | |
| 					}
 | |
| 
 | |
| 					var selectedFound bool
 | |
| 					for _, thread := range tr.Body.Threads {
 | |
| 						if thread.Id == se.Body.ThreadId {
 | |
| 							selectedFound = true
 | |
| 							break
 | |
| 						}
 | |
| 					}
 | |
| 					if !selectedFound {
 | |
| 						t.Errorf("got %#v, want ThreadId=%d\n", tr.Body.Threads, se.Body.ThreadId)
 | |
| 					}
 | |
| 				},
 | |
| 				disconnect: true,
 | |
| 			}})
 | |
| 	})
 | |
| }
 | |
| 
 | |
| func TestGoroutineLabels(t *testing.T) {
 | |
| 	tests := []struct {
 | |
| 		showPprofLabelsConfig   []string
 | |
| 		expectedPrefixWithLabel string
 | |
| 	}{
 | |
| 		{[]string{}, "* [Go 1]"},
 | |
| 		{[]string{"k1"}, "* [Go 1 v1]"},
 | |
| 		{[]string{"k2"}, "* [Go 1 v2]"},
 | |
| 		{[]string{"k2", "k1"}, "* [Go 1 k2:v2 k1:v1]"}, // When passing keys explicitly, we show them in the given order
 | |
| 		{[]string{"unknown"}, "* [Go 1]"},
 | |
| 		{[]string{"unknown", "k1"}, "* [Go 1 k1:v1]"},
 | |
| 		{[]string{"*"}, "* [Go 1 k1:v1 k2:v2]"}, // Special case for showing all labels; labels are shown sorted by key
 | |
| 	}
 | |
| 	for _, tc := range tests {
 | |
| 		runTest(t, "goroutineLabels", func(client *daptest.Client, fixture protest.Fixture) {
 | |
| 			runDebugSessionWithBPs(t, client, "launch",
 | |
| 				// Launch
 | |
| 				func() {
 | |
| 					client.LaunchRequestWithArgs(map[string]interface{}{
 | |
| 						"mode":                 "exec",
 | |
| 						"program":              fixture.Path,
 | |
| 						"hideSystemGoroutines": true,
 | |
| 						"showPprofLabels":      tc.showPprofLabelsConfig,
 | |
| 						"stopOnEntry":          !stopOnEntry,
 | |
| 					})
 | |
| 				},
 | |
| 				// Breakpoints are set within the program
 | |
| 				"", []int{},
 | |
| 				[]onBreakpoint{{
 | |
| 					execute: func() {
 | |
| 						client.ThreadsRequest()
 | |
| 						tr := client.ExpectThreadsResponse(t)
 | |
| 						if len(tr.Body.Threads) != 1 {
 | |
| 							t.Errorf("got %d threads, expected 1\n", len(tr.Body.Threads))
 | |
| 						}
 | |
| 						// The first breakpoint is before the call to pprof.Do; no labels yet:
 | |
| 						expectedPrefix := "* [Go 1]"
 | |
| 						if !strings.HasPrefix(tr.Body.Threads[0].Name, expectedPrefix) {
 | |
| 							t.Errorf("got %s, expected %s\n", tr.Body.Threads[0].Name, expectedPrefix)
 | |
| 						}
 | |
| 
 | |
| 						client.ContinueRequest(1)
 | |
| 						client.ExpectContinueResponse(t)
 | |
| 						client.ExpectStoppedEvent(t)
 | |
| 						checkStop(t, client, 1, "main.f", 21)
 | |
| 						client.ThreadsRequest()
 | |
| 						tr = client.ExpectThreadsResponse(t)
 | |
| 						if len(tr.Body.Threads) != 1 {
 | |
| 							t.Errorf("got %d threads, expected 1\n", len(tr.Body.Threads))
 | |
| 						}
 | |
| 						// The second breakpoint is inside pprof.Do, so there are labels:
 | |
| 						if !strings.HasPrefix(tr.Body.Threads[0].Name, tc.expectedPrefixWithLabel) {
 | |
| 							t.Errorf("got %s, expected %s\n", tr.Body.Threads[0].Name, tc.expectedPrefixWithLabel)
 | |
| 						}
 | |
| 					},
 | |
| 					disconnect: true,
 | |
| 				}},
 | |
| 			)
 | |
| 		})
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func TestHideSystemGoroutinesRequest(t *testing.T) {
 | |
| 	tests := []struct{ hideSystemGoroutines bool }{
 | |
| 		{hideSystemGoroutines: true},
 | |
| 		{hideSystemGoroutines: false},
 | |
| 	}
 | |
| 	for _, tt := range tests {
 | |
| 		runTest(t, "goroutinestackprog", func(client *daptest.Client, fixture protest.Fixture) {
 | |
| 			runDebugSessionWithBPs(t, client, "launch",
 | |
| 				// Launch
 | |
| 				func() {
 | |
| 					client.LaunchRequestWithArgs(map[string]interface{}{
 | |
| 						"mode":                 "exec",
 | |
| 						"program":              fixture.Path,
 | |
| 						"hideSystemGoroutines": tt.hideSystemGoroutines,
 | |
| 						"stopOnEntry":          !stopOnEntry,
 | |
| 					})
 | |
| 				},
 | |
| 				// Set breakpoints
 | |
| 				fixture.Source, []int{25},
 | |
| 				[]onBreakpoint{{
 | |
| 					execute: func() {
 | |
| 						checkStop(t, client, 1, "main.main", 25)
 | |
| 
 | |
| 						client.ThreadsRequest()
 | |
| 						tr := client.ExpectThreadsResponse(t)
 | |
| 
 | |
| 						// The user process creates 10 goroutines in addition to the
 | |
| 						// main goroutine, for a total of 11 goroutines.
 | |
| 						userCount := 11
 | |
| 						if tt.hideSystemGoroutines {
 | |
| 							if len(tr.Body.Threads) != userCount {
 | |
| 								t.Errorf("got %d goroutines, expected %d\n", len(tr.Body.Threads), userCount)
 | |
| 							}
 | |
| 						} else {
 | |
| 							if len(tr.Body.Threads) <= userCount {
 | |
| 								t.Errorf("got %d goroutines, expected >%d\n", len(tr.Body.Threads), userCount)
 | |
| 							}
 | |
| 						}
 | |
| 					},
 | |
| 					disconnect: true,
 | |
| 				}})
 | |
| 		})
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // TestScopesAndVariablesRequests executes to a breakpoint and tests different
 | |
| // configurations of 'scopes' and 'variables' requests.
 | |
| func TestScopesAndVariablesRequests(t *testing.T) {
 | |
| 	runTest(t, "testvariables", func(client *daptest.Client, fixture protest.Fixture) {
 | |
| 		runDebugSessionWithBPs(t, client, "launch",
 | |
| 			// Launch
 | |
| 			func() {
 | |
| 				client.LaunchRequestWithArgs(map[string]interface{}{
 | |
| 					"mode": "exec", "program": fixture.Path, "showGlobalVariables": true, "backend": "default",
 | |
| 				})
 | |
| 			},
 | |
| 			// Breakpoints are set within the program
 | |
| 			fixture.Source, []int{},
 | |
| 			[]onBreakpoint{{
 | |
| 				// Stop at first breakpoint
 | |
| 				execute: func() {
 | |
| 					client.StackTraceRequest(1, 0, 20)
 | |
| 					stack := client.ExpectStackTraceResponse(t)
 | |
| 
 | |
| 					checkStackFramesExact(t, stack, "main.foobar", []int{65, 66}, 1000, 4, 4)
 | |
| 
 | |
| 					client.ScopesRequest(1000)
 | |
| 					scopes := client.ExpectScopesResponse(t)
 | |
| 					checkScope(t, scopes, 0, "Locals", localsScope)
 | |
| 					checkScope(t, scopes, 1, "Globals (package main)", globalsScope)
 | |
| 
 | |
| 					// Globals
 | |
| 
 | |
| 					client.VariablesRequest(globalsScope)
 | |
| 					globals := client.ExpectVariablesResponse(t)
 | |
| 					checkVarExact(t, globals, 0, "p1", "main.p1", "10", "int", noChildren)
 | |
| 
 | |
| 					// Locals
 | |
| 
 | |
| 					client.VariablesRequest(localsScope)
 | |
| 					locals := client.ExpectVariablesResponse(t)
 | |
| 					checkChildren(t, locals, "Locals", 33)
 | |
| 					checkVarExact(t, locals, 0, "baz", "baz", `"bazburzum"`, "string", noChildren)
 | |
| 					ref := checkVarExact(t, locals, 1, "bar", "bar", `main.FooBar {Baz: 10, Bur: "lorem"}`, "main.FooBar", hasChildren)
 | |
| 					if ref > 0 {
 | |
| 						client.VariablesRequest(ref)
 | |
| 						bar := client.ExpectVariablesResponse(t)
 | |
| 						checkChildren(t, bar, "bar", 2)
 | |
| 						checkVarExact(t, bar, 0, "Baz", "bar.Baz", "10", "int", noChildren)
 | |
| 						checkVarExact(t, bar, 1, "Bur", "bar.Bur", `"lorem"`, "string", noChildren)
 | |
| 						validateEvaluateName(t, client, bar, 0)
 | |
| 						validateEvaluateName(t, client, bar, 1)
 | |
| 					}
 | |
| 
 | |
| 					// reflect.Kind == Bool
 | |
| 					checkVarExact(t, locals, -1, "b1", "b1", "true", "bool", noChildren)
 | |
| 					checkVarExact(t, locals, -1, "b2", "b2", "false", "bool", noChildren)
 | |
| 					// reflect.Kind == Int
 | |
| 					checkVarExact(t, locals, -1, "a2", "a2", "6", "int", noChildren)
 | |
| 					checkVarExact(t, locals, -1, "neg", "neg", "-1", "int", noChildren)
 | |
| 					// reflect.Kind == Int8
 | |
| 					checkVarExact(t, locals, -1, "i8", "i8", "1", "int8", noChildren)
 | |
| 					// reflect.Kind == Int16 - see testvariables2
 | |
| 					// reflect.Kind == Int32 - see testvariables2
 | |
| 					// reflect.Kind == Int64 - see testvariables2
 | |
| 					// reflect.Kind == Uint
 | |
| 					// reflect.Kind == Uint8
 | |
| 					checkVarExact(t, locals, -1, "u8", "u8", "255 = 0xff", "uint8", noChildren)
 | |
| 					// reflect.Kind == Uint16
 | |
| 					checkVarExact(t, locals, -1, "u16", "u16", "65535 = 0xffff", "uint16", noChildren)
 | |
| 					// reflect.Kind == Uint32
 | |
| 					checkVarExact(t, locals, -1, "u32", "u32", "4294967295 = 0xffffffff", "uint32", noChildren)
 | |
| 					// reflect.Kind == Uint64
 | |
| 					checkVarExact(t, locals, -1, "u64", "u64", "18446744073709551615 = 0xffffffffffffffff", "uint64", noChildren)
 | |
| 					// reflect.Kind == Uintptr
 | |
| 					checkVarExact(t, locals, -1, "up", "up", "5 = 0x5", "uintptr", noChildren)
 | |
| 					// reflect.Kind == Float32
 | |
| 					checkVarExact(t, locals, -1, "f32", "f32", "1.2", "float32", noChildren)
 | |
| 					// reflect.Kind == Float64
 | |
| 					checkVarExact(t, locals, -1, "a3", "a3", "7.23", "float64", noChildren)
 | |
| 					// reflect.Kind == Complex64
 | |
| 					ref = checkVarExact(t, locals, -1, "c64", "c64", "(1 + 2i)", "complex64", hasChildren)
 | |
| 					if ref > 0 {
 | |
| 						client.VariablesRequest(ref)
 | |
| 						c64 := client.ExpectVariablesResponse(t)
 | |
| 						checkChildren(t, c64, "c64", 2)
 | |
| 						checkVarExact(t, c64, 0, "real", "", "1", "float32", noChildren)
 | |
| 						checkVarExact(t, c64, 1, "imaginary", "", "2", "float32", noChildren)
 | |
| 					}
 | |
| 					// reflect.Kind == Complex128
 | |
| 					ref = checkVarExact(t, locals, -1, "c128", "c128", "(2 + 3i)", "complex128", hasChildren)
 | |
| 					if ref > 0 {
 | |
| 						client.VariablesRequest(ref)
 | |
| 						c128 := client.ExpectVariablesResponse(t)
 | |
| 						checkChildren(t, c128, "c128", 2)
 | |
| 						checkVarExact(t, c128, 0, "real", "", "2", "float64", noChildren)
 | |
| 						checkVarExact(t, c128, 1, "imaginary", "", "3", "float64", noChildren)
 | |
| 					}
 | |
| 					// reflect.Kind == Array
 | |
| 					ref = checkVarExact(t, locals, -1, "a4", "a4", "[2]int [1,2]", "[2]int", hasChildren)
 | |
| 					if ref > 0 {
 | |
| 						client.VariablesRequest(ref)
 | |
| 						a4 := client.ExpectVariablesResponse(t)
 | |
| 						checkChildren(t, a4, "a4", 2)
 | |
| 						checkVarExact(t, a4, 0, "[0]", "a4[0]", "1", "int", noChildren)
 | |
| 						checkVarExact(t, a4, 1, "[1]", "a4[1]", "2", "int", noChildren)
 | |
| 					}
 | |
| 					ref = checkVarExact(t, locals, -1, "a11", "a11", `[3]main.FooBar [{Baz: 1, Bur: "a"},{Baz: 2, Bur: "b"},{Baz: 3, Bur: "c"}]`, "[3]main.FooBar", hasChildren)
 | |
| 					if ref > 0 {
 | |
| 						client.VariablesRequest(ref)
 | |
| 						a11 := client.ExpectVariablesResponse(t)
 | |
| 						checkChildren(t, a11, "a11", 3)
 | |
| 						checkVarExact(t, a11, 0, "[0]", "a11[0]", `main.FooBar {Baz: 1, Bur: "a"}`, "main.FooBar", hasChildren)
 | |
| 						ref = checkVarExact(t, a11, 1, "[1]", "a11[1]", `main.FooBar {Baz: 2, Bur: "b"}`, "main.FooBar", hasChildren)
 | |
| 						if ref > 0 {
 | |
| 							client.VariablesRequest(ref)
 | |
| 							a11_1 := client.ExpectVariablesResponse(t)
 | |
| 							checkChildren(t, a11_1, "a11[1]", 2)
 | |
| 							checkVarExact(t, a11_1, 0, "Baz", "a11[1].Baz", "2", "int", noChildren)
 | |
| 							checkVarExact(t, a11_1, 1, "Bur", "a11[1].Bur", `"b"`, "string", noChildren)
 | |
| 							validateEvaluateName(t, client, a11_1, 0)
 | |
| 							validateEvaluateName(t, client, a11_1, 1)
 | |
| 						}
 | |
| 						checkVarExact(t, a11, 2, "[2]", "a11[2]", `main.FooBar {Baz: 3, Bur: "c"}`, "main.FooBar", hasChildren)
 | |
| 					}
 | |
| 
 | |
| 					// reflect.Kind == Chan - see testvariables2
 | |
| 					// reflect.Kind == Func - see testvariables2
 | |
| 					// reflect.Kind == Interface - see testvariables2
 | |
| 					// reflect.Kind == Map - see testvariables2
 | |
| 					// reflect.Kind == Ptr
 | |
| 					ref = checkVarExact(t, locals, -1, "a7", "a7", `*main.FooBar {Baz: 5, Bur: "strum"}`, "*main.FooBar", hasChildren)
 | |
| 					if ref > 0 {
 | |
| 						client.VariablesRequest(ref)
 | |
| 						a7 := client.ExpectVariablesResponse(t)
 | |
| 						checkChildren(t, a7, "a7", 1)
 | |
| 						ref = checkVarExact(t, a7, 0, "", "(*a7)", `main.FooBar {Baz: 5, Bur: "strum"}`, "main.FooBar", hasChildren)
 | |
| 						if ref > 0 {
 | |
| 							client.VariablesRequest(ref)
 | |
| 							a7val := client.ExpectVariablesResponse(t)
 | |
| 							checkChildren(t, a7val, "*a7", 2)
 | |
| 							checkVarExact(t, a7val, 0, "Baz", "(*a7).Baz", "5", "int", noChildren)
 | |
| 							checkVarExact(t, a7val, 1, "Bur", "(*a7).Bur", `"strum"`, "string", noChildren)
 | |
| 							validateEvaluateName(t, client, a7val, 0)
 | |
| 							validateEvaluateName(t, client, a7val, 1)
 | |
| 						}
 | |
| 					}
 | |
| 					// TODO(polina): how to test for "nil" (without type) and "void"?
 | |
| 					checkVarExact(t, locals, -1, "a9", "a9", "*main.FooBar nil", "*main.FooBar", noChildren)
 | |
| 					// reflect.Kind == Slice
 | |
| 					ref = checkVarExact(t, locals, -1, "a5", "a5", "[]int len: 5, cap: 5, [1,2,3,4,5]", "[]int", hasChildren)
 | |
| 					if ref > 0 {
 | |
| 						client.VariablesRequest(ref)
 | |
| 						a5 := client.ExpectVariablesResponse(t)
 | |
| 						checkChildren(t, a5, "a5", 5)
 | |
| 						checkVarExact(t, a5, 0, "[0]", "a5[0]", "1", "int", noChildren)
 | |
| 						checkVarExact(t, a5, 4, "[4]", "a5[4]", "5", "int", noChildren)
 | |
| 						validateEvaluateName(t, client, a5, 0)
 | |
| 						validateEvaluateName(t, client, a5, 1)
 | |
| 					}
 | |
| 					ref = checkVarExact(t, locals, -1, "a12", "a12", `[]main.FooBar len: 2, cap: 2, [{Baz: 4, Bur: "d"},{Baz: 5, Bur: "e"}]`, "[]main.FooBar", hasChildren)
 | |
| 					if ref > 0 {
 | |
| 						client.VariablesRequest(ref)
 | |
| 						a12 := client.ExpectVariablesResponse(t)
 | |
| 						checkChildren(t, a12, "a12", 2)
 | |
| 						checkVarExact(t, a12, 0, "[0]", "a12[0]", `main.FooBar {Baz: 4, Bur: "d"}`, "main.FooBar", hasChildren)
 | |
| 						ref = checkVarExact(t, a12, 1, "[1]", "a12[1]", `main.FooBar {Baz: 5, Bur: "e"}`, "main.FooBar", hasChildren)
 | |
| 						if ref > 0 {
 | |
| 							client.VariablesRequest(ref)
 | |
| 							a12_1 := client.ExpectVariablesResponse(t)
 | |
| 							checkChildren(t, a12_1, "a12[1]", 2)
 | |
| 							checkVarExact(t, a12_1, 0, "Baz", "a12[1].Baz", "5", "int", noChildren)
 | |
| 							checkVarExact(t, a12_1, 1, "Bur", "a12[1].Bur", `"e"`, "string", noChildren)
 | |
| 							validateEvaluateName(t, client, a12_1, 0)
 | |
| 							validateEvaluateName(t, client, a12_1, 1)
 | |
| 						}
 | |
| 					}
 | |
| 					ref = checkVarExact(t, locals, -1, "a13", "a13", `[]*main.FooBar len: 3, cap: 3, [*{Baz: 6, Bur: "f"},*{Baz: 7, Bur: "g"},*{Baz: 8, Bur: "h"}]`, "[]*main.FooBar", hasChildren)
 | |
| 					if ref > 0 {
 | |
| 						client.VariablesRequest(ref)
 | |
| 						a13 := client.ExpectVariablesResponse(t)
 | |
| 						checkChildren(t, a13, "a13", 3)
 | |
| 						checkVarExact(t, a13, 0, "[0]", "a13[0]", `*main.FooBar {Baz: 6, Bur: "f"}`, "*main.FooBar", hasChildren)
 | |
| 						checkVarExact(t, a13, 1, "[1]", "a13[1]", `*main.FooBar {Baz: 7, Bur: "g"}`, "*main.FooBar", hasChildren)
 | |
| 						ref = checkVarExact(t, a13, 2, "[2]", "a13[2]", `*main.FooBar {Baz: 8, Bur: "h"}`, "*main.FooBar", hasChildren)
 | |
| 						if ref > 0 {
 | |
| 							client.VariablesRequest(ref)
 | |
| 							a13_2 := client.ExpectVariablesResponse(t)
 | |
| 							checkChildren(t, a13_2, "a13[2]", 1)
 | |
| 							ref = checkVarExact(t, a13_2, 0, "", "(*a13[2])", `main.FooBar {Baz: 8, Bur: "h"}`, "main.FooBar", hasChildren)
 | |
| 							validateEvaluateName(t, client, a13_2, 0)
 | |
| 							if ref > 0 {
 | |
| 								client.VariablesRequest(ref)
 | |
| 								val := client.ExpectVariablesResponse(t)
 | |
| 								checkChildren(t, val, "*a13[2]", 2)
 | |
| 								checkVarExact(t, val, 0, "Baz", "(*a13[2]).Baz", "8", "int", noChildren)
 | |
| 								checkVarExact(t, val, 1, "Bur", "(*a13[2]).Bur", `"h"`, "string", noChildren)
 | |
| 								validateEvaluateName(t, client, val, 0)
 | |
| 								validateEvaluateName(t, client, val, 1)
 | |
| 							}
 | |
| 						}
 | |
| 					}
 | |
| 					// reflect.Kind == String
 | |
| 					checkVarExact(t, locals, -1, "a1", "a1", `"foofoofoofoofoofoo"`, "string", noChildren)
 | |
| 					checkVarExact(t, locals, -1, "a10", "a10", `"ofo"`, "string", noChildren)
 | |
| 					// reflect.Kind == Struct
 | |
| 					ref = checkVarExact(t, locals, -1, "a6", "a6", `main.FooBar {Baz: 8, Bur: "word"}`, "main.FooBar", hasChildren)
 | |
| 					if ref > 0 {
 | |
| 						client.VariablesRequest(ref)
 | |
| 						a6 := client.ExpectVariablesResponse(t)
 | |
| 						checkChildren(t, a6, "a6", 2)
 | |
| 						checkVarExact(t, a6, 0, "Baz", "a6.Baz", "8", "int", noChildren)
 | |
| 						checkVarExact(t, a6, 1, "Bur", "a6.Bur", `"word"`, "string", noChildren)
 | |
| 					}
 | |
| 					ref = checkVarExact(t, locals, -1, "a8", "a8", `main.FooBar2 {Bur: 10, Baz: "feh"}`, "main.FooBar2", hasChildren)
 | |
| 					if ref > 0 {
 | |
| 						client.VariablesRequest(ref)
 | |
| 						a8 := client.ExpectVariablesResponse(t)
 | |
| 						checkChildren(t, a8, "a8", 2)
 | |
| 						checkVarExact(t, a8, 0, "Bur", "a8.Bur", "10", "int", noChildren)
 | |
| 						checkVarExact(t, a8, 1, "Baz", "a8.Baz", `"feh"`, "string", noChildren)
 | |
| 					}
 | |
| 					// reflect.Kind == UnsafePointer - see testvariables2
 | |
| 				},
 | |
| 				disconnect: false,
 | |
| 			}, {
 | |
| 				// Stop at second breakpoint
 | |
| 				execute: func() {
 | |
| 					// Frame ids get reset at each breakpoint.
 | |
| 					client.StackTraceRequest(1, 0, 20)
 | |
| 					stack := client.ExpectStackTraceResponse(t)
 | |
| 					checkStackFramesExact(t, stack, "main.barfoo", 27, 1000, 5, 5)
 | |
| 
 | |
| 					client.ScopesRequest(1000)
 | |
| 					scopes := client.ExpectScopesResponse(t)
 | |
| 					checkScope(t, scopes, 0, "Locals", localsScope)
 | |
| 					checkScope(t, scopes, 1, "Globals (package main)", globalsScope)
 | |
| 
 | |
| 					client.ScopesRequest(1111)
 | |
| 					erres := client.ExpectInvisibleErrorResponse(t)
 | |
| 					if !checkErrorMessageFormat(erres.Body.Error, "Unable to list locals: unknown frame id 1111") {
 | |
| 						t.Errorf("\ngot %#v\nwant Format=\"Unable to list locals: unknown frame id 1111\"", erres)
 | |
| 					}
 | |
| 
 | |
| 					client.VariablesRequest(localsScope)
 | |
| 					locals := client.ExpectVariablesResponse(t)
 | |
| 					checkChildren(t, locals, "Locals", 1)
 | |
| 					checkVarExact(t, locals, -1, "a1", "a1", `"bur"`, "string", noChildren)
 | |
| 
 | |
| 					client.VariablesRequest(globalsScope)
 | |
| 					globals := client.ExpectVariablesResponse(t)
 | |
| 					checkVarExact(t, globals, 0, "p1", "main.p1", "10", "int", noChildren)
 | |
| 
 | |
| 					client.VariablesRequest(7777)
 | |
| 					erres = client.ExpectInvisibleErrorResponse(t)
 | |
| 					if !checkErrorMessageFormat(erres.Body.Error, "Unable to lookup variable: unknown reference 7777") {
 | |
| 						t.Errorf("\ngot %#v\nwant Format=\"Unable to lookup variable: unknown reference 7777\"", erres)
 | |
| 					}
 | |
| 				},
 | |
| 				disconnect: false,
 | |
| 			}})
 | |
| 	})
 | |
| }
 | |
| 
 | |
| // TestScopesAndVariablesRequests2 executes to a breakpoint and tests different
 | |
| // configurations of 'scopes' and 'variables' requests.
 | |
| func TestScopesAndVariablesRequests2(t *testing.T) {
 | |
| 	runTest(t, "testvariables2", func(client *daptest.Client, fixture protest.Fixture) {
 | |
| 		runDebugSessionWithBPs(t, client, "launch",
 | |
| 			// Launch
 | |
| 			func() {
 | |
| 				client.LaunchRequest("exec", fixture.Path, !stopOnEntry)
 | |
| 			},
 | |
| 			// Breakpoints are set within the program
 | |
| 			fixture.Source, []int{},
 | |
| 			[]onBreakpoint{{
 | |
| 				execute: func() {
 | |
| 					client.StackTraceRequest(1, 0, 20)
 | |
| 					stack := client.ExpectStackTraceResponse(t)
 | |
| 					checkStackFramesExact(t, stack, "main.main", -1, 1000, 3, 3)
 | |
| 
 | |
| 					client.ScopesRequest(1000)
 | |
| 					scopes := client.ExpectScopesResponse(t)
 | |
| 					checkScope(t, scopes, 0, "Locals", localsScope)
 | |
| 				},
 | |
| 				disconnect: false,
 | |
| 			}, {
 | |
| 				execute: func() {
 | |
| 					client.StackTraceRequest(1, 0, 20)
 | |
| 					stack := client.ExpectStackTraceResponse(t)
 | |
| 					checkStackFramesExact(t, stack, "main.main", -1, 1000, 3, 3)
 | |
| 
 | |
| 					client.ScopesRequest(1000)
 | |
| 					scopes := client.ExpectScopesResponse(t)
 | |
| 					if len(scopes.Body.Scopes) > 1 {
 | |
| 						t.Errorf("\ngot  %#v\nwant len(scopes)=1 (Arguments & Locals)", scopes)
 | |
| 					}
 | |
| 					checkScope(t, scopes, 0, "Locals", localsScope)
 | |
| 
 | |
| 					// Locals
 | |
| 					client.VariablesRequest(localsScope)
 | |
| 					locals := client.ExpectVariablesResponse(t)
 | |
| 
 | |
| 					// reflect.Kind == Bool - see testvariables
 | |
| 					// reflect.Kind == Int - see testvariables
 | |
| 					// reflect.Kind == Int8
 | |
| 					checkVarExact(t, locals, -1, "ni8", "ni8", "-5", "int8", noChildren)
 | |
| 					// reflect.Kind == Int16
 | |
| 					checkVarExact(t, locals, -1, "ni16", "ni16", "-5", "int16", noChildren)
 | |
| 					// reflect.Kind == Int32
 | |
| 					checkVarExact(t, locals, -1, "ni32", "ni32", "-5", "int32", noChildren)
 | |
| 					// reflect.Kind == Int64
 | |
| 					checkVarExact(t, locals, -1, "ni64", "ni64", "-5", "int64", noChildren)
 | |
| 					// reflect.Kind == Uint
 | |
| 					// reflect.Kind == Uint8 - see testvariables
 | |
| 					// reflect.Kind == Uint16 - see testvariables
 | |
| 					// reflect.Kind == Uint32 - see testvariables
 | |
| 					// reflect.Kind == Uint64 - see testvariables
 | |
| 					// reflect.Kind == Uintptr - see testvariables
 | |
| 					// reflect.Kind == Float32 - see testvariables
 | |
| 					// reflect.Kind == Float64
 | |
| 					checkVarExact(t, locals, -1, "pinf", "pinf", "+Inf", "float64", noChildren)
 | |
| 					checkVarExact(t, locals, -1, "ninf", "ninf", "-Inf", "float64", noChildren)
 | |
| 					checkVarExact(t, locals, -1, "nan", "nan", "NaN", "float64", noChildren)
 | |
| 					// reflect.Kind == Complex64 - see testvariables
 | |
| 					// reflect.Kind == Complex128 - see testvariables
 | |
| 					// reflect.Kind == Array
 | |
| 					checkVarExact(t, locals, -1, "a0", "a0", "[0]int []", "[0]int", noChildren)
 | |
| 					// reflect.Kind == Chan
 | |
| 					ref := checkVarExact(t, locals, -1, "ch1", "ch1", "chan int 4/11", "chan int", hasChildren)
 | |
| 					if ref > 0 {
 | |
| 						client.VariablesRequest(ref)
 | |
| 						ch1 := client.ExpectVariablesResponse(t)
 | |
| 						checkChildren(t, ch1, "ch1", 11)
 | |
| 						checkVarExact(t, ch1, 0, "qcount", "ch1.qcount", "4 = 0x4", "uint", noChildren)
 | |
| 						checkVarRegex(t, ch1, 10, "lock", "ch1.lock", `runtime\.mutex {.*key: 0.*}`, `runtime\.mutex`, hasChildren)
 | |
| 						validateEvaluateName(t, client, ch1, 0)
 | |
| 						validateEvaluateName(t, client, ch1, 10)
 | |
| 					}
 | |
| 					checkVarExact(t, locals, -1, "chnil", "chnil", "chan int nil", "chan int", noChildren)
 | |
| 					// reflect.Kind == Func
 | |
| 					checkVarExact(t, locals, -1, "fn1", "fn1", "main.afunc", "main.functype", noChildren)
 | |
| 					checkVarExact(t, locals, -1, "fn2", "fn2", "nil", "main.functype", noChildren)
 | |
| 					// reflect.Kind == Interface
 | |
| 					checkVarExact(t, locals, -1, "ifacenil", "ifacenil", "interface {} nil", "interface {}", noChildren)
 | |
| 					ref = checkVarExact(t, locals, -1, "iface2", "iface2", "interface {}(string) \"test\"", "interface {}(string)", hasChildren)
 | |
| 					if ref > 0 {
 | |
| 						client.VariablesRequest(ref)
 | |
| 						iface2 := client.ExpectVariablesResponse(t)
 | |
| 						checkChildren(t, iface2, "iface2", 1)
 | |
| 						checkVarExact(t, iface2, 0, "data", "iface2.(data)", `"test"`, "string", noChildren)
 | |
| 						validateEvaluateName(t, client, iface2, 0)
 | |
| 					}
 | |
| 					ref = checkVarExact(t, locals, -1, "iface4", "iface4", "interface {}([]go/constant.Value) [4]", "interface {}([]go/constant.Value)", hasChildren)
 | |
| 					if ref > 0 {
 | |
| 						client.VariablesRequest(ref)
 | |
| 						iface4 := client.ExpectVariablesResponse(t)
 | |
| 						checkChildren(t, iface4, "iface4", 1)
 | |
| 						ref = checkVarExact(t, iface4, 0, "data", "iface4.(data)", "[]go/constant.Value len: 1, cap: 1, [4]", "[]go/constant.Value", hasChildren)
 | |
| 						if ref > 0 {
 | |
| 							client.VariablesRequest(ref)
 | |
| 							iface4data := client.ExpectVariablesResponse(t)
 | |
| 							checkChildren(t, iface4data, "iface4.data", 1)
 | |
| 							ref = checkVarExact(t, iface4data, 0, "[0]", "iface4.(data)[0]", "go/constant.Value(go/constant.int64Val) 4", "go/constant.Value(go/constant.int64Val)", hasChildren)
 | |
| 							if ref > 0 {
 | |
| 								client.VariablesRequest(ref)
 | |
| 								iface4data0 := client.ExpectVariablesResponse(t)
 | |
| 								checkChildren(t, iface4data0, "iface4.data[0]", 1)
 | |
| 								checkVarExact(t, iface4data0, 0, "data", "iface4.(data)[0].(data)", "4", "go/constant.int64Val", noChildren)
 | |
| 								validateEvaluateName(t, client, iface4data0, 0)
 | |
| 							}
 | |
| 						}
 | |
| 					}
 | |
| 					checkVarExact(t, locals, -1, "errnil", "errnil", "error nil", "error", noChildren)
 | |
| 					ref = checkVarExact(t, locals, -1, "err1", "err1", "error(*main.astruct) *{A: 1, B: 2}", "error(*main.astruct)", hasChildren)
 | |
| 					if ref > 0 {
 | |
| 						client.VariablesRequest(ref)
 | |
| 						err1 := client.ExpectVariablesResponse(t)
 | |
| 						checkChildren(t, err1, "err1", 1)
 | |
| 						checkVarExact(t, err1, 0, "data", "err1.(data)", "*main.astruct {A: 1, B: 2}", "*main.astruct", hasChildren)
 | |
| 						validateEvaluateName(t, client, err1, 0)
 | |
| 					}
 | |
| 					ref = checkVarExact(t, locals, -1, "ptrinf", "ptrinf", "*interface {}(**interface {}) **...", "*interface {}", hasChildren)
 | |
| 					if ref > 0 {
 | |
| 						client.VariablesRequest(ref)
 | |
| 						ptrinf_val := client.ExpectVariablesResponse(t)
 | |
| 						checkChildren(t, ptrinf_val, "*ptrinf", 1)
 | |
| 						ref = checkVarExact(t, ptrinf_val, 0, "", "(*ptrinf)", "interface {}(**interface {}) **...", "interface {}(**interface {})", hasChildren)
 | |
| 						if ref > 0 {
 | |
| 							client.VariablesRequest(ref)
 | |
| 							ptrinf_val_data := client.ExpectVariablesResponse(t)
 | |
| 							checkChildren(t, ptrinf_val_data, "(*ptrinf).data", 1)
 | |
| 							checkVarExact(t, ptrinf_val_data, 0, "data", "(*ptrinf).(data)", "**interface {}(**interface {}) ...", "**interface {}", hasChildren)
 | |
| 							validateEvaluateName(t, client, ptrinf_val_data, 0)
 | |
| 						}
 | |
| 					}
 | |
| 					// reflect.Kind == Map
 | |
| 					checkVarExact(t, locals, -1, "mnil", "mnil", "map[string]main.astruct nil", "map[string]main.astruct", noChildren)
 | |
| 					// key - scalar, value - compound
 | |
| 					ref = checkVarExact(t, locals, -1, "m2", "m2", "map[int]*main.astruct [1: *{A: 10, B: 11}, ]", "map[int]*main.astruct", hasChildren)
 | |
| 					if ref > 0 {
 | |
| 						client.VariablesRequest(ref)
 | |
| 						m2 := client.ExpectVariablesResponse(t)
 | |
| 						checkChildren(t, m2, "m2", 2) // each key-value represented by a single child
 | |
| 						checkVarExact(t, m2, 0, "len()", "len(m2)", "1", "int", noChildren)
 | |
| 						ref = checkVarExact(t, m2, 1, "1", "m2[1]", "*main.astruct {A: 10, B: 11}", "int: *main.astruct", hasChildren)
 | |
| 						if ref > 0 {
 | |
| 							client.VariablesRequest(ref)
 | |
| 							m2kv1 := client.ExpectVariablesResponse(t)
 | |
| 							checkChildren(t, m2kv1, "m2[1]", 1)
 | |
| 							ref = checkVarExact(t, m2kv1, 0, "", "(*m2[1])", "main.astruct {A: 10, B: 11}", "main.astruct", hasChildren)
 | |
| 							if ref > 0 {
 | |
| 								client.VariablesRequest(ref)
 | |
| 								m2kv1deref := client.ExpectVariablesResponse(t)
 | |
| 								checkChildren(t, m2kv1deref, "*m2[1]", 2)
 | |
| 								checkVarExact(t, m2kv1deref, 0, "A", "(*m2[1]).A", "10", "int", noChildren)
 | |
| 								checkVarExact(t, m2kv1deref, 1, "B", "(*m2[1]).B", "11", "int", noChildren)
 | |
| 								validateEvaluateName(t, client, m2kv1deref, 0)
 | |
| 								validateEvaluateName(t, client, m2kv1deref, 1)
 | |
| 							}
 | |
| 						}
 | |
| 					}
 | |
| 					// key - compound, value - scalar
 | |
| 					ref = checkVarExact(t, locals, -1, "m3", "m3", "map[main.astruct]int [{A: 1, B: 1}: 42, {A: 2, B: 2}: 43, ]", "map[main.astruct]int", hasChildren)
 | |
| 					if ref > 0 {
 | |
| 						client.VariablesRequest(ref)
 | |
| 						m3 := client.ExpectVariablesResponse(t)
 | |
| 						checkChildren(t, m3, "m3", 3) // each key-value represented by a single child
 | |
| 						checkVarExact(t, m3, 0, "len()", "len(m3)", "2", "int", noChildren)
 | |
| 						ref = checkVarRegex(t, m3, 1, `main\.astruct {A: 1, B: 1}`, `m3\[\(\*\(\*"main.astruct"\)\(0x[0-9a-f]+\)\)\]`, "42", "int", hasChildren)
 | |
| 						if ref > 0 {
 | |
| 							client.VariablesRequest(ref)
 | |
| 							m3kv0 := client.ExpectVariablesResponse(t)
 | |
| 							checkChildren(t, m3kv0, "m3[0]", 2)
 | |
| 							checkVarRegex(t, m3kv0, 0, "A", `\(*\(*"main\.astruct"\)\(0x[0-9a-f]+\)\)\.A`, "1", "int", noChildren)
 | |
| 							validateEvaluateName(t, client, m3kv0, 0)
 | |
| 						}
 | |
| 						ref = checkVarRegex(t, m3, 2, `main\.astruct {A: 2, B: 2}`, `m3\[\(\*\(\*"main.astruct"\)\(0x[0-9a-f]+\)\)\]`, "43", "", hasChildren)
 | |
| 						if ref > 0 { // inspect another key from another key-value child
 | |
| 							client.VariablesRequest(ref)
 | |
| 							m3kv1 := client.ExpectVariablesResponse(t)
 | |
| 							checkChildren(t, m3kv1, "m3[1]", 2)
 | |
| 							checkVarRegex(t, m3kv1, 1, "B", `\(*\(*"main\.astruct"\)\(0x[0-9a-f]+\)\)\.B`, "2", "int", noChildren)
 | |
| 							validateEvaluateName(t, client, m3kv1, 1)
 | |
| 						}
 | |
| 					}
 | |
| 					// key - compound, value - compound
 | |
| 					ref = checkVarExact(t, locals, -1, "m4", "m4", "map[main.astruct]main.astruct [{A: 1, B: 1}: {A: 11, B: 11}, {A: 2, B: 2}: {A: 22, B: 22}, ]", "map[main.astruct]main.astruct", hasChildren)
 | |
| 					if ref > 0 {
 | |
| 						client.VariablesRequest(ref)
 | |
| 						m4 := client.ExpectVariablesResponse(t)
 | |
| 						checkChildren(t, m4, "m4", 5) // each key and value represented by a child, so double the key-value count
 | |
| 						checkVarExact(t, m4, 0, "len()", "len(m4)", "2", "int", noChildren)
 | |
| 						checkVarRegex(t, m4, 1, `\[key 0\]`, `\(\*\(\*"main\.astruct"\)\(0x[0-9a-f]+\)\)`, `main\.astruct {A: 1, B: 1}`, `main\.astruct`, hasChildren)
 | |
| 						checkVarRegex(t, m4, 2, `\[val 0\]`, `m4\[\(\*\(\*"main\.astruct"\)\(0x[0-9a-f]+\)\)\]`, `main\.astruct {A: 11, B: 11}`, `main\.astruct`, hasChildren)
 | |
| 						ref = checkVarRegex(t, m4, 3, `\[key 1\]`, `\(\*\(\*"main\.astruct"\)\(0x[0-9a-f]+\)\)`, `main\.astruct {A: 2, B: 2}`, `main\.astruct`, hasChildren)
 | |
| 						if ref > 0 {
 | |
| 							client.VariablesRequest(ref)
 | |
| 							m4Key1 := client.ExpectVariablesResponse(t)
 | |
| 							checkChildren(t, m4Key1, "m4Key1", 2)
 | |
| 							checkVarRegex(t, m4Key1, 0, "A", `\(\*\(\*"main\.astruct"\)\(0x[0-9a-f]+\)\)\.A`, "2", "int", noChildren)
 | |
| 							checkVarRegex(t, m4Key1, 1, "B", `\(\*\(\*"main\.astruct"\)\(0x[0-9a-f]+\)\)\.B`, "2", "int", noChildren)
 | |
| 							validateEvaluateName(t, client, m4Key1, 0)
 | |
| 							validateEvaluateName(t, client, m4Key1, 1)
 | |
| 						}
 | |
| 						ref = checkVarRegex(t, m4, 4, `\[val 1\]`, `m4\[\(\*\(\*"main\.astruct"\)\(0x[0-9a-f]+\)\)\]`, `main\.astruct {A: 22, B: 22}`, "main.astruct", hasChildren)
 | |
| 						if ref > 0 {
 | |
| 							client.VariablesRequest(ref)
 | |
| 							m4Val1 := client.ExpectVariablesResponse(t)
 | |
| 							checkChildren(t, m4Val1, "m4Val1", 2)
 | |
| 							checkVarRegex(t, m4Val1, 0, "A", `m4\[\(\*\(\*"main\.astruct"\)\(0x[0-9a-f]+\)\)\]\.A`, "22", "int", noChildren)
 | |
| 							checkVarRegex(t, m4Val1, 1, "B", `m4\[\(\*\(\*"main\.astruct"\)\(0x[0-9a-f]+\)\)\]\.B`, "22", "int", noChildren)
 | |
| 							validateEvaluateName(t, client, m4Val1, 0)
 | |
| 							validateEvaluateName(t, client, m4Val1, 1)
 | |
| 						}
 | |
| 					}
 | |
| 					checkVarExact(t, locals, -1, "emptymap", "emptymap", "map[string]string []", "map[string]string", noChildren)
 | |
| 					// reflect.Kind == Ptr
 | |
| 					ref = checkVarExact(t, locals, -1, "pp1", "pp1", "**1", "**int", hasChildren)
 | |
| 					if ref > 0 {
 | |
| 						client.VariablesRequest(ref)
 | |
| 						pp1val := client.ExpectVariablesResponse(t)
 | |
| 						checkChildren(t, pp1val, "*pp1", 1)
 | |
| 						ref = checkVarExact(t, pp1val, 0, "", "(*pp1)", "*1", "*int", hasChildren)
 | |
| 						if ref > 0 {
 | |
| 							client.VariablesRequest(ref)
 | |
| 							pp1valval := client.ExpectVariablesResponse(t)
 | |
| 							checkChildren(t, pp1valval, "*(*pp1)", 1)
 | |
| 							checkVarExact(t, pp1valval, 0, "", "(*(*pp1))", "1", "int", noChildren)
 | |
| 							validateEvaluateName(t, client, pp1valval, 0)
 | |
| 						}
 | |
| 					}
 | |
| 					// reflect.Kind == Slice
 | |
| 					ref = checkVarExact(t, locals, -1, "zsslice", "zsslice", "[]struct {} len: 3, cap: 3, [{},{},{}]", "[]struct {}", hasChildren)
 | |
| 					if ref > 0 {
 | |
| 						client.VariablesRequest(ref)
 | |
| 						zsslice := client.ExpectVariablesResponse(t)
 | |
| 						checkChildren(t, zsslice, "zsslice", 3)
 | |
| 						checkVarExact(t, zsslice, 2, "[2]", "zsslice[2]", "struct {} {}", "struct {}", noChildren)
 | |
| 						validateEvaluateName(t, client, zsslice, 2)
 | |
| 					}
 | |
| 					checkVarExact(t, locals, -1, "emptyslice", "emptyslice", "[]string len: 0, cap: 0, []", "[]string", noChildren)
 | |
| 					checkVarExact(t, locals, -1, "nilslice", "nilslice", "[]int len: 0, cap: 0, nil", "[]int", noChildren)
 | |
| 					// reflect.Kind == String
 | |
| 					checkVarExact(t, locals, -1, "longstr", "longstr", longstr, "string", noChildren)
 | |
| 					// reflect.Kind == Struct
 | |
| 					checkVarExact(t, locals, -1, "zsvar", "zsvar", "struct {} {}", "struct {}", noChildren)
 | |
| 					// reflect.Kind == UnsafePointer
 | |
| 					// TODO(polina): how do I test for unsafe.Pointer(nil)?
 | |
| 					checkVarRegex(t, locals, -1, "upnil", "upnil", `unsafe\.Pointer\(0x0\)`, "int", noChildren)
 | |
| 					checkVarRegex(t, locals, -1, "up1", "up1", `unsafe\.Pointer\(0x[0-9a-f]+\)`, "int", noChildren)
 | |
| 
 | |
| 					// Test unreadable variable
 | |
| 					ref = checkVarRegex(t, locals, -1, "unread", "unread", `\*\(unreadable .+\)`, "int", hasChildren)
 | |
| 					if ref > 0 {
 | |
| 						client.VariablesRequest(ref)
 | |
| 						val := client.ExpectVariablesResponse(t)
 | |
| 						checkChildren(t, val, "*unread", 1)
 | |
| 						checkVarRegex(t, val, 0, "^$", `\(\*unread\)`, `\(unreadable .+\)`, "int", noChildren)
 | |
| 						validateEvaluateName(t, client, val, 0)
 | |
| 					}
 | |
| 				},
 | |
| 				disconnect: true,
 | |
| 			}})
 | |
| 	})
 | |
| }
 | |
| 
 | |
| // TestScopesRequestsOptimized executes to a breakpoint and tests different
 | |
| // that the name of the "Locals" scope is correctly annotated with
 | |
| // a warning about debugging an optimized function.
 | |
| func TestScopesRequestsOptimized(t *testing.T) {
 | |
| 	runTestBuildFlags(t, "testvariables", func(client *daptest.Client, fixture protest.Fixture) {
 | |
| 		runDebugSessionWithBPs(t, client, "launch",
 | |
| 			// Launch
 | |
| 			func() {
 | |
| 				client.LaunchRequestWithArgs(map[string]interface{}{
 | |
| 					"mode": "exec", "program": fixture.Path, "showGlobalVariables": true,
 | |
| 				})
 | |
| 			},
 | |
| 			// Breakpoints are set within the program
 | |
| 			fixture.Source, []int{},
 | |
| 			[]onBreakpoint{{
 | |
| 				// Stop at first breakpoint
 | |
| 				execute: func() {
 | |
| 					client.StackTraceRequest(1, 0, 20)
 | |
| 					stack := client.ExpectStackTraceResponse(t)
 | |
| 
 | |
| 					checkStackFramesExact(t, stack, "main.foobar", -1, 1000, 4, 4)
 | |
| 
 | |
| 					client.ScopesRequest(1000)
 | |
| 					scopes := client.ExpectScopesResponse(t)
 | |
| 					checkScope(t, scopes, 0, "Locals (warning: optimized function)", localsScope)
 | |
| 					checkScope(t, scopes, 1, "Globals (package main)", globalsScope)
 | |
| 				},
 | |
| 				disconnect: false,
 | |
| 			}, {
 | |
| 				// Stop at second breakpoint
 | |
| 				execute: func() {
 | |
| 					// Frame ids get reset at each breakpoint.
 | |
| 					client.StackTraceRequest(1, 0, 20)
 | |
| 					stack := client.ExpectStackTraceResponse(t)
 | |
| 					checkStackFramesExact(t, stack, "main.barfoo", 27, 1000, 5, 5)
 | |
| 
 | |
| 					client.ScopesRequest(1000)
 | |
| 					scopes := client.ExpectScopesResponse(t)
 | |
| 					checkScope(t, scopes, 0, "Locals (warning: optimized function)", localsScope)
 | |
| 					checkScope(t, scopes, 1, "Globals (package main)", globalsScope)
 | |
| 				},
 | |
| 				disconnect: false,
 | |
| 			}})
 | |
| 	},
 | |
| 		protest.EnableOptimization, false)
 | |
| }
 | |
| 
 | |
| // TestVariablesLoading exposes test cases where variables might be partially or
 | |
| // fully unloaded.
 | |
| func TestVariablesLoading(t *testing.T) {
 | |
| 	runTest(t, "testvariables2", func(client *daptest.Client, fixture protest.Fixture) {
 | |
| 		runDebugSessionWithBPs(t, client, "launch",
 | |
| 			// Launch
 | |
| 			func() {
 | |
| 				client.LaunchRequest("exec", fixture.Path, !stopOnEntry)
 | |
| 			},
 | |
| 			// Breakpoints are set within the program
 | |
| 			fixture.Source, []int{},
 | |
| 			[]onBreakpoint{{
 | |
| 				execute:    func() {},
 | |
| 				disconnect: false,
 | |
| 			}, {
 | |
| 				execute: func() {
 | |
| 					// Change default config values to trigger certain unloaded corner cases
 | |
| 					saveDefaultConfig := DefaultLoadConfig
 | |
| 					DefaultLoadConfig.MaxStructFields = 5
 | |
| 					DefaultLoadConfig.MaxStringLen = 64
 | |
| 					// Set the MaxArrayValues = 33 to execute a bug for map handling where
 | |
| 					// a request for  2*MaxArrayValues indexed map children would not correctly
 | |
| 					// reslice the map. See https://github.com/golang/vscode-go/issues/2351.
 | |
| 					DefaultLoadConfig.MaxArrayValues = 33
 | |
| 					defer func() {
 | |
| 						DefaultLoadConfig = saveDefaultConfig
 | |
| 					}()
 | |
| 
 | |
| 					client.StackTraceRequest(1, 0, 0)
 | |
| 					client.ExpectStackTraceResponse(t)
 | |
| 
 | |
| 					client.ScopesRequest(1000)
 | |
| 					client.ExpectScopesResponse(t)
 | |
| 
 | |
| 					client.VariablesRequest(localsScope)
 | |
| 					locals := client.ExpectVariablesResponse(t)
 | |
| 
 | |
| 					// String partially missing based on LoadConfig.MaxStringLen
 | |
| 					// See also TestVariableLoadingOfLongStrings
 | |
| 					checkVarExact(t, locals, -1, "longstr", "longstr", longstrLoaded64, "string", noChildren)
 | |
| 
 | |
| 					checkArrayChildren := func(t *testing.T, longarr *dap.VariablesResponse, parentName string, start int) {
 | |
| 						t.Helper()
 | |
| 						for i, child := range longarr.Body.Variables {
 | |
| 							idx := start + i
 | |
| 							if child.Name != fmt.Sprintf("[%d]", idx) || child.EvaluateName != fmt.Sprintf("%s[%d]", parentName, idx) {
 | |
| 								t.Errorf("Expected %s[%d] to have Name=\"[%d]\" EvaluateName=\"%s[%d]\", got %#v", parentName, idx, idx, parentName, idx, child)
 | |
| 							}
 | |
| 						}
 | |
| 					}
 | |
| 
 | |
| 					// Array not fully loaded based on LoadConfig.MaxArrayValues.
 | |
| 					// Expect to be able to load array by paging.
 | |
| 					ref := checkVarExactIndexed(t, locals, -1, "longarr", "longarr", "[100]int [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,...+67 more]", "[100]int", hasChildren, 100, 0)
 | |
| 					if ref > 0 {
 | |
| 						client.VariablesRequest(ref)
 | |
| 						longarr := client.ExpectVariablesResponse(t)
 | |
| 						checkChildren(t, longarr, "longarr", 33)
 | |
| 						checkArrayChildren(t, longarr, "longarr", 0)
 | |
| 
 | |
| 						client.IndexedVariablesRequest(ref, 0, 100)
 | |
| 						longarr = client.ExpectVariablesResponse(t)
 | |
| 						checkChildren(t, longarr, "longarr", 100)
 | |
| 						checkArrayChildren(t, longarr, "longarr", 0)
 | |
| 
 | |
| 						client.IndexedVariablesRequest(ref, 50, 50)
 | |
| 						longarr = client.ExpectVariablesResponse(t)
 | |
| 						checkChildren(t, longarr, "longarr", 50)
 | |
| 						checkArrayChildren(t, longarr, "longarr", 50)
 | |
| 					}
 | |
| 
 | |
| 					// Slice not fully loaded based on LoadConfig.MaxArrayValues.
 | |
| 					// Expect to be able to load slice by paging.
 | |
| 					ref = checkVarExactIndexed(t, locals, -1, "longslice", "longslice", "[]int len: 100, cap: 100, [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,...+67 more]", "[]int", hasChildren, 100, 0)
 | |
| 					if ref > 0 {
 | |
| 						client.VariablesRequest(ref)
 | |
| 						longarr := client.ExpectVariablesResponse(t)
 | |
| 						checkChildren(t, longarr, "longslice", 33)
 | |
| 						checkArrayChildren(t, longarr, "longslice", 0)
 | |
| 
 | |
| 						client.IndexedVariablesRequest(ref, 0, 100)
 | |
| 						longarr = client.ExpectVariablesResponse(t)
 | |
| 						checkChildren(t, longarr, "longslice", 100)
 | |
| 						checkArrayChildren(t, longarr, "longslice", 0)
 | |
| 
 | |
| 						client.IndexedVariablesRequest(ref, 50, 50)
 | |
| 						longarr = client.ExpectVariablesResponse(t)
 | |
| 						checkChildren(t, longarr, "longslice", 50)
 | |
| 						checkArrayChildren(t, longarr, "longslice", 50)
 | |
| 					}
 | |
| 
 | |
| 					// Map not fully loaded based on LoadConfig.MaxArrayValues
 | |
| 					// Expect to be able to load map by paging.
 | |
| 					ref = checkVarRegexIndexed(t, locals, -1, "m1", "m1", `map\[string\]main\.astruct \[.+\.\.\.`, `map\[string\]main\.astruct`, hasChildren, 66, 1)
 | |
| 					if ref > 0 {
 | |
| 						client.VariablesRequest(ref)
 | |
| 						m1 := client.ExpectVariablesResponse(t)
 | |
| 						checkChildren(t, m1, "m1", 34)
 | |
| 
 | |
| 						client.IndexedVariablesRequest(ref, 0, 66)
 | |
| 						m1 = client.ExpectVariablesResponse(t)
 | |
| 						checkChildren(t, m1, "m1", 66)
 | |
| 
 | |
| 						client.IndexedVariablesRequest(ref, 0, 33)
 | |
| 						m1part1 := client.ExpectVariablesResponse(t)
 | |
| 						checkChildren(t, m1part1, "m1", 33)
 | |
| 
 | |
| 						client.IndexedVariablesRequest(ref, 33, 33)
 | |
| 						m1part2 := client.ExpectVariablesResponse(t)
 | |
| 						checkChildren(t, m1part2, "m1", 33)
 | |
| 
 | |
| 						if len(m1part1.Body.Variables)+len(m1part2.Body.Variables) == len(m1.Body.Variables) {
 | |
| 							for i, got := range m1part1.Body.Variables {
 | |
| 								want := m1.Body.Variables[i]
 | |
| 								if got.Name != want.Name || got.Value != want.Value {
 | |
| 									t.Errorf("got %#v, want Name=%q Value=%q", got, want.Name, want.Value)
 | |
| 								}
 | |
| 							}
 | |
| 							for i, got := range m1part2.Body.Variables {
 | |
| 								want := m1.Body.Variables[i+len(m1part1.Body.Variables)]
 | |
| 								if got.Name != want.Name || got.Value != want.Value {
 | |
| 									t.Errorf("got %#v, want Name=%q Value=%q", got, want.Name, want.Value)
 | |
| 								}
 | |
| 							}
 | |
| 						}
 | |
| 						client.NamedVariablesRequest(ref)
 | |
| 						named := client.ExpectVariablesResponse(t)
 | |
| 						checkChildren(t, named, "m1", 1)
 | |
| 						checkVarExact(t, named, 0, "len()", "len(m1)", "66", "int", noChildren)
 | |
| 					}
 | |
| 
 | |
| 					// Struct partially missing based on LoadConfig.MaxStructFields
 | |
| 					ref = checkVarExact(t, locals, -1, "sd", "sd", "(loaded 5/6) main.D {u1: 0, u2: 0, u3: 0, u4: 0, u5: 0,...+1 more}", "main.D", hasChildren)
 | |
| 					if ref > 0 {
 | |
| 						client.VariablesRequest(ref)
 | |
| 						sd := client.ExpectVariablesResponse(t)
 | |
| 						checkChildren(t, sd, "sd", 5)
 | |
| 					}
 | |
| 
 | |
| 					// Fully missing struct auto-loaded when reaching LoadConfig.MaxVariableRecurse (also tests evaluateName corner case)
 | |
| 					ref = checkVarRegex(t, locals, -1, "c1", "c1", `main\.cstruct {pb: \*main\.bstruct {a: \(\*main\.astruct\)\(0x[0-9a-f]+\)}, sa: []\*main\.astruct len: 3, cap: 3, [\*\(\*main\.astruct\)\(0x[0-9a-f]+\),\*\(\*main\.astruct\)\(0x[0-9a-f]+\),\*\(\*main.astruct\)\(0x[0-9a-f]+\)]}`, `main\.cstruct`, hasChildren)
 | |
| 					if ref > 0 {
 | |
| 						client.VariablesRequest(ref)
 | |
| 						c1 := client.ExpectVariablesResponse(t)
 | |
| 						checkChildren(t, c1, "c1", 2)
 | |
| 						ref = checkVarRegex(t, c1, 1, "sa", `c1\.sa`, `\[\]\*main\.astruct len: 3, cap: 3, \[\*\(\*main\.astruct\)\(0x[0-9a-f]+\),\*\(\*main\.astruct\)\(0x[0-9a-f]+\),\*\(\*main\.astruct\)\(0x[0-9a-f]+\)\]`, `\[\]\*main\.astruct`, hasChildren)
 | |
| 						if ref > 0 {
 | |
| 							client.VariablesRequest(ref)
 | |
| 							c1sa := client.ExpectVariablesResponse(t)
 | |
| 							checkChildren(t, c1sa, "c1.sa", 3)
 | |
| 							ref = checkVarRegex(t, c1sa, 0, `\[0\]`, `c1\.sa\[0\]`, `\*\(\*main\.astruct\)\(0x[0-9a-f]+\)`, `\*main\.astruct`, hasChildren)
 | |
| 							if ref > 0 {
 | |
| 								// Auto-loading of fully missing struct children happens here
 | |
| 								client.VariablesRequest(ref)
 | |
| 								c1sa0 := client.ExpectVariablesResponse(t)
 | |
| 								checkChildren(t, c1sa0, "c1.sa[0]", 1)
 | |
| 								// TODO(polina): there should be children here once we support auto loading
 | |
| 								checkVarExact(t, c1sa0, 0, "", "(*c1.sa[0])", "main.astruct {A: 1, B: 2}", "main.astruct", hasChildren)
 | |
| 							}
 | |
| 						}
 | |
| 					}
 | |
| 
 | |
| 					// Fully missing struct auto-loaded when hitting LoadConfig.MaxVariableRecurse (also tests evaluateName corner case)
 | |
| 					ref = checkVarRegex(t, locals, -1, "aas", "aas", `\[\]main\.a len: 1, cap: 1, \[{aas: \[\]main\.a len: 1, cap: 1, \[\(\*main\.a\)\(0x[0-9a-f]+\)\]}\]`, `\[\]main\.a`, hasChildren)
 | |
| 					if ref > 0 {
 | |
| 						client.VariablesRequest(ref)
 | |
| 						aas := client.ExpectVariablesResponse(t)
 | |
| 						checkChildren(t, aas, "aas", 1)
 | |
| 						ref = checkVarRegex(t, aas, 0, "[0]", `aas\[0\]`, `main\.a {aas: \[\]main.a len: 1, cap: 1, \[\(\*main\.a\)\(0x[0-9a-f]+\)\]}`, `main\.a`, hasChildren)
 | |
| 						if ref > 0 {
 | |
| 							client.VariablesRequest(ref)
 | |
| 							aas0 := client.ExpectVariablesResponse(t)
 | |
| 							checkChildren(t, aas0, "aas[0]", 1)
 | |
| 							ref = checkVarRegex(t, aas0, 0, "aas", `aas\[0\]\.aas`, `\[\]main\.a len: 1, cap: 1, \[\(\*main\.a\)\(0x[0-9a-f]+\)\]`, `\[\]main\.a`, hasChildren)
 | |
| 							if ref > 0 {
 | |
| 								// Auto-loading of fully missing struct children happens here
 | |
| 								client.VariablesRequest(ref)
 | |
| 								aas0aas := client.ExpectVariablesResponse(t)
 | |
| 								checkChildren(t, aas0aas, "aas[0].aas", 1)
 | |
| 								// TODO(polina): there should be a child here once we support auto loading - test for "aas[0].aas[0].aas"
 | |
| 								ref = checkVarRegex(t, aas0aas, 0, "[0]", `aas\[0\]\.aas\[0\]`, `main\.a {aas: \[\]main\.a len: 1, cap: 1, \[\(\*main\.a\)\(0x[0-9a-f]+\)\]}`, "main.a", hasChildren)
 | |
| 								if ref > 0 {
 | |
| 									client.VariablesRequest(ref)
 | |
| 									aas0aas0 := client.ExpectVariablesResponse(t)
 | |
| 									checkChildren(t, aas0aas, "aas[0].aas[0]", 1)
 | |
| 									checkVarRegex(t, aas0aas0, 0, "aas", `aas\[0\]\.aas\[0\]\.aas`, `\[\]main\.a len: 1, cap: 1, \[\(\*main\.a\)\(0x[0-9a-f]+\)\]`, `\[\]main\.a`, hasChildren)
 | |
| 								}
 | |
| 							}
 | |
| 						}
 | |
| 					}
 | |
| 
 | |
| 					// Fully missing map auto-loaded when hitting LoadConfig.MaxVariableRecurse (also tests evaluateName corner case)
 | |
| 					ref = checkVarExact(t, locals, -1, "tm", "tm", "main.truncatedMap {v: []map[string]main.astruct len: 1, cap: 1, [[...]]}", "main.truncatedMap", hasChildren)
 | |
| 					if ref > 0 {
 | |
| 						client.VariablesRequest(ref)
 | |
| 						tm := client.ExpectVariablesResponse(t)
 | |
| 						checkChildren(t, tm, "tm", 1)
 | |
| 						ref = checkVarExact(t, tm, 0, "v", "tm.v", "[]map[string]main.astruct len: 1, cap: 1, [[...]]", "[]map[string]main.astruct", hasChildren)
 | |
| 						if ref > 0 {
 | |
| 							// Auto-loading of fully missing map chidlren happens here, but they get truncated at MaxArrayValues
 | |
| 							client.VariablesRequest(ref)
 | |
| 							tmV := client.ExpectVariablesResponse(t)
 | |
| 							checkChildren(t, tmV, "tm.v", 1)
 | |
| 							ref = checkVarRegex(t, tmV, 0, `\[0\]`, `tm\.v\[0\]`, `map\[string\]main\.astruct \[.+\.\.\.`, `map\[string\]main\.astruct`, hasChildren)
 | |
| 							if ref > 0 {
 | |
| 								client.VariablesRequest(ref)
 | |
| 								tmV0 := client.ExpectVariablesResponse(t)
 | |
| 								checkChildren(t, tmV0, "tm.v[0]", 34)
 | |
| 							}
 | |
| 						}
 | |
| 					}
 | |
| 
 | |
| 					// Auto-loading works with call return variables as well
 | |
| 					protest.MustSupportFunctionCalls(t, testBackend)
 | |
| 					client.EvaluateRequest("call rettm()", 1000, "repl")
 | |
| 					got := client.ExpectEvaluateResponse(t)
 | |
| 					ref = checkEval(t, got, "main.truncatedMap {v: []map[string]main.astruct len: 1, cap: 1, [[...]]}", hasChildren)
 | |
| 					if ref > 0 {
 | |
| 						client.VariablesRequest(ref)
 | |
| 						rv := client.ExpectVariablesResponse(t)
 | |
| 						checkChildren(t, rv, "rv", 1)
 | |
| 						ref = checkVarExact(t, rv, 0, "~r0", "", "main.truncatedMap {v: []map[string]main.astruct len: 1, cap: 1, [[...]]}", "main.truncatedMap", hasChildren)
 | |
| 						if ref > 0 {
 | |
| 							client.VariablesRequest(ref)
 | |
| 							tm := client.ExpectVariablesResponse(t)
 | |
| 							checkChildren(t, tm, "tm", 1)
 | |
| 							ref = checkVarExact(t, tm, 0, "v", "", "[]map[string]main.astruct len: 1, cap: 1, [[...]]", "[]map[string]main.astruct", hasChildren)
 | |
| 							if ref > 0 {
 | |
| 								// Auto-loading of fully missing map chidlren happens here, but they get trancated at MaxArrayValues
 | |
| 								client.VariablesRequest(ref)
 | |
| 								tmV := client.ExpectVariablesResponse(t)
 | |
| 								checkChildren(t, tmV, "tm.v", 1)
 | |
| 								// TODO(polina): this evaluate name is not usable - it should be empty
 | |
| 								ref = checkVarRegex(t, tmV, 0, `\[0\]`, `\[0\]`, `map\[string\]main\.astruct \[.+\.\.\.`, `map\[string\]main\.astruct`, hasChildren)
 | |
| 								if ref > 0 {
 | |
| 									client.VariablesRequest(ref)
 | |
| 									tmV0 := client.ExpectVariablesResponse(t)
 | |
| 									checkChildren(t, tmV0, "tm.v[0]", 34)
 | |
| 								}
 | |
| 							}
 | |
| 						}
 | |
| 					}
 | |
| 
 | |
| 					// TODO(polina): need fully missing array/slice test case
 | |
| 
 | |
| 					// Zero slices, structs and maps are not treated as fully missing
 | |
| 					// See zsvar, zsslice,, emptyslice, emptymap, a0
 | |
| 				},
 | |
| 				disconnect: true,
 | |
| 			}})
 | |
| 	})
 | |
| 	runTest(t, "testvariables", func(client *daptest.Client, fixture protest.Fixture) {
 | |
| 		runDebugSessionWithBPs(t, client, "launch",
 | |
| 			// Launch
 | |
| 			func() {
 | |
| 				client.LaunchRequest("exec", fixture.Path, !stopOnEntry)
 | |
| 			},
 | |
| 			// Breakpoints are set within the program
 | |
| 			fixture.Source, []int{},
 | |
| 			[]onBreakpoint{{
 | |
| 				execute: func() {
 | |
| 					DefaultLoadConfig.FollowPointers = false
 | |
| 					defer func() { DefaultLoadConfig.FollowPointers = true }()
 | |
| 
 | |
| 					client.StackTraceRequest(1, 0, 0)
 | |
| 					client.ExpectStackTraceResponse(t)
 | |
| 
 | |
| 					loadvars := func(frame int) {
 | |
| 						client.ScopesRequest(frame)
 | |
| 						scopes := client.ExpectScopesResponse(t)
 | |
| 						localsRef := 0
 | |
| 						for _, s := range scopes.Body.Scopes {
 | |
| 							if s.Name == "Locals" {
 | |
| 								localsRef = s.VariablesReference
 | |
| 							}
 | |
| 						}
 | |
| 
 | |
| 						client.VariablesRequest(localsRef)
 | |
| 						locals := client.ExpectVariablesResponse(t)
 | |
| 
 | |
| 						// Interface auto-loaded when hitting LoadConfig.MaxVariableRecurse=1
 | |
| 
 | |
| 						ref := checkVarRegex(t, locals, -1, "ni", "ni", `\[\]interface {} len: 1, cap: 1, \[\[\]interface {} len: 1, cap: 1, \[\*\(\*interface {}\)\(0x[0-9a-f]+\)\]\]`, `\[\]interface {}`, hasChildren)
 | |
| 						if ref > 0 {
 | |
| 							client.VariablesRequest(ref)
 | |
| 							ni := client.ExpectVariablesResponse(t)
 | |
| 							ref = checkVarRegex(t, ni, 0, `\[0\]`, `ni\[0\]`, `interface \{\}\(\[\]interface \{\}\) \[\*\(\*interface \{\}\)\(0x[0-9a-f]+\)\]`, "interface {}", hasChildren)
 | |
| 							if ref > 0 {
 | |
| 								client.VariablesRequest(ref)
 | |
| 								niI1 := client.ExpectVariablesResponse(t)
 | |
| 								ref = checkVarRegex(t, niI1, 0, "data", `ni\[0\]\.\(data\)`, `\[\]interface {} len: 1, cap: 1, \[\*\(\*interface {}\)\(0x[0-9a-f]+\)`, `\[\]interface {}`, hasChildren)
 | |
| 								if ref > 0 {
 | |
| 									// Auto-loading happens here
 | |
| 									client.VariablesRequest(ref)
 | |
| 									niI1Data := client.ExpectVariablesResponse(t)
 | |
| 									ref = checkVarExact(t, niI1Data, 0, "[0]", "ni[0].(data)[0]", "interface {}(int) 123", "interface {}(int)", hasChildren)
 | |
| 									if ref > 0 {
 | |
| 										client.VariablesRequest(ref)
 | |
| 										niI1DataI2 := client.ExpectVariablesResponse(t)
 | |
| 										checkVarExact(t, niI1DataI2, 0, "data", "ni[0].(data)[0].(data)", "123", "int", noChildren)
 | |
| 									}
 | |
| 								}
 | |
| 							}
 | |
| 						}
 | |
| 
 | |
| 						// Pointer values loaded even with LoadConfig.FollowPointers=false
 | |
| 						checkVarExact(t, locals, -1, "a7", "a7", "*main.FooBar {Baz: 5, Bur: \"strum\"}", "*main.FooBar", hasChildren)
 | |
| 
 | |
| 						// Auto-loading works on results of evaluate expressions as well
 | |
| 						client.EvaluateRequest("a7", frame, "repl")
 | |
| 						checkEval(t, client.ExpectEvaluateResponse(t), "*main.FooBar {Baz: 5, Bur: \"strum\"}", hasChildren)
 | |
| 
 | |
| 						client.EvaluateRequest("&a7", frame, "repl")
 | |
| 						pa7 := client.ExpectEvaluateResponse(t)
 | |
| 						ref = checkEvalRegex(t, pa7, `\*\(\*main\.FooBar\)\(0x[0-9a-f]+\)`, hasChildren)
 | |
| 						if ref > 0 {
 | |
| 							client.VariablesRequest(ref)
 | |
| 							a7 := client.ExpectVariablesResponse(t)
 | |
| 							checkVarExact(t, a7, 0, "a7", "(*(&a7))", "*main.FooBar {Baz: 5, Bur: \"strum\"}", "*main.FooBar", hasChildren)
 | |
| 						}
 | |
| 					}
 | |
| 
 | |
| 					// Frame-independent loading expressions allow us to auto-load
 | |
| 					// variables in any frame, not just topmost.
 | |
| 					loadvars(1000 /*first topmost frame*/)
 | |
| 					// step into another function
 | |
| 					client.SetFunctionBreakpointsRequest([]dap.FunctionBreakpoint{{Name: "main.barfoo"}})
 | |
| 					client.ExpectSetFunctionBreakpointsResponse(t)
 | |
| 					client.ContinueRequest(1)
 | |
| 					client.ExpectContinueResponse(t)
 | |
| 					client.ExpectStoppedEvent(t)
 | |
| 					checkStop(t, client, 1, "main.barfoo", -1)
 | |
| 					loadvars(1001 /*second frame here is same as topmost above*/)
 | |
| 				},
 | |
| 				disconnect: true,
 | |
| 			}})
 | |
| 	})
 | |
| }
 | |
| 
 | |
| // TestVariablesMetadata exposes test cases where variables contain metadata that
 | |
| // can be accessed by requesting named variables.
 | |
| func TestVariablesMetadata(t *testing.T) {
 | |
| 	runTest(t, "testvariables2", func(client *daptest.Client, fixture protest.Fixture) {
 | |
| 		runDebugSessionWithBPs(t, client, "launch",
 | |
| 			// Launch
 | |
| 			func() {
 | |
| 				client.LaunchRequest("exec", fixture.Path, !stopOnEntry)
 | |
| 			},
 | |
| 			// Breakpoints are set within the program
 | |
| 			fixture.Source, []int{},
 | |
| 			[]onBreakpoint{{
 | |
| 				execute:    func() {},
 | |
| 				disconnect: false,
 | |
| 			}, {
 | |
| 				execute: func() {
 | |
| 					checkStop(t, client, 1, "main.main", -1)
 | |
| 
 | |
| 					client.VariablesRequest(localsScope)
 | |
| 					locals := client.ExpectVariablesResponse(t)
 | |
| 
 | |
| 					checkNamedChildren := func(ref int, name, typeStr string, vals []string, evaluate bool) {
 | |
| 						// byteslice, request named variables
 | |
| 						client.NamedVariablesRequest(ref)
 | |
| 						named := client.ExpectVariablesResponse(t)
 | |
| 						checkChildren(t, named, name, 1)
 | |
| 						checkVarExact(t, named, 0, "string()", "", "\"tèst\"", "string", false)
 | |
| 
 | |
| 						client.VariablesRequest(ref)
 | |
| 						all := client.ExpectVariablesResponse(t)
 | |
| 						checkChildren(t, all, name, len(vals)+1)
 | |
| 						checkVarExact(t, all, 0, "string()", "", "\"tèst\"", "string", false)
 | |
| 						for i, v := range vals {
 | |
| 							idx := fmt.Sprintf("[%d]", i)
 | |
| 							evalName := fmt.Sprintf("%s[%d]", name, i)
 | |
| 							if evaluate {
 | |
| 								evalName = fmt.Sprintf("(%s)[%d]", name, i)
 | |
| 							}
 | |
| 							checkVarExact(t, all, i+1, idx, evalName, v, typeStr, false)
 | |
| 						}
 | |
| 					}
 | |
| 
 | |
| 					bytes := []string{"116 = 0x74", "195 = 0xc3", "168 = 0xa8", "115 = 0x73", "116 = 0x74"}
 | |
| 					runes := []string{"116", "232", "115", "116"}
 | |
| 
 | |
| 					// byteslice
 | |
| 					ref := checkVarExactIndexed(t, locals, -1, "byteslice", "byteslice", "[]uint8 len: 5, cap: 5, [116,195,168,115,116]", "[]uint8", true, 5, 1)
 | |
| 					checkNamedChildren(ref, "byteslice", "uint8", bytes, false)
 | |
| 
 | |
| 					client.EvaluateRequest("byteslice", 0, "")
 | |
| 					got := client.ExpectEvaluateResponse(t)
 | |
| 					ref = checkEvalIndexed(t, got, "[]uint8 len: 5, cap: 5, [116,195,168,115,116]", hasChildren, 5, 1)
 | |
| 					checkNamedChildren(ref, "byteslice", "uint8", bytes, true)
 | |
| 
 | |
| 					// runeslice
 | |
| 					ref = checkVarExactIndexed(t, locals, -1, "runeslice", "runeslice", "[]int32 len: 4, cap: 4, [116,232,115,116]", "[]int32", true, 4, 1)
 | |
| 					checkNamedChildren(ref, "runeslice", "int32", runes, false)
 | |
| 
 | |
| 					client.EvaluateRequest("runeslice", 0, "repl")
 | |
| 					got = client.ExpectEvaluateResponse(t)
 | |
| 					ref = checkEvalIndexed(t, got, "[]int32 len: 4, cap: 4, [116,232,115,116]", hasChildren, 4, 1)
 | |
| 					checkNamedChildren(ref, "runeslice", "int32", runes, true)
 | |
| 
 | |
| 					// bytearray
 | |
| 					ref = checkVarExactIndexed(t, locals, -1, "bytearray", "bytearray", "[5]uint8 [116,195,168,115,116]", "[5]uint8", true, 5, 1)
 | |
| 					checkNamedChildren(ref, "bytearray", "uint8", bytes, false)
 | |
| 
 | |
| 					client.EvaluateRequest("bytearray", 0, "hover")
 | |
| 					got = client.ExpectEvaluateResponse(t)
 | |
| 					ref = checkEvalIndexed(t, got, "[5]uint8 [116,195,168,115,116]", hasChildren, 5, 1)
 | |
| 					checkNamedChildren(ref, "bytearray", "uint8", bytes, true)
 | |
| 
 | |
| 					// runearray
 | |
| 					ref = checkVarExactIndexed(t, locals, -1, "runearray", "runearray", "[4]int32 [116,232,115,116]", "[4]int32", true, 4, 1)
 | |
| 					checkNamedChildren(ref, "runearray", "int32", runes, false)
 | |
| 
 | |
| 					client.EvaluateRequest("runearray", 0, "watch")
 | |
| 					got = client.ExpectEvaluateResponse(t)
 | |
| 					ref = checkEvalIndexed(t, got, "[4]int32 [116,232,115,116]", hasChildren, 4, 1)
 | |
| 					checkNamedChildren(ref, "runearray", "int32", runes, true)
 | |
| 
 | |
| 					// string feature is not available with user-defined byte types
 | |
| 					ref = checkVarExactIndexed(t, locals, -1, "bytestypeslice", "bytestypeslice", "[]main.Byte len: 5, cap: 5, [116,195,168,115,116]", "[]main.Byte", true, 5, 1)
 | |
| 					client.NamedVariablesRequest(ref)
 | |
| 					namedchildren := client.ExpectVariablesResponse(t)
 | |
| 					checkChildren(t, namedchildren, "bytestypeslice as string", 0)
 | |
| 					ref = checkVarExactIndexed(t, locals, -1, "bytetypearray", "bytetypearray", "[5]main.Byte [116,195,168,115,116]", "[5]main.Byte", true, 5, 1)
 | |
| 					client.NamedVariablesRequest(ref)
 | |
| 					namedchildren = client.ExpectVariablesResponse(t)
 | |
| 					checkChildren(t, namedchildren, "bytetypearray as string", 0)
 | |
| 				},
 | |
| 				disconnect: true,
 | |
| 			}})
 | |
| 	})
 | |
| }
 | |
| 
 | |
| // TestGlobalScopeAndVariables launches the program with showGlobalVariables
 | |
| // arg set, executes to a breakpoint in the main package and tests that global
 | |
| // package main variables got loaded. It then steps into a function
 | |
| // in another package and tests that globals scope got updated to those vars.
 | |
| func TestGlobalScopeAndVariables(t *testing.T) {
 | |
| 	runTest(t, "consts", func(client *daptest.Client, fixture protest.Fixture) {
 | |
| 		runDebugSessionWithBPs(t, client, "launch",
 | |
| 			// Launch
 | |
| 			func() {
 | |
| 				client.LaunchRequestWithArgs(map[string]interface{}{
 | |
| 					"mode": "exec", "program": fixture.Path, "showGlobalVariables": true, "showRegisters": true,
 | |
| 				})
 | |
| 			},
 | |
| 			// Breakpoints are set within the program
 | |
| 			fixture.Source, []int{},
 | |
| 			[]onBreakpoint{{
 | |
| 				// Stop at line 36
 | |
| 				execute: func() {
 | |
| 					if runtime.GOARCH == "386" && goversion.VersionAfterOrEqual(runtime.Version(), 1, 18) && !goversion.VersionAfterOrEqual(runtime.Version(), 1, 21) {
 | |
| 						client.StepInRequest(1)
 | |
| 						client.ExpectStepInResponse(t)
 | |
| 						client.ExpectStoppedEvent(t)
 | |
| 					}
 | |
| 					client.StackTraceRequest(1, 0, 20)
 | |
| 					stack := client.ExpectStackTraceResponse(t)
 | |
| 					checkStackFramesExact(t, stack, "main.main", 36, 1000, 3, 3)
 | |
| 
 | |
| 					client.ScopesRequest(1000)
 | |
| 					scopes := client.ExpectScopesResponse(t)
 | |
| 					checkScope(t, scopes, 0, "Locals", localsScope)
 | |
| 					checkScope(t, scopes, 1, "Globals (package main)", globalsScope)
 | |
| 					checkScope(t, scopes, 2, "Registers", globalsScope+1)
 | |
| 
 | |
| 					client.VariablesRequest(globalsScope)
 | |
| 					client.ExpectVariablesResponse(t)
 | |
| 					// The program has no user-defined globals.
 | |
| 					// Depending on the Go version, there might
 | |
| 					// be some runtime globals (e.g. main..inittask)
 | |
| 					// so testing for the total number is too fragile.
 | |
| 
 | |
| 					// Step into pkg.AnotherMethod()
 | |
| 					client.StepInRequest(1)
 | |
| 					client.ExpectStepInResponse(t)
 | |
| 					client.ExpectStoppedEvent(t)
 | |
| 
 | |
| 					client.StackTraceRequest(1, 0, 20)
 | |
| 					stack = client.ExpectStackTraceResponse(t)
 | |
| 					checkStackFramesExact(t, stack, "", 13, 1000, 4, 4)
 | |
| 
 | |
| 					client.ScopesRequest(1000)
 | |
| 					scopes = client.ExpectScopesResponse(t)
 | |
| 					checkScope(t, scopes, 0, "Locals", localsScope)
 | |
| 					checkScope(t, scopes, 1, "Globals (package github.com/go-delve/delve/_fixtures/internal/dir0/pkg)", globalsScope)
 | |
| 
 | |
| 					client.VariablesRequest(globalsScope)
 | |
| 					globals := client.ExpectVariablesResponse(t)
 | |
| 					checkChildren(t, globals, "Globals", 1)
 | |
| 					ref := checkVarExact(t, globals, 0, "SomeVar", "github.com/go-delve/delve/_fixtures/internal/dir0/pkg.SomeVar", "pkg.SomeType {X: 0}", "github.com/go-delve/delve/_fixtures/internal/dir0/pkg.SomeType", hasChildren)
 | |
| 
 | |
| 					if ref > 0 {
 | |
| 						client.VariablesRequest(ref)
 | |
| 						somevar := client.ExpectVariablesResponse(t)
 | |
| 						checkChildren(t, somevar, "SomeVar", 1)
 | |
| 						// TODO(polina): unlike main.p, this prefix won't work
 | |
| 						checkVarExact(t, somevar, 0, "X", "github.com/go-delve/delve/_fixtures/internal/dir0/pkg.SomeVar.X", "0", "float64", noChildren)
 | |
| 					}
 | |
| 				},
 | |
| 				disconnect: false,
 | |
| 			}})
 | |
| 	})
 | |
| }
 | |
| 
 | |
| // TestRegisterScopeAndVariables launches the program with showRegisters
 | |
| // arg set, executes to a breakpoint in the main package and tests that the registers
 | |
| // got loaded. It then steps into a function in another package and tests that
 | |
| // the registers were updated by checking PC.
 | |
| func TestRegistersScopeAndVariables(t *testing.T) {
 | |
| 	if runtime.GOARCH == "ppc64le" {
 | |
| 		t.Skip("skipped on ppc64le: broken")
 | |
| 	}
 | |
| 	runTest(t, "consts", func(client *daptest.Client, fixture protest.Fixture) {
 | |
| 		runDebugSessionWithBPs(t, client, "launch",
 | |
| 			// Launch
 | |
| 			func() {
 | |
| 				client.LaunchRequestWithArgs(map[string]interface{}{
 | |
| 					"mode": "exec", "program": fixture.Path, "showRegisters": true,
 | |
| 				})
 | |
| 			},
 | |
| 			// Breakpoints are set within the program
 | |
| 			fixture.Source, []int{},
 | |
| 			[]onBreakpoint{{
 | |
| 				// Stop at line 36
 | |
| 				execute: func() {
 | |
| 					if runtime.GOARCH == "386" && goversion.VersionAfterOrEqual(runtime.Version(), 1, 18) && !goversion.VersionAfterOrEqual(runtime.Version(), 1, 21) {
 | |
| 						client.StepInRequest(1)
 | |
| 						client.ExpectStepInResponse(t)
 | |
| 						client.ExpectStoppedEvent(t)
 | |
| 					}
 | |
| 
 | |
| 					client.StackTraceRequest(1, 0, 20)
 | |
| 					stack := client.ExpectStackTraceResponse(t)
 | |
| 					checkStackFramesExact(t, stack, "main.main", 36, 1000, 3, 3)
 | |
| 
 | |
| 					client.ScopesRequest(1000)
 | |
| 					scopes := client.ExpectScopesResponse(t)
 | |
| 					checkScope(t, scopes, 0, "Locals", localsScope)
 | |
| 					registersScope := localsScope + 1
 | |
| 					checkScope(t, scopes, 1, "Registers", registersScope)
 | |
| 
 | |
| 					// Check that instructionPointer points to the InstructionPointerReference.
 | |
| 					pc, err := getPC(t, client, 1)
 | |
| 					if err != nil {
 | |
| 						t.Error(pc)
 | |
| 					}
 | |
| 
 | |
| 					client.VariablesRequest(registersScope)
 | |
| 					vr := client.ExpectVariablesResponse(t)
 | |
| 					if len(vr.Body.Variables) == 0 {
 | |
| 						t.Fatal("no registers returned")
 | |
| 					}
 | |
| 					idx := findPcReg(vr.Body.Variables)
 | |
| 					if idx < 0 {
 | |
| 						t.Fatalf("got %#v, want a reg with instruction pointer", vr.Body.Variables)
 | |
| 					}
 | |
| 					pcReg := vr.Body.Variables[idx]
 | |
| 					gotPc, err := strconv.ParseUint(pcReg.Value, 0, 64)
 | |
| 					if err != nil {
 | |
| 						t.Error(err)
 | |
| 					}
 | |
| 					name := strings.TrimSpace(pcReg.Name)
 | |
| 					if gotPc != pc || pcReg.EvaluateName != fmt.Sprintf("_%s", strings.ToUpper(name)) {
 | |
| 						t.Errorf("got %#v,\nwant Name=%s Value=%#x EvaluateName=%q", pcReg, name, pc, fmt.Sprintf("_%s", strings.ToUpper(name)))
 | |
| 					}
 | |
| 
 | |
| 					// The program has no user-defined globals.
 | |
| 					// Depending on the Go version, there might
 | |
| 					// be some runtime globals (e.g. main..inittask)
 | |
| 					// so testing for the total number is too fragile.
 | |
| 
 | |
| 					// Step into pkg.AnotherMethod()
 | |
| 					client.StepInRequest(1)
 | |
| 					client.ExpectStepInResponse(t)
 | |
| 					client.ExpectStoppedEvent(t)
 | |
| 
 | |
| 					client.StackTraceRequest(1, 0, 20)
 | |
| 					stack = client.ExpectStackTraceResponse(t)
 | |
| 					checkStackFramesExact(t, stack, "", 13, 1000, 4, 4)
 | |
| 
 | |
| 					client.ScopesRequest(1000)
 | |
| 					scopes = client.ExpectScopesResponse(t)
 | |
| 					checkScope(t, scopes, 0, "Locals", localsScope)
 | |
| 					checkScope(t, scopes, 1, "Registers", registersScope)
 | |
| 
 | |
| 					// Check that rip points to the InstructionPointerReference.
 | |
| 					pc, err = getPC(t, client, 1)
 | |
| 					if err != nil {
 | |
| 						t.Error(pc)
 | |
| 					}
 | |
| 					client.VariablesRequest(registersScope)
 | |
| 					vr = client.ExpectVariablesResponse(t)
 | |
| 					if len(vr.Body.Variables) == 0 {
 | |
| 						t.Fatal("no registers returned")
 | |
| 					}
 | |
| 
 | |
| 					idx = findPcReg(vr.Body.Variables)
 | |
| 					if idx < 0 {
 | |
| 						t.Fatalf("got %#v, want a reg with instruction pointer", vr.Body.Variables)
 | |
| 					}
 | |
| 					pcReg = vr.Body.Variables[idx]
 | |
| 					gotPc, err = strconv.ParseUint(pcReg.Value, 0, 64)
 | |
| 					if err != nil {
 | |
| 						t.Error(err)
 | |
| 					}
 | |
| 
 | |
| 					if gotPc != pc || pcReg.EvaluateName != fmt.Sprintf("_%s", strings.ToUpper(name)) {
 | |
| 						t.Errorf("got %#v,\nwant Name=%s Value=%#x EvaluateName=%q", pcReg, name, pc, fmt.Sprintf("_%s", strings.ToUpper(name)))
 | |
| 					}
 | |
| 				},
 | |
| 				disconnect: false,
 | |
| 			}})
 | |
| 	})
 | |
| }
 | |
| 
 | |
| func findPcReg(regs []dap.Variable) int {
 | |
| 	for i, reg := range regs {
 | |
| 		if isPcReg(reg) {
 | |
| 			return i
 | |
| 		}
 | |
| 	}
 | |
| 	return -1
 | |
| }
 | |
| 
 | |
| func isPcReg(reg dap.Variable) bool {
 | |
| 	pcRegNames := []string{"rip", "pc", "eip"}
 | |
| 	for _, name := range pcRegNames {
 | |
| 		if name == strings.TrimSpace(reg.Name) {
 | |
| 			return true
 | |
| 		}
 | |
| 	}
 | |
| 	return false
 | |
| }
 | |
| 
 | |
| // TestShadowedVariables executes to a breakpoint and checks the shadowed
 | |
| // variable is named correctly.
 | |
| func TestShadowedVariables(t *testing.T) {
 | |
| 	runTest(t, "testshadow", func(client *daptest.Client, fixture protest.Fixture) {
 | |
| 		runDebugSessionWithBPs(t, client, "launch",
 | |
| 			// Launch
 | |
| 			func() {
 | |
| 				client.LaunchRequestWithArgs(map[string]interface{}{
 | |
| 					"mode": "exec", "program": fixture.Path, "showGlobalVariables": true,
 | |
| 				})
 | |
| 			},
 | |
| 			// Breakpoints are set within the program
 | |
| 			fixture.Source, []int{},
 | |
| 			[]onBreakpoint{{
 | |
| 				// Stop at line 13
 | |
| 				execute: func() {
 | |
| 					client.StackTraceRequest(1, 0, 20)
 | |
| 					stack := client.ExpectStackTraceResponse(t)
 | |
| 					checkStackFramesExact(t, stack, "main.main", 13, 1000, 3, 3)
 | |
| 
 | |
| 					client.ScopesRequest(1000)
 | |
| 					scopes := client.ExpectScopesResponse(t)
 | |
| 					checkScope(t, scopes, 0, "Locals", localsScope)
 | |
| 					checkScope(t, scopes, 1, "Globals (package main)", globalsScope)
 | |
| 
 | |
| 					client.VariablesRequest(localsScope)
 | |
| 					locals := client.ExpectVariablesResponse(t)
 | |
| 
 | |
| 					checkVarExact(t, locals, 0, "(a)", "a", "0", "int", !hasChildren)
 | |
| 					checkVarExact(t, locals, 1, "a", "a", "1", "int", !hasChildren)
 | |
| 
 | |
| 					// Check that the non-shadowed of "a" is returned from evaluate request.
 | |
| 					validateEvaluateName(t, client, locals, 1)
 | |
| 				},
 | |
| 				disconnect: false,
 | |
| 			}})
 | |
| 	})
 | |
| }
 | |
| 
 | |
| // Tests that 'stackTraceDepth' from LaunchRequest is parsed and passed to
 | |
| // stacktrace requests handlers.
 | |
| func TestLaunchRequestWithStackTraceDepth(t *testing.T) {
 | |
| 	runTest(t, "increment", func(client *daptest.Client, fixture protest.Fixture) {
 | |
| 		var stResp *dap.StackTraceResponse
 | |
| 		runDebugSessionWithBPs(t, client, "launch",
 | |
| 			// Launch
 | |
| 			func() {
 | |
| 				client.LaunchRequestWithArgs(map[string]interface{}{
 | |
| 					"mode": "exec", "program": fixture.Path, "stackTraceDepth": 1,
 | |
| 				})
 | |
| 			},
 | |
| 			// Set breakpoints
 | |
| 			fixture.Source, []int{8},
 | |
| 			[]onBreakpoint{{ // Stop at line 8
 | |
| 				execute: func() {
 | |
| 					client.StackTraceRequest(1, 0, 0)
 | |
| 					stResp = client.ExpectStackTraceResponse(t)
 | |
| 					checkStackFramesHasMore(t, stResp, "main.Increment", 8, 1000, 1 /*returned*/, 2 /*available*/)
 | |
| 				},
 | |
| 				disconnect: false,
 | |
| 			}})
 | |
| 	})
 | |
| }
 | |
| 
 | |
| type Breakpoint struct {
 | |
| 	line      int
 | |
| 	path      string
 | |
| 	verified  bool
 | |
| 	msgPrefix string
 | |
| }
 | |
| 
 | |
| func expectSetBreakpointsResponse(t *testing.T, client *daptest.Client, bps []Breakpoint) {
 | |
| 	t.Helper()
 | |
| 	checkSetBreakpointsResponse(t, bps, client.ExpectSetBreakpointsResponse(t))
 | |
| }
 | |
| 
 | |
| func checkSetBreakpointsResponse(t *testing.T, bps []Breakpoint, got *dap.SetBreakpointsResponse) {
 | |
| 	t.Helper()
 | |
| 	checkBreakpoints(t, bps, got.Body.Breakpoints)
 | |
| }
 | |
| 
 | |
| func checkBreakpoints(t *testing.T, bps []Breakpoint, breakpoints []dap.Breakpoint) {
 | |
| 	t.Helper()
 | |
| 	if len(breakpoints) != len(bps) {
 | |
| 		t.Errorf("got %#v,\nwant len(Breakpoints)=%d", breakpoints, len(bps))
 | |
| 		return
 | |
| 	}
 | |
| 	for i, bp := range breakpoints {
 | |
| 		if bps[i].line < 0 && !bps[i].verified {
 | |
| 			if bp.Verified != bps[i].verified || !stringContainsCaseInsensitive(bp.Message, bps[i].msgPrefix) {
 | |
| 				t.Errorf("got breakpoints[%d] = %#v, \nwant %#v", i, bp, bps[i])
 | |
| 			}
 | |
| 			continue
 | |
| 		}
 | |
| 		if bp.Line != bps[i].line || bp.Verified != bps[i].verified || bp.Source.Path != bps[i].path ||
 | |
| 			!strings.HasPrefix(bp.Message, bps[i].msgPrefix) {
 | |
| 			t.Errorf("got breakpoints[%d] = %#v, \nwant %#v", i, bp, bps[i])
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // TestSetBreakpoint executes to a breakpoint and tests different
 | |
| // configurations of setBreakpoint requests.
 | |
| func TestSetBreakpoint(t *testing.T) {
 | |
| 	runTest(t, "loopprog", func(client *daptest.Client, fixture protest.Fixture) {
 | |
| 		runDebugSessionWithBPs(t, client, "launch",
 | |
| 			// Launch
 | |
| 			func() {
 | |
| 				client.LaunchRequest("exec", fixture.Path, !stopOnEntry)
 | |
| 			},
 | |
| 			// Set breakpoints
 | |
| 			fixture.Source, []int{16}, // b main.main
 | |
| 			[]onBreakpoint{{
 | |
| 				execute: func() {
 | |
| 					checkStop(t, client, 1, "main.main", 16)
 | |
| 
 | |
| 					// Set two breakpoints at the next two lines in main
 | |
| 					client.SetBreakpointsRequest(fixture.Source, []int{17, 18})
 | |
| 					expectSetBreakpointsResponse(t, client, []Breakpoint{{17, fixture.Source, true, ""}, {18, fixture.Source, true, ""}})
 | |
| 
 | |
| 					// Clear 17, reset 18
 | |
| 					client.SetBreakpointsRequest(fixture.Source, []int{18})
 | |
| 					expectSetBreakpointsResponse(t, client, []Breakpoint{{18, fixture.Source, true, ""}})
 | |
| 
 | |
| 					// Skip 17, continue to 18
 | |
| 					client.ContinueRequest(1)
 | |
| 					client.ExpectContinueResponse(t)
 | |
| 					client.ExpectStoppedEvent(t)
 | |
| 					checkStop(t, client, 1, "main.main", 18)
 | |
| 
 | |
| 					// Set another breakpoint inside the loop in loop(), twice to trigger error
 | |
| 					client.SetBreakpointsRequest(fixture.Source, []int{8, 8})
 | |
| 					expectSetBreakpointsResponse(t, client, []Breakpoint{{8, fixture.Source, true, ""}, {-1, "", false, "breakpoint already exists"}})
 | |
| 
 | |
| 					// Continue into the loop
 | |
| 					client.ContinueRequest(1)
 | |
| 					client.ExpectContinueResponse(t)
 | |
| 					client.ExpectStoppedEvent(t)
 | |
| 					checkStop(t, client, 1, "main.loop", 8)
 | |
| 					client.VariablesRequest(localsScope)
 | |
| 					locals := client.ExpectVariablesResponse(t)
 | |
| 					checkVarExact(t, locals, 0, "i", "i", "0", "int", noChildren) // i == 0
 | |
| 
 | |
| 					// Edit the breakpoint to add a condition
 | |
| 					client.SetBreakpointsRequestWithArgs(fixture.Source, []int{8}, map[int]string{8: "i == 3"}, nil, nil)
 | |
| 					expectSetBreakpointsResponse(t, client, []Breakpoint{{8, fixture.Source, true, ""}})
 | |
| 
 | |
| 					// Continue until condition is hit
 | |
| 					client.ContinueRequest(1)
 | |
| 					client.ExpectContinueResponse(t)
 | |
| 					client.ExpectStoppedEvent(t)
 | |
| 					checkStop(t, client, 1, "main.loop", 8)
 | |
| 					client.VariablesRequest(localsScope)
 | |
| 					locals = client.ExpectVariablesResponse(t)
 | |
| 					checkVarExact(t, locals, 0, "i", "i", "3", "int", noChildren) // i == 3
 | |
| 
 | |
| 					// Edit the breakpoint to remove a condition
 | |
| 					client.SetBreakpointsRequestWithArgs(fixture.Source, []int{8}, map[int]string{8: ""}, nil, nil)
 | |
| 					expectSetBreakpointsResponse(t, client, []Breakpoint{{8, fixture.Source, true, ""}})
 | |
| 
 | |
| 					// Continue for one more loop iteration
 | |
| 					client.ContinueRequest(1)
 | |
| 					client.ExpectContinueResponse(t)
 | |
| 					client.ExpectStoppedEvent(t)
 | |
| 					checkStop(t, client, 1, "main.loop", 8)
 | |
| 					client.VariablesRequest(localsScope)
 | |
| 					locals = client.ExpectVariablesResponse(t)
 | |
| 					checkVarExact(t, locals, 0, "i", "i", "4", "int", noChildren) // i == 4
 | |
| 
 | |
| 					// Set at a line without a statement
 | |
| 					client.SetBreakpointsRequest(fixture.Source, []int{1000})
 | |
| 					expectSetBreakpointsResponse(t, client, []Breakpoint{{-1, "", false, "could not find statement"}}) // all cleared, none set
 | |
| 				},
 | |
| 				// The program has an infinite loop, so we must kill it by disconnecting.
 | |
| 				disconnect: true,
 | |
| 			}})
 | |
| 	})
 | |
| }
 | |
| 
 | |
| // TestSetInstructionBreakpoint executes to a breakpoint and tests different
 | |
| // configurations of setInstructionBreakpoint requests.
 | |
| func TestSetInstructionBreakpoint(t *testing.T) {
 | |
| 	runTest(t, "loopprog", func(client *daptest.Client, fixture protest.Fixture) {
 | |
| 		runDebugSessionWithBPs(t, client, "launch",
 | |
| 			// Launch
 | |
| 			func() {
 | |
| 				client.LaunchRequest("exec", fixture.Path, !stopOnEntry)
 | |
| 			},
 | |
| 			// Set breakpoints
 | |
| 			fixture.Source, []int{16}, // b main.main
 | |
| 			[]onBreakpoint{{
 | |
| 				execute: func() {
 | |
| 					checkStop(t, client, 1, "main.main", 16)
 | |
| 
 | |
| 					// Set two breakpoints in the loop
 | |
| 					client.SetBreakpointsRequest(fixture.Source, []int{8, 9})
 | |
| 					expectSetBreakpointsResponse(t, client, []Breakpoint{{8, fixture.Source, true, ""}, {9, fixture.Source, true, ""}})
 | |
| 
 | |
| 					// Continue to the two breakpoints and get the instructionPointerReference.
 | |
| 					client.ContinueRequest(1)
 | |
| 					client.ExpectContinueResponse(t)
 | |
| 					client.ExpectStoppedEvent(t)
 | |
| 					checkStop(t, client, 1, "main.loop", 8)
 | |
| 
 | |
| 					client.StackTraceRequest(1, 0, 1)
 | |
| 					st := client.ExpectStackTraceResponse(t)
 | |
| 					if len(st.Body.StackFrames) < 1 {
 | |
| 						t.Fatalf("\ngot  %#v\nwant len(stackframes) => 1", st)
 | |
| 					}
 | |
| 					pc8 := st.Body.StackFrames[0].InstructionPointerReference
 | |
| 
 | |
| 					client.ContinueRequest(1)
 | |
| 					client.ExpectContinueResponse(t)
 | |
| 					client.ExpectStoppedEvent(t)
 | |
| 					checkStop(t, client, 1, "main.loop", 9)
 | |
| 
 | |
| 					client.StackTraceRequest(1, 0, 1)
 | |
| 					st = client.ExpectStackTraceResponse(t)
 | |
| 					if len(st.Body.StackFrames) < 1 {
 | |
| 						t.Fatalf("\ngot  %#v\nwant len(stackframes) => 1", st)
 | |
| 					}
 | |
| 					pc9 := st.Body.StackFrames[0].InstructionPointerReference
 | |
| 
 | |
| 					// Clear the source breakpoints.
 | |
| 					// TODO(suzmue): there is an existing issue that breakpoints with identical locations
 | |
| 					// from different setBreakpoints, setFunctionBreakpoints, setInstructionBreakpoints
 | |
| 					// requests will prevent subsequent ones from being set.
 | |
| 					client.SetBreakpointsRequest(fixture.Source, []int{})
 | |
| 					expectSetBreakpointsResponse(t, client, []Breakpoint{})
 | |
| 
 | |
| 					// Set the breakpoints using the instruction pointer references.
 | |
| 					client.SetInstructionBreakpointsRequest([]dap.InstructionBreakpoint{{InstructionReference: pc8}, {InstructionReference: pc9}})
 | |
| 					bps := client.ExpectSetInstructionBreakpointsResponse(t).Body.Breakpoints
 | |
| 					checkBreakpoints(t, []Breakpoint{{line: 8, path: fixture.Source, verified: true}, {line: 9, path: fixture.Source, verified: true}}, bps)
 | |
| 
 | |
| 					// Continue to the two breakpoints and get the instructionPointerReference.
 | |
| 					client.ContinueRequest(1)
 | |
| 					client.ExpectContinueResponse(t)
 | |
| 					client.ExpectStoppedEvent(t)
 | |
| 					checkStop(t, client, 1, "main.loop", 8)
 | |
| 
 | |
| 					client.ContinueRequest(1)
 | |
| 					client.ExpectContinueResponse(t)
 | |
| 					client.ExpectStoppedEvent(t)
 | |
| 					checkStop(t, client, 1, "main.loop", 9)
 | |
| 
 | |
| 					// Remove the breakpoint on line 8 and continue.
 | |
| 					client.SetInstructionBreakpointsRequest([]dap.InstructionBreakpoint{{InstructionReference: pc9}})
 | |
| 					bps = client.ExpectSetInstructionBreakpointsResponse(t).Body.Breakpoints
 | |
| 					checkBreakpoints(t, []Breakpoint{{line: 9, path: fixture.Source, verified: true}}, bps)
 | |
| 
 | |
| 					client.ContinueRequest(1)
 | |
| 					client.ExpectContinueResponse(t)
 | |
| 					client.ExpectStoppedEvent(t)
 | |
| 					checkStop(t, client, 1, "main.loop", 9)
 | |
| 
 | |
| 					// Set two breakpoints and expect an error on the second one.
 | |
| 					client.SetInstructionBreakpointsRequest([]dap.InstructionBreakpoint{{InstructionReference: pc8}, {InstructionReference: pc8}})
 | |
| 					bps = client.ExpectSetInstructionBreakpointsResponse(t).Body.Breakpoints
 | |
| 					checkBreakpoints(t, []Breakpoint{{line: 8, path: fixture.Source, verified: true}, {line: -1, path: "", verified: false, msgPrefix: "breakpoint already exists"}}, bps)
 | |
| 
 | |
| 					// Add a condition
 | |
| 					client.SetInstructionBreakpointsRequest([]dap.InstructionBreakpoint{{InstructionReference: pc8, Condition: "i == 100"}})
 | |
| 					bps = client.ExpectSetInstructionBreakpointsResponse(t).Body.Breakpoints
 | |
| 					checkBreakpoints(t, []Breakpoint{{line: 8, path: fixture.Source, verified: true}}, bps)
 | |
| 
 | |
| 					client.ContinueRequest(1)
 | |
| 					client.ExpectContinueResponse(t)
 | |
| 					client.ExpectStoppedEvent(t)
 | |
| 					checkStop(t, client, 1, "main.loop", 8)
 | |
| 
 | |
| 					client.VariablesRequest(localsScope)
 | |
| 					locals := client.ExpectVariablesResponse(t)
 | |
| 					checkVarExact(t, locals, 0, "i", "i", "100", "int", noChildren) // i == 100
 | |
| 				},
 | |
| 				// The program has an infinite loop, so we must kill it by disconnecting.
 | |
| 				disconnect: true,
 | |
| 			}})
 | |
| 	})
 | |
| }
 | |
| 
 | |
| func TestPauseAtStop(t *testing.T) {
 | |
| 	runTest(t, "loopprog", func(client *daptest.Client, fixture protest.Fixture) {
 | |
| 		runDebugSessionWithBPs(t, client, "launch",
 | |
| 			// Launch
 | |
| 			func() {
 | |
| 				client.LaunchRequest("exec", fixture.Path, !stopOnEntry)
 | |
| 			},
 | |
| 			// Set breakpoints
 | |
| 			fixture.Source, []int{16},
 | |
| 			[]onBreakpoint{{
 | |
| 				execute: func() {
 | |
| 					checkStop(t, client, 1, "main.main", 16)
 | |
| 
 | |
| 					client.SetBreakpointsRequest(fixture.Source, []int{6, 8})
 | |
| 					expectSetBreakpointsResponse(t, client, []Breakpoint{{6, fixture.Source, true, ""}, {8, fixture.Source, true, ""}})
 | |
| 
 | |
| 					// Send a pause request while stopped on a cleared breakpoint.
 | |
| 					client.PauseRequest(1)
 | |
| 					client.ExpectPauseResponse(t)
 | |
| 
 | |
| 					client.ContinueRequest(1)
 | |
| 					client.ExpectContinueResponse(t)
 | |
| 					client.ExpectStoppedEvent(t)
 | |
| 					checkStop(t, client, 1, "main.loop", 6)
 | |
| 
 | |
| 					// Send a pause request while stopped on a breakpoint.
 | |
| 					client.PauseRequest(1)
 | |
| 					client.ExpectPauseResponse(t)
 | |
| 
 | |
| 					client.ContinueRequest(1)
 | |
| 					client.ExpectContinueResponse(t)
 | |
| 					se := client.ExpectStoppedEvent(t)
 | |
| 					if se.Body.Reason != "breakpoint" {
 | |
| 						t.Errorf("got %#v, expected breakpoint", se)
 | |
| 					}
 | |
| 					checkStop(t, client, 1, "main.loop", 8)
 | |
| 
 | |
| 					// Send a pause request while stopped after stepping.
 | |
| 					client.NextRequest(1)
 | |
| 					client.ExpectNextResponse(t)
 | |
| 					client.ExpectStoppedEvent(t)
 | |
| 					checkStop(t, client, 1, "main.loop", 9)
 | |
| 
 | |
| 					client.PauseRequest(1)
 | |
| 					client.ExpectPauseResponse(t)
 | |
| 
 | |
| 					client.ContinueRequest(1)
 | |
| 					client.ExpectContinueResponse(t)
 | |
| 
 | |
| 					client.ExpectStoppedEvent(t)
 | |
| 					checkStop(t, client, 1, "main.loop", 8)
 | |
| 				},
 | |
| 				// The program has an infinite loop, so we must kill it by disconnecting.
 | |
| 				disconnect: true,
 | |
| 			}})
 | |
| 	})
 | |
| }
 | |
| 
 | |
| func checkHitBreakpointIds(t *testing.T, se *dap.StoppedEvent, reason string, id int) {
 | |
| 	if se.Body.ThreadId != 1 || se.Body.Reason != reason || len(se.Body.HitBreakpointIds) != 1 || se.Body.HitBreakpointIds[0] != id {
 | |
| 		t.Errorf("got %#v, want Reason=%q, ThreadId=1, HitBreakpointIds=[]int{%d}", se, reason, id)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // TestHitBreakpointIds executes to a breakpoint and tests that
 | |
| // the breakpoint ids in the stopped event are correct.
 | |
| func TestHitBreakpointIds(t *testing.T) {
 | |
| 	runTest(t, "locationsprog", func(client *daptest.Client, fixture protest.Fixture) {
 | |
| 		runDebugSessionWithBPs(t, client, "launch",
 | |
| 			// Launch
 | |
| 			func() {
 | |
| 				client.LaunchRequest("exec", fixture.Path, !stopOnEntry)
 | |
| 			},
 | |
| 			// Set breakpoints
 | |
| 			fixture.Source, []int{30}, // b main.main
 | |
| 			[]onBreakpoint{{
 | |
| 				execute: func() {
 | |
| 					checkStop(t, client, 1, "main.main", 30)
 | |
| 
 | |
| 					// Set two source breakpoints and two function breakpoints.
 | |
| 					client.SetBreakpointsRequest(fixture.Source, []int{23, 33})
 | |
| 					sourceBps := client.ExpectSetBreakpointsResponse(t).Body.Breakpoints
 | |
| 					checkBreakpoints(t, []Breakpoint{{line: 23, path: fixture.Source, verified: true}, {line: 33, path: fixture.Source, verified: true}}, sourceBps)
 | |
| 
 | |
| 					client.SetFunctionBreakpointsRequest([]dap.FunctionBreakpoint{
 | |
| 						{Name: "anotherFunction"},
 | |
| 						{Name: "anotherFunction:1"},
 | |
| 					})
 | |
| 					functionBps := client.ExpectSetFunctionBreakpointsResponse(t).Body.Breakpoints
 | |
| 					checkBreakpoints(t, []Breakpoint{{line: 26, path: fixture.Source, verified: true}, {line: 27, path: fixture.Source, verified: true}}, functionBps)
 | |
| 
 | |
| 					client.ContinueRequest(1)
 | |
| 					client.ExpectContinueResponse(t)
 | |
| 					se := client.ExpectStoppedEvent(t)
 | |
| 					checkHitBreakpointIds(t, se, "breakpoint", sourceBps[1].Id)
 | |
| 					checkStop(t, client, 1, "main.main", 33)
 | |
| 
 | |
| 					client.ContinueRequest(1)
 | |
| 					client.ExpectContinueResponse(t)
 | |
| 					se = client.ExpectStoppedEvent(t)
 | |
| 					checkHitBreakpointIds(t, se, "breakpoint", sourceBps[0].Id)
 | |
| 					checkStop(t, client, 1, "main.(*SomeType).SomeFunction", 23)
 | |
| 
 | |
| 					client.ContinueRequest(1)
 | |
| 					client.ExpectContinueResponse(t)
 | |
| 					se = client.ExpectStoppedEvent(t)
 | |
| 					checkHitBreakpointIds(t, se, "function breakpoint", functionBps[0].Id)
 | |
| 					checkStop(t, client, 1, "main.anotherFunction", 26)
 | |
| 
 | |
| 					client.ContinueRequest(1)
 | |
| 					client.ExpectContinueResponse(t)
 | |
| 					se = client.ExpectStoppedEvent(t)
 | |
| 
 | |
| 					checkHitBreakpointIds(t, se, "function breakpoint", functionBps[1].Id)
 | |
| 
 | |
| 					checkStop(t, client, 1, "main.anotherFunction", 27)
 | |
| 				},
 | |
| 				disconnect: true,
 | |
| 			}})
 | |
| 	})
 | |
| }
 | |
| 
 | |
| func stringContainsCaseInsensitive(got, want string) bool {
 | |
| 	return strings.Contains(strings.ToLower(got), strings.ToLower(want))
 | |
| }
 | |
| 
 | |
| // TestSetFunctionBreakpoints is inspired by service/test.TestClientServer_FindLocations.
 | |
| func TestSetFunctionBreakpoints(t *testing.T) {
 | |
| 	runTest(t, "locationsprog", func(client *daptest.Client, fixture protest.Fixture) {
 | |
| 		runDebugSessionWithBPs(t, client, "launch",
 | |
| 			// Launch
 | |
| 			func() {
 | |
| 				client.LaunchRequest("exec", fixture.Path, !stopOnEntry)
 | |
| 			},
 | |
| 			// Set breakpoints
 | |
| 			fixture.Source, []int{30}, // b main.main
 | |
| 			[]onBreakpoint{{
 | |
| 				execute: func() {
 | |
| 					checkStop(t, client, 1, "main.main", 30)
 | |
| 
 | |
| 					type Breakpoint struct {
 | |
| 						line       int
 | |
| 						sourceName string
 | |
| 						verified   bool
 | |
| 						errMsg     string
 | |
| 					}
 | |
| 					expectSetFunctionBreakpointsResponse := func(bps []Breakpoint) {
 | |
| 						t.Helper()
 | |
| 						got := client.ExpectSetFunctionBreakpointsResponse(t)
 | |
| 						if len(got.Body.Breakpoints) != len(bps) {
 | |
| 							t.Errorf("got %#v,\nwant len(Breakpoints)=%d", got, len(bps))
 | |
| 							return
 | |
| 						}
 | |
| 						for i, bp := range got.Body.Breakpoints {
 | |
| 							if bps[i].line < 0 && !bps[i].verified {
 | |
| 								if bp.Verified != bps[i].verified || !stringContainsCaseInsensitive(bp.Message, bps[i].errMsg) {
 | |
| 									t.Errorf("got breakpoints[%d] = %#v, \nwant %#v", i, bp, bps[i])
 | |
| 								}
 | |
| 								continue
 | |
| 							}
 | |
| 							// Some function breakpoints may be in packages that have been imported and we do not control, so
 | |
| 							// we do not always want to check breakpoint lines.
 | |
| 							if (bps[i].line >= 0 && bp.Line != bps[i].line) || bp.Verified != bps[i].verified || bp.Source.Name != bps[i].sourceName {
 | |
| 								t.Errorf("got breakpoints[%d] = %#v, \nwant %#v", i, bp, bps[i])
 | |
| 							}
 | |
| 						}
 | |
| 					}
 | |
| 
 | |
| 					client.SetFunctionBreakpointsRequest([]dap.FunctionBreakpoint{
 | |
| 						{Name: "anotherFunction"},
 | |
| 					})
 | |
| 					expectSetFunctionBreakpointsResponse([]Breakpoint{{26, filepath.Base(fixture.Source), true, ""}})
 | |
| 
 | |
| 					client.SetFunctionBreakpointsRequest([]dap.FunctionBreakpoint{
 | |
| 						{Name: "main.anotherFunction"},
 | |
| 					})
 | |
| 					expectSetFunctionBreakpointsResponse([]Breakpoint{{26, filepath.Base(fixture.Source), true, ""}})
 | |
| 
 | |
| 					client.SetFunctionBreakpointsRequest([]dap.FunctionBreakpoint{
 | |
| 						{Name: "SomeType.String"},
 | |
| 					})
 | |
| 					expectSetFunctionBreakpointsResponse([]Breakpoint{{14, filepath.Base(fixture.Source), true, ""}})
 | |
| 
 | |
| 					client.SetFunctionBreakpointsRequest([]dap.FunctionBreakpoint{
 | |
| 						{Name: "(*SomeType).String"},
 | |
| 					})
 | |
| 					expectSetFunctionBreakpointsResponse([]Breakpoint{{14, filepath.Base(fixture.Source), true, ""}})
 | |
| 
 | |
| 					client.SetFunctionBreakpointsRequest([]dap.FunctionBreakpoint{
 | |
| 						{Name: "main.SomeType.String"},
 | |
| 					})
 | |
| 					expectSetFunctionBreakpointsResponse([]Breakpoint{{14, filepath.Base(fixture.Source), true, ""}})
 | |
| 
 | |
| 					client.SetFunctionBreakpointsRequest([]dap.FunctionBreakpoint{
 | |
| 						{Name: "main.(*SomeType).String"},
 | |
| 					})
 | |
| 					expectSetFunctionBreakpointsResponse([]Breakpoint{{14, filepath.Base(fixture.Source), true, ""}})
 | |
| 
 | |
| 					// Test line offsets
 | |
| 					client.SetFunctionBreakpointsRequest([]dap.FunctionBreakpoint{
 | |
| 						{Name: "main.anotherFunction:1"},
 | |
| 					})
 | |
| 					expectSetFunctionBreakpointsResponse([]Breakpoint{{27, filepath.Base(fixture.Source), true, ""}})
 | |
| 
 | |
| 					// Test function names in imported package.
 | |
| 					// Issue #275
 | |
| 					client.SetFunctionBreakpointsRequest([]dap.FunctionBreakpoint{
 | |
| 						{Name: "io/ioutil.ReadFile"},
 | |
| 					})
 | |
| 					expectSetFunctionBreakpointsResponse([]Breakpoint{{-1, "ioutil.go", true, ""}})
 | |
| 
 | |
| 					// Issue #296
 | |
| 					client.SetFunctionBreakpointsRequest([]dap.FunctionBreakpoint{
 | |
| 						{Name: "/io/ioutil.ReadFile"},
 | |
| 					})
 | |
| 					expectSetFunctionBreakpointsResponse([]Breakpoint{{-1, "ioutil.go", true, ""}})
 | |
| 					client.SetFunctionBreakpointsRequest([]dap.FunctionBreakpoint{
 | |
| 						{Name: "ioutil.ReadFile"},
 | |
| 					})
 | |
| 					expectSetFunctionBreakpointsResponse([]Breakpoint{{-1, "ioutil.go", true, ""}})
 | |
| 
 | |
| 					// Function Breakpoint name also accepts breakpoints that are specified as file:line.
 | |
| 					// TODO(suzmue): We could return an error, but it probably is not necessary since breakpoints,
 | |
| 					// and function breakpoints come in with different requests.
 | |
| 					client.SetFunctionBreakpointsRequest([]dap.FunctionBreakpoint{
 | |
| 						{Name: fmt.Sprintf("%s:14", fixture.Source)},
 | |
| 					})
 | |
| 					expectSetFunctionBreakpointsResponse([]Breakpoint{{14, filepath.Base(fixture.Source), true, ""}})
 | |
| 					client.SetFunctionBreakpointsRequest([]dap.FunctionBreakpoint{
 | |
| 						{Name: fmt.Sprintf("%s:14", filepath.Base(fixture.Source))},
 | |
| 					})
 | |
| 					expectSetFunctionBreakpointsResponse([]Breakpoint{{14, filepath.Base(fixture.Source), true, ""}})
 | |
| 
 | |
| 					// Expect error for ambiguous function name.
 | |
| 					client.SetFunctionBreakpointsRequest([]dap.FunctionBreakpoint{
 | |
| 						{Name: "String"},
 | |
| 					})
 | |
| 					expectSetFunctionBreakpointsResponse([]Breakpoint{{-1, "", false, "Location \"String\" ambiguous"}})
 | |
| 
 | |
| 					client.SetFunctionBreakpointsRequest([]dap.FunctionBreakpoint{
 | |
| 						{Name: "main.String"},
 | |
| 					})
 | |
| 					expectSetFunctionBreakpointsResponse([]Breakpoint{{-1, "", false, "Location \"main.String\" ambiguous"}})
 | |
| 
 | |
| 					// Expect error for function that does not exist.
 | |
| 					client.SetFunctionBreakpointsRequest([]dap.FunctionBreakpoint{
 | |
| 						{Name: "fakeFunction"},
 | |
| 					})
 | |
| 					expectSetFunctionBreakpointsResponse([]Breakpoint{{-1, "", false, "location \"fakeFunction\" not found"}})
 | |
| 
 | |
| 					// Expect error for negative line number.
 | |
| 					client.SetFunctionBreakpointsRequest([]dap.FunctionBreakpoint{
 | |
| 						{Name: "main.anotherFunction:-1"},
 | |
| 					})
 | |
| 					expectSetFunctionBreakpointsResponse([]Breakpoint{{-1, "", false, "line offset negative or not a number"}})
 | |
| 
 | |
| 					// Expect error when function name is regex.
 | |
| 					client.SetFunctionBreakpointsRequest([]dap.FunctionBreakpoint{
 | |
| 						{Name: `/^.*String.*$/`},
 | |
| 					})
 | |
| 					expectSetFunctionBreakpointsResponse([]Breakpoint{{-1, "", false, "breakpoint name \"/^.*String.*$/\" could not be parsed as a function"}})
 | |
| 
 | |
| 					// Expect error when function name is an offset.
 | |
| 					client.SetFunctionBreakpointsRequest([]dap.FunctionBreakpoint{
 | |
| 						{Name: "+1"},
 | |
| 					})
 | |
| 					expectSetFunctionBreakpointsResponse([]Breakpoint{{-1, filepath.Base(fixture.Source), false, "breakpoint name \"+1\" could not be parsed as a function"}})
 | |
| 
 | |
| 					// Expect error when function name is a line number.
 | |
| 					client.SetFunctionBreakpointsRequest([]dap.FunctionBreakpoint{
 | |
| 						{Name: "14"},
 | |
| 					})
 | |
| 					expectSetFunctionBreakpointsResponse([]Breakpoint{{-1, filepath.Base(fixture.Source), false, "breakpoint name \"14\" could not be parsed as a function"}})
 | |
| 
 | |
| 					// Expect error when function name is an address.
 | |
| 					client.SetFunctionBreakpointsRequest([]dap.FunctionBreakpoint{
 | |
| 						{Name: "*b"},
 | |
| 					})
 | |
| 					expectSetFunctionBreakpointsResponse([]Breakpoint{{-1, filepath.Base(fixture.Source), false, "breakpoint name \"*b\" could not be parsed as a function"}})
 | |
| 
 | |
| 					// Expect error when function name is a relative path.
 | |
| 					client.SetFunctionBreakpointsRequest([]dap.FunctionBreakpoint{
 | |
| 						{Name: fmt.Sprintf(".%s%s:14", string(filepath.Separator), filepath.Base(fixture.Source))},
 | |
| 					})
 | |
| 					// This relative path could also be caught by the parser, so we should not match the error message.
 | |
| 					expectSetFunctionBreakpointsResponse([]Breakpoint{{-1, filepath.Base(fixture.Source), false, ""}})
 | |
| 					client.SetFunctionBreakpointsRequest([]dap.FunctionBreakpoint{
 | |
| 						{Name: fmt.Sprintf("..%s%s:14", string(filepath.Separator), filepath.Base(fixture.Source))},
 | |
| 					})
 | |
| 					// This relative path could also be caught by the parser, so we should not match the error message.
 | |
| 					expectSetFunctionBreakpointsResponse([]Breakpoint{{-1, filepath.Base(fixture.Source), false, ""}})
 | |
| 
 | |
| 					// Test multiple function breakpoints.
 | |
| 					client.SetFunctionBreakpointsRequest([]dap.FunctionBreakpoint{
 | |
| 						{Name: "SomeType.String"}, {Name: "anotherFunction"},
 | |
| 					})
 | |
| 					expectSetFunctionBreakpointsResponse([]Breakpoint{{14, filepath.Base(fixture.Source), true, ""}, {26, filepath.Base(fixture.Source), true, ""}})
 | |
| 
 | |
| 					// Test multiple breakpoints to the same location.
 | |
| 					client.SetFunctionBreakpointsRequest([]dap.FunctionBreakpoint{
 | |
| 						{Name: "SomeType.String"}, {Name: "(*SomeType).String"},
 | |
| 					})
 | |
| 					expectSetFunctionBreakpointsResponse([]Breakpoint{{14, filepath.Base(fixture.Source), true, ""}, {-1, "", false, "breakpoint exists"}})
 | |
| 
 | |
| 					// Set two breakpoints at SomeType.String and SomeType.SomeFunction.
 | |
| 					client.SetFunctionBreakpointsRequest([]dap.FunctionBreakpoint{
 | |
| 						{Name: "SomeType.String"}, {Name: "SomeType.SomeFunction"},
 | |
| 					})
 | |
| 					expectSetFunctionBreakpointsResponse([]Breakpoint{{14, filepath.Base(fixture.Source), true, ""}, {22, filepath.Base(fixture.Source), true, ""}})
 | |
| 
 | |
| 					// Clear SomeType.String, reset SomeType.SomeFunction (SomeType.String is called before SomeType.SomeFunction).
 | |
| 					client.SetFunctionBreakpointsRequest([]dap.FunctionBreakpoint{
 | |
| 						{Name: "SomeType.SomeFunction"},
 | |
| 					})
 | |
| 					expectSetFunctionBreakpointsResponse([]Breakpoint{{22, filepath.Base(fixture.Source), true, ""}})
 | |
| 
 | |
| 					// Expect the next breakpoint to be at SomeType.SomeFunction.
 | |
| 					client.ContinueRequest(1)
 | |
| 					client.ExpectContinueResponse(t)
 | |
| 
 | |
| 					if se := client.ExpectStoppedEvent(t); se.Body.Reason != "function breakpoint" || se.Body.ThreadId != 1 {
 | |
| 						t.Errorf("got %#v, want Reason=\"function breakpoint\", ThreadId=1", se)
 | |
| 					}
 | |
| 					checkStop(t, client, 1, "main.(*SomeType).SomeFunction", 22)
 | |
| 
 | |
| 					// Set a breakpoint at the next line in the program.
 | |
| 					client.SetBreakpointsRequest(fixture.Source, []int{23})
 | |
| 					got := client.ExpectSetBreakpointsResponse(t)
 | |
| 					if len(got.Body.Breakpoints) != 1 {
 | |
| 						t.Errorf("got %#v,\nwant len(Breakpoints)=%d", got, 1)
 | |
| 						return
 | |
| 					}
 | |
| 					bp := got.Body.Breakpoints[0]
 | |
| 					if bp.Line != 23 || bp.Verified != true || bp.Source.Path != fixture.Source {
 | |
| 						t.Errorf("got breakpoints[0] = %#v, \nwant Line=23 Verified=true Source.Path=%q", bp, fixture.Source)
 | |
| 					}
 | |
| 
 | |
| 					// Set a function breakpoint, this should not clear the breakpoint that was set in the previous setBreakpoints request.
 | |
| 					client.SetFunctionBreakpointsRequest([]dap.FunctionBreakpoint{
 | |
| 						{Name: "anotherFunction"},
 | |
| 					})
 | |
| 					expectSetFunctionBreakpointsResponse([]Breakpoint{{26, filepath.Base(fixture.Source), true, ""}})
 | |
| 
 | |
| 					// Expect the next breakpoint to be at line 23.
 | |
| 					client.ContinueRequest(1)
 | |
| 					client.ExpectContinueResponse(t)
 | |
| 
 | |
| 					if se := client.ExpectStoppedEvent(t); se.Body.Reason != "breakpoint" || se.Body.ThreadId != 1 {
 | |
| 						t.Errorf("got %#v, want Reason=\"breakpoint\", ThreadId=1", se)
 | |
| 					}
 | |
| 					checkStop(t, client, 1, "main.(*SomeType).SomeFunction", 23)
 | |
| 
 | |
| 					// Set a breakpoint, this should not clear the breakpoint that was set in the previous setFunctionBreakpoints request.
 | |
| 					client.SetBreakpointsRequest(fixture.Source, []int{37})
 | |
| 					got = client.ExpectSetBreakpointsResponse(t)
 | |
| 					if len(got.Body.Breakpoints) != 1 {
 | |
| 						t.Errorf("got %#v,\nwant len(Breakpoints)=%d", got, 1)
 | |
| 						return
 | |
| 					}
 | |
| 					bp = got.Body.Breakpoints[0]
 | |
| 					if bp.Line != 37 || bp.Verified != true || bp.Source.Path != fixture.Source {
 | |
| 						t.Errorf("got breakpoints[0] = %#v, \nwant Line=23 Verified=true Source.Path=%q", bp, fixture.Source)
 | |
| 					}
 | |
| 
 | |
| 					// Expect the next breakpoint to be at line anotherFunction.
 | |
| 					client.ContinueRequest(1)
 | |
| 					client.ExpectContinueResponse(t)
 | |
| 
 | |
| 					if se := client.ExpectStoppedEvent(t); se.Body.Reason != "function breakpoint" || se.Body.ThreadId != 1 {
 | |
| 						t.Errorf("got %#v, want Reason=\"function breakpoint\", ThreadId=1", se)
 | |
| 					}
 | |
| 					checkStop(t, client, 1, "main.anotherFunction", 26)
 | |
| 				},
 | |
| 				disconnect: true,
 | |
| 			}})
 | |
| 	})
 | |
| }
 | |
| 
 | |
| // TestLogPoints executes to a breakpoint and tests that log points
 | |
| // send OutputEvents and do not halt program execution.
 | |
| func TestLogPoints(t *testing.T) {
 | |
| 	runTest(t, "callme", func(client *daptest.Client, fixture protest.Fixture) {
 | |
| 		runDebugSessionWithBPs(t, client, "launch",
 | |
| 			// Launch
 | |
| 			func() {
 | |
| 				client.LaunchRequest("exec", fixture.Path, !stopOnEntry)
 | |
| 			},
 | |
| 			// Set breakpoints
 | |
| 			fixture.Source, []int{23},
 | |
| 			[]onBreakpoint{{
 | |
| 				// Stop at line 23
 | |
| 				execute: func() {
 | |
| 					checkStop(t, client, 1, "main.main", 23)
 | |
| 					bps := []int{6, 25, 27, 16}
 | |
| 					logMessages := map[int]string{6: "{i*2}: in callme!", 16: "in callme2!"}
 | |
| 					client.SetBreakpointsRequestWithArgs(fixture.Source, bps, nil, nil, logMessages)
 | |
| 					client.ExpectSetBreakpointsResponse(t)
 | |
| 
 | |
| 					client.ContinueRequest(1)
 | |
| 					client.ExpectContinueResponse(t)
 | |
| 
 | |
| 					for i := 0; i < 5; i++ {
 | |
| 						se := client.ExpectStoppedEvent(t)
 | |
| 						if se.Body.Reason != "breakpoint" || se.Body.ThreadId != 1 {
 | |
| 							t.Errorf("got stopped event = %#v, \nwant Reason=\"breakpoint\" ThreadId=1", se)
 | |
| 						}
 | |
| 						checkStop(t, client, 1, "main.main", 25)
 | |
| 
 | |
| 						client.ContinueRequest(1)
 | |
| 						client.ExpectContinueResponse(t)
 | |
| 						checkLogMessage(t, client.ExpectOutputEvent(t), 1, fmt.Sprintf("%d: in callme!", i*2), fixture.Source, 6)
 | |
| 					}
 | |
| 					se := client.ExpectStoppedEvent(t)
 | |
| 					if se.Body.Reason != "breakpoint" || se.Body.ThreadId != 1 {
 | |
| 						t.Errorf("got stopped event = %#v, \nwant Reason=\"breakpoint\" ThreadId=1", se)
 | |
| 					}
 | |
| 					checkStop(t, client, 1, "main.main", 27)
 | |
| 
 | |
| 					client.NextRequest(1)
 | |
| 					client.ExpectNextResponse(t)
 | |
| 
 | |
| 					checkLogMessage(t, client.ExpectOutputEvent(t), 1, "in callme2!", fixture.Source, 16)
 | |
| 
 | |
| 					se = client.ExpectStoppedEvent(t)
 | |
| 					if se.Body.Reason != "step" || se.Body.ThreadId != 1 {
 | |
| 						t.Errorf("got stopped event = %#v, \nwant Reason=\"step\" ThreadId=1", se)
 | |
| 					}
 | |
| 					checkStop(t, client, 1, "main.main", 28)
 | |
| 				},
 | |
| 				disconnect: true,
 | |
| 			}})
 | |
| 	})
 | |
| }
 | |
| 
 | |
| // TestLogPointsShowFullValue tests that log points will not truncate the string value.
 | |
| func TestLogPointsShowFullValue(t *testing.T) {
 | |
| 	runTest(t, "longstrings", func(client *daptest.Client, fixture protest.Fixture) {
 | |
| 		runDebugSessionWithBPs(t, client, "launch",
 | |
| 			// Launch
 | |
| 			func() {
 | |
| 				client.LaunchRequest("exec", fixture.Path, !stopOnEntry)
 | |
| 			},
 | |
| 			// Set breakpoints
 | |
| 			fixture.Source, []int{16},
 | |
| 			[]onBreakpoint{{
 | |
| 				execute: func() {
 | |
| 					checkStop(t, client, 1, "main.main", 16)
 | |
| 					bps := []int{19}
 | |
| 					logMessages := map[int]string{19: "{&s4097}"}
 | |
| 					client.SetBreakpointsRequestWithArgs(fixture.Source, bps, nil, nil, logMessages)
 | |
| 					client.ExpectSetBreakpointsResponse(t)
 | |
| 
 | |
| 					client.ContinueRequest(1)
 | |
| 					client.ExpectContinueResponse(t)
 | |
| 					checkLogMessage(t, client.ExpectOutputEvent(t), 1, "*\"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx...+3585 more\"", fixture.Source, 19)
 | |
| 
 | |
| 					se := client.ExpectStoppedEvent(t)
 | |
| 					if se.Body.Reason != "breakpoint" || se.Body.ThreadId != 1 {
 | |
| 						t.Errorf("got stopped event = %#v, \nwant Reason=\"breakpoint\" ThreadId=1", se)
 | |
| 					}
 | |
| 					checkStop(t, client, 1, "main.main", 22)
 | |
| 				},
 | |
| 				disconnect: true,
 | |
| 			}})
 | |
| 	})
 | |
| }
 | |
| 
 | |
| func checkLogMessage(t *testing.T, oe *dap.OutputEvent, goid int, text, path string, line int) {
 | |
| 	t.Helper()
 | |
| 	prefix := "> [Go "
 | |
| 	if goid >= 0 {
 | |
| 		prefix += strconv.Itoa(goid) + "]"
 | |
| 	}
 | |
| 	if oe.Body.Category != "stdout" || !strings.HasPrefix(oe.Body.Output, prefix) || !strings.HasSuffix(oe.Body.Output, text+"\n") {
 | |
| 		t.Errorf("got output event = %#v, \nwant Category=\"stdout\" Output=\"%s: %s\\n\"", oe, prefix, text)
 | |
| 	}
 | |
| 	if oe.Body.Line != line || oe.Body.Source == nil || oe.Body.Source.Path != path {
 | |
| 		t.Errorf("got output event = %#v, \nwant Line=%d Source.Path=%s", oe, line, path)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // TestHaltPreventsAutoResume tests that a pause request issued while processing
 | |
| // log messages will result in a real stop.
 | |
| func TestHaltPreventsAutoResume(t *testing.T) {
 | |
| 	runTest(t, "callme", func(client *daptest.Client, fixture protest.Fixture) {
 | |
| 		runDebugSessionWithBPs(t, client, "launch", // Launch
 | |
| 			func() {
 | |
| 				client.LaunchRequest("exec", fixture.Path, !stopOnEntry)
 | |
| 			},
 | |
| 			// Set breakpoints
 | |
| 			fixture.Source, []int{23},
 | |
| 			[]onBreakpoint{{
 | |
| 				execute: func() {
 | |
| 					savedResumeOnce := resumeOnceAndCheckStop
 | |
| 					defer func() {
 | |
| 						resumeOnceAndCheckStop = savedResumeOnce
 | |
| 					}()
 | |
| 					checkStop(t, client, 1, "main.main", 23)
 | |
| 					bps := []int{6, 25}
 | |
| 					logMessages := map[int]string{6: "in callme!"}
 | |
| 					client.SetBreakpointsRequestWithArgs(fixture.Source, bps, nil, nil, logMessages)
 | |
| 					client.ExpectSetBreakpointsResponse(t)
 | |
| 
 | |
| 					for i := 0; i < 5; i++ {
 | |
| 						// Reset the handler to the default behavior.
 | |
| 						resumeOnceAndCheckStop = savedResumeOnce
 | |
| 
 | |
| 						// Expect a pause request while stopped not to interrupt continue.
 | |
| 						client.PauseRequest(1)
 | |
| 						client.ExpectPauseResponse(t)
 | |
| 
 | |
| 						client.ContinueRequest(1)
 | |
| 						client.ExpectContinueResponse(t)
 | |
| 						se := client.ExpectStoppedEvent(t)
 | |
| 						if se.Body.Reason != "breakpoint" || se.Body.ThreadId != 1 {
 | |
| 							t.Errorf("got stopped event = %#v, \nwant Reason=\"breakpoint\" ThreadId=1", se)
 | |
| 						}
 | |
| 						checkStop(t, client, 1, "main.main", 25)
 | |
| 
 | |
| 						pauseDoneChan := make(chan struct{}, 1)
 | |
| 						outputDoneChan := make(chan struct{}, 1)
 | |
| 						// Send a halt request when trying to resume the program after being
 | |
| 						// interrupted. This should allow the log message to be processed,
 | |
| 						// but keep the process from continuing beyond the line.
 | |
| 						resumeOnceAndCheckStop = func(s *Session, command string, allowNextStateChange *syncflag) (*api.DebuggerState, error) {
 | |
| 							// This should trigger after the log message is sent, but before
 | |
| 							// execution is resumed.
 | |
| 							if command == api.DirectionCongruentContinue {
 | |
| 								go func() {
 | |
| 									<-outputDoneChan
 | |
| 									defer close(pauseDoneChan)
 | |
| 									client.PauseRequest(1)
 | |
| 									client.ExpectPauseResponse(t)
 | |
| 								}()
 | |
| 								// Wait for the pause to be complete.
 | |
| 								<-pauseDoneChan
 | |
| 							}
 | |
| 							return s.resumeOnceAndCheckStop(command, allowNextStateChange)
 | |
| 						}
 | |
| 
 | |
| 						client.ContinueRequest(1)
 | |
| 						client.ExpectContinueResponse(t)
 | |
| 						checkLogMessage(t, client.ExpectOutputEvent(t), 1, "in callme!", fixture.Source, 6)
 | |
| 						// Signal that the output event has been received.
 | |
| 						close(outputDoneChan)
 | |
| 						// Wait for the pause to be complete.
 | |
| 						<-pauseDoneChan
 | |
| 						se = client.ExpectStoppedEvent(t)
 | |
| 						if se.Body.Reason != "pause" {
 | |
| 							t.Errorf("got stopped event = %#v, \nwant Reason=\"pause\"", se)
 | |
| 						}
 | |
| 						checkStop(t, client, 1, "main.callme", 6)
 | |
| 					}
 | |
| 				},
 | |
| 				disconnect: true,
 | |
| 			}})
 | |
| 	})
 | |
| }
 | |
| 
 | |
| // TestConcurrentBreakpointsLogPoints tests that a breakpoint set in the main
 | |
| // goroutine is hit the correct number of times and log points set in the
 | |
| // children goroutines produce the correct number of output events.
 | |
| func TestConcurrentBreakpointsLogPoints(t *testing.T) {
 | |
| 	if runtime.GOOS == "windows" && runtime.GOARCH == "arm64" {
 | |
| 		t.Skip("broken")
 | |
| 	}
 | |
| 	tests := []struct {
 | |
| 		name        string
 | |
| 		fixture     string
 | |
| 		start       int
 | |
| 		breakpoints []int
 | |
| 	}{
 | |
| 		{
 | |
| 			name:        "source breakpoints",
 | |
| 			fixture:     "goroutinestackprog",
 | |
| 			breakpoints: []int{23},
 | |
| 		},
 | |
| 		{
 | |
| 			name:    "hardcoded breakpoint",
 | |
| 			fixture: "goroutinebreak",
 | |
| 		},
 | |
| 	}
 | |
| 	for _, tt := range tests {
 | |
| 		t.Run(tt.name, func(t *testing.T) {
 | |
| 			runTest(t, tt.fixture, func(client *daptest.Client, fixture protest.Fixture) {
 | |
| 				client.InitializeRequest()
 | |
| 				client.ExpectInitializeResponseAndCapabilities(t)
 | |
| 
 | |
| 				client.LaunchRequest("exec", fixture.Path, !stopOnEntry)
 | |
| 				client.ExpectInitializedEvent(t)
 | |
| 				client.ExpectLaunchResponse(t)
 | |
| 
 | |
| 				bps := append([]int{8}, tt.breakpoints...)
 | |
| 				logMessages := map[int]string{8: "hello"}
 | |
| 				client.SetBreakpointsRequestWithArgs(fixture.Source, bps, nil, nil, logMessages)
 | |
| 				client.ExpectSetBreakpointsResponse(t)
 | |
| 
 | |
| 				client.ConfigurationDoneRequest()
 | |
| 				client.ExpectConfigurationDoneResponse(t)
 | |
| 
 | |
| 				// There may be up to 1 breakpoint and any number of log points that are
 | |
| 				// hit concurrently. We should get a stopped event every time the breakpoint
 | |
| 				// is hit and an output event for each log point hit.
 | |
| 				var oeCount, seCount int
 | |
| 				for oeCount < 10 || seCount < 10 {
 | |
| 					switch m := client.ExpectMessage(t).(type) {
 | |
| 					case *dap.StoppedEvent:
 | |
| 						if m.Body.Reason != "breakpoint" || !m.Body.AllThreadsStopped || m.Body.ThreadId != 1 {
 | |
| 							t.Errorf("\ngot  %#v\nwant Reason='breakpoint' AllThreadsStopped=true ThreadId=1", m)
 | |
| 						}
 | |
| 						seCount++
 | |
| 						client.ContinueRequest(1)
 | |
| 					case *dap.OutputEvent:
 | |
| 						checkLogMessage(t, m, -1, "hello", fixture.Source, 8)
 | |
| 						oeCount++
 | |
| 					case *dap.ContinueResponse:
 | |
| 					case *dap.TerminatedEvent:
 | |
| 						t.Fatalf("\nexpected 10 output events and 10 stopped events, got %d output events and %d stopped events", oeCount, seCount)
 | |
| 					default:
 | |
| 						t.Fatalf("Unexpected message type: expect StoppedEvent, OutputEvent, or ContinueResponse, got %#v", m)
 | |
| 					}
 | |
| 				}
 | |
| 				// TODO(suzmue): The dap server may identify some false
 | |
| 				// positives for hard coded breakpoints, so there may still
 | |
| 				// be more stopped events.
 | |
| 				client.DisconnectRequestWithKillOption(true)
 | |
| 			})
 | |
| 		})
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func TestSetBreakpointWhileRunning(t *testing.T) {
 | |
| 	runTest(t, "integrationprog", func(client *daptest.Client, fixture protest.Fixture) {
 | |
| 		runDebugSessionWithBPs(t, client, "launch",
 | |
| 			// Launch
 | |
| 			func() {
 | |
| 				client.LaunchRequest("exec", fixture.Path, !stopOnEntry)
 | |
| 			},
 | |
| 			// Set breakpoints
 | |
| 			fixture.Source, []int{16},
 | |
| 			[]onBreakpoint{{
 | |
| 				execute: func() {
 | |
| 					// The program loops 3 times over lines 14-15-8-9-10-16
 | |
| 					checkStop(t, client, 1, "main.main", 16) // Line that sleeps for 1 second
 | |
| 
 | |
| 					// We can set breakpoints while nexting
 | |
| 					client.NextRequest(1)
 | |
| 					client.ExpectNextResponse(t)
 | |
| 					client.SetBreakpointsRequest(fixture.Source, []int{15}) // [16,] => [15,]
 | |
| 					checkSetBreakpointsResponse(t, []Breakpoint{{15, fixture.Source, true, ""}}, client.ExpectSetBreakpointsResponse(t))
 | |
| 					se := client.ExpectStoppedEvent(t)
 | |
| 					if se.Body.Reason != "step" || !se.Body.AllThreadsStopped || se.Body.ThreadId != 1 {
 | |
| 						t.Errorf("\ngot  %#v\nwant Reason='step' AllThreadsStopped=true ThreadId=1", se)
 | |
| 					}
 | |
| 					checkStop(t, client, 1, "main.main", 14)
 | |
| 					client.ContinueRequest(1)
 | |
| 					client.ExpectContinueResponse(t)
 | |
| 					se = client.ExpectStoppedEvent(t)
 | |
| 					if se.Body.Reason != "breakpoint" || !se.Body.AllThreadsStopped || se.Body.ThreadId != 1 {
 | |
| 						t.Errorf("\ngot  %#v\nwant Reason='breakpoint' AllThreadsStopped=true ThreadId=1", se)
 | |
| 					}
 | |
| 					checkStop(t, client, 1, "main.main", 15)
 | |
| 
 | |
| 					// We can set breakpoints while continuing
 | |
| 					client.ContinueRequest(1)
 | |
| 					client.ExpectContinueResponse(t)
 | |
| 					client.SetBreakpointsRequest(fixture.Source, []int{9}) // [15,] => [9,]
 | |
| 					checkSetBreakpointsResponse(t, []Breakpoint{{9, fixture.Source, true, ""}}, client.ExpectSetBreakpointsResponse(t))
 | |
| 					se = client.ExpectStoppedEvent(t)
 | |
| 					if se.Body.Reason != "breakpoint" || !se.Body.AllThreadsStopped || se.Body.ThreadId != 1 {
 | |
| 						t.Errorf("\ngot  %#v\nwant Reason='breakpoint' AllThreadsStopped=true ThreadId=1", se)
 | |
| 					}
 | |
| 					checkStop(t, client, 1, "main.sayhi", 9)
 | |
| 				},
 | |
| 				disconnect: true,
 | |
| 			}})
 | |
| 	})
 | |
| }
 | |
| 
 | |
| func TestSetFunctionBreakpointWhileRunning(t *testing.T) {
 | |
| 	runTest(t, "integrationprog", func(client *daptest.Client, fixture protest.Fixture) {
 | |
| 		runDebugSessionWithBPs(t, client, "launch",
 | |
| 			// Launch
 | |
| 			func() {
 | |
| 				client.LaunchRequest("exec", fixture.Path, !stopOnEntry)
 | |
| 			},
 | |
| 			// Set breakpoints
 | |
| 			fixture.Source, []int{16},
 | |
| 			[]onBreakpoint{{
 | |
| 				execute: func() {
 | |
| 					// The program loops 3 times over lines 14-15-8-9-10-16
 | |
| 					checkStop(t, client, 1, "main.main", 16) // Line that sleeps for 1 second
 | |
| 
 | |
| 					// We can set breakpoints while nexting
 | |
| 					client.NextRequest(1)
 | |
| 					client.ExpectNextResponse(t)
 | |
| 					client.SetFunctionBreakpointsRequest([]dap.FunctionBreakpoint{{Name: "main.sayhi"}}) // [16,] => [16, 8]
 | |
| 					checkBreakpoints(t, []Breakpoint{{8, fixture.Source, true, ""}}, client.ExpectSetFunctionBreakpointsResponse(t).Body.Breakpoints)
 | |
| 					client.SetBreakpointsRequest(fixture.Source, []int{}) // [16,8] => [8]
 | |
| 					expectSetBreakpointsResponse(t, client, []Breakpoint{})
 | |
| 					se := client.ExpectStoppedEvent(t)
 | |
| 					if se.Body.Reason != "step" || !se.Body.AllThreadsStopped || se.Body.ThreadId != 1 {
 | |
| 						t.Errorf("\ngot  %#v\nwant Reason='step' AllThreadsStopped=true ThreadId=1", se)
 | |
| 					}
 | |
| 					checkStop(t, client, 1, "main.main", 14)
 | |
| 
 | |
| 					// Make sure we can hit the breakpoints.
 | |
| 					client.ContinueRequest(1)
 | |
| 					client.ExpectContinueResponse(t)
 | |
| 					se = client.ExpectStoppedEvent(t)
 | |
| 					if se.Body.Reason != "function breakpoint" || !se.Body.AllThreadsStopped || se.Body.ThreadId != 1 {
 | |
| 						t.Errorf("\ngot  %#v\nwant Reason='function breakpoint' AllThreadsStopped=true ThreadId=1", se)
 | |
| 					}
 | |
| 					checkStop(t, client, 1, "main.sayhi", 8)
 | |
| 
 | |
| 					// We can set breakpoints while continuing
 | |
| 					client.ContinueRequest(1)
 | |
| 					client.ExpectContinueResponse(t)
 | |
| 					client.SetFunctionBreakpointsRequest([]dap.FunctionBreakpoint{}) // [8,] => []
 | |
| 					checkBreakpoints(t, []Breakpoint{}, client.ExpectSetFunctionBreakpointsResponse(t).Body.Breakpoints)
 | |
| 					client.SetBreakpointsRequest(fixture.Source, []int{16}) // [] => [16]
 | |
| 					expectSetBreakpointsResponse(t, client, []Breakpoint{{16, fixture.Source, true, ""}})
 | |
| 					se = client.ExpectStoppedEvent(t)
 | |
| 					if se.Body.Reason != "breakpoint" || !se.Body.AllThreadsStopped || se.Body.ThreadId != 1 {
 | |
| 						t.Errorf("\ngot  %#v\nwant Reason='breakpoint' AllThreadsStopped=true ThreadId=1", se)
 | |
| 					}
 | |
| 					checkStop(t, client, 1, "main.main", 16)
 | |
| 				},
 | |
| 				disconnect: true,
 | |
| 			}})
 | |
| 	})
 | |
| }
 | |
| 
 | |
| func TestHitConditionBreakpoints(t *testing.T) {
 | |
| 	runTest(t, "break", func(client *daptest.Client, fixture protest.Fixture) {
 | |
| 		runDebugSessionWithBPs(t, client, "launch",
 | |
| 			// Launch
 | |
| 			func() {
 | |
| 				client.LaunchRequest("exec", fixture.Path, !stopOnEntry)
 | |
| 			},
 | |
| 			// Set breakpoints
 | |
| 			fixture.Source, []int{4},
 | |
| 			[]onBreakpoint{{
 | |
| 				execute: func() {
 | |
| 					client.SetBreakpointsRequestWithArgs(fixture.Source, []int{7}, nil, map[int]string{7: "4"}, nil)
 | |
| 					expectSetBreakpointsResponse(t, client, []Breakpoint{{7, fixture.Source, true, ""}})
 | |
| 
 | |
| 					client.ContinueRequest(1)
 | |
| 					client.ExpectContinueResponse(t)
 | |
| 					client.ExpectStoppedEvent(t)
 | |
| 					checkStop(t, client, 1, "main.main", 7)
 | |
| 
 | |
| 					// Check that we are stopped at the correct value of i.
 | |
| 					client.VariablesRequest(localsScope)
 | |
| 					locals := client.ExpectVariablesResponse(t)
 | |
| 					checkVarExact(t, locals, 0, "i", "i", "4", "int", noChildren)
 | |
| 
 | |
| 					// Change the hit condition.
 | |
| 					client.SetBreakpointsRequestWithArgs(fixture.Source, []int{7}, nil, map[int]string{7: "% 2"}, nil)
 | |
| 					expectSetBreakpointsResponse(t, client, []Breakpoint{{7, fixture.Source, true, ""}})
 | |
| 
 | |
| 					client.ContinueRequest(1)
 | |
| 					client.ExpectContinueResponse(t)
 | |
| 					client.ExpectStoppedEvent(t)
 | |
| 					checkStop(t, client, 1, "main.main", 7)
 | |
| 
 | |
| 					// Check that we are stopped at the correct value of i.
 | |
| 					client.VariablesRequest(localsScope)
 | |
| 					locals = client.ExpectVariablesResponse(t)
 | |
| 					checkVarExact(t, locals, 0, "i", "i", "6", "int", noChildren)
 | |
| 
 | |
| 					// Expect an error if an assignment is passed.
 | |
| 					client.SetBreakpointsRequestWithArgs(fixture.Source, []int{7}, nil, map[int]string{7: "= 2"}, nil)
 | |
| 					expectSetBreakpointsResponse(t, client, []Breakpoint{{-1, "", false, ""}})
 | |
| 
 | |
| 					// Change the hit condition.
 | |
| 					client.SetBreakpointsRequestWithArgs(fixture.Source, []int{7}, nil, map[int]string{7: "< 8"}, nil)
 | |
| 					expectSetBreakpointsResponse(t, client, []Breakpoint{{7, fixture.Source, true, ""}})
 | |
| 					client.ContinueRequest(1)
 | |
| 					client.ExpectContinueResponse(t)
 | |
| 					client.ExpectStoppedEvent(t)
 | |
| 					checkStop(t, client, 1, "main.main", 7)
 | |
| 
 | |
| 					// Check that we are stopped at the correct value of i.
 | |
| 					client.VariablesRequest(localsScope)
 | |
| 					locals = client.ExpectVariablesResponse(t)
 | |
| 					checkVarExact(t, locals, 0, "i", "i", "7", "int", noChildren)
 | |
| 
 | |
| 					client.ContinueRequest(1)
 | |
| 					client.ExpectContinueResponse(t)
 | |
| 
 | |
| 					client.ExpectTerminatedEvent(t)
 | |
| 				},
 | |
| 				disconnect: false,
 | |
| 			}})
 | |
| 	})
 | |
| }
 | |
| 
 | |
| // TestLaunchSubstitutePath sets a breakpoint using a path
 | |
| // that does not exist and expects the substitutePath attribute
 | |
| // in the launch configuration to take care of the mapping.
 | |
| func TestLaunchSubstitutePath(t *testing.T) {
 | |
| 	runTest(t, "loopprog", func(client *daptest.Client, fixture protest.Fixture) {
 | |
| 		substitutePathTestHelper(t, fixture, client, "launch", map[string]interface{}{"mode": "exec", "program": fixture.Path})
 | |
| 	})
 | |
| }
 | |
| 
 | |
| // TestAttachSubstitutePath sets a breakpoint using a path
 | |
| // that does not exist and expects the substitutePath attribute
 | |
| // in the launch configuration to take care of the mapping.
 | |
| func TestAttachSubstitutePath(t *testing.T) {
 | |
| 	if runtime.GOOS == "windows" {
 | |
| 		t.Skip("test skipped on Windows, see https://delve.teamcity.com/project/Delve_windows for details")
 | |
| 	}
 | |
| 	runTest(t, "loopprog", func(client *daptest.Client, fixture protest.Fixture) {
 | |
| 		cmd := execFixture(t, fixture)
 | |
| 
 | |
| 		substitutePathTestHelper(t, fixture, client, "attach", map[string]interface{}{"mode": "local", "processId": cmd.Process.Pid})
 | |
| 	})
 | |
| }
 | |
| 
 | |
| func substitutePathTestHelper(t *testing.T, fixture protest.Fixture, client *daptest.Client, request string, launchAttachConfig map[string]interface{}) {
 | |
| 	t.Helper()
 | |
| 	nonexistentDir := filepath.Join(string(filepath.Separator), "path", "that", "does", "not", "exist")
 | |
| 	if runtime.GOOS == "windows" {
 | |
| 		nonexistentDir = "C:" + nonexistentDir
 | |
| 	}
 | |
| 
 | |
| 	launchAttachConfig["stopOnEntry"] = false
 | |
| 	// The rules in 'substitutePath' will be applied as follows:
 | |
| 	// - mapping paths from client to server:
 | |
| 	//		The first rule["from"] to match a prefix of 'path' will be applied:
 | |
| 	//			strings.Replace(path, rule["from"], rule["to"], 1)
 | |
| 	// - mapping paths from server to client:
 | |
| 	//		The first rule["to"] to match a prefix of 'path' will be applied:
 | |
| 	//			strings.Replace(path, rule["to"], rule["from"], 1)
 | |
| 	launchAttachConfig["substitutePath"] = []map[string]string{
 | |
| 		{"from": nonexistentDir, "to": filepath.Dir(fixture.Source)},
 | |
| 		// Since the path mappings are ordered, when converting from client path to
 | |
| 		// server path, this mapping will not apply, because nonexistentDir appears in
 | |
| 		// an earlier rule.
 | |
| 		{"from": nonexistentDir, "to": "this_is_a_bad_path"},
 | |
| 		// Since the path mappings are ordered, when converting from server path to
 | |
| 		// client path, this mapping will not apply, because filepath.Dir(fixture.Source)
 | |
| 		// appears in an earlier rule.
 | |
| 		{"from": "this_is_a_bad_path", "to": filepath.Dir(fixture.Source)},
 | |
| 	}
 | |
| 
 | |
| 	runDebugSessionWithBPs(t, client, request,
 | |
| 		func() {
 | |
| 			switch request {
 | |
| 			case "attach":
 | |
| 				client.AttachRequest(launchAttachConfig)
 | |
| 				client.ExpectCapabilitiesEventSupportTerminateDebuggee(t)
 | |
| 			case "launch":
 | |
| 				client.LaunchRequestWithArgs(launchAttachConfig)
 | |
| 			default:
 | |
| 				t.Fatalf("invalid request: %s", request)
 | |
| 			}
 | |
| 		},
 | |
| 		// Set breakpoints
 | |
| 		filepath.Join(nonexistentDir, "loopprog.go"), []int{8},
 | |
| 		[]onBreakpoint{{
 | |
| 			execute: func() {
 | |
| 				checkStop(t, client, 1, "main.loop", 8)
 | |
| 			},
 | |
| 			disconnect: true,
 | |
| 		}})
 | |
| }
 | |
| 
 | |
| // execFixture runs the binary fixture.Path and hooks up stdout and stderr
 | |
| // to os.Stdout and os.Stderr.
 | |
| func execFixture(t *testing.T, fixture protest.Fixture) *exec.Cmd {
 | |
| 	t.Helper()
 | |
| 	// TODO(polina): do I need to sanity check testBackend and runtime.GOOS?
 | |
| 	cmd := exec.Command(fixture.Path)
 | |
| 	cmd.Stdout = os.Stdout
 | |
| 	cmd.Stderr = os.Stderr
 | |
| 	if err := cmd.Start(); err != nil {
 | |
| 		t.Fatal(err)
 | |
| 	}
 | |
| 	return cmd
 | |
| }
 | |
| 
 | |
| // TestWorkingDir executes to a breakpoint and tests that the specified
 | |
| // working directory is the one used to run the program.
 | |
| func TestWorkingDir(t *testing.T) {
 | |
| 	runTest(t, "workdir", func(client *daptest.Client, fixture protest.Fixture) {
 | |
| 		wd := os.TempDir()
 | |
| 		// For Darwin `os.TempDir()` returns `/tmp` which is symlink to `/private/tmp`.
 | |
| 		if runtime.GOOS == "darwin" {
 | |
| 			wd = "/private/tmp"
 | |
| 		}
 | |
| 		runDebugSessionWithBPs(t, client, "launch",
 | |
| 			// Launch
 | |
| 			func() {
 | |
| 				client.LaunchRequestWithArgs(map[string]interface{}{
 | |
| 					"mode":        "exec",
 | |
| 					"program":     fixture.Path,
 | |
| 					"stopOnEntry": false,
 | |
| 					"cwd":         wd,
 | |
| 				})
 | |
| 			},
 | |
| 			// Set breakpoints
 | |
| 			fixture.Source, []int{10}, // b main.main
 | |
| 			[]onBreakpoint{{
 | |
| 				execute: func() {
 | |
| 					checkStop(t, client, 1, "main.main", 10)
 | |
| 					client.VariablesRequest(localsScope)
 | |
| 					locals := client.ExpectVariablesResponse(t)
 | |
| 					checkChildren(t, locals, "Locals", 2)
 | |
| 					for i := range locals.Body.Variables {
 | |
| 						switch locals.Body.Variables[i].Name {
 | |
| 						case "pwd":
 | |
| 							checkVarExact(t, locals, i, "pwd", "pwd", fmt.Sprintf("%q", wd), "string", noChildren)
 | |
| 						case "err":
 | |
| 							checkVarExact(t, locals, i, "err", "err", "error nil", "error", noChildren)
 | |
| 						}
 | |
| 					}
 | |
| 				},
 | |
| 				disconnect: false,
 | |
| 			}})
 | |
| 	})
 | |
| }
 | |
| 
 | |
| // checkEval is a helper for verifying the values within an EvaluateResponse.
 | |
| //
 | |
| //	value - the value of the evaluated expression
 | |
| //	hasRef - true if the evaluated expression should have children and therefore a non-0 variable reference
 | |
| //	ref - reference to retrieve children of this evaluated expression (0 if none)
 | |
| func checkEval(t *testing.T, got *dap.EvaluateResponse, value string, hasRef bool) (ref int) {
 | |
| 	t.Helper()
 | |
| 	if got.Body.Result != value || (got.Body.VariablesReference > 0) != hasRef {
 | |
| 		t.Errorf("\ngot  %#v\nwant Result=%q hasRef=%t", got, value, hasRef)
 | |
| 	}
 | |
| 	return got.Body.VariablesReference
 | |
| }
 | |
| 
 | |
| // checkEvalIndexed is a helper for verifying the values within an EvaluateResponse.
 | |
| //
 | |
| //	value - the value of the evaluated expression
 | |
| //	hasRef - true if the evaluated expression should have children and therefore a non-0 variable reference
 | |
| //	ref - reference to retrieve children of this evaluated expression (0 if none)
 | |
| func checkEvalIndexed(t *testing.T, got *dap.EvaluateResponse, value string, hasRef bool, indexed, named int) (ref int) {
 | |
| 	t.Helper()
 | |
| 	if got.Body.Result != value || (got.Body.VariablesReference > 0) != hasRef || got.Body.IndexedVariables != indexed || got.Body.NamedVariables != named {
 | |
| 		t.Errorf("\ngot  %#v\nwant Result=%q hasRef=%t IndexedVariables=%d NamedVariables=%d", got, value, hasRef, indexed, named)
 | |
| 	}
 | |
| 	return got.Body.VariablesReference
 | |
| }
 | |
| 
 | |
| func checkEvalRegex(t *testing.T, got *dap.EvaluateResponse, valueRegex string, hasRef bool) (ref int) {
 | |
| 	t.Helper()
 | |
| 	matched, _ := regexp.MatchString(valueRegex, got.Body.Result)
 | |
| 	if !matched || (got.Body.VariablesReference > 0) != hasRef {
 | |
| 		t.Errorf("\ngot  %#v\nwant Result=%q hasRef=%t", got, valueRegex, hasRef)
 | |
| 	}
 | |
| 	return got.Body.VariablesReference
 | |
| }
 | |
| 
 | |
| func TestEvaluateRequest(t *testing.T) {
 | |
| 	runTest(t, "testvariables", func(client *daptest.Client, fixture protest.Fixture) {
 | |
| 		runDebugSessionWithBPs(t, client, "launch",
 | |
| 			// Launch
 | |
| 			func() {
 | |
| 				client.LaunchRequest("exec", fixture.Path, !stopOnEntry)
 | |
| 			},
 | |
| 			fixture.Source, []int{}, // Breakpoint set in the program
 | |
| 			[]onBreakpoint{{ // Stop at first breakpoint
 | |
| 				execute: func() {
 | |
| 					checkStop(t, client, 1, "main.foobar", []int{65, 66})
 | |
| 
 | |
| 					// Variable lookup
 | |
| 					client.EvaluateRequest("a2", 1000, "this context will be ignored")
 | |
| 					got := client.ExpectEvaluateResponse(t)
 | |
| 					checkEval(t, got, "6", noChildren)
 | |
| 
 | |
| 					client.EvaluateRequest("a5", 1000, "this context will be ignored")
 | |
| 					got = client.ExpectEvaluateResponse(t)
 | |
| 					ref := checkEval(t, got, "[]int len: 5, cap: 5, [1,2,3,4,5]", hasChildren)
 | |
| 					if ref > 0 {
 | |
| 						client.VariablesRequest(ref)
 | |
| 						a5 := client.ExpectVariablesResponse(t)
 | |
| 						checkChildren(t, a5, "a5", 5)
 | |
| 						checkVarExact(t, a5, 0, "[0]", "(a5)[0]", "1", "int", noChildren)
 | |
| 						checkVarExact(t, a5, 4, "[4]", "(a5)[4]", "5", "int", noChildren)
 | |
| 						validateEvaluateName(t, client, a5, 0)
 | |
| 						validateEvaluateName(t, client, a5, 4)
 | |
| 					}
 | |
| 
 | |
| 					// Variable lookup that's not fully loaded
 | |
| 					client.EvaluateRequest("ba", 1000, "this context will be ignored")
 | |
| 					got = client.ExpectEvaluateResponse(t)
 | |
| 					checkEvalIndexed(t, got, "[]int len: 200, cap: 200, [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,...+136 more]", hasChildren, 200, 0)
 | |
| 
 | |
| 					// All (binary and unary) on basic types except <-, ++ and --
 | |
| 					client.EvaluateRequest("1+1", 1000, "this context will be ignored")
 | |
| 					got = client.ExpectEvaluateResponse(t)
 | |
| 					checkEval(t, got, "2", noChildren)
 | |
| 
 | |
| 					// Comparison operators on any type
 | |
| 					client.EvaluateRequest("1<2", 1000, "this context will be ignored")
 | |
| 					got = client.ExpectEvaluateResponse(t)
 | |
| 					checkEval(t, got, "true", noChildren)
 | |
| 
 | |
| 					// Type casts between numeric types
 | |
| 					client.EvaluateRequest("int(2.3)", 1000, "this context will be ignored")
 | |
| 					got = client.ExpectEvaluateResponse(t)
 | |
| 					checkEval(t, got, "2", noChildren)
 | |
| 
 | |
| 					// Type casts of integer constants into any pointer type and vice versa
 | |
| 					client.EvaluateRequest("(*int)(2)", 1000, "this context will be ignored")
 | |
| 					got = client.ExpectEvaluateResponse(t)
 | |
| 					ref = checkEvalRegex(t, got, `\*\(unreadable .+\)`, hasChildren)
 | |
| 					if ref > 0 {
 | |
| 						client.VariablesRequest(ref)
 | |
| 						val := client.ExpectVariablesResponse(t)
 | |
| 						checkChildren(t, val, "*(*int)(2)", 1)
 | |
| 						checkVarRegex(t, val, 0, "^$", `\(\*\(\(\*int\)\(2\)\)\)`, `\(unreadable .+\)`, "int", noChildren)
 | |
| 						validateEvaluateName(t, client, val, 0)
 | |
| 					}
 | |
| 
 | |
| 					// Type casts between string, []byte and []rune
 | |
| 					client.EvaluateRequest("[]byte(\"ABC€\")", 1000, "this context will be ignored")
 | |
| 					got = client.ExpectEvaluateResponse(t)
 | |
| 					checkEvalIndexed(t, got, "[]uint8 len: 6, cap: 6, [65,66,67,226,130,172]", noChildren, 6, 1)
 | |
| 
 | |
| 					// Struct member access (i.e. somevar.memberfield)
 | |
| 					client.EvaluateRequest("ms.Nest.Level", 1000, "this context will be ignored")
 | |
| 					got = client.ExpectEvaluateResponse(t)
 | |
| 					checkEval(t, got, "1", noChildren)
 | |
| 
 | |
| 					// Slicing and indexing operators on arrays, slices and strings
 | |
| 					client.EvaluateRequest("a5[4]", 1000, "this context will be ignored")
 | |
| 					got = client.ExpectEvaluateResponse(t)
 | |
| 					checkEval(t, got, "5", noChildren)
 | |
| 
 | |
| 					// Map access
 | |
| 					client.EvaluateRequest("mp[1]", 1000, "this context will be ignored")
 | |
| 					got = client.ExpectEvaluateResponse(t)
 | |
| 					ref = checkEval(t, got, "interface {}(int) 42", hasChildren)
 | |
| 					if ref > 0 {
 | |
| 						client.VariablesRequest(ref)
 | |
| 						expr := client.ExpectVariablesResponse(t)
 | |
| 						checkChildren(t, expr, "mp[1]", 1)
 | |
| 						checkVarExact(t, expr, 0, "data", "(mp[1]).(data)", "42", "int", noChildren)
 | |
| 						validateEvaluateName(t, client, expr, 0)
 | |
| 					}
 | |
| 
 | |
| 					// Pointer dereference
 | |
| 					client.EvaluateRequest("*ms.Nest", 1000, "this context will be ignored")
 | |
| 					got = client.ExpectEvaluateResponse(t)
 | |
| 					ref = checkEvalRegex(t, got, `main\.Nest {Level: 1, Nest: \*main.Nest {Level: 2, Nest: \*\(\*main\.Nest\)\(0x[0-9a-f]+\)}}`, hasChildren)
 | |
| 					if ref > 0 {
 | |
| 						client.VariablesRequest(ref)
 | |
| 						expr := client.ExpectVariablesResponse(t)
 | |
| 						checkChildren(t, expr, "*ms.Nest", 2)
 | |
| 						checkVarExact(t, expr, 0, "Level", "(*ms.Nest).Level", "1", "int", noChildren)
 | |
| 						validateEvaluateName(t, client, expr, 0)
 | |
| 					}
 | |
| 
 | |
| 					// Calls to builtin functions: cap, len, complex, imag and real
 | |
| 					client.EvaluateRequest("len(a5)", 1000, "this context will be ignored")
 | |
| 					got = client.ExpectEvaluateResponse(t)
 | |
| 					checkEval(t, got, "5", noChildren)
 | |
| 
 | |
| 					// Type assertion on interface variables (i.e. somevar.(concretetype))
 | |
| 					client.EvaluateRequest("mp[1].(int)", 1000, "this context will be ignored")
 | |
| 					got = client.ExpectEvaluateResponse(t)
 | |
| 					checkEval(t, got, "42", noChildren)
 | |
| 				},
 | |
| 				disconnect: false,
 | |
| 			}, { // Stop at second breakpoint
 | |
| 				execute: func() {
 | |
| 					checkStop(t, client, 1, "main.barfoo", 27)
 | |
| 
 | |
| 					// Top-most frame
 | |
| 					client.EvaluateRequest("a1", 1000, "this context will be ignored")
 | |
| 					got := client.ExpectEvaluateResponse(t)
 | |
| 					checkEval(t, got, "\"bur\"", noChildren)
 | |
| 					// No frame defaults to top-most frame
 | |
| 					client.EvaluateRequest("a1", 0, "this context will be ignored")
 | |
| 					got = client.ExpectEvaluateResponse(t)
 | |
| 					checkEval(t, got, "\"bur\"", noChildren)
 | |
| 					// Next frame
 | |
| 					client.EvaluateRequest("a1", 1001, "this context will be ignored")
 | |
| 					got = client.ExpectEvaluateResponse(t)
 | |
| 					checkEval(t, got, "\"foofoofoofoofoofoo\"", noChildren)
 | |
| 					// Next frame
 | |
| 					client.EvaluateRequest("a1", 1002, "any context but watch")
 | |
| 					erres := client.ExpectVisibleErrorResponse(t)
 | |
| 					if !checkErrorMessageFormat(erres.Body.Error, "Unable to evaluate expression: could not find symbol value for a1") {
 | |
| 						t.Errorf("\ngot %#v\nwant Format=\"Unable to evaluate expression: could not find symbol value for a1\"", erres)
 | |
| 					}
 | |
| 					client.EvaluateRequest("a1", 1002, "watch")
 | |
| 					erres = client.ExpectInvisibleErrorResponse(t)
 | |
| 					if !checkErrorMessageFormat(erres.Body.Error, "Unable to evaluate expression: could not find symbol value for a1") {
 | |
| 						t.Errorf("\ngot %#v\nwant Format=\"Unable to evaluate expression: could not find symbol value for a1\"", erres)
 | |
| 					}
 | |
| 					client.EvaluateRequest("a1", 1002, "repl")
 | |
| 					erres = client.ExpectInvisibleErrorResponse(t)
 | |
| 					if !checkErrorMessageFormat(erres.Body.Error, "Unable to evaluate expression: could not find symbol value for a1") {
 | |
| 						t.Errorf("\ngot %#v\nwant Format=\"Unable to evaluate expression: could not find symbol value for a1\"", erres)
 | |
| 					}
 | |
| 					client.EvaluateRequest("a1", 1002, "hover")
 | |
| 					erres = client.ExpectInvisibleErrorResponse(t)
 | |
| 					if !checkErrorMessageFormat(erres.Body.Error, "Unable to evaluate expression: could not find symbol value for a1") {
 | |
| 						t.Errorf("\ngot %#v\nwant Format=\"Unable to evaluate expression: could not find symbol value for a1\"", erres)
 | |
| 					}
 | |
| 					client.EvaluateRequest("a1", 1002, "clipboard")
 | |
| 					erres = client.ExpectVisibleErrorResponse(t)
 | |
| 					if !checkErrorMessageFormat(erres.Body.Error, "Unable to evaluate expression: could not find symbol value for a1") {
 | |
| 						t.Errorf("\ngot %#v\nwant Format=\"Unable to evaluate expression: could not find symbol value for a1\"", erres)
 | |
| 					}
 | |
| 				},
 | |
| 				disconnect: false,
 | |
| 			}})
 | |
| 	})
 | |
| }
 | |
| 
 | |
| func formatConfig(depth int, showGlobals, showRegisters bool, goroutineFilters string, showPprofLabels []string, hideSystemGoroutines bool, substitutePath [][2]string) string {
 | |
| 	formatStr := `stackTraceDepth	%d
 | |
| showGlobalVariables	%v
 | |
| showRegisters	%v
 | |
| goroutineFilters	%q
 | |
| showPprofLabels	%v
 | |
| hideSystemGoroutines	%v
 | |
| substitutePath	%v
 | |
| `
 | |
| 	return fmt.Sprintf(formatStr, depth, showGlobals, showRegisters, goroutineFilters, showPprofLabels, hideSystemGoroutines, substitutePath)
 | |
| }
 | |
| 
 | |
| func TestEvaluateCommandRequest(t *testing.T) {
 | |
| 	runTest(t, "testvariables", func(client *daptest.Client, fixture protest.Fixture) {
 | |
| 		runDebugSessionWithBPs(t, client, "launch",
 | |
| 			// Launch
 | |
| 			func() {
 | |
| 				client.LaunchRequest("exec", fixture.Path, !stopOnEntry)
 | |
| 			},
 | |
| 			fixture.Source, []int{}, // Breakpoint set in the program
 | |
| 			[]onBreakpoint{{ // Stop at first breakpoint
 | |
| 				execute: func() {
 | |
| 					checkStop(t, client, 1, "main.foobar", []int{65, 66})
 | |
| 
 | |
| 					// Request help.
 | |
| 					const dlvHelp = `The following commands are available:
 | |
|     dlv help (alias: h) 	 Prints the help message.
 | |
|     dlv config 	 Changes configuration parameters.
 | |
|     dlv sources (alias: s) 	 Print list of source files.
 | |
| 
 | |
| Type 'dlv help' followed by a command for full documentation.
 | |
| `
 | |
| 					client.EvaluateRequest("dlv help", 1000, "repl")
 | |
| 					got := client.ExpectEvaluateResponse(t)
 | |
| 					checkEval(t, got, dlvHelp, noChildren)
 | |
| 
 | |
| 					client.EvaluateRequest("dlv help config", 1000, "repl")
 | |
| 					got = client.ExpectEvaluateResponse(t)
 | |
| 					checkEval(t, got, msgConfig, noChildren)
 | |
| 
 | |
| 					// Test config.
 | |
| 					client.EvaluateRequest("dlv config", 1000, "repl")
 | |
| 					client.ExpectErrorResponse(t)
 | |
| 
 | |
| 					client.EvaluateRequest("dlv config -list", 1000, "repl")
 | |
| 					got = client.ExpectEvaluateResponse(t)
 | |
| 					checkEval(t, got, formatConfig(50, false, false, "", []string{}, false, [][2]string{}), noChildren)
 | |
| 
 | |
| 					// Read and modify showGlobalVariables.
 | |
| 					client.EvaluateRequest("dlv config -list showGlobalVariables", 1000, "repl")
 | |
| 					got = client.ExpectEvaluateResponse(t)
 | |
| 					checkEval(t, got, "showGlobalVariables\tfalse\n", noChildren)
 | |
| 
 | |
| 					client.ScopesRequest(1000)
 | |
| 					scopes := client.ExpectScopesResponse(t)
 | |
| 					if len(scopes.Body.Scopes) > 1 {
 | |
| 						t.Errorf("\ngot  %#v\nwant len(scopes)=1 (Locals)", scopes)
 | |
| 					}
 | |
| 					checkScope(t, scopes, 0, "Locals", -1)
 | |
| 
 | |
| 					client.EvaluateRequest("dlv config showGlobalVariables true", 1000, "repl")
 | |
| 					client.ExpectInvalidatedEvent(t)
 | |
| 					got = client.ExpectEvaluateResponse(t)
 | |
| 					checkEval(t, got, "showGlobalVariables\ttrue\n\nUpdated", noChildren)
 | |
| 
 | |
| 					client.EvaluateRequest("dlv config -list", 1000, "repl")
 | |
| 					got = client.ExpectEvaluateResponse(t)
 | |
| 					checkEval(t, got, formatConfig(50, true, false, "", []string{}, false, [][2]string{}), noChildren)
 | |
| 
 | |
| 					client.ScopesRequest(1000)
 | |
| 					scopes = client.ExpectScopesResponse(t)
 | |
| 					if len(scopes.Body.Scopes) < 2 {
 | |
| 						t.Errorf("\ngot  %#v\nwant len(scopes)=2 (Locals & Globals)", scopes)
 | |
| 					}
 | |
| 					checkScope(t, scopes, 0, "Locals", -1)
 | |
| 					checkScope(t, scopes, 1, "Globals (package main)", -1)
 | |
| 
 | |
| 					// Read and modify substitutePath.
 | |
| 					client.EvaluateRequest("dlv config -list substitutePath", 1000, "repl")
 | |
| 					got = client.ExpectEvaluateResponse(t)
 | |
| 					checkEval(t, got, "substitutePath\t[]\n", noChildren)
 | |
| 
 | |
| 					client.EvaluateRequest(fmt.Sprintf("dlv config substitutePath %q %q", "my/client/path", "your/server/path"), 1000, "repl")
 | |
| 					got = client.ExpectEvaluateResponse(t)
 | |
| 					checkEval(t, got, "substitutePath\t[[my/client/path your/server/path]]\n\nUpdated", noChildren)
 | |
| 
 | |
| 					client.EvaluateRequest(fmt.Sprintf("dlv config substitutePath %q %q", "my/client/path", "new/your/server/path"), 1000, "repl")
 | |
| 					got = client.ExpectEvaluateResponse(t)
 | |
| 					checkEval(t, got, "substitutePath\t[[my/client/path new/your/server/path]]\n\nUpdated", noChildren)
 | |
| 
 | |
| 					client.EvaluateRequest(fmt.Sprintf("dlv config substitutePath %q", "my/client/path"), 1000, "repl")
 | |
| 					got = client.ExpectEvaluateResponse(t)
 | |
| 					checkEval(t, got, "substitutePath\t[]\n\nUpdated", noChildren)
 | |
| 
 | |
| 					// Test sources.
 | |
| 					client.EvaluateRequest("dlv sources", 1000, "repl")
 | |
| 					got = client.ExpectEvaluateResponse(t)
 | |
| 					if !strings.Contains(got.Body.Result, fixture.Source) {
 | |
| 						t.Errorf("\ngot: %#v, want sources contains %s", got, fixture.Source)
 | |
| 					}
 | |
| 
 | |
| 					client.EvaluateRequest(fmt.Sprintf("dlv sources .*%s", strings.ReplaceAll(filepath.Base(fixture.Source), ".", "\\.")), 1000, "repl")
 | |
| 					got = client.ExpectEvaluateResponse(t)
 | |
| 					if got.Body.Result != fixture.Source {
 | |
| 						t.Errorf("\ngot: %#v, want sources=%q", got, fixture.Source)
 | |
| 					}
 | |
| 
 | |
| 					client.EvaluateRequest("dlv sources nonexistentsource", 1000, "repl")
 | |
| 					got = client.ExpectEvaluateResponse(t)
 | |
| 					if got.Body.Result != "" {
 | |
| 						t.Errorf("\ngot: %#v, want sources=\"\"", got)
 | |
| 					}
 | |
| 
 | |
| 					// Test bad inputs.
 | |
| 					client.EvaluateRequest("dlv help bad", 1000, "repl")
 | |
| 					client.ExpectErrorResponse(t)
 | |
| 
 | |
| 					client.EvaluateRequest("dlv bad", 1000, "repl")
 | |
| 					client.ExpectErrorResponse(t)
 | |
| 				},
 | |
| 				disconnect: true,
 | |
| 			}})
 | |
| 	})
 | |
| }
 | |
| 
 | |
| // From testvariables2 fixture
 | |
| const (
 | |
| 	// As defined in the code
 | |
| 	longstr = `"very long string 0123456789a0123456789b0123456789c0123456789d0123456789e0123456789f0123456789g012345678h90123456789i0123456789j0123456789"`
 | |
| 	// Loaded with MaxStringLen=64
 | |
| 	longstrLoaded64   = `"very long string 0123456789a0123456789b0123456789c0123456789d012...+73 more"`
 | |
| 	longstrLoaded64re = `\"very long string 0123456789a0123456789b0123456789c0123456789d012\.\.\.\+73 more\"`
 | |
| )
 | |
| 
 | |
| // TestVariableValueTruncation tests that in certain cases
 | |
| // we truncate the loaded variable values to make display more user-friendly.
 | |
| func TestVariableValueTruncation(t *testing.T) {
 | |
| 	runTest(t, "testvariables2", func(client *daptest.Client, fixture protest.Fixture) {
 | |
| 		runDebugSessionWithBPs(t, client, "launch",
 | |
| 			// Launch
 | |
| 			func() {
 | |
| 				client.LaunchRequest("exec", fixture.Path, !stopOnEntry)
 | |
| 			},
 | |
| 			// Breakpoint set within the program
 | |
| 			fixture.Source, []int{},
 | |
| 			[]onBreakpoint{{
 | |
| 				execute: func() {
 | |
| 					checkStop(t, client, 1, "main.main", -1)
 | |
| 
 | |
| 					client.VariablesRequest(localsScope)
 | |
| 					locals := client.ExpectVariablesResponse(t)
 | |
| 
 | |
| 					// Compound variable values may be truncated
 | |
| 					m1Full := `map\[string\]main\.astruct \[(\"[A-Za-z]+\": {A: [0-9]+, B: [0-9]+}, )+,\.\.\.\+2 more\]`
 | |
| 					m1Part := `map\[string\]main\.astruct \[(\"[A-Za-z]+\": {A: [0-9]+, B: [0-9]+}, )+.+\.\.\.`
 | |
| 
 | |
| 					// In variable responses
 | |
| 					checkVarRegex(t, locals, -1, "m1", "m1", m1Part, `map\[string\]main\.astruct`, hasChildren)
 | |
| 
 | |
| 					// In evaluate responses (select contexts only)
 | |
| 					tests := []struct {
 | |
| 						context string
 | |
| 						want    string
 | |
| 					}{
 | |
| 						{"", m1Part},
 | |
| 						{"watch", m1Part},
 | |
| 						{"repl", m1Part},
 | |
| 						{"hover", m1Part},
 | |
| 						{"variables", m1Full}, // used for copy
 | |
| 						{"clipboard", m1Full}, // used for copy
 | |
| 						{"somethingelse", m1Part},
 | |
| 					}
 | |
| 					for _, tc := range tests {
 | |
| 						t.Run(tc.context, func(t *testing.T) {
 | |
| 							client.EvaluateRequest("m1", 0, tc.context)
 | |
| 							checkEvalRegex(t, client.ExpectEvaluateResponse(t), tc.want, hasChildren)
 | |
| 						})
 | |
| 					}
 | |
| 
 | |
| 					// Compound map keys may be truncated even further
 | |
| 					// As the keys are always inside of a map container,
 | |
| 					// this applies to variables requests only, not evaluate requests.
 | |
| 
 | |
| 					// key - compound, value - scalar (inlined key:value display) => truncate key if too long
 | |
| 					ref := checkVarExact(t, locals, -1, "m5", "m5", "map[main.C]int [{s: "+longstr+"}: 1, ]", "map[main.C]int", hasChildren)
 | |
| 					if ref > 0 {
 | |
| 						client.VariablesRequest(ref)
 | |
| 						// Key format: <truncated>... @<address>
 | |
| 						checkVarRegex(t, client.ExpectVariablesResponse(t), 1, `main\.C {s: "very long string 0123456789.+\.\.\. @ 0x[0-9a-f]+`, `m5\[\(\*\(\*"main\.C"\)\(0x[0-9a-f]+\)\)\]`, "1", `int`, hasChildren)
 | |
| 					}
 | |
| 					// key - scalar, value - scalar (inlined key:value display) => key not truncated
 | |
| 					ref = checkVarExact(t, locals, -1, "m6", "m6", "map[string]int ["+longstr+": 123, ]", "map[string]int", hasChildren)
 | |
| 					if ref > 0 {
 | |
| 						client.VariablesRequest(ref)
 | |
| 						checkVarExact(t, client.ExpectVariablesResponse(t), 1, longstr, `m6[`+longstr+`]`, "123", "string: int", noChildren)
 | |
| 					}
 | |
| 					// key - compound, value - compound (array-like display) => key not truncated
 | |
| 					ref = checkVarExact(t, locals, -1, "m7", "m7", "map[main.C]main.C [{s: "+longstr+"}: {s: \"hello\"}, ]", "map[main.C]main.C", hasChildren)
 | |
| 					if ref > 0 {
 | |
| 						client.VariablesRequest(ref)
 | |
| 						m7 := client.ExpectVariablesResponse(t)
 | |
| 						checkVarRegex(t, m7, 1, "[key 0]", `\(\*\(\*\"main\.C\"\)\(0x[0-9a-f]+\)\)`, `main\.C {s: `+longstr+`}`, `main\.C`, hasChildren)
 | |
| 					}
 | |
| 				},
 | |
| 				disconnect: true,
 | |
| 			}})
 | |
| 	})
 | |
| }
 | |
| 
 | |
| // TestVariableLoadingOfLongStrings tests that different string loading limits
 | |
| // apply that depending on the context.
 | |
| func TestVariableLoadingOfLongStrings(t *testing.T) {
 | |
| 	protest.MustSupportFunctionCalls(t, testBackend)
 | |
| 	runTest(t, "longstrings", func(client *daptest.Client, fixture protest.Fixture) {
 | |
| 		runDebugSessionWithBPs(t, client, "launch",
 | |
| 			// Launch
 | |
| 			func() {
 | |
| 				client.LaunchRequest("exec", fixture.Path, !stopOnEntry)
 | |
| 			},
 | |
| 			// Breakpoint set within the program
 | |
| 			fixture.Source, []int{},
 | |
| 			[]onBreakpoint{{
 | |
| 				execute: func() {
 | |
| 					checkStop(t, client, 1, "main.main", -1)
 | |
| 
 | |
| 					client.VariablesRequest(localsScope)
 | |
| 					locals := client.ExpectVariablesResponse(t)
 | |
| 
 | |
| 					// Limits vary for evaluate requests with different contexts
 | |
| 					tests := []struct {
 | |
| 						context string
 | |
| 						limit   int
 | |
| 					}{
 | |
| 						{"", DefaultLoadConfig.MaxStringLen},
 | |
| 						{"watch", DefaultLoadConfig.MaxStringLen},
 | |
| 						{"repl", maxSingleStringLen},
 | |
| 						{"hover", maxSingleStringLen},
 | |
| 						{"variables", maxSingleStringLen},
 | |
| 						{"clipboard", maxSingleStringLen},
 | |
| 						{"somethingelse", DefaultLoadConfig.MaxStringLen},
 | |
| 					}
 | |
| 					for _, tc := range tests {
 | |
| 						t.Run(tc.context, func(t *testing.T) {
 | |
| 							// Long string by itself (limits vary)
 | |
| 							client.EvaluateRequest("s4097", 0, tc.context)
 | |
| 							want := fmt.Sprintf(`"x+\.\.\.\+%d more"`, 4097-tc.limit)
 | |
| 							checkEvalRegex(t, client.ExpectEvaluateResponse(t), want, noChildren)
 | |
| 
 | |
| 							// Evaluated container variables return values with minimally loaded
 | |
| 							// strings, which are further truncated for displaying, so we
 | |
| 							// can't test for loading limit except in contexts where an untruncated
 | |
| 							// value is returned.
 | |
| 							client.EvaluateRequest("&s4097", 0, tc.context)
 | |
| 							switch tc.context {
 | |
| 							case "variables", "clipboard":
 | |
| 								want = fmt.Sprintf(`\*"x+\.\.\.\+%d more`, 4097-DefaultLoadConfig.MaxStringLen)
 | |
| 							default:
 | |
| 								want = fmt.Sprintf(`\*"x{%d}\.\.\.`, maxVarValueLen-2)
 | |
| 							}
 | |
| 							checkEvalRegex(t, client.ExpectEvaluateResponse(t), want, hasChildren)
 | |
| 						})
 | |
| 					}
 | |
| 
 | |
| 					// Long strings returned from calls are subject to a different limit,
 | |
| 					// same limit regardless of context
 | |
| 					for _, context := range []string{"", "watch", "repl", "variables", "hover", "clipboard", "somethingelse"} {
 | |
| 						t.Run(context, func(t *testing.T) {
 | |
| 							client.EvaluateRequest(`call buildString(4097)`, 1000, context)
 | |
| 							want := fmt.Sprintf(`"x+\.\.\.\+%d more"`, 4097-maxStringLenInCallRetVars)
 | |
| 							got := client.ExpectEvaluateResponse(t)
 | |
| 							checkEvalRegex(t, got, want, hasChildren)
 | |
| 						})
 | |
| 					}
 | |
| 
 | |
| 					// Variables requests use the most conservative loading limit
 | |
| 					checkVarRegex(t, locals, -1, "s513", "s513", `"x{512}\.\.\.\+1 more"`, "string", noChildren)
 | |
| 					// Container variables are subject to additional stricter value truncation that drops +more part
 | |
| 					checkVarRegex(t, locals, -1, "nested", "nested", `map\[int\]string \[513: \"x+\.\.\.`, "string", hasChildren)
 | |
| 				},
 | |
| 				disconnect: true,
 | |
| 			}})
 | |
| 	})
 | |
| }
 | |
| 
 | |
| func TestEvaluateCallRequest(t *testing.T) {
 | |
| 	protest.MustSupportFunctionCalls(t, testBackend)
 | |
| 	runTest(t, "fncall", func(client *daptest.Client, fixture protest.Fixture) {
 | |
| 		runDebugSessionWithBPs(t, client, "launch",
 | |
| 			// Launch
 | |
| 			func() {
 | |
| 				client.LaunchRequest("exec", fixture.Path, !stopOnEntry)
 | |
| 			},
 | |
| 			fixture.Source, []int{88},
 | |
| 			[]onBreakpoint{{ // Stop in makeclos()
 | |
| 				execute: func() {
 | |
| 					checkStop(t, client, 1, "main.makeclos", 88)
 | |
| 
 | |
| 					// Topmost frame: both types of expressions should work
 | |
| 					client.EvaluateRequest("callstacktrace", 1000, "this context will be ignored")
 | |
| 					client.ExpectEvaluateResponse(t)
 | |
| 					client.EvaluateRequest("call callstacktrace()", 1000, "this context will be ignored")
 | |
| 					client.ExpectEvaluateResponse(t)
 | |
| 
 | |
| 					// Next frame: only non-call expressions will work
 | |
| 					client.EvaluateRequest("callstacktrace", 1001, "this context will be ignored")
 | |
| 					client.ExpectEvaluateResponse(t)
 | |
| 					client.EvaluateRequest("call callstacktrace()", 1001, "not watch")
 | |
| 					erres := client.ExpectVisibleErrorResponse(t)
 | |
| 					if !checkErrorMessageFormat(erres.Body.Error, "Unable to evaluate expression: call is only supported with topmost stack frame") {
 | |
| 						t.Errorf("\ngot %#v\nwant Format=\"Unable to evaluate expression: call is only supported with topmost stack frame\"", erres)
 | |
| 					}
 | |
| 
 | |
| 					// A call can stop on a breakpoint
 | |
| 					client.EvaluateRequest("call callbreak()", 1000, "not watch")
 | |
| 					s := client.ExpectStoppedEvent(t)
 | |
| 					if s.Body.Reason != "hardcoded breakpoint" {
 | |
| 						t.Errorf("\ngot %#v\nwant Reason=\"hardcoded breakpoint\"", s)
 | |
| 					}
 | |
| 					erres = client.ExpectVisibleErrorResponse(t)
 | |
| 					if !checkErrorMessageFormat(erres.Body.Error, "Unable to evaluate expression: call stopped") {
 | |
| 						t.Errorf("\ngot %#v\nwant Format=\"Unable to evaluate expression: call stopped\"", erres)
 | |
| 					}
 | |
| 
 | |
| 					// A call during a call causes an error
 | |
| 					client.EvaluateRequest("call callstacktrace()", 1000, "not watch")
 | |
| 					erres = client.ExpectVisibleErrorResponse(t)
 | |
| 					if !checkErrorMessageFormat(erres.Body.Error, "Unable to evaluate expression: cannot call function while another function call is already in progress") {
 | |
| 						t.Errorf("\ngot %#v\nwant Format=\"Unable to evaluate expression: cannot call function while another function call is already in progress\"", erres)
 | |
| 					}
 | |
| 
 | |
| 					// Complete the call and get back to original breakpoint in makeclos()
 | |
| 					client.ContinueRequest(1)
 | |
| 					client.ExpectContinueResponse(t)
 | |
| 					client.ExpectStoppedEvent(t)
 | |
| 					checkStop(t, client, 1, "main.makeclos", 88)
 | |
| 
 | |
| 					// Inject a call for the same function that is stopped at breakpoint:
 | |
| 					// it might stop at the exact same breakpoint on the same goroutine,
 | |
| 					// but we should still detect that its an injected call that stopped
 | |
| 					// and not the return to the original point of injection after it
 | |
| 					// completed.
 | |
| 					client.EvaluateRequest("call makeclos(nil)", 1000, "not watch")
 | |
| 					stopped := client.ExpectStoppedEvent(t)
 | |
| 					erres = client.ExpectVisibleErrorResponse(t)
 | |
| 					if !checkErrorMessageFormat(erres.Body.Error, "Unable to evaluate expression: call stopped") {
 | |
| 						t.Errorf("\ngot %#v\nwant Format=\"Unable to evaluate expression: call stopped\"", erres)
 | |
| 					}
 | |
| 					checkStop(t, client, stopped.Body.ThreadId, "main.makeclos", 88)
 | |
| 
 | |
| 					// Complete the call and get back to original breakpoint in makeclos()
 | |
| 					client.ContinueRequest(1)
 | |
| 					client.ExpectContinueResponse(t)
 | |
| 					client.ExpectStoppedEvent(t)
 | |
| 					checkStop(t, client, 1, "main.makeclos", 88)
 | |
| 				},
 | |
| 				disconnect: false,
 | |
| 			}, { // Stop at runtime breakpoint
 | |
| 				execute: func() {
 | |
| 					checkStop(t, client, 1, "main.main", -1)
 | |
| 
 | |
| 					// No return values
 | |
| 					client.EvaluateRequest("call call0(1, 2)", 1000, "this context will be ignored")
 | |
| 					got := client.ExpectEvaluateResponse(t)
 | |
| 					checkEval(t, got, "", noChildren)
 | |
| 					// One unnamed return value
 | |
| 					client.EvaluateRequest("call call1(one, two)", 1000, "this context will be ignored")
 | |
| 					got = client.ExpectEvaluateResponse(t)
 | |
| 					ref := checkEval(t, got, "3", hasChildren)
 | |
| 					if ref > 0 {
 | |
| 						client.VariablesRequest(ref)
 | |
| 						rv := client.ExpectVariablesResponse(t)
 | |
| 						checkChildren(t, rv, "rv", 1)
 | |
| 						checkVarExact(t, rv, 0, "~r2", "", "3", "int", noChildren)
 | |
| 					}
 | |
| 					// One named return value
 | |
| 					// Panic doesn't panic, but instead returns the error as a named return variable
 | |
| 					client.EvaluateRequest("call callpanic()", 1000, "this context will be ignored")
 | |
| 					got = client.ExpectEvaluateResponse(t)
 | |
| 					ref = checkEval(t, got, `interface {}(string) "callpanic panicked"`, hasChildren)
 | |
| 					if ref > 0 {
 | |
| 						client.VariablesRequest(ref)
 | |
| 						rv := client.ExpectVariablesResponse(t)
 | |
| 						checkChildren(t, rv, "rv", 1)
 | |
| 						ref = checkVarExact(t, rv, 0, "~panic", "", `interface {}(string) "callpanic panicked"`, "interface {}(string)", hasChildren)
 | |
| 						if ref > 0 {
 | |
| 							client.VariablesRequest(ref)
 | |
| 							p := client.ExpectVariablesResponse(t)
 | |
| 							checkChildren(t, p, "~panic", 1)
 | |
| 							checkVarExact(t, p, 0, "data", "", "\"callpanic panicked\"", "string", noChildren)
 | |
| 						}
 | |
| 					}
 | |
| 					// Multiple return values
 | |
| 					client.EvaluateRequest("call call2(one, two)", 1000, "this context will be ignored")
 | |
| 					got = client.ExpectEvaluateResponse(t)
 | |
| 					ref = checkEval(t, got, "1, 2", hasChildren)
 | |
| 					if ref > 0 {
 | |
| 						client.VariablesRequest(ref)
 | |
| 						rvs := client.ExpectVariablesResponse(t)
 | |
| 						checkChildren(t, rvs, "rvs", 2)
 | |
| 						checkVarExact(t, rvs, 0, "~r2", "", "1", "int", noChildren)
 | |
| 						checkVarExact(t, rvs, 1, "~r3", "", "2", "int", noChildren)
 | |
| 					}
 | |
| 					// No frame defaults to top-most frame
 | |
| 					client.EvaluateRequest("call call1(one, two)", 0, "this context will be ignored")
 | |
| 					got = client.ExpectEvaluateResponse(t)
 | |
| 					checkEval(t, got, "3", hasChildren)
 | |
| 					// Extra spaces don't matter
 | |
| 					client.EvaluateRequest(" call  call1(one, one) ", 0, "this context will be ignored")
 | |
| 					got = client.ExpectEvaluateResponse(t)
 | |
| 					checkEval(t, got, "2", hasChildren)
 | |
| 					// Just 'call', even with extra space, is treated as {expression}
 | |
| 					client.EvaluateRequest("call ", 1000, "watch")
 | |
| 					got = client.ExpectEvaluateResponse(t)
 | |
| 					checkEval(t, got, "\"this is a variable named `call`\"", noChildren)
 | |
| 
 | |
| 					// Call error
 | |
| 					client.EvaluateRequest("call call1(one)", 1000, "watch")
 | |
| 					erres := client.ExpectInvisibleErrorResponse(t)
 | |
| 					if !checkErrorMessageFormat(erres.Body.Error, "Unable to evaluate expression: not enough arguments") {
 | |
| 						t.Errorf("\ngot %#v\nwant Format=\"Unable to evaluate expression: not enough arguments\"", erres)
 | |
| 					}
 | |
| 
 | |
| 					// Assignment - expect no error, but no return value.
 | |
| 					client.EvaluateRequest("call one = two", 1000, "this context will be ignored")
 | |
| 					got = client.ExpectEvaluateResponse(t)
 | |
| 					checkEval(t, got, "", noChildren)
 | |
| 					// Check one=two was applied.
 | |
| 					client.EvaluateRequest("one", 1000, "repl")
 | |
| 					got = client.ExpectEvaluateResponse(t)
 | |
| 					checkEval(t, got, "2", noChildren)
 | |
| 
 | |
| 					// Call can exit.
 | |
| 					client.EvaluateRequest("call callexit()", 1000, "this context will be ignored")
 | |
| 					client.ExpectTerminatedEvent(t)
 | |
| 					if res := client.ExpectVisibleErrorResponse(t); res.Body.Error == nil || !strings.Contains(res.Body.Error.Format, "terminated") {
 | |
| 						t.Errorf("\ngot %#v\nwant Format=.*terminated.*", res)
 | |
| 					}
 | |
| 				},
 | |
| 				terminated: true,
 | |
| 				disconnect: true,
 | |
| 			}})
 | |
| 	})
 | |
| }
 | |
| 
 | |
| func TestNextAndStep(t *testing.T) {
 | |
| 	runTest(t, "testinline", func(client *daptest.Client, fixture protest.Fixture) {
 | |
| 		runDebugSessionWithBPs(t, client, "launch",
 | |
| 			// Launch
 | |
| 			func() {
 | |
| 				client.LaunchRequest("exec", fixture.Path, !stopOnEntry)
 | |
| 			},
 | |
| 			// Set breakpoints
 | |
| 			fixture.Source, []int{11},
 | |
| 			[]onBreakpoint{{ // Stop at line 11
 | |
| 				execute: func() {
 | |
| 					checkStop(t, client, 1, "main.initialize", 11)
 | |
| 
 | |
| 					expectStop := func(fun string, line int) {
 | |
| 						t.Helper()
 | |
| 						se := client.ExpectStoppedEvent(t)
 | |
| 						if se.Body.Reason != "step" || se.Body.ThreadId != 1 || !se.Body.AllThreadsStopped {
 | |
| 							t.Errorf("got %#v, want Reason=\"step\", ThreadId=1, AllThreadsStopped=true", se)
 | |
| 						}
 | |
| 						checkStop(t, client, 1, fun, line)
 | |
| 					}
 | |
| 
 | |
| 					client.StepOutRequest(1)
 | |
| 					client.ExpectStepOutResponse(t)
 | |
| 					expectStop("main.main", 18)
 | |
| 
 | |
| 					client.NextRequest(1)
 | |
| 					client.ExpectNextResponse(t)
 | |
| 					expectStop("main.main", 19)
 | |
| 
 | |
| 					client.StepInRequest(1)
 | |
| 					client.ExpectStepInResponse(t)
 | |
| 					expectStop("main.inlineThis", 5)
 | |
| 
 | |
| 					client.NextRequest(-1000)
 | |
| 					client.ExpectNextResponse(t)
 | |
| 					if se := client.ExpectStoppedEvent(t); se.Body.Reason != "error" || se.Body.Text != "unknown goroutine -1000" {
 | |
| 						t.Errorf("got %#v, want Reason=\"error\", Text=\"unknown goroutine -1000\"", se)
 | |
| 					}
 | |
| 					checkStop(t, client, 1, "main.inlineThis", 5)
 | |
| 				},
 | |
| 				disconnect: false,
 | |
| 			}})
 | |
| 	})
 | |
| }
 | |
| 
 | |
| func TestHardCodedBreakpoints(t *testing.T) {
 | |
| 	runTest(t, "consts", func(client *daptest.Client, fixture protest.Fixture) {
 | |
| 		runDebugSessionWithBPs(t, client, "launch",
 | |
| 			// Launch
 | |
| 			func() {
 | |
| 				client.LaunchRequest("exec", fixture.Path, !stopOnEntry)
 | |
| 			},
 | |
| 			fixture.Source, []int{28},
 | |
| 			[]onBreakpoint{{ // Stop at line 28
 | |
| 				execute: func() {
 | |
| 					checkStop(t, client, 1, "main.main", 28)
 | |
| 
 | |
| 					client.ContinueRequest(1)
 | |
| 					client.ExpectContinueResponse(t)
 | |
| 					se := client.ExpectStoppedEvent(t)
 | |
| 					if se.Body.ThreadId != 1 || se.Body.Reason != "breakpoint" {
 | |
| 						t.Errorf("\ngot  %#v\nwant ThreadId=1 Reason=\"breakpoint\"", se)
 | |
| 					}
 | |
| 				},
 | |
| 				disconnect: false,
 | |
| 			}})
 | |
| 	})
 | |
| }
 | |
| 
 | |
| // TestStepInstruction executes to a breakpoint and tests stepping
 | |
| // a single instruction
 | |
| func TestStepInstruction(t *testing.T) {
 | |
| 	runTest(t, "testvariables", func(client *daptest.Client, fixture protest.Fixture) {
 | |
| 		runDebugSessionWithBPs(t, client, "launch",
 | |
| 			// Launch
 | |
| 			func() {
 | |
| 				client.LaunchRequest("exec", fixture.Path, !stopOnEntry)
 | |
| 			},
 | |
| 			// Set breakpoints
 | |
| 			fixture.Source, []int{32}, // b main.foobar
 | |
| 			[]onBreakpoint{{
 | |
| 				execute: func() {
 | |
| 					checkStop(t, client, 1, "main.foobar", 32)
 | |
| 
 | |
| 					pc, err := getPC(t, client, 1)
 | |
| 					if err != nil {
 | |
| 						t.Fatal(err)
 | |
| 					}
 | |
| 
 | |
| 					// The exact instructions may change due to compiler changes,
 | |
| 					// but we want to make sure that all of our instructions are
 | |
| 					// instantiating variables, since these should not include
 | |
| 					// jumps.
 | |
| 					verifyExpectedLocation := func() {
 | |
| 						client.StackTraceRequest(1, 0, 20)
 | |
| 						st := client.ExpectStackTraceResponse(t)
 | |
| 						if len(st.Body.StackFrames) < 1 {
 | |
| 							t.Errorf("\ngot  %#v\nwant len(stackframes) => 1", st)
 | |
| 						} else {
 | |
| 							// There is a hardcoded breakpoint on line 32. All of the
 | |
| 							// steps should be completed before that line.
 | |
| 							if st.Body.StackFrames[0].Line < 32 {
 | |
| 								t.Errorf("\ngot  %#v\nwant Line<32", st)
 | |
| 							}
 | |
| 							if st.Body.StackFrames[0].Name != "main.foobar" {
 | |
| 								t.Errorf("\ngot  %#v\nwant Name=\"main.foobar\"", st)
 | |
| 							}
 | |
| 						}
 | |
| 					}
 | |
| 
 | |
| 					// Next instruction.
 | |
| 					client.NextInstructionRequest(1)
 | |
| 					client.ExpectNextResponse(t)
 | |
| 					se := client.ExpectStoppedEvent(t)
 | |
| 					if se.Body.ThreadId != 1 || se.Body.Reason != "step" {
 | |
| 						t.Errorf("\ngot  %#v\nwant ThreadId=1 Reason=\"step\"", se)
 | |
| 					}
 | |
| 					verifyExpectedLocation()
 | |
| 					nextPC, err := getPC(t, client, 1)
 | |
| 					if err != nil {
 | |
| 						t.Fatal(err)
 | |
| 					}
 | |
| 					if nextPC <= pc {
 | |
| 						t.Errorf("got %#x, expected InstructionPointerReference>%#x", nextPC, pc)
 | |
| 					}
 | |
| 
 | |
| 					// StepIn instruction.
 | |
| 					pc = nextPC
 | |
| 					client.StepInInstructionRequest(1)
 | |
| 					client.ExpectStepInResponse(t)
 | |
| 					se = client.ExpectStoppedEvent(t)
 | |
| 					if se.Body.ThreadId != 1 || se.Body.Reason != "step" {
 | |
| 						t.Errorf("\ngot  %#v\nwant ThreadId=1 Reason=\"step\"", se)
 | |
| 					}
 | |
| 					verifyExpectedLocation()
 | |
| 					nextPC, err = getPC(t, client, 1)
 | |
| 					if err != nil {
 | |
| 						t.Fatal(err)
 | |
| 					}
 | |
| 					if nextPC <= pc {
 | |
| 						t.Errorf("got %#x, expected InstructionPointerReference>%#x", nextPC, pc)
 | |
| 					}
 | |
| 
 | |
| 					// StepOut Instruction.
 | |
| 					pc = nextPC
 | |
| 					client.StepOutInstructionRequest(1)
 | |
| 					client.ExpectStepOutResponse(t)
 | |
| 					se = client.ExpectStoppedEvent(t)
 | |
| 					if se.Body.ThreadId != 1 || se.Body.Reason != "step" {
 | |
| 						t.Errorf("\ngot  %#v\nwant ThreadId=1 Reason=\"step\"", se)
 | |
| 					}
 | |
| 					verifyExpectedLocation()
 | |
| 					nextPC, err = getPC(t, client, 1)
 | |
| 					if err != nil {
 | |
| 						t.Fatal(err)
 | |
| 					}
 | |
| 					if nextPC <= pc {
 | |
| 						t.Errorf("got %#x, expected InstructionPointerReference>%#x", nextPC, pc)
 | |
| 					}
 | |
| 				},
 | |
| 				disconnect: true,
 | |
| 			}})
 | |
| 	})
 | |
| }
 | |
| 
 | |
| func getPC(t *testing.T, client *daptest.Client, threadId int) (uint64, error) {
 | |
| 	client.StackTraceRequest(threadId, 0, 1)
 | |
| 	st := client.ExpectStackTraceResponse(t)
 | |
| 	if len(st.Body.StackFrames) < 1 {
 | |
| 		t.Fatalf("\ngot  %#v\nwant len(stackframes) => 1", st)
 | |
| 	}
 | |
| 	return strconv.ParseUint(st.Body.StackFrames[0].InstructionPointerReference, 0, 64)
 | |
| }
 | |
| 
 | |
| // TestNextParked tests that we can switched selected goroutine to a parked one
 | |
| // and perform next operation on it.
 | |
| func TestNextParked(t *testing.T) {
 | |
| 	runTest(t, "parallel_next", func(client *daptest.Client, fixture protest.Fixture) {
 | |
| 		runDebugSessionWithBPs(t, client, "launch",
 | |
| 			// Launch
 | |
| 			func() {
 | |
| 				client.LaunchRequest("exec", fixture.Path, !stopOnEntry)
 | |
| 			},
 | |
| 			// Set breakpoints
 | |
| 			fixture.Source, []int{15},
 | |
| 			[]onBreakpoint{{ // Stop at line 15
 | |
| 				execute: func() {
 | |
| 					if parkedGoid := testNextParkedHelper(t, client, fixture); parkedGoid >= 0 {
 | |
| 						client.NextRequest(parkedGoid)
 | |
| 						client.ExpectNextResponse(t)
 | |
| 
 | |
| 						se := client.ExpectStoppedEvent(t)
 | |
| 						if se.Body.ThreadId != parkedGoid {
 | |
| 							t.Fatalf("Next did not continue on the newly selected goroutine, expected %d got %d", parkedGoid, se.Body.ThreadId)
 | |
| 						}
 | |
| 					}
 | |
| 				},
 | |
| 				// Let the test harness continue to process termination
 | |
| 				// if it hasn't gotten there already.
 | |
| 				disconnect: false,
 | |
| 			}})
 | |
| 	})
 | |
| }
 | |
| 
 | |
| // Finds a goroutine other than the selected one that is parked inside of main.sayhi and therefore
 | |
| // still has a line to execute if resumed with next.
 | |
| func testNextParkedHelper(t *testing.T, client *daptest.Client, fixture protest.Fixture) int {
 | |
| 	t.Helper()
 | |
| 	// Set a breakpoint at main.sayhi
 | |
| 	client.SetBreakpointsRequest(fixture.Source, []int{8})
 | |
| 	client.ExpectSetBreakpointsResponse(t)
 | |
| 
 | |
| 	parkedGoid := -1
 | |
| 	for parkedGoid < 0 {
 | |
| 		client.ContinueRequest(1)
 | |
| 		client.ExpectContinueResponse(t)
 | |
| 		event := client.ExpectMessage(t)
 | |
| 		switch event.(type) {
 | |
| 		case *dap.StoppedEvent:
 | |
| 			// ok
 | |
| 		case *dap.TerminatedEvent:
 | |
| 			// This is very unlikely to happen. But in theory if all sayhi
 | |
| 			// goroutines are run serially, there will never be a second parked
 | |
| 			// sayhi goroutine when another breaks and we will keep trying
 | |
| 			// until process termination.
 | |
| 			return -1
 | |
| 		}
 | |
| 
 | |
| 		se := event.(*dap.StoppedEvent)
 | |
| 
 | |
| 		client.ThreadsRequest()
 | |
| 		threads := client.ExpectThreadsResponse(t)
 | |
| 
 | |
| 		// Search for a parked goroutine that we know for sure will have to be
 | |
| 		// resumed before the program can exit. This is a parked goroutine that:
 | |
| 		// 1. is executing main.sayhi
 | |
| 		// 2. hasn't called wg.Done yet
 | |
| 		// 3. is not the currently selected goroutine
 | |
| 		for _, g := range threads.Body.Threads {
 | |
| 			if g.Id == se.Body.ThreadId || g.Id == 0 {
 | |
| 				// Skip selected goroutine and goroutine 0
 | |
| 				continue
 | |
| 			}
 | |
| 			client.StackTraceRequest(g.Id, 0, 5)
 | |
| 			frames := client.ExpectStackTraceResponse(t)
 | |
| 			for _, frame := range frames.Body.StackFrames {
 | |
| 				// line 11 is the line where wg.Done is called
 | |
| 				if frame.Name == "main.sayhi" && frame.Line < 11 {
 | |
| 					parkedGoid = g.Id
 | |
| 					break
 | |
| 				}
 | |
| 			}
 | |
| 			if parkedGoid >= 0 {
 | |
| 				break
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	// Clear all breakpoints.
 | |
| 	client.SetBreakpointsRequest(fixture.Source, []int{})
 | |
| 	client.ExpectSetBreakpointsResponse(t)
 | |
| 	return parkedGoid
 | |
| }
 | |
| 
 | |
| // TestStepOutPreservesGoroutine is inspired by proc_test.TestStepOutPreservesGoroutine
 | |
| // and checks that StepOut preserves the currently selected goroutine.
 | |
| func TestStepOutPreservesGoroutine(t *testing.T) {
 | |
| 	// Checks that StepOut preserves the currently selected goroutine.
 | |
| 	rand.Seed(time.Now().Unix())
 | |
| 	runTest(t, "issue2113", func(client *daptest.Client, fixture protest.Fixture) {
 | |
| 		runDebugSessionWithBPs(t, client, "launch",
 | |
| 			// Launch
 | |
| 			func() {
 | |
| 				client.LaunchRequest("exec", fixture.Path, !stopOnEntry)
 | |
| 			},
 | |
| 			// Set breakpoints
 | |
| 			fixture.Source, []int{25},
 | |
| 			[]onBreakpoint{{ // Stop at line 25
 | |
| 				execute: func() {
 | |
| 					client.ContinueRequest(1)
 | |
| 					client.ExpectContinueResponse(t)
 | |
| 
 | |
| 					// The program contains runtime.Breakpoint()
 | |
| 					se := client.ExpectStoppedEvent(t)
 | |
| 
 | |
| 					client.ThreadsRequest()
 | |
| 					gs := client.ExpectThreadsResponse(t)
 | |
| 
 | |
| 					candg := []int{}
 | |
| 					bestg := []int{}
 | |
| 					for _, g := range gs.Body.Threads {
 | |
| 						// We do not need to check the thread that the program
 | |
| 						// is currently stopped on.
 | |
| 						if g.Id == se.Body.ThreadId {
 | |
| 							continue
 | |
| 						}
 | |
| 
 | |
| 						client.StackTraceRequest(g.Id, 0, 20)
 | |
| 						frames := client.ExpectStackTraceResponse(t)
 | |
| 						for _, frame := range frames.Body.StackFrames {
 | |
| 							if frame.Name == "main.coroutine" {
 | |
| 								candg = append(candg, g.Id)
 | |
| 								if strings.HasPrefix(frames.Body.StackFrames[0].Name, "runtime.") {
 | |
| 									bestg = append(bestg, g.Id)
 | |
| 								}
 | |
| 								break
 | |
| 							}
 | |
| 						}
 | |
| 					}
 | |
| 					var goroutineId int
 | |
| 					if len(bestg) > 0 {
 | |
| 						goroutineId = bestg[rand.Intn(len(bestg))]
 | |
| 						t.Logf("selected goroutine %d (best)\n", goroutineId)
 | |
| 					} else if len(candg) > 0 {
 | |
| 						goroutineId = candg[rand.Intn(len(candg))]
 | |
| 						t.Logf("selected goroutine %d\n", goroutineId)
 | |
| 
 | |
| 					}
 | |
| 
 | |
| 					if goroutineId != 0 {
 | |
| 						client.StepOutRequest(goroutineId)
 | |
| 						client.ExpectStepOutResponse(t)
 | |
| 					} else {
 | |
| 						client.ContinueRequest(-1)
 | |
| 						client.ExpectContinueResponse(t)
 | |
| 					}
 | |
| 
 | |
| 					switch e := client.ExpectMessage(t).(type) {
 | |
| 					case *dap.StoppedEvent:
 | |
| 						if e.Body.ThreadId != goroutineId {
 | |
| 							t.Fatalf("StepOut did not continue on the selected goroutine, expected %d got %d", goroutineId, e.Body.ThreadId)
 | |
| 						}
 | |
| 					case *dap.TerminatedEvent:
 | |
| 						t.Logf("program terminated")
 | |
| 					default:
 | |
| 						t.Fatalf("Unexpected event type: expect stopped or terminated event, got %#v", e)
 | |
| 					}
 | |
| 				},
 | |
| 				disconnect: false,
 | |
| 			}})
 | |
| 	})
 | |
| }
 | |
| 
 | |
| func checkStopOnNextWhileNextingError(t *testing.T, client *daptest.Client, threadID int) {
 | |
| 	t.Helper()
 | |
| 	oe := client.ExpectOutputEvent(t)
 | |
| 	if oe.Body.Category != "console" || oe.Body.Output != fmt.Sprintf("invalid command: %s\n", BetterNextWhileNextingError) {
 | |
| 		t.Errorf("\ngot  %#v\nwant Category=\"console\" Output=\"invalid command: %s\\n\"", oe, BetterNextWhileNextingError)
 | |
| 	}
 | |
| 	se := client.ExpectStoppedEvent(t)
 | |
| 	if se.Body.ThreadId != threadID || se.Body.Reason != "exception" || se.Body.Description != "invalid command" || se.Body.Text != BetterNextWhileNextingError {
 | |
| 		t.Errorf("\ngot  %#v\nwant ThreadId=%d Reason=\"exception\" Description=\"invalid command\" Text=\"%s\"", se, threadID, BetterNextWhileNextingError)
 | |
| 	}
 | |
| 	client.ExceptionInfoRequest(1)
 | |
| 	eInfo := client.ExpectExceptionInfoResponse(t)
 | |
| 	if eInfo.Body.ExceptionId != "invalid command" || eInfo.Body.Description != BetterNextWhileNextingError {
 | |
| 		t.Errorf("\ngot  %#v\nwant ExceptionId=\"invalid command\" Text=\"%s\"", eInfo, BetterNextWhileNextingError)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func TestBadAccess(t *testing.T) {
 | |
| 	if runtime.GOOS != "darwin" || testBackend != "lldb" {
 | |
| 		t.Skip("not applicable")
 | |
| 	}
 | |
| 	runTest(t, "issue2078", func(client *daptest.Client, fixture protest.Fixture) {
 | |
| 		runDebugSessionWithBPs(t, client, "launch",
 | |
| 			// Launch
 | |
| 			func() {
 | |
| 				client.LaunchRequest("exec", fixture.Path, !stopOnEntry)
 | |
| 			},
 | |
| 			// Set breakpoints
 | |
| 			fixture.Source, []int{4},
 | |
| 			[]onBreakpoint{{ // Stop at line 4
 | |
| 				execute: func() {
 | |
| 					checkStop(t, client, 1, "main.main", 4)
 | |
| 
 | |
| 					expectStoppedOnError := func(errorPrefix string) {
 | |
| 						t.Helper()
 | |
| 						se := client.ExpectStoppedEvent(t)
 | |
| 						if se.Body.ThreadId != 1 || se.Body.Reason != "exception" || (se.Body.Description != "runtime error" && se.Body.Description != "panic") || !strings.Contains(se.Body.Text, errorPrefix) {
 | |
| 							t.Errorf("\ngot  %#v\nwant ThreadId=1 Reason=\"exception\" Description=\"runtime error\" Text=\"%s\"", se, errorPrefix)
 | |
| 						}
 | |
| 						client.ExceptionInfoRequest(1)
 | |
| 						eInfo := client.ExpectExceptionInfoResponse(t)
 | |
| 						if (eInfo.Body.ExceptionId != "runtime error" && eInfo.Body.ExceptionId != "panic") || !strings.Contains(eInfo.Body.Description, errorPrefix) {
 | |
| 							t.Errorf("\ngot  %#v\nwant ExceptionId=\"runtime error\" Text=\"%s\"", eInfo, errorPrefix)
 | |
| 						}
 | |
| 					}
 | |
| 
 | |
| 					client.ContinueRequest(1)
 | |
| 					client.ExpectContinueResponse(t)
 | |
| 					expectStoppedOnError("invalid memory address or nil pointer dereference")
 | |
| 
 | |
| 					client.StackTraceRequest(1, 0, 2)
 | |
| 					st := client.ExpectStackTraceResponse(t)
 | |
| 					if len(st.Body.StackFrames) > 0 && st.Body.StackFrames[0].Name == "runtime.fatalpanic" {
 | |
| 						// this debugserver has --unmask-signals and it works correctly
 | |
| 						return
 | |
| 					}
 | |
| 
 | |
| 					client.NextRequest(1)
 | |
| 					client.ExpectNextResponse(t)
 | |
| 					expectStoppedOnError("invalid memory address or nil pointer dereference")
 | |
| 
 | |
| 					client.NextRequest(1)
 | |
| 					client.ExpectNextResponse(t)
 | |
| 					checkStopOnNextWhileNextingError(t, client, 1)
 | |
| 
 | |
| 					client.StepInRequest(1)
 | |
| 					client.ExpectStepInResponse(t)
 | |
| 					checkStopOnNextWhileNextingError(t, client, 1)
 | |
| 
 | |
| 					client.StepOutRequest(1)
 | |
| 					client.ExpectStepOutResponse(t)
 | |
| 					checkStopOnNextWhileNextingError(t, client, 1)
 | |
| 				},
 | |
| 				disconnect: true,
 | |
| 			}})
 | |
| 	})
 | |
| }
 | |
| 
 | |
| // TestNextWhileNexting is inspired by command_test.TestIssue387 and tests
 | |
| // that when 'next' is interrupted by a 'breakpoint', calling 'next'
 | |
| // again will produce an error with a helpful message, and 'continue'
 | |
| // will resume the program.
 | |
| func TestNextWhileNexting(t *testing.T) {
 | |
| 	// a breakpoint triggering during a 'next' operation will interrupt 'next''
 | |
| 	// Unlike the test for the terminal package, we cannot be certain
 | |
| 	// of the number of breakpoints we expect to hit, since multiple
 | |
| 	// breakpoints being hit at the same time is not supported in DAP stopped
 | |
| 	// events.
 | |
| 	runTest(t, "issue387", func(client *daptest.Client, fixture protest.Fixture) {
 | |
| 		runDebugSessionWithBPs(t, client, "launch",
 | |
| 			// Launch
 | |
| 			func() {
 | |
| 				client.LaunchRequest("exec", fixture.Path, !stopOnEntry)
 | |
| 			},
 | |
| 			// Set breakpoints
 | |
| 			fixture.Source, []int{15},
 | |
| 			[]onBreakpoint{{ // Stop at line 15
 | |
| 				execute: func() {
 | |
| 					checkStop(t, client, 1, "main.main", 15)
 | |
| 
 | |
| 					client.SetBreakpointsRequest(fixture.Source, []int{8})
 | |
| 					client.ExpectSetBreakpointsResponse(t)
 | |
| 
 | |
| 					client.ContinueRequest(1)
 | |
| 					client.ExpectContinueResponse(t)
 | |
| 
 | |
| 					bpSe := client.ExpectStoppedEvent(t)
 | |
| 					threadID := bpSe.Body.ThreadId
 | |
| 					checkStop(t, client, threadID, "main.dostuff", 8)
 | |
| 
 | |
| 					for pos := 9; pos < 11; pos++ {
 | |
| 						client.NextRequest(threadID)
 | |
| 						client.ExpectNextResponse(t)
 | |
| 
 | |
| 						stepInProgress := true
 | |
| 						for stepInProgress {
 | |
| 							m := client.ExpectStoppedEvent(t)
 | |
| 							switch m.Body.Reason {
 | |
| 							case "step":
 | |
| 								if !m.Body.AllThreadsStopped {
 | |
| 									t.Errorf("got %#v, want Reason=\"step\", AllThreadsStopped=true", m)
 | |
| 								}
 | |
| 								checkStop(t, client, m.Body.ThreadId, "main.dostuff", pos)
 | |
| 								stepInProgress = false
 | |
| 							case "breakpoint":
 | |
| 								if !m.Body.AllThreadsStopped {
 | |
| 									t.Errorf("got %#v, want Reason=\"breakpoint\", AllThreadsStopped=true", m)
 | |
| 								}
 | |
| 
 | |
| 								if stepInProgress {
 | |
| 									// We encountered a breakpoint on a different thread. We should have to resume execution
 | |
| 									// using continue.
 | |
| 									oe := client.ExpectOutputEvent(t)
 | |
| 									if oe.Body.Category != "console" || !strings.Contains(oe.Body.Output, "Step interrupted by a breakpoint.") {
 | |
| 										t.Errorf("\ngot  %#v\nwant Category=\"console\" Output=\"Step interrupted by a breakpoint.\"", oe)
 | |
| 									}
 | |
| 									client.NextRequest(m.Body.ThreadId)
 | |
| 									client.ExpectNextResponse(t)
 | |
| 									checkStopOnNextWhileNextingError(t, client, m.Body.ThreadId)
 | |
| 									// Continue since we have not finished the step request.
 | |
| 									client.ContinueRequest(threadID)
 | |
| 									client.ExpectContinueResponse(t)
 | |
| 								} else {
 | |
| 									checkStop(t, client, m.Body.ThreadId, "main.dostuff", 8)
 | |
| 									// Switch to stepping on this thread instead.
 | |
| 									pos = 8
 | |
| 									threadID = m.Body.ThreadId
 | |
| 								}
 | |
| 							default:
 | |
| 								t.Fatalf("got %#v, want StoppedEvent on step or breakpoint", m)
 | |
| 							}
 | |
| 						}
 | |
| 					}
 | |
| 				},
 | |
| 				disconnect: true,
 | |
| 			}})
 | |
| 	})
 | |
| }
 | |
| 
 | |
| func TestPanicBreakpointOnContinue(t *testing.T) {
 | |
| 	runTest(t, "panic", func(client *daptest.Client, fixture protest.Fixture) {
 | |
| 		runDebugSessionWithBPs(t, client, "launch",
 | |
| 			// Launch
 | |
| 			func() {
 | |
| 				client.LaunchRequest("exec", fixture.Path, !stopOnEntry)
 | |
| 			},
 | |
| 			// Set breakpoints
 | |
| 			fixture.Source, []int{5},
 | |
| 			[]onBreakpoint{{
 | |
| 				execute: func() {
 | |
| 					checkStop(t, client, 1, "main.main", 5)
 | |
| 
 | |
| 					client.ContinueRequest(1)
 | |
| 					client.ExpectContinueResponse(t)
 | |
| 
 | |
| 					text := "\"BOOM!\""
 | |
| 					se := client.ExpectStoppedEvent(t)
 | |
| 					if se.Body.ThreadId != 1 || se.Body.Reason != "exception" || se.Body.Description != "panic" || se.Body.Text != text {
 | |
| 						t.Errorf("\ngot  %#v\nwant ThreadId=1 Reason=\"exception\" Description=\"panic\" Text=%q", se, text)
 | |
| 					}
 | |
| 
 | |
| 					client.ExceptionInfoRequest(1)
 | |
| 					eInfo := client.ExpectExceptionInfoResponse(t)
 | |
| 					if eInfo.Body.ExceptionId != "panic" || eInfo.Body.Description != text {
 | |
| 						t.Errorf("\ngot  %#v\nwant ExceptionId=\"panic\" Description=%q", eInfo, text)
 | |
| 					}
 | |
| 
 | |
| 					client.StackTraceRequest(se.Body.ThreadId, 0, 20)
 | |
| 					st := client.ExpectStackTraceResponse(t)
 | |
| 					for i, frame := range st.Body.StackFrames {
 | |
| 						if strings.HasPrefix(frame.Name, "runtime.") {
 | |
| 							if frame.PresentationHint != "subtle" {
 | |
| 								t.Errorf("\ngot Body.StackFrames[%d]=%#v\nwant Source.PresentationHint=\"subtle\"", i, frame)
 | |
| 							}
 | |
| 						} else if frame.Source != nil && frame.Source.PresentationHint != "" {
 | |
| 							t.Errorf("\ngot Body.StackFrames[%d]=%#v\nwant Source.PresentationHint=\"\"", i, frame)
 | |
| 						}
 | |
| 					}
 | |
| 				},
 | |
| 				disconnect: true,
 | |
| 			}})
 | |
| 	})
 | |
| }
 | |
| 
 | |
| func TestPanicBreakpointOnNext(t *testing.T) {
 | |
| 	if !goversion.VersionAfterOrEqual(runtime.Version(), 1, 14) {
 | |
| 		// In Go 1.13, 'next' will step into the defer in the runtime
 | |
| 		// main function, instead of the next line in the main program.
 | |
| 		t.SkipNow()
 | |
| 	}
 | |
| 
 | |
| 	runTest(t, "panic", func(client *daptest.Client, fixture protest.Fixture) {
 | |
| 		runDebugSessionWithBPs(t, client, "launch",
 | |
| 			// Launch
 | |
| 			func() {
 | |
| 				client.LaunchRequest("exec", fixture.Path, !stopOnEntry)
 | |
| 			},
 | |
| 			// Set breakpoints
 | |
| 			fixture.Source, []int{5},
 | |
| 			[]onBreakpoint{{
 | |
| 				execute: func() {
 | |
| 					checkStop(t, client, 1, "main.main", 5)
 | |
| 
 | |
| 					client.NextRequest(1)
 | |
| 					client.ExpectNextResponse(t)
 | |
| 
 | |
| 					text := "\"BOOM!\""
 | |
| 					se := client.ExpectStoppedEvent(t)
 | |
| 					if se.Body.ThreadId != 1 || se.Body.Reason != "exception" || se.Body.Description != "panic" || se.Body.Text != text {
 | |
| 						t.Errorf("\ngot  %#v\nwant ThreadId=1 Reason=\"exception\" Description=\"panic\" Text=%q", se, text)
 | |
| 					}
 | |
| 
 | |
| 					client.ExceptionInfoRequest(1)
 | |
| 					eInfo := client.ExpectExceptionInfoResponse(t)
 | |
| 					if eInfo.Body.ExceptionId != "panic" || eInfo.Body.Description != text {
 | |
| 						t.Errorf("\ngot  %#v\nwant ExceptionId=\"panic\" Description=%q", eInfo, text)
 | |
| 					}
 | |
| 				},
 | |
| 				disconnect: true,
 | |
| 			}})
 | |
| 	})
 | |
| }
 | |
| 
 | |
| func TestFatalThrowBreakpoint(t *testing.T) {
 | |
| 	runTest(t, "fatalerror", func(client *daptest.Client, fixture protest.Fixture) {
 | |
| 		runDebugSessionWithBPs(t, client, "launch",
 | |
| 			// Launch
 | |
| 			func() {
 | |
| 				client.LaunchRequest("exec", fixture.Path, !stopOnEntry)
 | |
| 			},
 | |
| 			// Set breakpoints
 | |
| 			fixture.Source, []int{3},
 | |
| 			[]onBreakpoint{{
 | |
| 				execute: func() {
 | |
| 					checkStop(t, client, 1, "main.main", 3)
 | |
| 
 | |
| 					client.ContinueRequest(1)
 | |
| 					client.ExpectContinueResponse(t)
 | |
| 
 | |
| 					var text string
 | |
| 					// This does not work for Go 1.16.
 | |
| 					ver, _ := goversion.Parse(runtime.Version())
 | |
| 					if ver.Major != 1 || ver.Minor != 16 {
 | |
| 						text = "\"go of nil func value\""
 | |
| 					}
 | |
| 
 | |
| 					se := client.ExpectStoppedEvent(t)
 | |
| 					if se.Body.ThreadId != 1 || se.Body.Reason != "exception" || se.Body.Description != "fatal error" || se.Body.Text != text {
 | |
| 						t.Errorf("\ngot  %#v\nwant ThreadId=1 Reason=\"exception\" Description=\"fatal error\" Text=%q", se, text)
 | |
| 					}
 | |
| 
 | |
| 					// This does not work for Go 1.16.
 | |
| 					errorPrefix := text
 | |
| 					if errorPrefix == "" {
 | |
| 						errorPrefix = "Throw reason unavailable, see https://github.com/golang/go/issues/46425"
 | |
| 					}
 | |
| 					client.ExceptionInfoRequest(1)
 | |
| 					eInfo := client.ExpectExceptionInfoResponse(t)
 | |
| 					if eInfo.Body.ExceptionId != "fatal error" || !strings.HasPrefix(eInfo.Body.Description, errorPrefix) {
 | |
| 						t.Errorf("\ngot  %#v\nwant ExceptionId=\"runtime error\" Text=%s", eInfo, errorPrefix)
 | |
| 					}
 | |
| 				},
 | |
| 				disconnect: true,
 | |
| 			}})
 | |
| 	})
 | |
| 	runTest(t, "testdeadlock", func(client *daptest.Client, fixture protest.Fixture) {
 | |
| 		runDebugSessionWithBPs(t, client, "launch",
 | |
| 			// Launch
 | |
| 			func() {
 | |
| 				client.LaunchRequest("exec", fixture.Path, !stopOnEntry)
 | |
| 			},
 | |
| 			// Set breakpoints
 | |
| 			fixture.Source, []int{3},
 | |
| 			[]onBreakpoint{{
 | |
| 				execute: func() {
 | |
| 					checkStop(t, client, 1, "main.main", 3)
 | |
| 
 | |
| 					client.ContinueRequest(1)
 | |
| 					client.ExpectContinueResponse(t)
 | |
| 
 | |
| 					// This does not work for Go 1.16 so skip by detecting versions before or after 1.16.
 | |
| 					var text string
 | |
| 					if !goversion.VersionAfterOrEqual(runtime.Version(), 1, 16) || goversion.VersionAfterOrEqual(runtime.Version(), 1, 17) {
 | |
| 						text = "\"all goroutines are asleep - deadlock!\""
 | |
| 					}
 | |
| 					se := client.ExpectStoppedEvent(t)
 | |
| 					if se.Body.Reason != "exception" || se.Body.Description != "fatal error" || se.Body.Text != text {
 | |
| 						t.Errorf("\ngot  %#v\nwant Reason=\"exception\" Description=\"fatal error\" Text=%q", se, text)
 | |
| 					}
 | |
| 
 | |
| 					// TODO(suzmue): Get the exception info for the thread and check the description
 | |
| 					// includes "all goroutines are asleep - deadlock!".
 | |
| 					// Stopped events with no selected goroutines need to be supported to test deadlock.
 | |
| 				},
 | |
| 				disconnect: true,
 | |
| 			}})
 | |
| 	})
 | |
| }
 | |
| 
 | |
| // checkStop covers the standard sequence of requests issued by
 | |
| // a client at a breakpoint or another non-terminal stop event.
 | |
| // The details have been tested by other tests,
 | |
| // so this is just a sanity check.
 | |
| // Skips line check if line is -1.
 | |
| func checkStop(t *testing.T, client *daptest.Client, thread int, fname string, line interface{}) {
 | |
| 	t.Helper()
 | |
| 	client.ThreadsRequest()
 | |
| 	client.ExpectThreadsResponse(t)
 | |
| 
 | |
| 	client.CheckStopLocation(t, thread, fname, line)
 | |
| 
 | |
| 	client.ScopesRequest(1000)
 | |
| 	client.ExpectScopesResponse(t)
 | |
| 
 | |
| 	client.VariablesRequest(localsScope)
 | |
| 	client.ExpectVariablesResponse(t)
 | |
| }
 | |
| 
 | |
| // onBreakpoint specifies what the test harness should simulate at
 | |
| // a stopped breakpoint. First execute() is to be called to test
 | |
| // specified editor-driven or user-driven requests. Then if
 | |
| // disconnect is true, the test harness will abort the program
 | |
| // execution. Otherwise, a continue will be issued and the
 | |
| // program will continue to the next breakpoint or termination.
 | |
| // If terminated is true, we expect requests at this breakpoint
 | |
| // to result in termination.
 | |
| type onBreakpoint struct {
 | |
| 	execute    func()
 | |
| 	disconnect bool
 | |
| 	terminated bool
 | |
| }
 | |
| 
 | |
| // runDebugSessionWithBPs is a helper for executing the common init and shutdown
 | |
| // sequences for a program that does not stop on entry
 | |
| // while specifying breakpoints and unique launch/attach criteria via parameters.
 | |
| //
 | |
| //	cmd            - "launch" or "attach"
 | |
| //	cmdRequest     - a function that sends a launch or attach request,
 | |
| //	                 so the test author has full control of its arguments.
 | |
| //	                 Note that he rest of the test sequence assumes that
 | |
| //	                 stopOnEntry is false.
 | |
| //	 source        - source file path, needed to set breakpoints, "" if none to be set.
 | |
| //	 breakpoints   - list of lines, where breakpoints are to be set
 | |
| //	 onBPs         - list of test sequences to execute at each of the set breakpoints.
 | |
| func runDebugSessionWithBPs(t *testing.T, client *daptest.Client, cmd string, cmdRequest func(), source string, breakpoints []int, onBPs []onBreakpoint) {
 | |
| 	client.InitializeRequest()
 | |
| 	client.ExpectInitializeResponseAndCapabilities(t)
 | |
| 
 | |
| 	cmdRequest()
 | |
| 	client.ExpectInitializedEvent(t)
 | |
| 	switch cmd {
 | |
| 	case "launch":
 | |
| 		client.ExpectLaunchResponse(t)
 | |
| 	case "attach":
 | |
| 		client.ExpectAttachResponse(t)
 | |
| 	default:
 | |
| 		panic("expected launch or attach command")
 | |
| 	}
 | |
| 
 | |
| 	if source != "" {
 | |
| 		client.SetBreakpointsRequest(source, breakpoints)
 | |
| 		client.ExpectSetBreakpointsResponse(t)
 | |
| 	}
 | |
| 
 | |
| 	// Skip no-op setExceptionBreakpoints
 | |
| 
 | |
| 	client.ConfigurationDoneRequest()
 | |
| 	client.ExpectConfigurationDoneResponse(t)
 | |
| 
 | |
| 	// Program automatically continues to breakpoint or completion
 | |
| 
 | |
| 	// TODO(polina): See if we can make this more like withTestProcessArgs in proc_test:
 | |
| 	// a single function pointer gets called here and then if it wants to continue it calls
 | |
| 	// client.ContinueRequest/client.ExpectContinueResponse/client.ExpectStoppedEvent
 | |
| 	// (possibly using a helper function).
 | |
| 	for _, onBP := range onBPs {
 | |
| 		client.ExpectStoppedEvent(t)
 | |
| 		onBP.execute()
 | |
| 		if onBP.disconnect {
 | |
| 			client.DisconnectRequestWithKillOption(true)
 | |
| 			if onBP.terminated {
 | |
| 				client.ExpectOutputEventProcessExitedAnyStatus(t)
 | |
| 				client.ExpectOutputEventDetaching(t)
 | |
| 			} else {
 | |
| 				client.ExpectOutputEventDetachingKill(t)
 | |
| 			}
 | |
| 			client.ExpectDisconnectResponse(t)
 | |
| 			client.ExpectTerminatedEvent(t)
 | |
| 			return
 | |
| 		}
 | |
| 		client.ContinueRequest(1)
 | |
| 		client.ExpectContinueResponse(t)
 | |
| 		// "Continue" is triggered after the response is sent
 | |
| 	}
 | |
| 
 | |
| 	if cmd == "launch" { // Let the program run to completion
 | |
| 		client.ExpectTerminatedEvent(t)
 | |
| 	}
 | |
| 	client.DisconnectRequestWithKillOption(true)
 | |
| 	if cmd == "launch" {
 | |
| 		client.ExpectOutputEventProcessExitedAnyStatus(t)
 | |
| 		client.ExpectOutputEventDetaching(t)
 | |
| 	} else if cmd == "attach" {
 | |
| 		client.ExpectOutputEventDetachingKill(t)
 | |
| 	}
 | |
| 	client.ExpectDisconnectResponse(t)
 | |
| 	client.ExpectTerminatedEvent(t)
 | |
| }
 | |
| 
 | |
| // runDebugSession is a helper for executing the standard init and shutdown
 | |
| // sequences for a program that does not stop on entry
 | |
| // while specifying unique launch criteria via parameters.
 | |
| func runDebugSession(t *testing.T, client *daptest.Client, cmd string, cmdRequest func()) {
 | |
| 	runDebugSessionWithBPs(t, client, cmd, cmdRequest, "", nil, nil)
 | |
| }
 | |
| 
 | |
| func TestLaunchDebugRequest(t *testing.T) {
 | |
| 	rescueStderr := os.Stderr
 | |
| 	r, w, _ := os.Pipe()
 | |
| 	os.Stderr = w
 | |
| 	done := make(chan struct{})
 | |
| 
 | |
| 	var err []byte
 | |
| 
 | |
| 	go func() {
 | |
| 		err, _ = io.ReadAll(r)
 | |
| 		t.Log(string(err))
 | |
| 		close(done)
 | |
| 	}()
 | |
| 
 | |
| 	tmpBin := "__tmpBin"
 | |
| 	runTest(t, "increment", func(client *daptest.Client, fixture protest.Fixture) {
 | |
| 		// We reuse the harness that builds, but ignore the built binary,
 | |
| 		// only relying on the source to be built in response to LaunchRequest.
 | |
| 		runDebugSession(t, client, "launch", func() {
 | |
| 			client.LaunchRequestWithArgs(map[string]interface{}{
 | |
| 				"mode": "debug", "program": fixture.Source, "output": tmpBin,
 | |
| 			})
 | |
| 		})
 | |
| 	})
 | |
| 	// Wait for the test to finish to capture all stderr
 | |
| 	time.Sleep(100 * time.Millisecond)
 | |
| 
 | |
| 	w.Close()
 | |
| 	<-done
 | |
| 	os.Stderr = rescueStderr
 | |
| 
 | |
| 	rmErrRe := regexp.MustCompile(`could not remove .*\n`)
 | |
| 	rmErr := rmErrRe.FindString(string(err))
 | |
| 	if rmErr != "" {
 | |
| 		// On Windows, a file in use cannot be removed, resulting in "Access is denied".
 | |
| 		// When the process exits, Delve releases the binary by calling
 | |
| 		// BinaryInfo.Close(), but it appears that it is still in use (by Windows?)
 | |
| 		// shortly after. gobuild.Remove has a delay to address this, but
 | |
| 		// to avoid any test flakiness we guard against this failure here as well.
 | |
| 		if runtime.GOOS != "windows" || !stringContainsCaseInsensitive(rmErr, "Access is denied") {
 | |
| 			t.Fatalf("Binary removal failure:\n%s\n", rmErr)
 | |
| 		}
 | |
| 	} else {
 | |
| 		tmpBin = cleanExeName(tmpBin)
 | |
| 		// We did not get a removal error, but did we even try to remove before exiting?
 | |
| 		// Confirm that the binary did get removed.
 | |
| 		if _, err := os.Stat(tmpBin); err == nil || os.IsExist(err) {
 | |
| 			t.Fatal("Failed to remove temp binary", tmpBin)
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // TestLaunchRequestDefaults tests defaults for launch attribute that are explicit in other tests.
 | |
| func TestLaunchRequestDefaults(t *testing.T) {
 | |
| 	runTest(t, "increment", func(client *daptest.Client, fixture protest.Fixture) {
 | |
| 		runDebugSession(t, client, "launch", func() {
 | |
| 			client.LaunchRequestWithArgs(map[string]interface{}{
 | |
| 				"mode": "" /*"debug" by default*/, "program": fixture.Source, "output": "__mybin",
 | |
| 			})
 | |
| 		})
 | |
| 	})
 | |
| 	runTest(t, "increment", func(client *daptest.Client, fixture protest.Fixture) {
 | |
| 		runDebugSession(t, client, "launch", func() {
 | |
| 			client.LaunchRequestWithArgs(map[string]interface{}{
 | |
| 				/*"mode":"debug" by default*/ "program": fixture.Source, "output": "__mybin",
 | |
| 			})
 | |
| 		})
 | |
| 	})
 | |
| 	runTest(t, "increment", func(client *daptest.Client, fixture protest.Fixture) {
 | |
| 		runDebugSession(t, client, "launch", func() {
 | |
| 			// Use the temporary output binary.
 | |
| 			client.LaunchRequestWithArgs(map[string]interface{}{
 | |
| 				"mode": "debug", "program": fixture.Source,
 | |
| 			})
 | |
| 		})
 | |
| 	})
 | |
| }
 | |
| 
 | |
| // TestLaunchRequestOutputPath verifies that relative output binary path
 | |
| // is mapped to server's, not target's, working directory.
 | |
| func TestLaunchRequestOutputPath(t *testing.T) {
 | |
| 	runTest(t, "testargs", func(client *daptest.Client, fixture protest.Fixture) {
 | |
| 		inrel := "__somebin"
 | |
| 		wd, _ := os.Getwd()
 | |
| 		outabs := cleanExeName(filepath.Join(wd, inrel))
 | |
| 		runDebugSessionWithBPs(t, client, "launch",
 | |
| 			// Launch
 | |
| 			func() {
 | |
| 				client.LaunchRequestWithArgs(map[string]interface{}{
 | |
| 					"mode": "debug", "program": fixture.Source, "output": inrel,
 | |
| 					"cwd": filepath.Dir(wd),
 | |
| 				})
 | |
| 			},
 | |
| 			// Set breakpoints
 | |
| 			fixture.Source, []int{12},
 | |
| 			[]onBreakpoint{{
 | |
| 				execute: func() {
 | |
| 					checkStop(t, client, 1, "main.main", 12)
 | |
| 					client.EvaluateRequest("os.Args[0]", 1000, "repl")
 | |
| 					checkEval(t, client.ExpectEvaluateResponse(t), fmt.Sprintf("%q", outabs), noChildren)
 | |
| 				},
 | |
| 				disconnect: true,
 | |
| 			}})
 | |
| 	})
 | |
| }
 | |
| 
 | |
| func TestExitNonZeroStatus(t *testing.T) {
 | |
| 	runTest(t, "pr1055", func(client *daptest.Client, fixture protest.Fixture) {
 | |
| 		client.InitializeRequest()
 | |
| 		client.ExpectInitializeResponseAndCapabilities(t)
 | |
| 
 | |
| 		client.LaunchRequest("exec", fixture.Path, !stopOnEntry)
 | |
| 		client.ExpectInitializedEvent(t)
 | |
| 		client.ExpectLaunchResponse(t)
 | |
| 
 | |
| 		client.ConfigurationDoneRequest()
 | |
| 		client.ExpectConfigurationDoneResponse(t)
 | |
| 
 | |
| 		client.ExpectTerminatedEvent(t)
 | |
| 
 | |
| 		client.DisconnectRequest()
 | |
| 		// Check that the process exit status is 2.
 | |
| 		oep := client.ExpectOutputEventProcessExited(t, 2)
 | |
| 		if oep.Body.Category != "console" {
 | |
| 			t.Errorf("\ngot %#v\nwant Category='console'", oep)
 | |
| 		}
 | |
| 		oed := client.ExpectOutputEventDetaching(t)
 | |
| 		if oed.Body.Category != "console" {
 | |
| 			t.Errorf("\ngot %#v\nwant Category='console'", oed)
 | |
| 		}
 | |
| 		client.ExpectDisconnectResponse(t)
 | |
| 		client.ExpectTerminatedEvent(t)
 | |
| 	})
 | |
| }
 | |
| 
 | |
| func TestNoDebug_GoodExitStatus(t *testing.T) {
 | |
| 	runTest(t, "increment", func(client *daptest.Client, fixture protest.Fixture) {
 | |
| 		runNoDebugSession(t, client, func() {
 | |
| 			client.LaunchRequestWithArgs(map[string]interface{}{
 | |
| 				"noDebug": true, "mode": "debug", "program": fixture.Source, "output": "__mybin",
 | |
| 			})
 | |
| 		}, 0)
 | |
| 	})
 | |
| }
 | |
| 
 | |
| func TestNoDebug_BadExitStatus(t *testing.T) {
 | |
| 	runTest(t, "issue1101", func(client *daptest.Client, fixture protest.Fixture) {
 | |
| 		runNoDebugSession(t, client, func() {
 | |
| 			client.LaunchRequestWithArgs(map[string]interface{}{
 | |
| 				"noDebug": true, "mode": "exec", "program": fixture.Path,
 | |
| 			})
 | |
| 		}, 2)
 | |
| 	})
 | |
| }
 | |
| 
 | |
| // runNoDebugSession tests the session started with noDebug=true runs
 | |
| // to completion and logs termination status.
 | |
| func runNoDebugSession(t *testing.T, client *daptest.Client, launchRequest func(), exitStatus int) {
 | |
| 	client.InitializeRequest()
 | |
| 	client.ExpectInitializeResponseAndCapabilities(t)
 | |
| 
 | |
| 	launchRequest()
 | |
| 	// no initialized event.
 | |
| 	// noDebug mode applies only to "launch" requests.
 | |
| 	client.ExpectLaunchResponse(t)
 | |
| 
 | |
| 	client.ExpectOutputEventProcessExited(t, exitStatus)
 | |
| 	client.ExpectTerminatedEvent(t)
 | |
| 	client.DisconnectRequestWithKillOption(true)
 | |
| 	client.ExpectDisconnectResponse(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
 | |
| 		ExpectNoDebugError := func(cmd string) {
 | |
| 			er := client.ExpectErrorResponse(t)
 | |
| 			if !checkErrorMessageFormat(er.Body.Error, 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)
 | |
| 		terminated, disconnectResp := false, false
 | |
| 		for {
 | |
| 			m, err := client.ReadMessage()
 | |
| 			if err != nil {
 | |
| 				break
 | |
| 			}
 | |
| 			switch m := m.(type) {
 | |
| 			case *dap.OutputEvent:
 | |
| 				ok := false
 | |
| 				wants := []string{`Terminating process [0-9]+\n`, fmt.Sprintf(daptest.ProcessExited, "(-1|1)")}
 | |
| 				for _, want := range wants {
 | |
| 					if matched, _ := regexp.MatchString(want, m.Body.Output); matched {
 | |
| 						ok = true
 | |
| 						break
 | |
| 					}
 | |
| 				}
 | |
| 				if !ok {
 | |
| 					t.Errorf("\ngot %#v\nwant Output=%q\n", m, wants)
 | |
| 				}
 | |
| 			case *dap.TerminatedEvent:
 | |
| 				terminated = true
 | |
| 			case *dap.DisconnectResponse:
 | |
| 				disconnectResp = true
 | |
| 			default:
 | |
| 				t.Errorf("got unexpected message %#v", m)
 | |
| 			}
 | |
| 		}
 | |
| 		if !terminated {
 | |
| 			t.Errorf("did not get TerminatedEvent")
 | |
| 		}
 | |
| 		if !disconnectResp {
 | |
| 			t.Errorf("did not get DisconnectResponse")
 | |
| 		}
 | |
| 	})
 | |
| }
 | |
| 
 | |
| func TestLaunchRequestWithRelativeBuildPath(t *testing.T) {
 | |
| 	serverStopped := make(chan struct{})
 | |
| 	client := startDAPServerWithClient(t, false, serverStopped)
 | |
| 	defer client.Close()
 | |
| 
 | |
| 	fixdir := protest.FindFixturesDir()
 | |
| 	if filepath.IsAbs(fixdir) {
 | |
| 		t.Fatal("this test requires relative program path")
 | |
| 	}
 | |
| 	program := filepath.Join(protest.FindFixturesDir(), "buildtest")
 | |
| 
 | |
| 	// Use different working dir for target than dlv.
 | |
| 	// Program path will be interpreted relative to dlv's.
 | |
| 	dlvwd, _ := os.Getwd()
 | |
| 	runDebugSession(t, client, "launch", func() {
 | |
| 		client.LaunchRequestWithArgs(map[string]interface{}{
 | |
| 			"mode": "debug", "program": program, "cwd": filepath.Dir(dlvwd),
 | |
| 		})
 | |
| 	})
 | |
| 	<-serverStopped
 | |
| }
 | |
| 
 | |
| func TestLaunchRequestWithRelativeExecPath(t *testing.T) {
 | |
| 	runTest(t, "increment", func(client *daptest.Client, fixture protest.Fixture) {
 | |
| 		symlink := "./__thisexe"
 | |
| 		err := os.Symlink(fixture.Path, symlink)
 | |
| 		if err != nil {
 | |
| 			if runtime.GOOS == "windows" {
 | |
| 				t.Skip("this test requires symlinks to be enabled and allowed")
 | |
| 			} else {
 | |
| 				t.Fatal("unable to create relative symlink:", err)
 | |
| 			}
 | |
| 		}
 | |
| 		defer os.Remove(symlink)
 | |
| 		runDebugSession(t, client, "launch", func() {
 | |
| 			client.LaunchRequestWithArgs(map[string]interface{}{
 | |
| 				"mode": "exec", "program": symlink,
 | |
| 			})
 | |
| 		})
 | |
| 	})
 | |
| }
 | |
| 
 | |
| func TestLaunchTestRequest(t *testing.T) {
 | |
| 	orgWD, _ := os.Getwd()
 | |
| 	fixtures := protest.FindFixturesDir() // relative to current working directory.
 | |
| 	absoluteFixturesDir, _ := filepath.Abs(fixtures)
 | |
| 	absolutePkgDir, _ := filepath.Abs(filepath.Join(fixtures, "buildtest"))
 | |
| 	testFile := filepath.Join(absolutePkgDir, "main_test.go")
 | |
| 
 | |
| 	for _, tc := range []struct {
 | |
| 		name       string
 | |
| 		dlvWD      string
 | |
| 		launchArgs map[string]interface{}
 | |
| 		wantWD     string
 | |
| 	}{{
 | |
| 		name: "default",
 | |
| 		launchArgs: map[string]interface{}{
 | |
| 			"mode": "test", "program": absolutePkgDir,
 | |
| 		},
 | |
| 		wantWD: absolutePkgDir,
 | |
| 	}, {
 | |
| 		name: "output",
 | |
| 		launchArgs: map[string]interface{}{
 | |
| 			"mode": "test", "program": absolutePkgDir, "output": "test.out",
 | |
| 		},
 | |
| 		wantWD: absolutePkgDir,
 | |
| 	}, {
 | |
| 		name: "dlvCwd",
 | |
| 		launchArgs: map[string]interface{}{
 | |
| 			"mode": "test", "program": absolutePkgDir, "dlvCwd": ".",
 | |
| 		},
 | |
| 		wantWD: absolutePkgDir,
 | |
| 	}, {
 | |
| 		name: "dlvCwd2",
 | |
| 		launchArgs: map[string]interface{}{
 | |
| 			"mode": "test", "program": ".", "dlvCwd": absolutePkgDir,
 | |
| 		},
 | |
| 		wantWD: absolutePkgDir,
 | |
| 	}, {
 | |
| 		name: "cwd",
 | |
| 		launchArgs: map[string]interface{}{
 | |
| 			"mode": "test", "program": absolutePkgDir, "cwd": fixtures, // fixtures is relative to the current working directory.
 | |
| 		},
 | |
| 		wantWD: absoluteFixturesDir,
 | |
| 	}, {
 | |
| 		name:  "dlv runs outside of module",
 | |
| 		dlvWD: os.TempDir(),
 | |
| 		launchArgs: map[string]interface{}{
 | |
| 			"mode": "test", "program": absolutePkgDir, "dlvCwd": absoluteFixturesDir,
 | |
| 		},
 | |
| 		wantWD: absolutePkgDir,
 | |
| 	}, {
 | |
| 		name:  "dlv builds in dlvCwd but runs in cwd",
 | |
| 		dlvWD: fixtures,
 | |
| 		launchArgs: map[string]interface{}{
 | |
| 			"mode": "test", "program": absolutePkgDir, "dlvCwd": absolutePkgDir, "cwd": "..", // relative to dlvCwd.
 | |
| 		},
 | |
| 		wantWD: absoluteFixturesDir,
 | |
| 	}} {
 | |
| 		t.Run(tc.name, func(t *testing.T) {
 | |
| 			// Some test cases with dlvCwd or dlvWD change process working directory.
 | |
| 			defer os.Chdir(orgWD)
 | |
| 			if tc.dlvWD != "" {
 | |
| 				os.Chdir(tc.dlvWD)
 | |
| 				defer os.Chdir(orgWD)
 | |
| 			}
 | |
| 			serverStopped := make(chan struct{})
 | |
| 			client := startDAPServerWithClient(t, false, serverStopped)
 | |
| 			defer client.Close()
 | |
| 
 | |
| 			runDebugSessionWithBPs(t, client, "launch",
 | |
| 				func() { // Launch
 | |
| 					client.LaunchRequestWithArgs(tc.launchArgs)
 | |
| 				},
 | |
| 				testFile, []int{14},
 | |
| 				[]onBreakpoint{{
 | |
| 					execute: func() {
 | |
| 						checkStop(t, client, -1, "buildtest.TestCurrentDirectory", 14)
 | |
| 						client.VariablesRequest(1001) // Locals
 | |
| 						locals := client.ExpectVariablesResponse(t)
 | |
| 						checkChildren(t, locals, "Locals", 1)
 | |
| 						for i := range locals.Body.Variables {
 | |
| 							switch locals.Body.Variables[i].Name {
 | |
| 							case "wd": // The test's working directory is the package directory by default.
 | |
| 								checkVarExact(t, locals, i, "wd", "wd", fmt.Sprintf("%q", tc.wantWD), "string", noChildren)
 | |
| 							}
 | |
| 						}
 | |
| 					},
 | |
| 				}})
 | |
| 
 | |
| 			<-serverStopped
 | |
| 		})
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // Tests that 'args' from LaunchRequest are parsed and passed to the target
 | |
| // program. The target program exits without an error on success, and
 | |
| // panics on error, causing an unexpected StoppedEvent instead of
 | |
| // Terminated Event.
 | |
| func TestLaunchRequestWithArgs(t *testing.T) {
 | |
| 	runTest(t, "testargs", func(client *daptest.Client, fixture protest.Fixture) {
 | |
| 		runDebugSession(t, client, "launch", func() {
 | |
| 			client.LaunchRequestWithArgs(map[string]interface{}{
 | |
| 				"mode": "exec", "program": fixture.Path,
 | |
| 				"args": []string{"test", "pass flag"},
 | |
| 			})
 | |
| 		})
 | |
| 	})
 | |
| }
 | |
| 
 | |
| // Tests that 'buildFlags' from LaunchRequest are parsed and passed to the
 | |
| // compiler. The target program exits without an error on success, and
 | |
| // panics on error, causing an unexpected StoppedEvent instead of
 | |
| // TerminatedEvent.
 | |
| func TestLaunchRequestWithBuildFlags(t *testing.T) {
 | |
| 	runTest(t, "buildflagtest", func(client *daptest.Client, fixture protest.Fixture) {
 | |
| 		runDebugSession(t, client, "launch", func() {
 | |
| 			// We reuse the harness that builds, but ignore the built binary,
 | |
| 			// only relying on the source to be built in response to LaunchRequest.
 | |
| 			client.LaunchRequestWithArgs(map[string]interface{}{
 | |
| 				"mode": "debug", "program": fixture.Source, "output": "__mybin",
 | |
| 				"buildFlags": "-ldflags '-X main.Hello=World'",
 | |
| 			})
 | |
| 		})
 | |
| 	})
 | |
| }
 | |
| 
 | |
| func TestLaunchRequestWithBuildFlags2(t *testing.T) {
 | |
| 	runTest(t, "buildflagtest", func(client *daptest.Client, fixture protest.Fixture) {
 | |
| 		runDebugSession(t, client, "launch", func() {
 | |
| 			// We reuse the harness that builds, but ignore the built binary,
 | |
| 			// only relying on the source to be built in response to LaunchRequest.
 | |
| 			client.LaunchRequestWithArgs(map[string]interface{}{
 | |
| 				"mode": "debug", "program": fixture.Source, "output": "__mybin",
 | |
| 				"buildFlags": []string{"-ldflags", "-X main.Hello=World"},
 | |
| 			})
 | |
| 		})
 | |
| 	})
 | |
| }
 | |
| 
 | |
| func TestLaunchRequestWithEnv(t *testing.T) {
 | |
| 	// testenv fixture will lookup SOMEVAR with
 | |
| 	//   x, y := os.Lookup("SOMEVAR")
 | |
| 	// before stopping at runtime.Breakpoint.
 | |
| 
 | |
| 	type envMap map[string]*string
 | |
| 	strVar := func(s string) *string { return &s }
 | |
| 
 | |
| 	fixtures := protest.FindFixturesDir() // relative to current working directory.
 | |
| 	testFile, _ := filepath.Abs(filepath.Join(fixtures, "testenv.go"))
 | |
| 	for _, tc := range []struct {
 | |
| 		name      string
 | |
| 		initEnv   envMap
 | |
| 		launchEnv envMap
 | |
| 		wantX     string
 | |
| 		wantY     bool
 | |
| 	}{
 | |
| 		{
 | |
| 			name:    "no env",
 | |
| 			initEnv: envMap{"SOMEVAR": strVar("baz")},
 | |
| 			wantX:   "baz",
 | |
| 			wantY:   true,
 | |
| 		},
 | |
| 		{
 | |
| 			name:      "overwrite",
 | |
| 			initEnv:   envMap{"SOMEVAR": strVar("baz")},
 | |
| 			launchEnv: envMap{"SOMEVAR": strVar("bar")},
 | |
| 			wantX:     "bar",
 | |
| 			wantY:     true,
 | |
| 		},
 | |
| 		{
 | |
| 			name:      "unset",
 | |
| 			initEnv:   envMap{"SOMEVAR": strVar("baz")},
 | |
| 			launchEnv: envMap{"SOMEVAR": nil},
 | |
| 			wantX:     "",
 | |
| 			wantY:     false,
 | |
| 		},
 | |
| 		{
 | |
| 			name:      "empty value",
 | |
| 			initEnv:   envMap{"SOMEVAR": strVar("baz")},
 | |
| 			launchEnv: envMap{"SOMEVAR": strVar("")},
 | |
| 			wantX:     "",
 | |
| 			wantY:     true,
 | |
| 		},
 | |
| 		{
 | |
| 			name:      "set",
 | |
| 			launchEnv: envMap{"SOMEVAR": strVar("foo")},
 | |
| 			wantX:     "foo",
 | |
| 			wantY:     true,
 | |
| 		},
 | |
| 		{
 | |
| 			name:      "untouched",
 | |
| 			initEnv:   envMap{"SOMEVAR": strVar("baz")},
 | |
| 			launchEnv: envMap{"SOMEVAR2": nil, "SOMEVAR3": strVar("foo")},
 | |
| 			wantX:     "baz",
 | |
| 			wantY:     true,
 | |
| 		},
 | |
| 	} {
 | |
| 		t.Run(tc.name, func(t *testing.T) {
 | |
| 			for k, v := range tc.initEnv {
 | |
| 				if v != nil {
 | |
| 					t.Setenv(k, *v)
 | |
| 				}
 | |
| 			}
 | |
| 
 | |
| 			serverStopped := make(chan struct{})
 | |
| 			client := startDAPServerWithClient(t, false, serverStopped)
 | |
| 			defer client.Close()
 | |
| 
 | |
| 			runDebugSessionWithBPs(t, client, "launch", func() { // launch
 | |
| 				client.LaunchRequestWithArgs(map[string]interface{}{
 | |
| 					"mode":    "debug",
 | |
| 					"program": testFile,
 | |
| 					"env":     tc.launchEnv,
 | |
| 				})
 | |
| 			}, testFile, nil, // runtime.Breakpoint
 | |
| 				[]onBreakpoint{{
 | |
| 					execute: func() {
 | |
| 						client.EvaluateRequest("x", 1000, "whatever")
 | |
| 						gotX := client.ExpectEvaluateResponse(t)
 | |
| 						checkEval(t, gotX, fmt.Sprintf("%q", tc.wantX), false)
 | |
| 						client.EvaluateRequest("y", 1000, "whatever")
 | |
| 						gotY := client.ExpectEvaluateResponse(t)
 | |
| 						checkEval(t, gotY, fmt.Sprintf("%v", tc.wantY), false)
 | |
| 					},
 | |
| 					disconnect: true,
 | |
| 				}})
 | |
| 			<-serverStopped
 | |
| 		})
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func TestAttachRequest(t *testing.T) {
 | |
| 	if runtime.GOOS == "windows" {
 | |
| 		t.Skip("test skipped on Windows, see https://delve.teamcity.com/project/Delve_windows for details")
 | |
| 	}
 | |
| 	runTest(t, "loopprog", func(client *daptest.Client, fixture protest.Fixture) {
 | |
| 		// Start the program to attach to
 | |
| 		cmd := execFixture(t, fixture)
 | |
| 
 | |
| 		runDebugSessionWithBPs(t, client, "attach",
 | |
| 			// Attach
 | |
| 			func() {
 | |
| 				client.AttachRequest(map[string]interface{}{
 | |
| 					/*"mode": "local" by default*/ "processId": cmd.Process.Pid, "stopOnEntry": false,
 | |
| 				})
 | |
| 				client.ExpectCapabilitiesEventSupportTerminateDebuggee(t)
 | |
| 			},
 | |
| 			// Set breakpoints
 | |
| 			fixture.Source, []int{8},
 | |
| 			[]onBreakpoint{{
 | |
| 				// Stop at line 8
 | |
| 				execute: func() {
 | |
| 					checkStop(t, client, 1, "main.loop", 8)
 | |
| 					client.VariablesRequest(localsScope)
 | |
| 					locals := client.ExpectVariablesResponse(t)
 | |
| 					checkChildren(t, locals, "Locals", 1)
 | |
| 					checkVarRegex(t, locals, 0, "i", "i", "[0-9]+", "int", noChildren)
 | |
| 				},
 | |
| 				disconnect: true,
 | |
| 			}})
 | |
| 	})
 | |
| }
 | |
| 
 | |
| // Since we are in async mode while running, we might receive thee messages after pause request
 | |
| // in either order.
 | |
| func expectPauseResponseAndStoppedEvent(t *testing.T, client *daptest.Client) {
 | |
| 	t.Helper()
 | |
| 	for i := 0; i < 2; i++ {
 | |
| 		msg := client.ExpectMessage(t)
 | |
| 		switch m := msg.(type) {
 | |
| 		case *dap.StoppedEvent:
 | |
| 			if m.Body.Reason != "pause" || m.Body.ThreadId != 0 && m.Body.ThreadId != 1 {
 | |
| 				t.Errorf("\ngot %#v\nwant ThreadId=0/1 Reason='pause'", m)
 | |
| 			}
 | |
| 		case *dap.PauseResponse:
 | |
| 		default:
 | |
| 			t.Fatalf("got %#v, want StoppedEvent or PauseResponse", m)
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func TestPauseAndContinue(t *testing.T) {
 | |
| 	runTest(t, "loopprog", func(client *daptest.Client, fixture protest.Fixture) {
 | |
| 		runDebugSessionWithBPs(t, client, "launch",
 | |
| 			// Launch
 | |
| 			func() {
 | |
| 				client.LaunchRequest("exec", fixture.Path, !stopOnEntry)
 | |
| 			},
 | |
| 			// Set breakpoints
 | |
| 			fixture.Source, []int{6},
 | |
| 			[]onBreakpoint{{
 | |
| 				execute: func() {
 | |
| 					client.CheckStopLocation(t, 1, "main.loop", 6)
 | |
| 
 | |
| 					// Continue resumes all goroutines, so thread id is ignored
 | |
| 					client.ContinueRequest(12345)
 | |
| 					client.ExpectContinueResponse(t)
 | |
| 
 | |
| 					time.Sleep(time.Second)
 | |
| 
 | |
| 					// Halt pauses all goroutines, so thread id is ignored
 | |
| 					client.PauseRequest(56789)
 | |
| 					expectPauseResponseAndStoppedEvent(t, client)
 | |
| 
 | |
| 					// Pause will be a no-op at a pause: there will be no additional stopped events
 | |
| 					client.PauseRequest(1)
 | |
| 					client.ExpectPauseResponse(t)
 | |
| 				},
 | |
| 				// The program has an infinite loop, so we must kill it by disconnecting.
 | |
| 				disconnect: true,
 | |
| 			}})
 | |
| 	})
 | |
| }
 | |
| 
 | |
| func TestUnsupportedCommandResponses(t *testing.T) {
 | |
| 	var got *dap.ErrorResponse
 | |
| 	runTest(t, "increment", func(client *daptest.Client, fixture protest.Fixture) {
 | |
| 		seqCnt := 1
 | |
| 		expectUnsupportedCommand := func(cmd string) {
 | |
| 			t.Helper()
 | |
| 			got = client.ExpectUnsupportedCommandErrorResponse(t)
 | |
| 			if got.RequestSeq != seqCnt || got.Command != cmd {
 | |
| 				t.Errorf("\ngot  %#v\nwant RequestSeq=%d Command=%s", got, seqCnt, cmd)
 | |
| 			}
 | |
| 			seqCnt++
 | |
| 		}
 | |
| 
 | |
| 		client.RestartFrameRequest()
 | |
| 		expectUnsupportedCommand("restartFrame")
 | |
| 
 | |
| 		client.GotoRequest()
 | |
| 		expectUnsupportedCommand("goto")
 | |
| 
 | |
| 		client.SourceRequest()
 | |
| 		expectUnsupportedCommand("source")
 | |
| 
 | |
| 		client.TerminateThreadsRequest()
 | |
| 		expectUnsupportedCommand("terminateThreads")
 | |
| 
 | |
| 		client.StepInTargetsRequest()
 | |
| 		expectUnsupportedCommand("stepInTargets")
 | |
| 
 | |
| 		client.GotoTargetsRequest()
 | |
| 		expectUnsupportedCommand("gotoTargets")
 | |
| 
 | |
| 		client.CompletionsRequest()
 | |
| 		expectUnsupportedCommand("completions")
 | |
| 
 | |
| 		client.DataBreakpointInfoRequest()
 | |
| 		expectUnsupportedCommand("dataBreakpointInfo")
 | |
| 
 | |
| 		client.SetDataBreakpointsRequest()
 | |
| 		expectUnsupportedCommand("setDataBreakpoints")
 | |
| 
 | |
| 		client.BreakpointLocationsRequest()
 | |
| 		expectUnsupportedCommand("breakpointLocations")
 | |
| 
 | |
| 		client.ModulesRequest()
 | |
| 		expectUnsupportedCommand("modules")
 | |
| 
 | |
| 		client.DisconnectRequest()
 | |
| 		client.ExpectDisconnectResponse(t)
 | |
| 	})
 | |
| }
 | |
| 
 | |
| type helperForSetVariable struct {
 | |
| 	t *testing.T
 | |
| 	c *daptest.Client
 | |
| }
 | |
| 
 | |
| func (h *helperForSetVariable) expectSetVariable(ref int, name, value string) {
 | |
| 	h.t.Helper()
 | |
| 	h.expectSetVariable0(ref, name, value, false)
 | |
| }
 | |
| 
 | |
| func (h *helperForSetVariable) failSetVariable(ref int, name, value, wantErrInfo string) {
 | |
| 	h.t.Helper()
 | |
| 	h.failSetVariable0(ref, name, value, wantErrInfo, false)
 | |
| }
 | |
| 
 | |
| func (h *helperForSetVariable) failSetVariableAndStop(ref int, name, value, wantErrInfo string) {
 | |
| 	h.t.Helper()
 | |
| 	h.failSetVariable0(ref, name, value, wantErrInfo, true)
 | |
| }
 | |
| 
 | |
| func (h *helperForSetVariable) evaluate(expr, want string, hasRef bool) {
 | |
| 	h.t.Helper()
 | |
| 	h.c.EvaluateRequest(expr, 1000, "whatever")
 | |
| 	got := h.c.ExpectEvaluateResponse(h.t)
 | |
| 	checkEval(h.t, got, want, hasRef)
 | |
| }
 | |
| 
 | |
| func (h *helperForSetVariable) evaluateRegex(expr, want string, hasRef bool) {
 | |
| 	h.t.Helper()
 | |
| 	h.c.EvaluateRequest(expr, 1000, "whatever")
 | |
| 	got := h.c.ExpectEvaluateResponse(h.t)
 | |
| 	checkEvalRegex(h.t, got, want, hasRef)
 | |
| }
 | |
| 
 | |
| func (h *helperForSetVariable) expectSetVariable0(ref int, name, value string, wantStop bool) {
 | |
| 	h.t.Helper()
 | |
| 
 | |
| 	h.c.SetVariableRequest(ref, name, value)
 | |
| 	if wantStop {
 | |
| 		h.c.ExpectStoppedEvent(h.t)
 | |
| 	}
 | |
| 	if got, want := h.c.ExpectSetVariableResponse(h.t), value; got.Success != true || got.Body.Value != want {
 | |
| 		h.t.Errorf("SetVariableRequest(%v, %v)=%#v, want {Success=true, Body.Value=%q", name, value, got, want)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func (h *helperForSetVariable) failSetVariable0(ref int, name, value, wantErrInfo string, wantStop bool) {
 | |
| 	h.t.Helper()
 | |
| 
 | |
| 	h.c.SetVariableRequest(ref, name, value)
 | |
| 	if wantStop {
 | |
| 		h.c.ExpectStoppedEvent(h.t)
 | |
| 	}
 | |
| 	resp := h.c.ExpectErrorResponse(h.t)
 | |
| 	if got := resp.Body.Error; !stringContainsCaseInsensitive(got.Format, wantErrInfo) {
 | |
| 		h.t.Errorf("got %#v, want error string containing %v", got, wantErrInfo)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func (h *helperForSetVariable) variables(ref int) *dap.VariablesResponse {
 | |
| 	h.t.Helper()
 | |
| 	h.c.VariablesRequest(ref)
 | |
| 	return h.c.ExpectVariablesResponse(h.t)
 | |
| }
 | |
| 
 | |
| // TestSetVariable tests SetVariable features that do not need function call support.
 | |
| func TestSetVariable(t *testing.T) {
 | |
| 	runTest(t, "testvariables", func(client *daptest.Client, fixture protest.Fixture) {
 | |
| 		runDebugSessionWithBPs(t, client, "launch",
 | |
| 			func() {
 | |
| 				client.LaunchRequestWithArgs(map[string]interface{}{
 | |
| 					"mode": "exec", "program": fixture.Path, "showGlobalVariables": true,
 | |
| 				})
 | |
| 			},
 | |
| 			fixture.Source, []int{}, // breakpoints are set within the program.
 | |
| 			[]onBreakpoint{{
 | |
| 				execute: func() {
 | |
| 					tester := &helperForSetVariable{t, client}
 | |
| 
 | |
| 					checkStop(t, client, 1, "main.foobar", []int{65, 66})
 | |
| 
 | |
| 					// Local variables
 | |
| 					locals := tester.variables(localsScope)
 | |
| 
 | |
| 					// Args of foobar(baz string, bar FooBar)
 | |
| 					checkVarExact(t, locals, 1, "bar", "bar", `main.FooBar {Baz: 10, Bur: "lorem"}`, "main.FooBar", hasChildren)
 | |
| 					tester.failSetVariable(localsScope, "bar", `main.FooBar {Baz: 42, Bur: "ipsum"}`, "*ast.CompositeLit not implemented")
 | |
| 
 | |
| 					// Nested field.
 | |
| 					barRef := checkVarExact(t, locals, 1, "bar", "bar", `main.FooBar {Baz: 10, Bur: "lorem"}`, "main.FooBar", hasChildren)
 | |
| 					tester.expectSetVariable(barRef, "Baz", "42")
 | |
| 					tester.evaluate("bar", `main.FooBar {Baz: 42, Bur: "lorem"}`, hasChildren)
 | |
| 
 | |
| 					tester.failSetVariable(barRef, "Baz", `"string"`, "can not convert")
 | |
| 
 | |
| 					// int
 | |
| 					checkVarExact(t, locals, -1, "a2", "a2", "6", "int", noChildren)
 | |
| 					tester.expectSetVariable(localsScope, "a2", "42")
 | |
| 					tester.evaluate("a2", "42", noChildren)
 | |
| 
 | |
| 					tester.failSetVariable(localsScope, "a2", "false", "can not convert")
 | |
| 
 | |
| 					// float
 | |
| 					checkVarExact(t, locals, -1, "a3", "a3", "7.23", "float64", noChildren)
 | |
| 					tester.expectSetVariable(localsScope, "a3", "-0.1")
 | |
| 					tester.evaluate("a3", "-0.1", noChildren)
 | |
| 
 | |
| 					// array of int
 | |
| 					a4Ref := checkVarExact(t, locals, -1, "a4", "a4", "[2]int [1,2]", "[2]int", hasChildren)
 | |
| 					tester.expectSetVariable(a4Ref, "[1]", "-7")
 | |
| 					tester.evaluate("a4", "[2]int [1,-7]", hasChildren)
 | |
| 
 | |
| 					tester.failSetVariable(localsScope, "a4", "[2]int{3, 4}", "not implemented")
 | |
| 
 | |
| 					// slice of int
 | |
| 					a5Ref := checkVarExact(t, locals, -1, "a5", "a5", "[]int len: 5, cap: 5, [1,2,3,4,5]", "[]int", hasChildren)
 | |
| 					tester.expectSetVariable(a5Ref, "[3]", "100")
 | |
| 					tester.evaluate("a5", "[]int len: 5, cap: 5, [1,2,3,100,5]", hasChildren)
 | |
| 
 | |
| 					// composite literal and its nested fields.
 | |
| 					a7Ref := checkVarExact(t, locals, -1, "a7", "a7", `*main.FooBar {Baz: 5, Bur: "strum"}`, "*main.FooBar", hasChildren)
 | |
| 					a7Val := tester.variables(a7Ref)
 | |
| 					a7ValRef := checkVarExact(t, a7Val, -1, "", "(*a7)", `main.FooBar {Baz: 5, Bur: "strum"}`, "main.FooBar", hasChildren)
 | |
| 					tester.expectSetVariable(a7ValRef, "Baz", "7")
 | |
| 					tester.evaluate("(*a7)", `main.FooBar {Baz: 7, Bur: "strum"}`, hasChildren)
 | |
| 
 | |
| 					// pointer
 | |
| 					checkVarExact(t, locals, -1, "a9", "a9", `*main.FooBar nil`, "*main.FooBar", noChildren)
 | |
| 					tester.expectSetVariable(localsScope, "a9", "&a6")
 | |
| 					tester.evaluate("a9", `*main.FooBar {Baz: 8, Bur: "word"}`, hasChildren)
 | |
| 
 | |
| 					// slice of pointers
 | |
| 					a13Ref := checkVarExact(t, locals, -1, "a13", "a13", `[]*main.FooBar len: 3, cap: 3, [*{Baz: 6, Bur: "f"},*{Baz: 7, Bur: "g"},*{Baz: 8, Bur: "h"}]`, "[]*main.FooBar", hasChildren)
 | |
| 					a13 := tester.variables(a13Ref)
 | |
| 					a13c0Ref := checkVarExact(t, a13, -1, "[0]", "a13[0]", `*main.FooBar {Baz: 6, Bur: "f"}`, "*main.FooBar", hasChildren)
 | |
| 					a13c0 := tester.variables(a13c0Ref)
 | |
| 					a13c0valRef := checkVarExact(t, a13c0, -1, "", "(*a13[0])", `main.FooBar {Baz: 6, Bur: "f"}`, "main.FooBar", hasChildren)
 | |
| 					tester.expectSetVariable(a13c0valRef, "Baz", "777")
 | |
| 					tester.evaluate("a13[0]", `*main.FooBar {Baz: 777, Bur: "f"}`, hasChildren)
 | |
| 
 | |
| 					// complex
 | |
| 					tester.evaluate("c64", `(1 + 2i)`, hasChildren)
 | |
| 					tester.expectSetVariable(localsScope, "c64", "(2 + 3i)")
 | |
| 					tester.evaluate("c64", `(2 + 3i)`, hasChildren)
 | |
| 					// note: complex's real, imaginary part can't be directly mutable.
 | |
| 
 | |
| 					//
 | |
| 					// Global variables
 | |
| 					//    p1 = 10
 | |
| 					client.VariablesRequest(globalsScope)
 | |
| 					globals := client.ExpectVariablesResponse(t)
 | |
| 
 | |
| 					checkVarExact(t, globals, -1, "p1", "main.p1", "10", "int", noChildren)
 | |
| 					tester.expectSetVariable(globalsScope, "p1", "-10")
 | |
| 					tester.evaluate("p1", "-10", noChildren)
 | |
| 					tester.failSetVariable(globalsScope, "p1", "0.1", "can not convert")
 | |
| 				},
 | |
| 				disconnect: true,
 | |
| 			}})
 | |
| 	})
 | |
| 
 | |
| 	runTest(t, "testvariables2", func(client *daptest.Client, fixture protest.Fixture) {
 | |
| 		runDebugSessionWithBPs(t, client, "launch",
 | |
| 			func() {
 | |
| 				client.LaunchRequestWithArgs(map[string]interface{}{
 | |
| 					"mode": "exec", "program": fixture.Path, "showGlobalVariables": true,
 | |
| 				})
 | |
| 			},
 | |
| 			fixture.Source, []int{}, // breakpoints are set within the program.
 | |
| 			[]onBreakpoint{{
 | |
| 				execute: func() {
 | |
| 					tester := &helperForSetVariable{t, client}
 | |
| 
 | |
| 					checkStop(t, client, 1, "main.main", -1)
 | |
| 					locals := tester.variables(localsScope)
 | |
| 
 | |
| 					// channel
 | |
| 					tester.evaluate("chnil", "chan int nil", noChildren)
 | |
| 					tester.expectSetVariable(localsScope, "chnil", "ch1")
 | |
| 					tester.evaluate("chnil", "chan int 4/11", hasChildren)
 | |
| 
 | |
| 					// func
 | |
| 					tester.evaluate("fn2", "nil", noChildren)
 | |
| 					tester.expectSetVariable(localsScope, "fn2", "fn1")
 | |
| 					tester.evaluate("fn2", "main.afunc", noChildren)
 | |
| 
 | |
| 					// interface
 | |
| 					tester.evaluate("ifacenil", "interface {} nil", noChildren)
 | |
| 					tester.expectSetVariable(localsScope, "ifacenil", "iface1")
 | |
| 					tester.evaluate("ifacenil", "interface {}(*main.astruct) *{A: 1, B: 2}", hasChildren)
 | |
| 
 | |
| 					// interface.(data)
 | |
| 					iface1Ref := checkVarExact(t, locals, -1, "iface1", "iface1", "interface {}(*main.astruct) *{A: 1, B: 2}", "interface {}(*main.astruct)", hasChildren)
 | |
| 					iface1 := tester.variables(iface1Ref)
 | |
| 					iface1DataRef := checkVarExact(t, iface1, -1, "data", "iface1.(data)", "*main.astruct {A: 1, B: 2}", "*main.astruct", hasChildren)
 | |
| 					iface1Data := tester.variables(iface1DataRef)
 | |
| 					iface1DataValueRef := checkVarExact(t, iface1Data, -1, "", "(*iface1.(data))", "main.astruct {A: 1, B: 2}", "main.astruct", hasChildren)
 | |
| 					tester.expectSetVariable(iface1DataValueRef, "A", "2021")
 | |
| 					tester.evaluate("iface1", "interface {}(*main.astruct) *{A: 2021, B: 2}", hasChildren)
 | |
| 
 | |
| 					// map: string -> struct
 | |
| 					tester.evaluate(`m1["Malone"]`, "main.astruct {A: 2, B: 3}", hasChildren)
 | |
| 					m1Ref := checkVarRegex(t, locals, -1, "m1", "m1", `.*map\[string\]main\.astruct.*`, `map\[string\]main\.astruct`, hasChildren)
 | |
| 					m1 := tester.variables(m1Ref)
 | |
| 					elem1 := m1.Body.Variables[1]
 | |
| 					tester.expectSetVariable(elem1.VariablesReference, "A", "-9999")
 | |
| 					tester.expectSetVariable(elem1.VariablesReference, "B", "10000")
 | |
| 					tester.evaluate(elem1.EvaluateName, "main.astruct {A: -9999, B: 10000}", hasChildren)
 | |
| 
 | |
| 					// map: struct -> int
 | |
| 					m3Ref := checkVarExact(t, locals, -1, "m3", "m3", "map[main.astruct]int [{A: 1, B: 1}: 42, {A: 2, B: 2}: 43, ]", "map[main.astruct]int", hasChildren)
 | |
| 					tester.expectSetVariable(m3Ref, "main.astruct {A: 1, B: 1}", "8888")
 | |
| 					// note: updating keys is possible, but let's not promise anything.
 | |
| 					tester.evaluateRegex("m3", `.*\[\{A: 1, B: 1\}: 8888,.*`, hasChildren)
 | |
| 
 | |
| 					// map: struct -> struct
 | |
| 					m4Ref := checkVarRegex(t, locals, -1, "m4", "m4", `map\[main\.astruct]main\.astruct.*\[\{A: 1, B: 1\}: \{A: 11, B: 11\}.*`, `map\[main\.astruct\]main\.astruct`, hasChildren)
 | |
| 					m4 := tester.variables(m4Ref)
 | |
| 					m4Val1Ref := checkVarRegex(t, m4, -1, "[val 0]", `.*0x[0-9a-f]+.*`, `main.astruct.*`, `main\.astruct`, hasChildren)
 | |
| 					tester.expectSetVariable(m4Val1Ref, "A", "-9999")
 | |
| 					tester.evaluateRegex("m4", `.*A: -9999,.*`, hasChildren)
 | |
| 
 | |
| 					// unsigned pointer
 | |
| 					checkVarRegex(t, locals, -1, "up1", "up1", `unsafe\.Pointer\(0x[0-9a-f]+\)`, "unsafe.Pointer", noChildren)
 | |
| 					tester.expectSetVariable(localsScope, "up1", "unsafe.Pointer(0x0)")
 | |
| 					tester.evaluate("up1", "unsafe.Pointer(0x0)", noChildren)
 | |
| 
 | |
| 					// val := A{val: 1}
 | |
| 					valRef := checkVarExact(t, locals, -1, "val", "val", `main.A {val: 1}`, "main.A", hasChildren)
 | |
| 					tester.expectSetVariable(valRef, "val", "3")
 | |
| 					tester.evaluate("val", `main.A {val: 3}`, hasChildren)
 | |
| 				},
 | |
| 				disconnect: true,
 | |
| 			}})
 | |
| 	})
 | |
| }
 | |
| 
 | |
| // TestSetVariableWithCall tests SetVariable features that do not depend on function calls support.
 | |
| func TestSetVariableWithCall(t *testing.T) {
 | |
| 	protest.MustSupportFunctionCalls(t, testBackend)
 | |
| 
 | |
| 	runTest(t, "testvariables", func(client *daptest.Client, fixture protest.Fixture) {
 | |
| 		runDebugSessionWithBPs(t, client, "launch",
 | |
| 			func() {
 | |
| 				client.LaunchRequestWithArgs(map[string]interface{}{
 | |
| 					"mode": "exec", "program": fixture.Path, "showGlobalVariables": true,
 | |
| 				})
 | |
| 			},
 | |
| 			fixture.Source, []int{},
 | |
| 			[]onBreakpoint{{
 | |
| 				execute: func() {
 | |
| 					tester := &helperForSetVariable{t, client}
 | |
| 
 | |
| 					checkStop(t, client, 1, "main.foobar", []int{65, 66})
 | |
| 
 | |
| 					// Local variables
 | |
| 					locals := tester.variables(localsScope)
 | |
| 
 | |
| 					// Args of foobar(baz string, bar FooBar)
 | |
| 					checkVarExact(t, locals, 0, "baz", "baz", `"bazburzum"`, "string", noChildren)
 | |
| 					tester.expectSetVariable(localsScope, "baz", `"BazBurZum"`)
 | |
| 					tester.evaluate("baz", `"BazBurZum"`, noChildren)
 | |
| 
 | |
| 					barRef := checkVarExact(t, locals, 1, "bar", "bar", `main.FooBar {Baz: 10, Bur: "lorem"}`, "main.FooBar", hasChildren)
 | |
| 					tester.expectSetVariable(barRef, "Bur", `"ipsum"`)
 | |
| 					tester.evaluate("bar", `main.FooBar {Baz: 10, Bur: "ipsum"}`, hasChildren)
 | |
| 
 | |
| 					checkVarExact(t, locals, -1, "a1", "a1", `"foofoofoofoofoofoo"`, "string", noChildren)
 | |
| 					tester.expectSetVariable(localsScope, "a1", `"barbarbar"`)
 | |
| 					tester.evaluate("a1", `"barbarbar"`, noChildren)
 | |
| 
 | |
| 					a6Ref := checkVarExact(t, locals, -1, "a6", "a6", `main.FooBar {Baz: 8, Bur: "word"}`, "main.FooBar", hasChildren)
 | |
| 					tester.failSetVariable(a6Ref, "Bur", "false", "can not convert")
 | |
| 
 | |
| 					tester.expectSetVariable(a6Ref, "Bur", `"sentence"`)
 | |
| 					tester.evaluate("a6", `main.FooBar {Baz: 8, Bur: "sentence"}`, hasChildren)
 | |
| 
 | |
| 					// stop inside main.barfoo
 | |
| 					client.ContinueRequest(1)
 | |
| 					client.ExpectContinueResponse(t)
 | |
| 					client.ExpectStoppedEvent(t)
 | |
| 					checkStop(t, client, 1, "main.barfoo", -1)
 | |
| 
 | |
| 					// Test: set string 'a1' in main.barfoo.
 | |
| 					// This shouldn't affect 'a1' in main.foobar - we will check that in the next breakpoint.
 | |
| 					locals = tester.variables(localsScope)
 | |
| 					checkVarExact(t, locals, -1, "a1", "a1", `"bur"`, "string", noChildren)
 | |
| 					tester.expectSetVariable(localsScope, "a1", `"fur"`)
 | |
| 					tester.evaluate("a1", `"fur"`, noChildren)
 | |
| 					// We will check a1 in main.foobar isn't affected from the next breakpoint.
 | |
| 
 | |
| 					client.StackTraceRequest(1, 1, 20)
 | |
| 					res := client.ExpectStackTraceResponse(t)
 | |
| 					if len(res.Body.StackFrames) < 1 {
 | |
| 						t.Fatalf("stack trace response = %#v, wanted at least one stack frame", res)
 | |
| 					}
 | |
| 					outerFrame := res.Body.StackFrames[0].Id
 | |
| 					client.EvaluateRequest("a1", outerFrame, "whatever_context")
 | |
| 					evalRes := client.ExpectEvaluateResponse(t)
 | |
| 					checkEval(t, evalRes, `"barbarbar"`, noChildren)
 | |
| 				},
 | |
| 				disconnect: true,
 | |
| 			}})
 | |
| 	})
 | |
| 
 | |
| 	runTest(t, "fncall", func(client *daptest.Client, fixture protest.Fixture) {
 | |
| 		runDebugSessionWithBPs(t, client, "launch",
 | |
| 			func() {
 | |
| 				client.LaunchRequestWithArgs(map[string]interface{}{
 | |
| 					"mode": "exec", "program": fixture.Path, "showGlobalVariables": true,
 | |
| 				})
 | |
| 			},
 | |
| 			fixture.Source, []int{}, // breakpoints are set within the program.
 | |
| 			[]onBreakpoint{{
 | |
| 				// Stop at second breakpoint and set a1.
 | |
| 				execute: func() {
 | |
| 					tester := &helperForSetVariable{t, client}
 | |
| 
 | |
| 					checkStop(t, client, 1, "main.main", -1)
 | |
| 
 | |
| 					_ = tester.variables(localsScope)
 | |
| 
 | |
| 					// successful variable set using a function call.
 | |
| 					tester.expectSetVariable(localsScope, "str", `callstacktrace()`)
 | |
| 					tester.evaluateRegex("str", `.*in main.callstacktrace at.*`, noChildren)
 | |
| 
 | |
| 					tester.failSetVariableAndStop(localsScope, "str", `callpanic()`, `callpanic panicked`)
 | |
| 					checkStop(t, client, 1, "main.main", -1)
 | |
| 
 | |
| 					// breakpoint during a function call.
 | |
| 					tester.failSetVariableAndStop(localsScope, "str", `callbreak()`, "call stopped")
 | |
| 
 | |
| 					// TODO(hyangah): continue after this causes runtime error while resuming
 | |
| 					// unfinished injected call.
 | |
| 					//   runtime error: can not convert %!s(<nil>) constant to string
 | |
| 					// This can be reproducible with dlv cli. (`call str = callbreak(); continue`)
 | |
| 				},
 | |
| 				disconnect: true,
 | |
| 			}})
 | |
| 	})
 | |
| }
 | |
| 
 | |
| func TestOptionalNotYetImplementedResponses(t *testing.T) {
 | |
| 	var got *dap.ErrorResponse
 | |
| 	runTest(t, "increment", func(client *daptest.Client, fixture protest.Fixture) {
 | |
| 		seqCnt := 1
 | |
| 		expectNotYetImplemented := func(cmd string) {
 | |
| 			t.Helper()
 | |
| 			got = client.ExpectNotYetImplementedErrorResponse(t)
 | |
| 			if got.RequestSeq != seqCnt || got.Command != cmd {
 | |
| 				t.Errorf("\ngot  %#v\nwant RequestSeq=%d Command=%s", got, seqCnt, cmd)
 | |
| 			}
 | |
| 			seqCnt++
 | |
| 		}
 | |
| 
 | |
| 		client.TerminateRequest()
 | |
| 		expectNotYetImplemented("terminate")
 | |
| 
 | |
| 		client.RestartRequest()
 | |
| 		expectNotYetImplemented("restart")
 | |
| 
 | |
| 		client.SetExpressionRequest()
 | |
| 		expectNotYetImplemented("setExpression")
 | |
| 
 | |
| 		client.LoadedSourcesRequest()
 | |
| 		expectNotYetImplemented("loadedSources")
 | |
| 
 | |
| 		client.ReadMemoryRequest()
 | |
| 		expectNotYetImplemented("readMemory")
 | |
| 
 | |
| 		client.CancelRequest()
 | |
| 		expectNotYetImplemented("cancel")
 | |
| 
 | |
| 		client.DisconnectRequest()
 | |
| 		client.ExpectDisconnectResponse(t)
 | |
| 	})
 | |
| }
 | |
| 
 | |
| func TestBadLaunchRequests(t *testing.T) {
 | |
| 	runTest(t, "increment", func(client *daptest.Client, fixture protest.Fixture) {
 | |
| 		seqCnt := 1
 | |
| 		checkFailedToLaunch := func(response *dap.ErrorResponse) {
 | |
| 			t.Helper()
 | |
| 			if response.RequestSeq != seqCnt {
 | |
| 				t.Errorf("RequestSeq got %d, want %d", seqCnt, response.RequestSeq)
 | |
| 			}
 | |
| 			if response.Command != "launch" {
 | |
| 				t.Errorf("Command got %q, want \"launch\"", response.Command)
 | |
| 			}
 | |
| 			if response.Message != "Failed to launch" {
 | |
| 				t.Errorf("Message got %q, want \"Failed to launch\"", response.Message)
 | |
| 			}
 | |
| 			if !checkErrorMessageId(response.Body.Error, FailedToLaunch) {
 | |
| 				t.Errorf("Id got %v, want Id=%d", response.Body.Error, FailedToLaunch)
 | |
| 			}
 | |
| 			seqCnt++
 | |
| 		}
 | |
| 
 | |
| 		checkFailedToLaunchWithMessage := func(response *dap.ErrorResponse, errmsg string) {
 | |
| 			t.Helper()
 | |
| 			checkFailedToLaunch(response)
 | |
| 			if !checkErrorMessageFormat(response.Body.Error, errmsg) {
 | |
| 				t.Errorf("\ngot  %v\nwant Format=%q", response.Body.Error, errmsg)
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		// Test for the DAP-specific detailed error message.
 | |
| 		client.LaunchRequest("exec", "", stopOnEntry)
 | |
| 		checkFailedToLaunchWithMessage(client.ExpectVisibleErrorResponse(t),
 | |
| 			"Failed to launch: The program attribute is missing in debug configuration.")
 | |
| 
 | |
| 		// Bad "program"
 | |
| 		client.LaunchRequestWithArgs(map[string]interface{}{"mode": "debug", "program": 12345})
 | |
| 		checkFailedToLaunchWithMessage(client.ExpectVisibleErrorResponse(t),
 | |
| 			"Failed to launch: invalid debug configuration - cannot unmarshal number into \"program\" of type string")
 | |
| 
 | |
| 		client.LaunchRequestWithArgs(map[string]interface{}{"mode": "debug", "program": nil})
 | |
| 		checkFailedToLaunchWithMessage(client.ExpectVisibleErrorResponse(t),
 | |
| 			"Failed to launch: The program attribute is missing in debug configuration.")
 | |
| 
 | |
| 		client.LaunchRequestWithArgs(map[string]interface{}{"mode": "debug"})
 | |
| 		checkFailedToLaunchWithMessage(client.ExpectVisibleErrorResponse(t),
 | |
| 			"Failed to launch: The program attribute is missing in debug configuration.")
 | |
| 
 | |
| 		// Bad "mode"
 | |
| 		client.LaunchRequest("remote", fixture.Path, stopOnEntry)
 | |
| 		checkFailedToLaunchWithMessage(client.ExpectVisibleErrorResponse(t),
 | |
| 			"Failed to launch: invalid debug configuration - unsupported 'mode' attribute \"remote\"")
 | |
| 
 | |
| 		client.LaunchRequest("notamode", fixture.Path, stopOnEntry)
 | |
| 		checkFailedToLaunchWithMessage(client.ExpectVisibleErrorResponse(t),
 | |
| 			"Failed to launch: invalid debug configuration - unsupported 'mode' attribute \"notamode\"")
 | |
| 
 | |
| 		client.LaunchRequestWithArgs(map[string]interface{}{"mode": 12345, "program": fixture.Path})
 | |
| 		checkFailedToLaunchWithMessage(client.ExpectVisibleErrorResponse(t),
 | |
| 			"Failed to launch: invalid debug configuration - cannot unmarshal number into \"mode\" of type string")
 | |
| 
 | |
| 		client.LaunchRequestWithArgs(map[string]interface{}{"mode": ""}) // empty mode defaults to "debug" (not an error)
 | |
| 		checkFailedToLaunchWithMessage(client.ExpectVisibleErrorResponse(t),
 | |
| 			"Failed to launch: The program attribute is missing in debug configuration.")
 | |
| 
 | |
| 		client.LaunchRequestWithArgs(map[string]interface{}{}) // missing mode defaults to "debug" (not an error)
 | |
| 		checkFailedToLaunchWithMessage(client.ExpectVisibleErrorResponse(t),
 | |
| 			"Failed to launch: The program attribute is missing in debug configuration.")
 | |
| 
 | |
| 		// Bad "args"
 | |
| 		client.LaunchRequestWithArgs(map[string]interface{}{"mode": "exec", "program": fixture.Path, "args": "foobar"})
 | |
| 		checkFailedToLaunchWithMessage(client.ExpectVisibleErrorResponse(t),
 | |
| 			"Failed to launch: invalid debug configuration - cannot unmarshal string into \"args\" of type []string")
 | |
| 
 | |
| 		client.LaunchRequestWithArgs(map[string]interface{}{"mode": "exec", "program": fixture.Path, "args": 12345})
 | |
| 		checkFailedToLaunchWithMessage(client.ExpectVisibleErrorResponse(t),
 | |
| 			"Failed to launch: invalid debug configuration - cannot unmarshal number into \"args\" of type []string")
 | |
| 
 | |
| 		client.LaunchRequestWithArgs(map[string]interface{}{"mode": "exec", "program": fixture.Path, "args": []int{1, 2}})
 | |
| 		checkFailedToLaunchWithMessage(client.ExpectVisibleErrorResponse(t),
 | |
| 			"Failed to launch: invalid debug configuration - cannot unmarshal number into \"args\" of type string")
 | |
| 
 | |
| 		// Bad "buildFlags"
 | |
| 		client.LaunchRequestWithArgs(map[string]interface{}{"mode": "debug", "program": fixture.Source, "buildFlags": 123})
 | |
| 		checkFailedToLaunchWithMessage(client.ExpectVisibleErrorResponse(t),
 | |
| 			"Failed to launch: invalid debug configuration - cannot unmarshal number into \"buildFlags\" of type []string or string")
 | |
| 
 | |
| 		// Bad "backend"
 | |
| 		client.LaunchRequestWithArgs(map[string]interface{}{"mode": "debug", "program": fixture.Source, "backend": 123})
 | |
| 		checkFailedToLaunchWithMessage(client.ExpectVisibleErrorResponse(t),
 | |
| 			"Failed to launch: invalid debug configuration - cannot unmarshal number into \"backend\" of type string")
 | |
| 
 | |
| 		client.LaunchRequestWithArgs(map[string]interface{}{"mode": "debug", "program": fixture.Source, "backend": "foo"})
 | |
| 		checkFailedToLaunchWithMessage(client.ExpectVisibleErrorResponse(t),
 | |
| 			"Failed to launch: could not launch process: unknown backend \"foo\"")
 | |
| 
 | |
| 		// Bad "substitutePath"
 | |
| 		client.LaunchRequestWithArgs(map[string]interface{}{"mode": "debug", "program": fixture.Source, "substitutePath": 123})
 | |
| 		checkFailedToLaunchWithMessage(client.ExpectVisibleErrorResponse(t),
 | |
| 			"Failed to launch: invalid debug configuration - cannot unmarshal number into \"substitutePath\" of type {\"from\":string, \"to\":string}")
 | |
| 
 | |
| 		client.LaunchRequestWithArgs(map[string]interface{}{"mode": "debug", "program": fixture.Source, "substitutePath": []interface{}{123}})
 | |
| 		checkFailedToLaunchWithMessage(client.ExpectVisibleErrorResponse(t),
 | |
| 			"Failed to launch: invalid debug configuration - cannot use 123 as 'substitutePath' of type {\"from\":string, \"to\":string}")
 | |
| 
 | |
| 		client.LaunchRequestWithArgs(map[string]interface{}{"mode": "debug", "program": fixture.Source, "substitutePath": []interface{}{map[string]interface{}{"to": "path2"}}})
 | |
| 		checkFailedToLaunchWithMessage(client.ExpectVisibleErrorResponse(t),
 | |
| 			"Failed to launch: invalid debug configuration - 'substitutePath' requires both 'from' and 'to' entries")
 | |
| 
 | |
| 		client.LaunchRequestWithArgs(map[string]interface{}{"mode": "debug", "program": fixture.Source, "substitutePath": []interface{}{map[string]interface{}{"from": "path1", "to": 123}}})
 | |
| 		checkFailedToLaunchWithMessage(client.ExpectVisibleErrorResponse(t),
 | |
| 			"Failed to launch: invalid debug configuration - cannot use {\"from\":\"path1\",\"to\":123} as 'substitutePath' of type {\"from\":string, \"to\":string}")
 | |
| 		// Bad "cwd"
 | |
| 		client.LaunchRequestWithArgs(map[string]interface{}{"mode": "debug", "program": fixture.Source, "cwd": 123})
 | |
| 		checkFailedToLaunchWithMessage(client.ExpectVisibleErrorResponse(t),
 | |
| 			"Failed to launch: invalid debug configuration - cannot unmarshal number into \"cwd\" of type string")
 | |
| 
 | |
| 		// Skip detailed message checks for potentially different OS-specific errors.
 | |
| 		client.LaunchRequest("exec", fixture.Path+"_does_not_exist", stopOnEntry)
 | |
| 		checkFailedToLaunch(client.ExpectVisibleErrorResponse(t)) // No such file or directory
 | |
| 
 | |
| 		client.LaunchRequest("debug", fixture.Path+"_does_not_exist", stopOnEntry)
 | |
| 		oe := client.ExpectOutputEvent(t)
 | |
| 		if !strings.HasPrefix(oe.Body.Output, "Build Error: ") || oe.Body.Category != "stderr" {
 | |
| 			t.Errorf("got %#v, want Category=\"stderr\" Output=\"Build Error: ...\"", oe)
 | |
| 		}
 | |
| 		checkFailedToLaunch(client.ExpectInvisibleErrorResponse(t))
 | |
| 
 | |
| 		client.LaunchRequestWithArgs(map[string]interface{}{
 | |
| 			"request": "launch",
 | |
| 			/* mode: debug by default*/
 | |
| 			"program":     fixture.Path + "_does_not_exist",
 | |
| 			"stopOnEntry": stopOnEntry,
 | |
| 		})
 | |
| 		oe = client.ExpectOutputEvent(t)
 | |
| 		if !strings.HasPrefix(oe.Body.Output, "Build Error: ") || oe.Body.Category != "stderr" {
 | |
| 			t.Errorf("got %#v, want Category=\"stderr\" Output=\"Build Error: ...\"", oe)
 | |
| 		}
 | |
| 		checkFailedToLaunch(client.ExpectInvisibleErrorResponse(t))
 | |
| 
 | |
| 		client.LaunchRequest("exec", fixture.Source, stopOnEntry)
 | |
| 		checkFailedToLaunch(client.ExpectVisibleErrorResponse(t)) // Not an executable
 | |
| 
 | |
| 		client.LaunchRequestWithArgs(map[string]interface{}{"mode": "debug", "program": fixture.Source, "buildFlags": "-bad -flags"})
 | |
| 		oe = client.ExpectOutputEvent(t)
 | |
| 		if !strings.HasPrefix(oe.Body.Output, "Build Error: ") || oe.Body.Category != "stderr" {
 | |
| 			t.Errorf("got %#v, want Category=\"stderr\" Output=\"Build Error: ...\"", oe)
 | |
| 		}
 | |
| 		checkFailedToLaunchWithMessage(client.ExpectInvisibleErrorResponse(t), "Failed to launch: Build error: Check the debug console for details.")
 | |
| 		client.LaunchRequestWithArgs(map[string]interface{}{"mode": "debug", "program": fixture.Source, "noDebug": true, "buildFlags": "-bad -flags"})
 | |
| 		oe = client.ExpectOutputEvent(t)
 | |
| 		if !strings.HasPrefix(oe.Body.Output, "Build Error: ") || oe.Body.Category != "stderr" {
 | |
| 			t.Errorf("got %#v, want Category=\"stderr\" Output=\"Build Error: ...\"", oe)
 | |
| 		}
 | |
| 		checkFailedToLaunchWithMessage(client.ExpectInvisibleErrorResponse(t), "Failed to launch: Build error: Check the debug console for details.")
 | |
| 
 | |
| 		// Bad "cwd"
 | |
| 		client.LaunchRequestWithArgs(map[string]interface{}{"mode": "debug", "program": fixture.Source, "noDebug": false, "cwd": "dir/invalid"})
 | |
| 		checkFailedToLaunch(client.ExpectVisibleErrorResponse(t)) // invalid directory, the error message is system-dependent.
 | |
| 		client.LaunchRequestWithArgs(map[string]interface{}{"mode": "debug", "program": fixture.Source, "noDebug": true, "cwd": "dir/invalid"})
 | |
| 		checkFailedToLaunch(client.ExpectVisibleErrorResponse(t)) // invalid directory, the error message is system-dependent.
 | |
| 
 | |
| 		// Bad "noDebug"
 | |
| 		client.LaunchRequestWithArgs(map[string]interface{}{"mode": "debug", "program": fixture.Source, "noDebug": "true"})
 | |
| 		checkFailedToLaunchWithMessage(client.ExpectVisibleErrorResponse(t), "Failed to launch: invalid debug configuration - cannot unmarshal string into \"noDebug\" of type bool")
 | |
| 
 | |
| 		// Bad "replay" parameters
 | |
| 		// These errors come from dap layer
 | |
| 		client.LaunchRequestWithArgs(map[string]interface{}{"mode": "replay", "traceDirPath": ""})
 | |
| 		checkFailedToLaunchWithMessage(client.ExpectVisibleErrorResponse(t),
 | |
| 			"Failed to launch: The 'traceDirPath' attribute is missing in debug configuration.")
 | |
| 		client.LaunchRequestWithArgs(map[string]interface{}{"mode": "replay", "program": fixture.Source, "traceDirPath": ""})
 | |
| 		checkFailedToLaunchWithMessage(client.ExpectVisibleErrorResponse(t),
 | |
| 			"Failed to launch: The 'traceDirPath' attribute is missing in debug configuration.")
 | |
| 		// These errors come from debugger layer
 | |
| 		if _, err := exec.LookPath("rr"); err != nil {
 | |
| 			client.LaunchRequestWithArgs(map[string]interface{}{"mode": "replay", "backend": "ignored", "traceDirPath": ".."})
 | |
| 			checkFailedToLaunchWithMessage(client.ExpectVisibleErrorResponse(t),
 | |
| 				"Failed to launch: backend unavailable")
 | |
| 		}
 | |
| 
 | |
| 		// Bad "core" parameters
 | |
| 		// These errors come from dap layer
 | |
| 		client.LaunchRequestWithArgs(map[string]interface{}{"mode": "core", "coreFilePath": ""})
 | |
| 		checkFailedToLaunchWithMessage(client.ExpectVisibleErrorResponse(t),
 | |
| 			"Failed to launch: The program attribute is missing in debug configuration.")
 | |
| 		client.LaunchRequestWithArgs(map[string]interface{}{"mode": "core", "program": fixture.Source, "coreFilePath": ""})
 | |
| 		checkFailedToLaunchWithMessage(client.ExpectVisibleErrorResponse(t),
 | |
| 			"Failed to launch: The 'coreFilePath' attribute is missing in debug configuration.")
 | |
| 		// These errors come from debugger layer
 | |
| 		client.LaunchRequestWithArgs(map[string]interface{}{"mode": "core", "backend": "ignored", "program": fixture.Source, "coreFilePath": fixture.Source})
 | |
| 		checkFailedToLaunchWithMessage(client.ExpectVisibleErrorResponse(t),
 | |
| 			"Failed to launch: unrecognized core format")
 | |
| 
 | |
| 		// We failed to launch the program. Make sure shutdown still works.
 | |
| 		client.DisconnectRequest()
 | |
| 		dresp := client.ExpectDisconnectResponse(t)
 | |
| 		if dresp.RequestSeq != seqCnt {
 | |
| 			t.Errorf("got %#v, want RequestSeq=%d", dresp, seqCnt)
 | |
| 		}
 | |
| 	})
 | |
| }
 | |
| 
 | |
| func TestBadAttachRequest(t *testing.T) {
 | |
| 	runTest(t, "loopprog", func(client *daptest.Client, fixture protest.Fixture) {
 | |
| 		seqCnt := 1
 | |
| 		checkFailedToAttach := func(response *dap.ErrorResponse) {
 | |
| 			t.Helper()
 | |
| 			if response.RequestSeq != seqCnt {
 | |
| 				t.Errorf("RequestSeq got %d, want %d", seqCnt, response.RequestSeq)
 | |
| 			}
 | |
| 			if response.Command != "attach" {
 | |
| 				t.Errorf("Command got %q, want \"attach\"", response.Command)
 | |
| 			}
 | |
| 			if response.Message != "Failed to attach" {
 | |
| 				t.Errorf("Message got %q, want \"Failed to attach\"", response.Message)
 | |
| 			}
 | |
| 			if !checkErrorMessageId(response.Body.Error, FailedToAttach) {
 | |
| 				t.Errorf("Id got %v, want %d", response.Body.Error, FailedToAttach)
 | |
| 			}
 | |
| 			seqCnt++
 | |
| 		}
 | |
| 
 | |
| 		checkFailedToAttachWithMessage := func(response *dap.ErrorResponse, errmsg string) {
 | |
| 			t.Helper()
 | |
| 			checkFailedToAttach(response)
 | |
| 			if !checkErrorMessageFormat(response.Body.Error, errmsg) {
 | |
| 				t.Errorf("\ngot  %v\nwant Format=%q", response.Body.Error, errmsg)
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		// Bad "mode"
 | |
| 		client.AttachRequest(map[string]interface{}{"mode": "blah blah blah"})
 | |
| 		checkFailedToAttachWithMessage(client.ExpectVisibleErrorResponse(t),
 | |
| 			"Failed to attach: invalid debug configuration - unsupported 'mode' attribute \"blah blah blah\"")
 | |
| 
 | |
| 		client.AttachRequest(map[string]interface{}{"mode": 123})
 | |
| 		checkFailedToAttachWithMessage(client.ExpectVisibleErrorResponse(t),
 | |
| 			"Failed to attach: invalid debug configuration - cannot unmarshal number into \"mode\" of type string")
 | |
| 
 | |
| 		client.AttachRequest(map[string]interface{}{"mode": ""}) // empty mode defaults to "local" (not an error)
 | |
| 		checkFailedToAttachWithMessage(client.ExpectVisibleErrorResponse(t),
 | |
| 			"Failed to attach: The 'processId' attribute is missing in debug configuration")
 | |
| 
 | |
| 		client.AttachRequest(map[string]interface{}{}) // no mode defaults to "local" (not an error)
 | |
| 		checkFailedToAttachWithMessage(client.ExpectVisibleErrorResponse(t),
 | |
| 			"Failed to attach: The 'processId' attribute is missing in debug configuration")
 | |
| 
 | |
| 		// Bad "processId"
 | |
| 		client.AttachRequest(map[string]interface{}{"mode": "local"})
 | |
| 		checkFailedToAttachWithMessage(client.ExpectVisibleErrorResponse(t),
 | |
| 			"Failed to attach: The 'processId' attribute is missing in debug configuration")
 | |
| 
 | |
| 		client.AttachRequest(map[string]interface{}{"mode": "local", "processId": nil})
 | |
| 		checkFailedToAttachWithMessage(client.ExpectVisibleErrorResponse(t),
 | |
| 			"Failed to attach: The 'processId' attribute is missing in debug configuration")
 | |
| 
 | |
| 		client.AttachRequest(map[string]interface{}{"mode": "local", "processId": 0})
 | |
| 		checkFailedToAttachWithMessage(client.ExpectVisibleErrorResponse(t),
 | |
| 			"Failed to attach: The 'processId' attribute is missing in debug configuration")
 | |
| 
 | |
| 		client.AttachRequest(map[string]interface{}{"mode": "local", "processId": "1"})
 | |
| 		checkFailedToAttachWithMessage(client.ExpectVisibleErrorResponse(t),
 | |
| 			"Failed to attach: invalid debug configuration - cannot unmarshal string into \"processId\" of type int")
 | |
| 
 | |
| 		client.AttachRequest(map[string]interface{}{"mode": "local", "processId": 1})
 | |
| 		// The exact message varies on different systems, so skip that check
 | |
| 		checkFailedToAttach(client.ExpectVisibleErrorResponse(t)) // could not attach to pid 1
 | |
| 
 | |
| 		// This will make debugger.(*Debugger) panic, which we will catch as an internal error.
 | |
| 		client.AttachRequest(map[string]interface{}{"mode": "local", "processId": -1})
 | |
| 		er := client.ExpectInvisibleErrorResponse(t)
 | |
| 		if er.RequestSeq != seqCnt {
 | |
| 			t.Errorf("RequestSeq got %d, want %d", seqCnt, er.RequestSeq)
 | |
| 		}
 | |
| 		seqCnt++
 | |
| 		if er.Command != "" {
 | |
| 			t.Errorf("Command got %q, want \"attach\"", er.Command)
 | |
| 		}
 | |
| 		if !checkErrorMessageFormat(er.Body.Error, "Internal Error: runtime error: index out of range [0] with length 0") {
 | |
| 			t.Errorf("Message got %q, want \"Internal Error: runtime error: index out of range [0] with length 0\"", er.Message)
 | |
| 		}
 | |
| 		if !checkErrorMessageId(er.Body.Error, InternalError) {
 | |
| 			t.Errorf("Id got %v, want Id=%d", er.Body.Error, InternalError)
 | |
| 		}
 | |
| 
 | |
| 		// Bad "backend"
 | |
| 		client.AttachRequest(map[string]interface{}{"mode": "local", "processId": 1, "backend": 123})
 | |
| 		checkFailedToAttachWithMessage(client.ExpectVisibleErrorResponse(t),
 | |
| 			"Failed to attach: invalid debug configuration - cannot unmarshal number into \"backend\" of type string")
 | |
| 
 | |
| 		client.AttachRequest(map[string]interface{}{"mode": "local", "processId": 1, "backend": "foo"})
 | |
| 		checkFailedToAttachWithMessage(client.ExpectVisibleErrorResponse(t),
 | |
| 			"Failed to attach: could not attach to pid 1: unknown backend \"foo\"")
 | |
| 
 | |
| 		// We failed to attach to the program. Make sure shutdown still works.
 | |
| 		client.DisconnectRequest()
 | |
| 		dresp := client.ExpectDisconnectResponse(t)
 | |
| 		if dresp.RequestSeq != seqCnt {
 | |
| 			t.Errorf("got %#v, want RequestSeq=%d", dresp, seqCnt)
 | |
| 		}
 | |
| 	})
 | |
| }
 | |
| 
 | |
| func launchDebuggerWithTargetRunning(t *testing.T, fixture string) (*protest.Fixture, *debugger.Debugger) {
 | |
| 	t.Helper()
 | |
| 	fixbin, dbg := launchDebuggerWithTargetHalted(t, fixture)
 | |
| 	running := make(chan struct{})
 | |
| 	var err error
 | |
| 	go func() {
 | |
| 		t.Helper()
 | |
| 		_, err = dbg.Command(&api.DebuggerCommand{Name: api.Continue}, running)
 | |
| 		select {
 | |
| 		case <-running:
 | |
| 		default:
 | |
| 			close(running)
 | |
| 		}
 | |
| 	}()
 | |
| 	<-running
 | |
| 	if err != nil {
 | |
| 		t.Fatal("failed to continue on launch", err)
 | |
| 	}
 | |
| 	return fixbin, dbg
 | |
| }
 | |
| 
 | |
| func launchDebuggerWithTargetHalted(t *testing.T, fixture string) (*protest.Fixture, *debugger.Debugger) {
 | |
| 	t.Helper()
 | |
| 	fixbin := protest.BuildFixture(fixture, protest.AllNonOptimized)
 | |
| 	cfg := service.Config{
 | |
| 		ProcessArgs: []string{fixbin.Path},
 | |
| 		Debugger:    debugger.Config{Backend: "default"},
 | |
| 	}
 | |
| 	dbg, err := debugger.New(&cfg.Debugger, cfg.ProcessArgs) // debugger halts process on entry
 | |
| 	if err != nil {
 | |
| 		t.Fatal("failed to start debugger:", err)
 | |
| 	}
 | |
| 	return &fixbin, dbg
 | |
| }
 | |
| 
 | |
| func attachDebuggerWithTargetHalted(t *testing.T, fixture string) (*exec.Cmd, *debugger.Debugger) {
 | |
| 	t.Helper()
 | |
| 	fixbin := protest.BuildFixture(fixture, protest.AllNonOptimized)
 | |
| 	cmd := execFixture(t, fixbin)
 | |
| 	cfg := service.Config{Debugger: debugger.Config{Backend: "default", AttachPid: cmd.Process.Pid}}
 | |
| 	dbg, err := debugger.New(&cfg.Debugger, nil) // debugger halts process on entry
 | |
| 	if err != nil {
 | |
| 		t.Fatal("failed to start debugger:", err)
 | |
| 	}
 | |
| 	return cmd, dbg
 | |
| }
 | |
| 
 | |
| // runTestWithDebugger starts the server and sets its debugger, initializes a debug session,
 | |
| // runs test, then disconnects. Expects no running async handler at the end of test() (either
 | |
| // process is halted or debug session never launched.)
 | |
| func runTestWithDebugger(t *testing.T, dbg *debugger.Debugger, test func(c *daptest.Client)) {
 | |
| 	serverStopped := make(chan struct{})
 | |
| 	server, _ := startDAPServer(t, false, serverStopped)
 | |
| 	client := daptest.NewClient(server.listener.Addr().String())
 | |
| 	time.Sleep(100 * time.Millisecond) // Give time for connection to be set as dap.Session
 | |
| 	server.sessionMu.Lock()
 | |
| 	if server.session == nil {
 | |
| 		t.Fatal("DAP session is not ready")
 | |
| 	}
 | |
| 	// Mock dap.NewSession arguments, so
 | |
| 	// this dap.Server can be used as a proxy for
 | |
| 	// rpccommon.Server running dap.Session.
 | |
| 	server.session.config.Debugger.AttachPid = dbg.AttachPid()
 | |
| 	server.session.debugger = dbg
 | |
| 	server.sessionMu.Unlock()
 | |
| 	defer client.Close()
 | |
| 	client.InitializeRequest()
 | |
| 	client.ExpectInitializeResponseAndCapabilities(t)
 | |
| 
 | |
| 	test(client)
 | |
| 
 | |
| 	client.DisconnectRequest()
 | |
| 	if dbg.AttachPid() == 0 { // launched target
 | |
| 		client.ExpectOutputEventDetachingKill(t)
 | |
| 	} else { // attached to target
 | |
| 		client.ExpectOutputEventDetachingNoKill(t)
 | |
| 	}
 | |
| 	client.ExpectDisconnectResponse(t)
 | |
| 	client.ExpectTerminatedEvent(t)
 | |
| 
 | |
| 	<-serverStopped
 | |
| }
 | |
| 
 | |
| func TestAttachRemoteToDlvLaunchHaltedStopOnEntry(t *testing.T) {
 | |
| 	// Halted + stop on entry
 | |
| 	_, dbg := launchDebuggerWithTargetHalted(t, "increment")
 | |
| 	runTestWithDebugger(t, dbg, func(client *daptest.Client) {
 | |
| 		client.AttachRequest(map[string]interface{}{"mode": "remote", "stopOnEntry": true})
 | |
| 		client.ExpectInitializedEvent(t)
 | |
| 		client.ExpectAttachResponse(t)
 | |
| 		client.ConfigurationDoneRequest()
 | |
| 		client.ExpectStoppedEvent(t)
 | |
| 		client.ExpectConfigurationDoneResponse(t)
 | |
| 	})
 | |
| }
 | |
| 
 | |
| func TestAttachRemoteToDlvAttachHaltedStopOnEntry(t *testing.T) {
 | |
| 	cmd, dbg := attachDebuggerWithTargetHalted(t, "http_server")
 | |
| 	runTestWithDebugger(t, dbg, func(client *daptest.Client) {
 | |
| 		client.AttachRequest(map[string]interface{}{"mode": "remote", "stopOnEntry": true})
 | |
| 		client.ExpectCapabilitiesEventSupportTerminateDebuggee(t)
 | |
| 		client.ExpectInitializedEvent(t)
 | |
| 		client.ExpectAttachResponse(t)
 | |
| 		client.ConfigurationDoneRequest()
 | |
| 		client.ExpectStoppedEvent(t)
 | |
| 		client.ExpectConfigurationDoneResponse(t)
 | |
| 	})
 | |
| 	cmd.Process.Kill()
 | |
| }
 | |
| 
 | |
| func TestAttachRemoteToHaltedTargetContinueOnEntry(t *testing.T) {
 | |
| 	// Halted + continue on entry
 | |
| 	_, dbg := launchDebuggerWithTargetHalted(t, "http_server")
 | |
| 	runTestWithDebugger(t, dbg, func(client *daptest.Client) {
 | |
| 		client.AttachRequest(map[string]interface{}{"mode": "remote", "stopOnEntry": false})
 | |
| 		client.ExpectInitializedEvent(t)
 | |
| 		client.ExpectAttachResponse(t)
 | |
| 		client.ConfigurationDoneRequest()
 | |
| 		client.ExpectConfigurationDoneResponse(t)
 | |
| 		// Continuing
 | |
| 		time.Sleep(time.Second)
 | |
| 		// Halt to make the disconnect sequence more predictable.
 | |
| 		client.PauseRequest(1)
 | |
| 		expectPauseResponseAndStoppedEvent(t, client)
 | |
| 	})
 | |
| }
 | |
| 
 | |
| func TestAttachRemoteToRunningTargetStopOnEntry(t *testing.T) {
 | |
| 	fixture, dbg := launchDebuggerWithTargetRunning(t, "loopprog")
 | |
| 	runTestWithDebugger(t, dbg, func(client *daptest.Client) {
 | |
| 		client.AttachRequest(map[string]interface{}{"mode": "remote", "stopOnEntry": true})
 | |
| 		client.ExpectInitializedEvent(t)
 | |
| 		client.ExpectAttachResponse(t)
 | |
| 		// Target is halted here
 | |
| 		client.SetBreakpointsRequest(fixture.Source, []int{8})
 | |
| 		expectSetBreakpointsResponse(t, client, []Breakpoint{{8, fixture.Source, true, ""}})
 | |
| 		client.ConfigurationDoneRequest()
 | |
| 		client.ExpectStoppedEvent(t)
 | |
| 		client.ExpectConfigurationDoneResponse(t)
 | |
| 		client.ContinueRequest(1)
 | |
| 		client.ExpectContinueResponse(t)
 | |
| 		client.ExpectStoppedEvent(t)
 | |
| 		checkStop(t, client, 1, "main.loop", 8)
 | |
| 	})
 | |
| }
 | |
| 
 | |
| func TestAttachRemoteToRunningTargetContinueOnEntry(t *testing.T) {
 | |
| 	fixture, dbg := launchDebuggerWithTargetRunning(t, "loopprog")
 | |
| 	runTestWithDebugger(t, dbg, func(client *daptest.Client) {
 | |
| 		client.AttachRequest(map[string]interface{}{"mode": "remote", "stopOnEntry": false})
 | |
| 		client.ExpectInitializedEvent(t)
 | |
| 		client.ExpectAttachResponse(t)
 | |
| 		// Target is halted here
 | |
| 		client.SetBreakpointsRequest(fixture.Source, []int{8})
 | |
| 		expectSetBreakpointsResponse(t, client, []Breakpoint{{8, fixture.Source, true, ""}})
 | |
| 		client.ConfigurationDoneRequest()
 | |
| 		// Target is restarted here
 | |
| 		client.ExpectConfigurationDoneResponse(t)
 | |
| 		client.ExpectStoppedEvent(t)
 | |
| 		checkStop(t, client, 1, "main.loop", 8)
 | |
| 	})
 | |
| }
 | |
| 
 | |
| // MultiClientCloseServerMock mocks the rpccommon.Server using a dap.Server to exercise
 | |
| // the shutdown logic in dap.Session where it does NOT take down the server on close
 | |
| // in multi-client mode. (The dap mode of the rpccommon.Server is tested in dlv_test).
 | |
| // The dap.Server is a single-use server. Once its one and only session is closed,
 | |
| // the server and the target must be taken down manually for the test not to leak.
 | |
| type MultiClientCloseServerMock struct {
 | |
| 	impl      *Server
 | |
| 	debugger  *debugger.Debugger
 | |
| 	forceStop chan struct{}
 | |
| 	stopped   chan struct{}
 | |
| }
 | |
| 
 | |
| func NewMultiClientCloseServerMock(t *testing.T, fixture string) *MultiClientCloseServerMock {
 | |
| 	var s MultiClientCloseServerMock
 | |
| 	s.stopped = make(chan struct{})
 | |
| 	s.impl, s.forceStop = startDAPServer(t, false, s.stopped)
 | |
| 	_, s.debugger = launchDebuggerWithTargetHalted(t, "http_server")
 | |
| 	return &s
 | |
| }
 | |
| 
 | |
| func (s *MultiClientCloseServerMock) acceptNewClient(t *testing.T) *daptest.Client {
 | |
| 	client := daptest.NewClient(s.impl.listener.Addr().String())
 | |
| 	time.Sleep(100 * time.Millisecond) // Give time for connection to be set as dap.Session
 | |
| 	s.impl.sessionMu.Lock()
 | |
| 	if s.impl.session == nil {
 | |
| 		t.Fatal("dap session is not ready")
 | |
| 	}
 | |
| 	// A dap.Server doesn't support accept-multiclient, but we can use this
 | |
| 	// hack to test the inner connection logic that is used by a server that does.
 | |
| 	s.impl.session.config.AcceptMulti = true
 | |
| 	s.impl.session.debugger = s.debugger
 | |
| 	s.impl.sessionMu.Unlock()
 | |
| 	return client
 | |
| }
 | |
| 
 | |
| func (s *MultiClientCloseServerMock) stop(t *testing.T) {
 | |
| 	close(s.forceStop)
 | |
| 	// If the server doesn't have an active session,
 | |
| 	// closing it would leak the debugger with the target because
 | |
| 	// they are part of dap.Session.
 | |
| 	// We must take it down manually as if we are in rpccommon::ServerImpl::Stop.
 | |
| 	if s.debugger.IsRunning() {
 | |
| 		s.debugger.Command(&api.DebuggerCommand{Name: api.Halt}, nil)
 | |
| 	}
 | |
| 	s.debugger.Detach(true)
 | |
| }
 | |
| 
 | |
| func (s *MultiClientCloseServerMock) verifyStopped(t *testing.T) {
 | |
| 	if state, err := s.debugger.State(true /*nowait*/); err != proc.ErrProcessDetached && !processExited(state, err) {
 | |
| 		t.Errorf("target leak")
 | |
| 	}
 | |
| 	verifyServerStopped(t, s.impl)
 | |
| }
 | |
| 
 | |
| // TestAttachRemoteMultiClientDisconnect tests that remote attach doesn't take down
 | |
| // the server in multi-client mode unless terminateDebuggee is explicitly set.
 | |
| func TestAttachRemoteMultiClientDisconnect(t *testing.T) {
 | |
| 	closingClientSessionOnly := fmt.Sprintf(daptest.ClosingClient, "halted")
 | |
| 	detachingAndTerminating := "Detaching and terminating target process"
 | |
| 	tests := []struct {
 | |
| 		name              string
 | |
| 		disconnectRequest func(client *daptest.Client)
 | |
| 		expect            string
 | |
| 	}{
 | |
| 		{"default", func(c *daptest.Client) { c.DisconnectRequest() }, closingClientSessionOnly},
 | |
| 		{"terminate=true", func(c *daptest.Client) { c.DisconnectRequestWithKillOption(true) }, detachingAndTerminating},
 | |
| 		{"terminate=false", func(c *daptest.Client) { c.DisconnectRequestWithKillOption(false) }, closingClientSessionOnly},
 | |
| 	}
 | |
| 	for _, tc := range tests {
 | |
| 		t.Run(tc.name, func(t *testing.T) {
 | |
| 			server := NewMultiClientCloseServerMock(t, "increment")
 | |
| 			client := server.acceptNewClient(t)
 | |
| 			defer client.Close()
 | |
| 
 | |
| 			client.InitializeRequest()
 | |
| 			client.ExpectInitializeResponseAndCapabilities(t)
 | |
| 
 | |
| 			client.AttachRequest(map[string]interface{}{"mode": "remote", "stopOnEntry": true})
 | |
| 			client.ExpectCapabilitiesEventSupportTerminateDebuggee(t)
 | |
| 			client.ExpectInitializedEvent(t)
 | |
| 			client.ExpectAttachResponse(t)
 | |
| 			client.ConfigurationDoneRequest()
 | |
| 			client.ExpectStoppedEvent(t)
 | |
| 			client.ExpectConfigurationDoneResponse(t)
 | |
| 
 | |
| 			tc.disconnectRequest(client)
 | |
| 			e := client.ExpectOutputEvent(t)
 | |
| 			if matched, _ := regexp.MatchString(tc.expect, e.Body.Output); !matched {
 | |
| 				t.Errorf("\ngot %#v\nwant Output=%q", e, tc.expect)
 | |
| 			}
 | |
| 			client.ExpectDisconnectResponse(t)
 | |
| 			client.ExpectTerminatedEvent(t)
 | |
| 			time.Sleep(10 * time.Millisecond) // give time for things to shut down
 | |
| 
 | |
| 			if tc.expect == closingClientSessionOnly {
 | |
| 				// At this point a multi-client server is still running. but session should be done.
 | |
| 				verifySessionStopped(t, server.impl.session)
 | |
| 				// Verify target's running state.
 | |
| 				if server.debugger.IsRunning() {
 | |
| 					t.Errorf("\ngot running=true, want false")
 | |
| 				}
 | |
| 				server.stop(t)
 | |
| 			}
 | |
| 			<-server.stopped
 | |
| 			server.verifyStopped(t)
 | |
| 		})
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func TestLaunchAttachErrorWhenDebugInProgress(t *testing.T) {
 | |
| 	tests := []struct {
 | |
| 		name string
 | |
| 		dbg  func() *debugger.Debugger
 | |
| 	}{
 | |
| 		{"halted", func() *debugger.Debugger { _, dbg := launchDebuggerWithTargetHalted(t, "increment"); return dbg }},
 | |
| 		{"running", func() *debugger.Debugger { _, dbg := launchDebuggerWithTargetRunning(t, "loopprog"); return dbg }},
 | |
| 	}
 | |
| 	for _, tc := range tests {
 | |
| 		t.Run(tc.name, func(t *testing.T) {
 | |
| 			runTestWithDebugger(t, tc.dbg(), func(client *daptest.Client) {
 | |
| 				client.EvaluateRequest("1==1", 0 /*no frame specified*/, "repl")
 | |
| 				if tc.name == "running" {
 | |
| 					client.ExpectInvisibleErrorResponse(t)
 | |
| 				} else {
 | |
| 					client.ExpectEvaluateResponse(t)
 | |
| 				}
 | |
| 
 | |
| 				// Both launch and attach requests should go through for additional error checking
 | |
| 				client.AttachRequest(map[string]interface{}{"mode": "local", "processId": 100})
 | |
| 				er := client.ExpectVisibleErrorResponse(t)
 | |
| 				msgRe := regexp.MustCompile("Failed to attach: debug session already in progress at .+ - use remote mode to connect to a server with an active debug session")
 | |
| 				if er.Body.Error == nil || er.Body.Error.Id != FailedToAttach || !msgRe.MatchString(er.Body.Error.Format) {
 | |
| 					t.Errorf("got %#v, want Id=%d Format=%q", er.Body.Error, FailedToAttach, msgRe)
 | |
| 				}
 | |
| 				tests := []string{"debug", "test", "exec", "replay", "core"}
 | |
| 				for _, mode := range tests {
 | |
| 					t.Run(mode, func(t *testing.T) {
 | |
| 						client.LaunchRequestWithArgs(map[string]interface{}{"mode": mode})
 | |
| 						er := client.ExpectVisibleErrorResponse(t)
 | |
| 						msgRe := regexp.MustCompile("Failed to launch: debug session already in progress at .+ - use remote attach mode to connect to a server with an active debug session")
 | |
| 						if er.Body.Error == nil || er.Body.Error.Id != FailedToLaunch || !msgRe.MatchString(er.Body.Error.Format) {
 | |
| 							t.Errorf("got %#v, want Id=%d Format=%q", er.Body.Error, FailedToLaunch, msgRe)
 | |
| 						}
 | |
| 					})
 | |
| 				}
 | |
| 			})
 | |
| 		})
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func TestBadInitializeRequest(t *testing.T) {
 | |
| 	runInitializeTest := func(args dap.InitializeRequestArguments, err string) {
 | |
| 		t.Helper()
 | |
| 		// Only one initialize request is allowed, so use a new server
 | |
| 		// for each test.
 | |
| 		serverStopped := make(chan struct{})
 | |
| 		client := startDAPServerWithClient(t, false, serverStopped)
 | |
| 		defer client.Close()
 | |
| 
 | |
| 		client.InitializeRequestWithArgs(args)
 | |
| 		response := client.ExpectErrorResponse(t)
 | |
| 		if response.Command != "initialize" {
 | |
| 			t.Errorf("Command got %q, want \"launch\"", response.Command)
 | |
| 		}
 | |
| 		if response.Message != "Failed to initialize" {
 | |
| 			t.Errorf("Message got %q, want \"Failed to launch\"", response.Message)
 | |
| 		}
 | |
| 		if !checkErrorMessageId(response.Body.Error, FailedToInitialize) {
 | |
| 			t.Errorf("Id got %v, want Id=%d", response.Body.Error, FailedToInitialize)
 | |
| 		}
 | |
| 		if !checkErrorMessageFormat(response.Body.Error, err) {
 | |
| 			t.Errorf("\ngot  %q\nwant %q", response.Body.Error.Format, err)
 | |
| 		}
 | |
| 
 | |
| 		client.DisconnectRequest()
 | |
| 		client.ExpectDisconnectResponse(t)
 | |
| 		<-serverStopped
 | |
| 	}
 | |
| 
 | |
| 	// Bad path format.
 | |
| 	runInitializeTest(dap.InitializeRequestArguments{
 | |
| 		AdapterID:       "go",
 | |
| 		PathFormat:      "url", // unsupported 'pathFormat'
 | |
| 		LinesStartAt1:   true,
 | |
| 		ColumnsStartAt1: true,
 | |
| 		Locale:          "en-us",
 | |
| 	},
 | |
| 		"Failed to initialize: Unsupported 'pathFormat' value 'url'.",
 | |
| 	)
 | |
| 
 | |
| 	// LinesStartAt1 must be true.
 | |
| 	runInitializeTest(dap.InitializeRequestArguments{
 | |
| 		AdapterID:       "go",
 | |
| 		PathFormat:      "path",
 | |
| 		LinesStartAt1:   false, // only 1-based line numbers are supported
 | |
| 		ColumnsStartAt1: true,
 | |
| 		Locale:          "en-us",
 | |
| 	},
 | |
| 		"Failed to initialize: Only 1-based line numbers are supported.",
 | |
| 	)
 | |
| 
 | |
| 	// ColumnsStartAt1 must be true.
 | |
| 	runInitializeTest(dap.InitializeRequestArguments{
 | |
| 		AdapterID:       "go",
 | |
| 		PathFormat:      "path",
 | |
| 		LinesStartAt1:   true,
 | |
| 		ColumnsStartAt1: false, // only 1-based column numbers are supported
 | |
| 		Locale:          "en-us",
 | |
| 	},
 | |
| 		"Failed to initialize: Only 1-based column numbers are supported.",
 | |
| 	)
 | |
| }
 | |
| 
 | |
| func TestBadlyFormattedMessageToServer(t *testing.T) {
 | |
| 	runTest(t, "increment", func(client *daptest.Client, fixture protest.Fixture) {
 | |
| 		// Send a badly formatted message to the server, and expect it to close the
 | |
| 		// connection.
 | |
| 		client.BadRequest()
 | |
| 		time.Sleep(100 * time.Millisecond)
 | |
| 
 | |
| 		_, err := client.ReadMessage()
 | |
| 
 | |
| 		if err != io.EOF {
 | |
| 			t.Errorf("got err=%v, want io.EOF", err)
 | |
| 		}
 | |
| 	})
 | |
| 	runTest(t, "increment", func(client *daptest.Client, fixture protest.Fixture) {
 | |
| 		// Send an unknown request message to the server, and expect it to send
 | |
| 		// an error response.
 | |
| 		client.UnknownRequest()
 | |
| 		err := client.ExpectErrorResponse(t)
 | |
| 		if !checkErrorMessageFormat(err.Body.Error, "Internal Error: Request command 'unknown' is not supported (seq: 1)") || err.RequestSeq != 1 {
 | |
| 			t.Errorf("got %v, want  RequestSeq=1 Error=\"Internal Error: Request command 'unknown' is not supported (seq: 1)\"", err)
 | |
| 		}
 | |
| 
 | |
| 		// Make sure that the unknown request did not kill the server.
 | |
| 		client.InitializeRequest()
 | |
| 		client.ExpectInitializeResponse(t)
 | |
| 
 | |
| 		client.DisconnectRequest()
 | |
| 		client.ExpectDisconnectResponse(t)
 | |
| 	})
 | |
| }
 | |
| 
 | |
| func TestParseLogPoint(t *testing.T) {
 | |
| 	tests := []struct {
 | |
| 		name           string
 | |
| 		msg            string
 | |
| 		wantTracepoint bool
 | |
| 		wantFormat     string
 | |
| 		wantArgs       []string
 | |
| 		wantErr        bool
 | |
| 	}{
 | |
| 		// Test simple log messages.
 | |
| 		{name: "simple string", msg: "hello, world!", wantTracepoint: true, wantFormat: "hello, world!"},
 | |
| 		{name: "empty string", msg: "", wantTracepoint: false, wantErr: false},
 | |
| 		// Test parse eval expressions.
 | |
| 		{
 | |
| 			name:           "simple eval",
 | |
| 			msg:            "{x}",
 | |
| 			wantTracepoint: true,
 | |
| 			wantFormat:     "%s",
 | |
| 			wantArgs:       []string{"x"},
 | |
| 		},
 | |
| 		{
 | |
| 			name:           "type cast",
 | |
| 			msg:            "hello {string(x)}",
 | |
| 			wantTracepoint: true,
 | |
| 			wantFormat:     "hello %s",
 | |
| 			wantArgs:       []string{"string(x)"},
 | |
| 		},
 | |
| 		{
 | |
| 			name:           "multiple eval",
 | |
| 			msg:            "{x} {y} {z}",
 | |
| 			wantTracepoint: true,
 | |
| 			wantFormat:     "%s %s %s",
 | |
| 			wantArgs:       []string{"x", "y", "z"},
 | |
| 		},
 | |
| 		{
 | |
| 			name:           "eval expressions contain braces",
 | |
| 			msg:            "{interface{}(x)} {myType{y}} {[]myType{{z}}}",
 | |
| 			wantTracepoint: true,
 | |
| 			wantFormat:     "%s %s %s",
 | |
| 			wantArgs:       []string{"interface{}(x)", "myType{y}", "[]myType{{z}}"},
 | |
| 		},
 | |
| 		// Test parse errors.
 | |
| 		{name: "empty evaluation", msg: "{}", wantErr: true},
 | |
| 		{name: "empty space evaluation", msg: "{   \n}", wantErr: true},
 | |
| 		{name: "open brace missing closed", msg: "{", wantErr: true},
 | |
| 		{name: "closed brace missing open", msg: "}", wantErr: true},
 | |
| 		{name: "open brace in expression", msg: `{m["{"]}`, wantErr: true},
 | |
| 		{name: "closed brace in expression", msg: `{m["}"]}`, wantErr: true},
 | |
| 	}
 | |
| 	for _, tt := range tests {
 | |
| 		t.Run(tt.name, func(t *testing.T) {
 | |
| 			gotTracepoint, gotLogMessage, err := parseLogPoint(tt.msg)
 | |
| 			if gotTracepoint != tt.wantTracepoint {
 | |
| 				t.Errorf("parseLogPoint() tracepoint = %v, wantTracepoint %v", gotTracepoint, tt.wantTracepoint)
 | |
| 				return
 | |
| 			}
 | |
| 			if (err != nil) != tt.wantErr {
 | |
| 				t.Errorf("parseLogPoint() error = %v, wantErr %v", err, tt.wantErr)
 | |
| 				return
 | |
| 			}
 | |
| 			if !tt.wantTracepoint {
 | |
| 				return
 | |
| 			}
 | |
| 			if gotLogMessage == nil {
 | |
| 				t.Errorf("parseLogPoint() gotLogMessage = nil, want log message")
 | |
| 				return
 | |
| 			}
 | |
| 			if gotLogMessage.format != tt.wantFormat {
 | |
| 				t.Errorf("parseLogPoint() gotFormat = %v, want %v", gotLogMessage.format, tt.wantFormat)
 | |
| 			}
 | |
| 			if !reflect.DeepEqual(gotLogMessage.args, tt.wantArgs) {
 | |
| 				t.Errorf("parseLogPoint() gotArgs = %v, want %v", gotLogMessage.args, tt.wantArgs)
 | |
| 			}
 | |
| 		})
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func TestDisassemble(t *testing.T) {
 | |
| 	runTest(t, "increment", func(client *daptest.Client, fixture protest.Fixture) {
 | |
| 		runDebugSessionWithBPs(t, client, "launch",
 | |
| 			// Launch
 | |
| 			func() {
 | |
| 				client.LaunchRequest("exec", fixture.Path, !stopOnEntry)
 | |
| 			},
 | |
| 			// Set breakpoints
 | |
| 			fixture.Source, []int{17},
 | |
| 			[]onBreakpoint{{
 | |
| 				// Stop at line 17
 | |
| 				execute: func() {
 | |
| 					checkStop(t, client, 1, "main.main", 17)
 | |
| 
 | |
| 					client.StackTraceRequest(1, 0, 1)
 | |
| 					st := client.ExpectStackTraceResponse(t)
 | |
| 					if len(st.Body.StackFrames) < 1 {
 | |
| 						t.Fatalf("\ngot  %#v\nwant len(stackframes) => 1", st)
 | |
| 					}
 | |
| 					// Request the single instruction that the program is stopped at.
 | |
| 					pc := st.Body.StackFrames[0].InstructionPointerReference
 | |
| 					client.DisassembleRequest(pc, 0, 1)
 | |
| 					dr := client.ExpectDisassembleResponse(t)
 | |
| 					if len(dr.Body.Instructions) != 1 {
 | |
| 						t.Errorf("\ngot %#v\nwant len(instructions) = 1", dr)
 | |
| 					} else if dr.Body.Instructions[0].Address != pc {
 | |
| 						t.Errorf("\ngot %#v\nwant instructions[0].Address = %s", dr, pc)
 | |
| 					}
 | |
| 
 | |
| 					// Request the instruction that the program is stopped at, and the two
 | |
| 					// surrounding it.
 | |
| 					client.DisassembleRequest(pc, -1, 3)
 | |
| 					dr = client.ExpectDisassembleResponse(t)
 | |
| 					if len(dr.Body.Instructions) != 3 {
 | |
| 						t.Errorf("\ngot %#v\nwant len(instructions) = 3", dr)
 | |
| 					} else if dr.Body.Instructions[1].Address != pc {
 | |
| 						t.Errorf("\ngot %#v\nwant instructions[1].Address = %s", dr, pc)
 | |
| 					}
 | |
| 
 | |
| 					// Request zero instructions.
 | |
| 					client.DisassembleRequest(pc, 0, 0)
 | |
| 					dr = client.ExpectDisassembleResponse(t)
 | |
| 					if len(dr.Body.Instructions) != 0 {
 | |
| 						t.Errorf("\ngot %#v\nwant len(instructions) = 0", dr)
 | |
| 					}
 | |
| 
 | |
| 					// Request invalid instructions.
 | |
| 					checkInvalidInstruction := func(instructions []dap.DisassembledInstruction, count int, address uint64) {
 | |
| 						if len(instructions) != count {
 | |
| 							t.Errorf("\ngot %#v\nwant len(instructions) = %d", dr, count)
 | |
| 						}
 | |
| 						for i, got := range instructions {
 | |
| 							if got.Instruction != invalidInstruction.Instruction {
 | |
| 								t.Errorf("\ngot [%d].Instruction=%q\nwant = %#v", i, got.Instruction, invalidInstruction.Address)
 | |
| 							}
 | |
| 							addr, err := strconv.ParseUint(got.Address, 0, 64)
 | |
| 							if err != nil {
 | |
| 								t.Error(err)
 | |
| 								continue
 | |
| 							}
 | |
| 							if addr != address {
 | |
| 								t.Errorf("\ngot [%d].Address=%s\nwant = %#x", i, got.Address, address)
 | |
| 							}
 | |
| 						}
 | |
| 					}
 | |
| 					client.DisassembleRequest("0x0", 0, 10)
 | |
| 					checkInvalidInstruction(client.ExpectDisassembleResponse(t).Body.Instructions, 10, 0)
 | |
| 
 | |
| 					client.DisassembleRequest(fmt.Sprintf("%#x", uint64(math.MaxUint64)), 0, 10)
 | |
| 					checkInvalidInstruction(client.ExpectDisassembleResponse(t).Body.Instructions, 10, uint64(math.MaxUint64))
 | |
| 
 | |
| 					// Bad request, not a number.
 | |
| 					client.DisassembleRequest("hello, world!", 0, 1)
 | |
| 					client.ExpectErrorResponse(t)
 | |
| 
 | |
| 					// Bad request, not an address in program.
 | |
| 					client.DisassembleRequest("0x5", 0, 100)
 | |
| 					client.ExpectErrorResponse(t)
 | |
| 				},
 | |
| 				disconnect: true,
 | |
| 			}},
 | |
| 		)
 | |
| 	})
 | |
| }
 | |
| 
 | |
| func TestAlignPCs(t *testing.T) {
 | |
| 	NUM_FUNCS := 10
 | |
| 	// Create fake functions to test align PCs.
 | |
| 	funcs := make([]proc.Function, NUM_FUNCS)
 | |
| 	for i := 0; i < len(funcs); i++ {
 | |
| 		funcs[i] = proc.Function{
 | |
| 			Entry: uint64(100 + i*10),
 | |
| 			End:   uint64(100 + i*10 + 5),
 | |
| 		}
 | |
| 	}
 | |
| 	bi := &proc.BinaryInfo{
 | |
| 		Functions: funcs,
 | |
| 	}
 | |
| 	type args struct {
 | |
| 		start uint64
 | |
| 		end   uint64
 | |
| 	}
 | |
| 	tests := []struct {
 | |
| 		name      string
 | |
| 		args      args
 | |
| 		wantStart uint64
 | |
| 		wantEnd   uint64
 | |
| 	}{
 | |
| 		{
 | |
| 			name: "out of bounds",
 | |
| 			args: args{
 | |
| 				start: funcs[0].Entry - 5,
 | |
| 				end:   funcs[NUM_FUNCS-1].End + 5,
 | |
| 			},
 | |
| 			wantStart: funcs[0].Entry,         // start of first function
 | |
| 			wantEnd:   funcs[NUM_FUNCS-1].End, // end of last function
 | |
| 		},
 | |
| 		{
 | |
| 			name: "same function",
 | |
| 			args: args{
 | |
| 				start: funcs[1].Entry + 1,
 | |
| 				end:   funcs[1].Entry + 2,
 | |
| 			},
 | |
| 			wantStart: funcs[1].Entry, // start of containing function
 | |
| 			wantEnd:   funcs[1].End,   // end of containing function
 | |
| 		},
 | |
| 		{
 | |
| 			name: "between functions",
 | |
| 			args: args{
 | |
| 				start: funcs[1].End + 1,
 | |
| 				end:   funcs[1].End + 2,
 | |
| 			},
 | |
| 			wantStart: funcs[1].Entry, // start of function before
 | |
| 			wantEnd:   funcs[2].Entry, // start of function after
 | |
| 		},
 | |
| 		{
 | |
| 			name: "start of function",
 | |
| 			args: args{
 | |
| 				start: funcs[2].Entry,
 | |
| 				end:   funcs[5].Entry,
 | |
| 			},
 | |
| 			wantStart: funcs[2].Entry, // start of current function
 | |
| 			wantEnd:   funcs[5].End,   // end of current function
 | |
| 		},
 | |
| 		{
 | |
| 			name: "end of function",
 | |
| 			args: args{
 | |
| 				start: funcs[4].End,
 | |
| 				end:   funcs[8].End,
 | |
| 			},
 | |
| 			wantStart: funcs[4].Entry, // start of current function
 | |
| 			wantEnd:   funcs[9].Entry, // start of next function
 | |
| 		},
 | |
| 	}
 | |
| 	for _, tt := range tests {
 | |
| 		t.Run(tt.name, func(t *testing.T) {
 | |
| 			gotStart, gotEnd := alignPCs(bi, tt.args.start, tt.args.end)
 | |
| 			if gotStart != tt.wantStart {
 | |
| 				t.Errorf("alignPCs() got start = %v, want %v", gotStart, tt.wantStart)
 | |
| 			}
 | |
| 			if gotEnd != tt.wantEnd {
 | |
| 				t.Errorf("alignPCs() got end = %v, want %v", gotEnd, tt.wantEnd)
 | |
| 			}
 | |
| 		})
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func TestFindInstructions(t *testing.T) {
 | |
| 	numInstructions := 100
 | |
| 	startPC := 0x1000
 | |
| 	procInstructions := make([]proc.AsmInstruction, numInstructions)
 | |
| 	for i := 0; i < len(procInstructions); i++ {
 | |
| 		procInstructions[i] = proc.AsmInstruction{
 | |
| 			Loc: proc.Location{
 | |
| 				PC: uint64(startPC + 2*i),
 | |
| 			},
 | |
| 		}
 | |
| 	}
 | |
| 	type args struct {
 | |
| 		addr   uint64
 | |
| 		offset int
 | |
| 		count  int
 | |
| 	}
 | |
| 	tests := []struct {
 | |
| 		name             string
 | |
| 		args             args
 | |
| 		wantInstructions []proc.AsmInstruction
 | |
| 		wantOffset       int
 | |
| 		wantErr          bool
 | |
| 	}{
 | |
| 		{
 | |
| 			name: "request all",
 | |
| 			args: args{
 | |
| 				addr:   uint64(startPC),
 | |
| 				offset: 0,
 | |
| 				count:  100,
 | |
| 			},
 | |
| 			wantInstructions: procInstructions,
 | |
| 			wantOffset:       0,
 | |
| 			wantErr:          false,
 | |
| 		},
 | |
| 		{
 | |
| 			name: "request all (with offset)",
 | |
| 			args: args{
 | |
| 				addr:   uint64(startPC + numInstructions), // the instruction addr at numInstructions/2
 | |
| 				offset: -numInstructions / 2,
 | |
| 				count:  numInstructions,
 | |
| 			},
 | |
| 			wantInstructions: procInstructions,
 | |
| 			wantOffset:       0,
 | |
| 			wantErr:          false,
 | |
| 		},
 | |
| 		{
 | |
| 			name: "request half (with offset)",
 | |
| 			args: args{
 | |
| 				addr:   uint64(startPC),
 | |
| 				offset: 0,
 | |
| 				count:  numInstructions / 2,
 | |
| 			},
 | |
| 			wantInstructions: procInstructions[:numInstructions/2],
 | |
| 			wantOffset:       0,
 | |
| 			wantErr:          false,
 | |
| 		},
 | |
| 		{
 | |
| 			name: "request half (with offset)",
 | |
| 			args: args{
 | |
| 				addr:   uint64(startPC),
 | |
| 				offset: numInstructions / 2,
 | |
| 				count:  numInstructions / 2,
 | |
| 			},
 | |
| 			wantInstructions: procInstructions[numInstructions/2:],
 | |
| 			wantOffset:       0,
 | |
| 			wantErr:          false,
 | |
| 		},
 | |
| 		{
 | |
| 			name: "request too many",
 | |
| 			args: args{
 | |
| 				addr:   uint64(startPC),
 | |
| 				offset: 0,
 | |
| 				count:  numInstructions * 2,
 | |
| 			},
 | |
| 			wantInstructions: procInstructions,
 | |
| 			wantOffset:       0,
 | |
| 			wantErr:          false,
 | |
| 		},
 | |
| 		{
 | |
| 			name: "request too many with offset",
 | |
| 			args: args{
 | |
| 				addr:   uint64(startPC),
 | |
| 				offset: -numInstructions,
 | |
| 				count:  numInstructions * 2,
 | |
| 			},
 | |
| 			wantInstructions: procInstructions,
 | |
| 			wantOffset:       numInstructions,
 | |
| 			wantErr:          false,
 | |
| 		},
 | |
| 		{
 | |
| 			name: "request out of bounds",
 | |
| 			args: args{
 | |
| 				addr:   uint64(startPC),
 | |
| 				offset: -numInstructions,
 | |
| 				count:  numInstructions,
 | |
| 			},
 | |
| 			wantInstructions: []proc.AsmInstruction{},
 | |
| 			wantOffset:       0,
 | |
| 			wantErr:          false,
 | |
| 		},
 | |
| 		{
 | |
| 			name: "request out of bounds",
 | |
| 			args: args{
 | |
| 				addr:   uint64(startPC + 2*(numInstructions-1)),
 | |
| 				offset: 1,
 | |
| 				count:  numInstructions,
 | |
| 			},
 | |
| 			wantInstructions: []proc.AsmInstruction{},
 | |
| 			wantOffset:       0,
 | |
| 			wantErr:          false,
 | |
| 		},
 | |
| 		{
 | |
| 			name: "addr out of bounds (low)",
 | |
| 			args: args{
 | |
| 				addr:   0,
 | |
| 				offset: 0,
 | |
| 				count:  100,
 | |
| 			},
 | |
| 			wantInstructions: nil,
 | |
| 			wantOffset:       -1,
 | |
| 			wantErr:          true,
 | |
| 		},
 | |
| 		{
 | |
| 			name: "addr out of bounds (high)",
 | |
| 			args: args{
 | |
| 				addr:   uint64(startPC + 2*(numInstructions+1)),
 | |
| 				offset: -10,
 | |
| 				count:  20,
 | |
| 			},
 | |
| 			wantInstructions: nil,
 | |
| 			wantOffset:       -1,
 | |
| 			wantErr:          true,
 | |
| 		},
 | |
| 		{
 | |
| 			name: "addr not aligned",
 | |
| 			args: args{
 | |
| 				addr:   uint64(startPC + 1),
 | |
| 				offset: 0,
 | |
| 				count:  20,
 | |
| 			},
 | |
| 			wantInstructions: nil,
 | |
| 			wantOffset:       -1,
 | |
| 			wantErr:          true,
 | |
| 		},
 | |
| 	}
 | |
| 	for _, tt := range tests {
 | |
| 		t.Run(tt.name, func(t *testing.T) {
 | |
| 			gotInstructions, gotOffset, err := findInstructions(procInstructions, tt.args.addr, tt.args.offset, tt.args.count)
 | |
| 			if (err != nil) != tt.wantErr {
 | |
| 				t.Errorf("findInstructions() error = %v, wantErr %v", err, tt.wantErr)
 | |
| 				return
 | |
| 			}
 | |
| 			if !reflect.DeepEqual(gotInstructions, tt.wantInstructions) {
 | |
| 				t.Errorf("findInstructions() got instructions = %v, want %v", gotInstructions, tt.wantInstructions)
 | |
| 			}
 | |
| 			if gotOffset != tt.wantOffset {
 | |
| 				t.Errorf("findInstructions() got offset = %v, want %v", gotOffset, tt.wantOffset)
 | |
| 			}
 | |
| 		})
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func TestDisassembleCgo(t *testing.T) {
 | |
| 	// Test that disassembling a program containing cgo code does not create problems.
 | |
| 	// See issue #3040
 | |
| 	runTestBuildFlags(t, "cgodisass", func(client *daptest.Client, fixture protest.Fixture) {
 | |
| 		runDebugSessionWithBPs(t, client, "launch",
 | |
| 			// Launch
 | |
| 			func() {
 | |
| 				client.LaunchRequest("exec", fixture.Path, !stopOnEntry)
 | |
| 			},
 | |
| 			// Set breakpoints
 | |
| 			fixture.Source, []int{11},
 | |
| 			[]onBreakpoint{{
 | |
| 				execute: func() {
 | |
| 					checkStop(t, client, 1, "main.main", 11)
 | |
| 
 | |
| 					client.StackTraceRequest(1, 0, 1)
 | |
| 					st := client.ExpectStackTraceResponse(t)
 | |
| 					if len(st.Body.StackFrames) < 1 {
 | |
| 						t.Fatalf("\ngot  %#v\nwant len(stackframes) => 1", st)
 | |
| 					}
 | |
| 
 | |
| 					// Request the single instruction that the program is stopped at.
 | |
| 					pc := st.Body.StackFrames[0].InstructionPointerReference
 | |
| 
 | |
| 					client.DisassembleRequest(pc, -200, 400)
 | |
| 					client.ExpectDisassembleResponse(t)
 | |
| 				},
 | |
| 				disconnect: true,
 | |
| 			}},
 | |
| 		)
 | |
| 	},
 | |
| 		protest.AllNonOptimized, true)
 | |
| }
 | |
| 
 | |
| func TestRedirect(t *testing.T) {
 | |
| 	runTest(t, "out_redirect", func(client *daptest.Client, fixture protest.Fixture) {
 | |
| 		// 1 >> initialize, << initialize
 | |
| 		client.InitializeRequest()
 | |
| 		initResp := client.ExpectInitializeResponseAndCapabilities(t)
 | |
| 		if initResp.Seq != 0 || initResp.RequestSeq != 1 {
 | |
| 			t.Errorf("\ngot %#v\nwant Seq=0, RequestSeq=1", initResp)
 | |
| 		}
 | |
| 
 | |
| 		// 2 >> launch, << initialized, << launch
 | |
| 		client.LaunchRequestWithArgs(map[string]interface{}{
 | |
| 			"request":    "launch",
 | |
| 			"mode":       "debug",
 | |
| 			"program":    fixture.Source,
 | |
| 			"outputMode": "remote",
 | |
| 		})
 | |
| 		initEvent := client.ExpectInitializedEvent(t)
 | |
| 		if initEvent.Seq != 0 {
 | |
| 			t.Errorf("\ngot %#v\nwant Seq=0", initEvent)
 | |
| 		}
 | |
| 		launchResp := client.ExpectLaunchResponse(t)
 | |
| 		if launchResp.Seq != 0 || launchResp.RequestSeq != 2 {
 | |
| 			t.Errorf("\ngot %#v\nwant Seq=0, RequestSeq=2", launchResp)
 | |
| 		}
 | |
| 
 | |
| 		// 5 >> configurationDone, << stopped, << configurationDone
 | |
| 		client.ConfigurationDoneRequest()
 | |
| 
 | |
| 		cdResp := client.ExpectConfigurationDoneResponse(t)
 | |
| 		if cdResp.Seq != 0 || cdResp.RequestSeq != 3 {
 | |
| 			t.Errorf("\ngot %#v\nwant Seq=0, RequestSeq=5", cdResp)
 | |
| 		}
 | |
| 
 | |
| 		// 6 << output, << terminated
 | |
| 		var (
 | |
| 			stdout = bytes.NewBufferString("")
 | |
| 			stderr = bytes.NewBufferString("")
 | |
| 		)
 | |
| 
 | |
| 	terminatedPoint:
 | |
| 		for {
 | |
| 			message := client.ExpectMessage(t)
 | |
| 			switch m := message.(type) {
 | |
| 			case *dap.OutputEvent:
 | |
| 				switch m.Body.Category {
 | |
| 				case "stdout":
 | |
| 					stdout.WriteString(m.Body.Output)
 | |
| 				case "stderr":
 | |
| 					stderr.WriteString(m.Body.Output)
 | |
| 				default:
 | |
| 					t.Errorf("\ngot %#v\nwant Category='stdout' or 'stderr'", m)
 | |
| 				}
 | |
| 			case *dap.TerminatedEvent:
 | |
| 				break terminatedPoint
 | |
| 			default:
 | |
| 				t.Errorf("\n got %#v, want *dap.OutputEvent or *dap.TerminateResponse", m)
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		var (
 | |
| 			expectStdout = "hello world!\nhello world!"
 | |
| 			expectStderr = "hello world!\nhello world! error!"
 | |
| 		)
 | |
| 
 | |
| 		// check output
 | |
| 		if expectStdout != stdout.String() {
 | |
| 			t.Errorf("\n got stdout: len:%d\n%s\nwant: len:%d\n%s", stdout.Len(), stdout.String(), len(expectStdout), expectStdout)
 | |
| 		}
 | |
| 
 | |
| 		if expectStderr != stderr.String() {
 | |
| 			t.Errorf("\n got stderr: len:%d \n%s\nwant: len:%d\n%s", stderr.Len(), stderr.String(), len(expectStderr), expectStderr)
 | |
| 		}
 | |
| 
 | |
| 		// 7 >> disconnect, << disconnect
 | |
| 		client.DisconnectRequest()
 | |
| 		oep := client.ExpectOutputEventProcessExited(t, 0)
 | |
| 		if oep.Seq != 0 || oep.Body.Category != "console" {
 | |
| 			t.Errorf("\ngot %#v\nwant Seq=0 Category='console'", oep)
 | |
| 		}
 | |
| 		oed := client.ExpectOutputEventDetaching(t)
 | |
| 		if oed.Seq != 0 || oed.Body.Category != "console" {
 | |
| 			t.Errorf("\ngot %#v\nwant Seq=0 Category='console'", oed)
 | |
| 		}
 | |
| 		dResp := client.ExpectDisconnectResponse(t)
 | |
| 		if dResp.Seq != 0 || dResp.RequestSeq != 4 {
 | |
| 			t.Errorf("\ngot %#v\nwant Seq=0, RequestSeq=43", dResp)
 | |
| 		}
 | |
| 		client.ExpectTerminatedEvent(t)
 | |
| 	})
 | |
| }
 | |
| 
 | |
| // Helper functions for checking ErrorMessage field values.
 | |
| 
 | |
| func checkErrorMessageId(er *dap.ErrorMessage, id int) bool {
 | |
| 	return er != nil && er.Id == id
 | |
| }
 | |
| 
 | |
| func checkErrorMessageFormat(er *dap.ErrorMessage, fmt string) bool {
 | |
| 	return er != nil && er.Format == fmt
 | |
| }
 |