2020-02-15 19:52:53 +00:00
|
|
|
package dap
|
|
|
|
|
|
|
|
import (
|
2021-01-06 17:07:47 +00:00
|
|
|
"bufio"
|
2020-02-15 19:52:53 +00:00
|
|
|
"flag"
|
2021-03-23 03:06:09 +00:00
|
|
|
"fmt"
|
2020-02-27 04:45:48 +00:00
|
|
|
"io"
|
2021-02-24 16:19:07 +00:00
|
|
|
"io/ioutil"
|
2021-04-02 16:19:16 +00:00
|
|
|
"math/rand"
|
2020-02-15 19:52:53 +00:00
|
|
|
"net"
|
|
|
|
"os"
|
2020-12-28 17:14:15 +00:00
|
|
|
"os/exec"
|
2020-03-04 17:22:51 +00:00
|
|
|
"path/filepath"
|
2020-08-11 15:34:27 +00:00
|
|
|
"regexp"
|
2020-08-17 07:07:53 +00:00
|
|
|
"runtime"
|
2020-03-10 19:29:06 +00:00
|
|
|
"strings"
|
2020-02-15 19:52:53 +00:00
|
|
|
"testing"
|
|
|
|
"time"
|
|
|
|
|
2020-08-17 07:07:53 +00:00
|
|
|
"github.com/go-delve/delve/pkg/goversion"
|
2020-02-15 19:52:53 +00:00
|
|
|
"github.com/go-delve/delve/pkg/logflags"
|
|
|
|
protest "github.com/go-delve/delve/pkg/proc/test"
|
|
|
|
"github.com/go-delve/delve/service"
|
|
|
|
"github.com/go-delve/delve/service/dap/daptest"
|
2020-04-13 18:07:15 +00:00
|
|
|
"github.com/go-delve/delve/service/debugger"
|
2020-02-15 19:52:53 +00:00
|
|
|
"github.com/google/go-dap"
|
|
|
|
)
|
|
|
|
|
2020-03-04 17:22:51 +00:00
|
|
|
const stopOnEntry bool = true
|
2020-08-11 15:34:27 +00:00
|
|
|
const hasChildren bool = true
|
|
|
|
const noChildren bool = false
|
2020-03-04 17:22:51 +00:00
|
|
|
|
2020-08-24 17:21:51 +00:00
|
|
|
var testBackend string
|
|
|
|
|
2020-02-15 19:52:53 +00:00
|
|
|
func TestMain(m *testing.M) {
|
2021-05-04 19:53:42 +00:00
|
|
|
logOutputVal := ""
|
|
|
|
if _, isTeamCityTest := os.LookupEnv("TEAMCITY_VERSION"); isTeamCityTest {
|
|
|
|
logOutputVal = "debugger,dap"
|
|
|
|
}
|
2020-02-15 19:52:53 +00:00
|
|
|
var logOutput string
|
2021-05-04 19:53:42 +00:00
|
|
|
flag.StringVar(&logOutput, "log-output", logOutputVal, "configures log output")
|
2020-02-15 19:52:53 +00:00
|
|
|
flag.Parse()
|
|
|
|
logflags.Setup(logOutput != "", logOutput, "")
|
2020-08-24 17:21:51 +00:00
|
|
|
protest.DefaultTestBackend(&testBackend)
|
2020-02-15 19:52:53 +00:00
|
|
|
os.Exit(protest.RunTestsWithFixtures(m))
|
|
|
|
}
|
|
|
|
|
2020-02-27 04:45:48 +00:00
|
|
|
// name is for _fixtures/<name>.go
|
|
|
|
func runTest(t *testing.T, name string, test func(c *daptest.Client, f protest.Fixture)) {
|
2020-12-28 17:14:15 +00:00
|
|
|
var buildFlags protest.BuildFlags = protest.AllNonOptimized
|
2020-02-27 04:45:48 +00:00
|
|
|
fixture := protest.BuildFixture(name, buildFlags)
|
|
|
|
|
2021-05-06 07:56:29 +00:00
|
|
|
// Start the DAP server.
|
|
|
|
client := startDapServer(t)
|
|
|
|
// client.Close will close the client connectinon, which will cause a connection error
|
|
|
|
// on the server side and signal disconnect to unblock Stop() above.
|
|
|
|
defer client.Close()
|
|
|
|
|
|
|
|
test(client, fixture)
|
|
|
|
}
|
|
|
|
|
|
|
|
func startDapServer(t *testing.T) *daptest.Client {
|
2020-02-27 04:45:48 +00:00
|
|
|
// Start the DAP server.
|
2020-02-15 19:52:53 +00:00
|
|
|
listener, err := net.Listen("tcp", ":0")
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
2020-02-27 04:45:48 +00:00
|
|
|
disconnectChan := make(chan struct{})
|
|
|
|
server := NewServer(&service.Config{
|
2020-02-15 19:52:53 +00:00
|
|
|
Listener: listener,
|
2020-02-27 04:45:48 +00:00
|
|
|
DisconnectChan: disconnectChan,
|
2020-04-13 18:07:15 +00:00
|
|
|
Debugger: debugger.Config{
|
|
|
|
Backend: "default",
|
|
|
|
},
|
2020-02-15 19:52:53 +00:00
|
|
|
})
|
|
|
|
server.Run()
|
|
|
|
// Give server time to start listening for clients
|
|
|
|
time.Sleep(100 * time.Millisecond)
|
|
|
|
|
2020-02-27 04:45:48 +00:00
|
|
|
// 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.
|
|
|
|
go func() {
|
|
|
|
<-disconnectChan
|
2021-04-21 20:28:15 +00:00
|
|
|
server.Stop()
|
2020-02-27 04:45:48 +00:00
|
|
|
}()
|
2020-02-15 19:52:53 +00:00
|
|
|
|
2020-02-27 04:45:48 +00:00
|
|
|
client := daptest.NewClient(listener.Addr().String())
|
2021-05-06 07:56:29 +00:00
|
|
|
return client
|
2020-02-15 19:52:53 +00:00
|
|
|
}
|
|
|
|
|
2020-12-28 17:14:15 +00:00
|
|
|
// TestLaunchStopOnEntry emulates the message exchange that can be observed with
|
|
|
|
// VS Code for the most basic launch debug session with "stopOnEntry" enabled:
|
2020-03-10 19:29:06 +00:00
|
|
|
// - 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
|
2020-11-12 23:24:31 +00:00
|
|
|
// : 8 << error (Unable to produce stack trace)
|
2020-03-10 19:29:06 +00:00
|
|
|
// : 9 >> stackTrace
|
2020-11-12 23:24:31 +00:00
|
|
|
// : 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
|
2020-03-10 19:29:06 +00:00
|
|
|
// - Program runs to completion : << terminated event
|
2020-11-12 23:24:31 +00:00
|
|
|
// : 13 >> disconnect
|
2021-04-12 21:50:15 +00:00
|
|
|
// : << output event (Process exited)
|
|
|
|
// : << output event (Detaching)
|
2020-11-12 23:24:31 +00:00
|
|
|
// : 13 << disconnect
|
2020-03-10 19:29:06 +00:00
|
|
|
// This test exhaustively tests Seq and RequestSeq on all messages from the
|
|
|
|
// server. Other tests do not necessarily need to repeat all these checks.
|
2020-12-28 17:14:15 +00:00
|
|
|
func TestLaunchStopOnEntry(t *testing.T) {
|
2020-02-15 19:52:53 +00:00
|
|
|
runTest(t, "increment", func(client *daptest.Client, fixture protest.Fixture) {
|
2020-03-10 19:29:06 +00:00
|
|
|
// 1 >> initialize, << initialize
|
2020-02-15 19:52:53 +00:00
|
|
|
client.InitializeRequest()
|
2021-05-06 09:11:45 +00:00
|
|
|
initResp := client.ExpectInitializeResponseAndCapabilities(t)
|
2020-03-10 19:29:06 +00:00
|
|
|
if initResp.Seq != 0 || initResp.RequestSeq != 1 {
|
|
|
|
t.Errorf("\ngot %#v\nwant Seq=0, RequestSeq=1", initResp)
|
2020-02-24 17:36:34 +00:00
|
|
|
}
|
2020-02-15 19:52:53 +00:00
|
|
|
|
2020-03-10 19:29:06 +00:00
|
|
|
// 2 >> launch, << initialized, << launch
|
2020-03-04 17:22:51 +00:00
|
|
|
client.LaunchRequest("exec", fixture.Path, stopOnEntry)
|
2020-03-10 19:29:06 +00:00
|
|
|
initEvent := client.ExpectInitializedEvent(t)
|
|
|
|
if initEvent.Seq != 0 {
|
|
|
|
t.Errorf("\ngot %#v\nwant Seq=0", initEvent)
|
2020-02-24 17:36:34 +00:00
|
|
|
}
|
|
|
|
launchResp := client.ExpectLaunchResponse(t)
|
2020-03-10 19:29:06 +00:00
|
|
|
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)
|
2020-02-24 17:36:34 +00:00
|
|
|
}
|
2020-02-15 19:52:53 +00:00
|
|
|
|
2020-03-10 19:29:06 +00:00
|
|
|
// 4 >> setExceptionBreakpoints, << setExceptionBreakpoints
|
2020-02-15 19:52:53 +00:00
|
|
|
client.SetExceptionBreakpointsRequest()
|
2020-03-10 19:29:06 +00:00
|
|
|
sebpResp := client.ExpectSetExceptionBreakpointsResponse(t)
|
|
|
|
if sebpResp.Seq != 0 || sebpResp.RequestSeq != 4 {
|
|
|
|
t.Errorf("\ngot %#v\nwant Seq=0, RequestSeq=4", sebpResp)
|
2020-02-24 17:36:34 +00:00
|
|
|
}
|
2020-02-15 19:52:53 +00:00
|
|
|
|
2020-03-10 19:29:06 +00:00
|
|
|
// 5 >> configurationDone, << stopped, << configurationDone
|
2020-02-15 19:52:53 +00:00
|
|
|
client.ConfigurationDoneRequest()
|
2020-02-24 17:36:34 +00:00
|
|
|
stopEvent := client.ExpectStoppedEvent(t)
|
|
|
|
if stopEvent.Seq != 0 ||
|
2020-04-01 19:51:31 +00:00
|
|
|
stopEvent.Body.Reason != "entry" ||
|
2020-02-24 17:36:34 +00:00
|
|
|
stopEvent.Body.ThreadId != 1 ||
|
|
|
|
!stopEvent.Body.AllThreadsStopped {
|
2020-04-01 19:51:31 +00:00
|
|
|
t.Errorf("\ngot %#v\nwant Seq=0, Body={Reason=\"entry\", ThreadId=1, AllThreadsStopped=true}", stopEvent)
|
2020-02-24 17:36:34 +00:00
|
|
|
}
|
|
|
|
cdResp := client.ExpectConfigurationDoneResponse(t)
|
2020-03-10 19:29:06 +00:00
|
|
|
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)
|
2020-02-24 17:36:34 +00:00
|
|
|
}
|
2020-02-15 19:52:53 +00:00
|
|
|
|
2020-03-10 19:29:06 +00:00
|
|
|
// 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)
|
|
|
|
}
|
|
|
|
|
2020-11-12 23:24:31 +00:00
|
|
|
// 8 >> stackTrace, << error
|
2020-07-01 18:01:17 +00:00
|
|
|
client.StackTraceRequest(1, 0, 20)
|
2021-05-06 09:11:45 +00:00
|
|
|
stResp := client.ExpectInvisibleErrorResponse(t)
|
2020-07-01 18:01:17 +00:00
|
|
|
if stResp.Seq != 0 || stResp.RequestSeq != 8 || stResp.Body.Error.Format != "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)
|
2020-03-10 19:29:06 +00:00
|
|
|
}
|
|
|
|
|
2020-11-12 23:24:31 +00:00
|
|
|
// 9 >> stackTrace, << error
|
2020-07-01 18:01:17 +00:00
|
|
|
client.StackTraceRequest(1, 0, 20)
|
2021-05-06 09:11:45 +00:00
|
|
|
stResp = client.ExpectInvisibleErrorResponse(t)
|
2020-07-01 18:01:17 +00:00
|
|
|
if stResp.Seq != 0 || stResp.RequestSeq != 9 || stResp.Body.Error.Id != 2004 {
|
|
|
|
t.Errorf("\ngot %#v\nwant Seq=0, RequestSeq=9 Id=2004", stResp)
|
2020-03-10 19:29:06 +00:00
|
|
|
}
|
|
|
|
|
2020-11-12 23:24:31 +00:00
|
|
|
// 10 >> evaluate, << error
|
|
|
|
client.EvaluateRequest("foo", 0 /*no frame specified*/, "repl")
|
2021-05-06 09:11:45 +00:00
|
|
|
erResp := client.ExpectInvisibleErrorResponse(t)
|
2020-11-12 23:24:31 +00:00
|
|
|
if erResp.Seq != 0 || erResp.RequestSeq != 10 || erResp.Body.Error.Id != 2009 {
|
|
|
|
t.Errorf("\ngot %#v\nwant Seq=0, RequestSeq=10 Id=2009", erResp)
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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
|
2020-02-15 19:52:53 +00:00
|
|
|
client.ContinueRequest(1)
|
2020-02-24 17:36:34 +00:00
|
|
|
contResp := client.ExpectContinueResponse(t)
|
2020-11-12 23:24:31 +00:00
|
|
|
if contResp.Seq != 0 || contResp.RequestSeq != 12 || !contResp.Body.AllThreadsContinued {
|
|
|
|
t.Errorf("\ngot %#v\nwant Seq=0, RequestSeq=12 Body.AllThreadsContinued=true", contResp)
|
2020-03-10 19:29:06 +00:00
|
|
|
}
|
|
|
|
termEvent := client.ExpectTerminatedEvent(t)
|
|
|
|
if termEvent.Seq != 0 {
|
|
|
|
t.Errorf("\ngot %#v\nwant Seq=0", termEvent)
|
2020-02-24 17:36:34 +00:00
|
|
|
}
|
|
|
|
|
2020-11-12 23:24:31 +00:00
|
|
|
// 13 >> disconnect, << disconnect
|
2020-03-10 19:29:06 +00:00
|
|
|
client.DisconnectRequest()
|
2021-04-12 21:50:15 +00:00
|
|
|
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)
|
2021-04-21 20:28:15 +00:00
|
|
|
if oed.Seq != 0 || oed.Body.Category != "console" {
|
2021-04-12 21:50:15 +00:00
|
|
|
t.Errorf("\ngot %#v\nwant Seq=0 Category='console'", oed)
|
|
|
|
}
|
2020-03-10 19:29:06 +00:00
|
|
|
dResp := client.ExpectDisconnectResponse(t)
|
2020-11-12 23:24:31 +00:00
|
|
|
if dResp.Seq != 0 || dResp.RequestSeq != 13 {
|
|
|
|
t.Errorf("\ngot %#v\nwant Seq=0, RequestSeq=13", dResp)
|
2020-02-24 17:36:34 +00:00
|
|
|
}
|
2020-03-10 19:29:06 +00:00
|
|
|
})
|
|
|
|
}
|
2020-02-15 19:52:53 +00:00
|
|
|
|
2020-12-28 17:14:15 +00:00
|
|
|
// TestAttachStopOnEntry is like TestLaunchStopOnEntry, but with attach request.
|
|
|
|
func TestAttachStopOnEntry(t *testing.T) {
|
|
|
|
if runtime.GOOS == "freebsd" {
|
|
|
|
t.SkipNow()
|
|
|
|
}
|
|
|
|
runTest(t, "loopprog", func(client *daptest.Client, fixture protest.Fixture) {
|
|
|
|
// Start the program to attach to
|
|
|
|
cmd := exec.Command(fixture.Path)
|
2021-01-06 17:07:47 +00:00
|
|
|
stdout, err := cmd.StdoutPipe()
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
2020-12-28 17:14:15 +00:00
|
|
|
cmd.Stderr = os.Stderr
|
|
|
|
if err := cmd.Start(); err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
2021-01-06 17:07:47 +00:00
|
|
|
// 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\"")
|
|
|
|
}
|
2020-12-28 17:14:15 +00:00
|
|
|
|
|
|
|
// 1 >> initialize, << initialize
|
|
|
|
client.InitializeRequest()
|
2021-05-06 09:11:45 +00:00
|
|
|
initResp := client.ExpectInitializeResponseAndCapabilities(t)
|
2020-12-28 17:14:15 +00:00
|
|
|
if initResp.Seq != 0 || initResp.RequestSeq != 1 {
|
|
|
|
t.Errorf("\ngot %#v\nwant Seq=0, RequestSeq=1", initResp)
|
|
|
|
}
|
|
|
|
|
2021-01-06 17:07:47 +00:00
|
|
|
// 2 >> attach, << initialized, << attach
|
2020-12-28 17:14:15 +00:00
|
|
|
client.AttachRequest(
|
|
|
|
map[string]interface{}{"mode": "local", "processId": cmd.Process.Pid, "stopOnEntry": true})
|
|
|
|
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)
|
2021-01-06 17:07:47 +00:00
|
|
|
// 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)
|
2020-12-28 17:14:15 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// 7 >> threads, << threads
|
|
|
|
client.ThreadsRequest()
|
2021-01-06 17:07:47 +00:00
|
|
|
client.ExpectThreadsResponse(t)
|
2020-12-28 17:14:15 +00:00
|
|
|
|
2021-01-06 17:07:47 +00:00
|
|
|
// 8 >> stackTrace, << response
|
2020-12-28 17:14:15 +00:00
|
|
|
client.StackTraceRequest(1, 0, 20)
|
2021-01-06 17:07:47 +00:00
|
|
|
client.ExpectStackTraceResponse(t)
|
2020-12-28 17:14:15 +00:00
|
|
|
|
2021-01-06 17:07:47 +00:00
|
|
|
// 9 >> stackTrace, << response
|
2020-12-28 17:14:15 +00:00
|
|
|
client.StackTraceRequest(1, 0, 20)
|
2021-01-06 17:07:47 +00:00
|
|
|
client.ExpectStackTraceResponse(t)
|
2020-12-28 17:14:15 +00:00
|
|
|
|
|
|
|
// 10 >> evaluate, << error
|
|
|
|
client.EvaluateRequest("foo", 0 /*no frame specified*/, "repl")
|
2021-05-06 09:11:45 +00:00
|
|
|
erResp := client.ExpectInvisibleErrorResponse(t)
|
2020-12-28 17:14:15 +00:00
|
|
|
if erResp.Seq != 0 || erResp.RequestSeq != 10 || erResp.Body.Error.Id != 2009 {
|
|
|
|
t.Errorf("\ngot %#v\nwant Seq=0, RequestSeq=10 Id=2009", erResp)
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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)
|
|
|
|
}
|
|
|
|
|
2021-05-04 19:49:52 +00:00
|
|
|
// 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)
|
|
|
|
}
|
2020-12-28 17:14:15 +00:00
|
|
|
|
|
|
|
// TODO(polina): once https://github.com/go-delve/delve/issues/2259 is
|
|
|
|
// fixed, test with kill=false.
|
|
|
|
|
2021-05-04 19:49:52 +00:00
|
|
|
// 13 >> disconnect, << disconnect
|
2020-12-28 17:14:15 +00:00
|
|
|
client.DisconnectRequestWithKillOption(true)
|
2021-05-04 19:49:52 +00:00
|
|
|
|
|
|
|
// Both of these scenarios are somehow possible.
|
|
|
|
// Even though the program has an infininte loop,
|
|
|
|
// it apears that a halt can cause it to terminate.
|
|
|
|
// Since we are in async mode while running, we might receive messages in either order.
|
|
|
|
msg := client.ExpectMessage(t)
|
|
|
|
switch m := msg.(type) {
|
|
|
|
case *dap.StoppedEvent:
|
|
|
|
if m.Seq != 0 || m.Body.Reason != "pause" { // continue is interrupted
|
|
|
|
t.Errorf("\ngot %#v\nwant Seq=0 Reason='pause'", m)
|
|
|
|
}
|
|
|
|
oed := client.ExpectOutputEventDetachingKill(t)
|
|
|
|
if oed.Seq != 0 || oed.Body.Category != "console" {
|
|
|
|
t.Errorf("\ngot %#v\nwant Seq=0 Category='console'", oed)
|
|
|
|
}
|
|
|
|
case *dap.TerminatedEvent:
|
|
|
|
if m.Seq != 0 {
|
|
|
|
t.Errorf("\ngot %#v\nwant Seq=0'", m)
|
|
|
|
}
|
|
|
|
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)
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
t.Fatalf("got %#v, want StoppedEvent or TerminatedEvent", m)
|
2021-04-12 21:50:15 +00:00
|
|
|
}
|
2020-12-28 17:14:15 +00:00
|
|
|
dResp := client.ExpectDisconnectResponse(t)
|
2021-05-04 19:49:52 +00:00
|
|
|
if dResp.Seq != 0 || dResp.RequestSeq != 13 {
|
|
|
|
t.Errorf("\ngot %#v\nwant Seq=0, RequestSeq=13", dResp)
|
2020-12-28 17:14:15 +00:00
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2020-03-10 19:29:06 +00:00
|
|
|
// 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()
|
2021-05-06 09:11:45 +00:00
|
|
|
client.ExpectInitializeResponseAndCapabilities(t)
|
2020-03-10 19:29:06 +00:00
|
|
|
|
|
|
|
// 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)
|
2021-05-04 19:49:52 +00:00
|
|
|
// "Continue" happens behind the scenes on another goroutine
|
2020-03-10 19:29:06 +00:00
|
|
|
|
2021-05-04 19:49:52 +00:00
|
|
|
// 6 >> threads, << threads
|
|
|
|
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 m.Seq != 0 || m.RequestSeq != 6 {
|
|
|
|
t.Errorf("\ngot %#v\nwant Seq=0, RequestSeq=6", m)
|
|
|
|
}
|
|
|
|
// Single current thread is sent when the program is running
|
|
|
|
// because DAP spec expects at least one thread.
|
2021-05-17 07:07:55 +00:00
|
|
|
// Also accept empty already-terminated response.
|
|
|
|
if len(m.Body.Threads) != 0 && (len(m.Body.Threads) != 1 || m.Body.Threads[0].Id != -1 || m.Body.Threads[0].Name != "Current") {
|
|
|
|
t.Errorf("\ngot %#v\nwant Id=-1, Name=\"Current\" or empty", m.Body.Threads)
|
2021-05-04 19:49:52 +00:00
|
|
|
}
|
|
|
|
case *dap.TerminatedEvent:
|
|
|
|
default:
|
|
|
|
t.Fatalf("got %#v, want ThreadsResponse or TerminatedEvent", m)
|
|
|
|
}
|
|
|
|
}
|
2020-03-10 19:29:06 +00:00
|
|
|
|
|
|
|
// It is possible for the program to terminate before the initial
|
2021-05-04 19:49:52 +00:00
|
|
|
// threads request is processed. And in that case, the
|
|
|
|
// response can be empty
|
|
|
|
// 7 >> threads, << threads
|
2020-03-10 19:29:06 +00:00
|
|
|
client.ThreadsRequest()
|
|
|
|
tResp := client.ExpectThreadsResponse(t)
|
2021-05-04 19:49:52 +00:00
|
|
|
if tResp.Seq != 0 || tResp.RequestSeq != 7 || len(tResp.Body.Threads) != 0 {
|
|
|
|
t.Errorf("\ngot %#v\nwant Seq=0, RequestSeq=7 len(Threads)=0", tResp)
|
2020-03-10 19:29:06 +00:00
|
|
|
}
|
|
|
|
|
2021-05-04 19:49:52 +00:00
|
|
|
// 8 >> disconnect, << disconnect
|
2020-02-15 19:52:53 +00:00
|
|
|
client.DisconnectRequest()
|
2021-04-12 21:50:15 +00:00
|
|
|
client.ExpectOutputEventProcessExited(t, 0)
|
|
|
|
client.ExpectOutputEventDetaching(t)
|
2020-02-24 17:36:34 +00:00
|
|
|
dResp := client.ExpectDisconnectResponse(t)
|
2021-05-04 19:49:52 +00:00
|
|
|
if dResp.Seq != 0 || dResp.RequestSeq != 8 {
|
|
|
|
t.Errorf("\ngot %#v\nwant Seq=0, RequestSeq=8", dResp)
|
2020-02-24 17:36:34 +00:00
|
|
|
}
|
2020-02-15 19:52:53 +00:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2020-10-02 16:18:33 +00:00
|
|
|
// TestPreSetBreakpoint corresponds to a debug session that is configured to
|
2020-03-10 19:29:06 +00:00
|
|
|
// continue on entry with a pre-set breakpoint.
|
2020-10-02 16:18:33 +00:00
|
|
|
func TestPreSetBreakpoint(t *testing.T) {
|
2020-02-15 19:52:53 +00:00
|
|
|
runTest(t, "increment", func(client *daptest.Client, fixture protest.Fixture) {
|
|
|
|
client.InitializeRequest()
|
2021-05-06 09:11:45 +00:00
|
|
|
client.ExpectInitializeResponseAndCapabilities(t)
|
2020-02-15 19:52:53 +00:00
|
|
|
|
2020-03-04 17:22:51 +00:00
|
|
|
client.LaunchRequest("exec", fixture.Path, !stopOnEntry)
|
2020-02-24 17:36:34 +00:00
|
|
|
client.ExpectInitializedEvent(t)
|
2020-03-10 19:29:06 +00:00
|
|
|
client.ExpectLaunchResponse(t)
|
2020-02-15 19:52:53 +00:00
|
|
|
|
2020-10-02 16:18:33 +00:00
|
|
|
client.SetBreakpointsRequest(fixture.Source, []int{8})
|
2020-02-24 17:36:34 +00:00
|
|
|
sResp := client.ExpectSetBreakpointsResponse(t)
|
|
|
|
if len(sResp.Body.Breakpoints) != 1 {
|
|
|
|
t.Errorf("got %#v, want len(Breakpoints)=1", sResp)
|
|
|
|
}
|
|
|
|
bkpt0 := sResp.Body.Breakpoints[0]
|
2021-02-21 16:02:42 +00:00
|
|
|
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)
|
2020-02-24 17:36:34 +00:00
|
|
|
}
|
2020-02-15 19:52:53 +00:00
|
|
|
|
|
|
|
client.SetExceptionBreakpointsRequest()
|
2020-02-24 17:36:34 +00:00
|
|
|
client.ExpectSetExceptionBreakpointsResponse(t)
|
2020-02-15 19:52:53 +00:00
|
|
|
|
|
|
|
client.ConfigurationDoneRequest()
|
2020-03-10 19:29:06 +00:00
|
|
|
client.ExpectConfigurationDoneResponse(t)
|
2021-05-04 19:49:52 +00:00
|
|
|
// This triggers "continue" on a separate goroutine
|
2020-03-10 19:29:06 +00:00
|
|
|
|
2021-05-04 19:49:52 +00:00
|
|
|
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:
|
2021-05-15 05:34:59 +00:00
|
|
|
// 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)
|
2021-05-04 19:49:52 +00:00
|
|
|
}
|
|
|
|
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)
|
|
|
|
}
|
2020-02-24 17:36:34 +00:00
|
|
|
}
|
2020-03-10 19:29:06 +00:00
|
|
|
|
2021-05-04 19:49:52 +00:00
|
|
|
// Threads-StackTrace-Scopes-Variables request waterfall is
|
|
|
|
// triggered on stop event.
|
2020-03-10 19:29:06 +00:00
|
|
|
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)
|
|
|
|
}
|
2020-11-30 17:43:37 +00:00
|
|
|
reMain, _ := regexp.Compile(`\* \[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"}
|
2020-03-10 19:29:06 +00:00
|
|
|
for _, got := range tResp.Body.Threads {
|
2020-11-30 17:43:37 +00:00
|
|
|
if got.Id != 1 && !reMain.MatchString(got.Name) && !strings.Contains(got.Name, "runtime") {
|
2020-03-10 19:29:06 +00:00
|
|
|
t.Errorf("\ngot %#v\nwant []dap.Thread{%#v, %#v, ...}", tResp.Body.Threads, wantMain, wantRuntime)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-07-01 18:01:17 +00:00
|
|
|
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 {
|
2020-07-08 17:20:05 +00:00
|
|
|
expectFrame := func(got dap.StackFrame, id int, name string, sourceName string, line int) {
|
2020-07-01 18:01:17 +00:00
|
|
|
t.Helper()
|
|
|
|
if got.Id != id || got.Name != name {
|
2020-07-08 17:20:05 +00:00
|
|
|
t.Errorf("\ngot %#v\nwant Id=%d Name=%s", got, id, name)
|
2020-07-01 18:01:17 +00:00
|
|
|
}
|
|
|
|
if (sourceName != "" && got.Source.Name != sourceName) || (line > 0 && got.Line != line) {
|
2020-07-08 17:20:05 +00:00
|
|
|
t.Errorf("\ngot %#v\nwant Source.Name=%s Line=%d", got, sourceName, line)
|
2020-07-01 18:01:17 +00:00
|
|
|
}
|
|
|
|
}
|
2020-07-08 17:20:05 +00:00
|
|
|
expectFrame(stResp.Body.StackFrames[0], 1000, "main.Increment", "increment.go", 8)
|
|
|
|
expectFrame(stResp.Body.StackFrames[1], 1001, "main.Increment", "increment.go", 11)
|
|
|
|
expectFrame(stResp.Body.StackFrames[2], 1002, "main.Increment", "increment.go", 11)
|
|
|
|
expectFrame(stResp.Body.StackFrames[3], 1003, "main.main", "increment.go", 17)
|
|
|
|
expectFrame(stResp.Body.StackFrames[4], 1004, "runtime.main", "proc.go", -1)
|
|
|
|
expectFrame(stResp.Body.StackFrames[5], 1005, "runtime.goexit", "", -1)
|
2020-07-01 18:01:17 +00:00
|
|
|
}
|
|
|
|
|
2020-08-11 15:34:27 +00:00
|
|
|
client.ScopesRequest(1000)
|
|
|
|
scopes := client.ExpectScopesResponse(t)
|
2020-09-15 20:14:55 +00:00
|
|
|
if len(scopes.Body.Scopes) > 2 {
|
|
|
|
t.Errorf("\ngot %#v\nwant len(Scopes)=2 (Arguments & Locals)", scopes)
|
|
|
|
}
|
2020-08-11 15:34:27 +00:00
|
|
|
expectScope(t, scopes, 0, "Arguments", 1000)
|
|
|
|
expectScope(t, scopes, 1, "Locals", 1001)
|
|
|
|
|
|
|
|
client.VariablesRequest(1000) // Arguments
|
|
|
|
args := client.ExpectVariablesResponse(t)
|
|
|
|
expectChildren(t, args, "Arguments", 2)
|
2021-05-10 18:34:42 +00:00
|
|
|
expectVarExact(t, args, 0, "y", "y", "0", "uint", noChildren)
|
|
|
|
expectVarExact(t, args, 1, "~r1", "", "0", "uint", noChildren)
|
2020-08-11 15:34:27 +00:00
|
|
|
|
|
|
|
client.VariablesRequest(1001) // Locals
|
|
|
|
locals := client.ExpectVariablesResponse(t)
|
|
|
|
expectChildren(t, locals, "Locals", 0)
|
2020-07-01 18:01:17 +00:00
|
|
|
|
|
|
|
client.ContinueRequest(1)
|
2020-08-24 17:21:51 +00:00
|
|
|
ctResp := client.ExpectContinueResponse(t)
|
|
|
|
if !ctResp.Body.AllThreadsContinued {
|
|
|
|
t.Errorf("\ngot %#v\nwant AllThreadsContinued=true", ctResp.Body)
|
|
|
|
}
|
2020-07-01 18:01:17 +00:00
|
|
|
// "Continue" is triggered after the response is sent
|
|
|
|
|
|
|
|
client.ExpectTerminatedEvent(t)
|
|
|
|
client.DisconnectRequest()
|
2021-04-12 21:50:15 +00:00
|
|
|
client.ExpectOutputEventProcessExited(t, 0)
|
|
|
|
client.ExpectOutputEventDetaching(t)
|
2020-07-01 18:01:17 +00:00
|
|
|
client.ExpectDisconnectResponse(t)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2020-07-08 17:20:05 +00:00
|
|
|
// expectStackFrames is a helper for verifying the values within StackTraceResponse.
|
2020-11-30 17:43:09 +00:00
|
|
|
// wantStartName - name of the first returned frame (ignored if "")
|
|
|
|
// wantStartLine - file line of the first returned frame (ignored if <0).
|
2020-07-08 17:20:05 +00:00
|
|
|
// wantStartID - id of the first frame returned (ignored if wantFrames is 0).
|
2020-11-30 17:43:09 +00:00
|
|
|
// wantFrames - number of frames returned (length of StackTraceResponse.Body.StackFrames array).
|
|
|
|
// wantTotalFrames - total number of stack frames available (StackTraceResponse.Body.TotalFrames).
|
2020-07-08 17:20:05 +00:00
|
|
|
func expectStackFrames(t *testing.T, got *dap.StackTraceResponse,
|
2020-11-30 17:43:09 +00:00
|
|
|
wantStartName string, wantStartLine, wantStartID, wantFrames, wantTotalFrames int) {
|
|
|
|
t.Helper()
|
|
|
|
expectStackFramesNamed("", t, got, wantStartName, wantStartLine, wantStartID, wantFrames, wantTotalFrames)
|
|
|
|
}
|
|
|
|
|
|
|
|
func expectStackFramesNamed(testName string, t *testing.T, got *dap.StackTraceResponse,
|
|
|
|
wantStartName string, wantStartLine, wantStartID, wantFrames, wantTotalFrames int) {
|
2020-07-08 17:20:05 +00:00
|
|
|
t.Helper()
|
|
|
|
if got.Body.TotalFrames != wantTotalFrames {
|
2020-11-30 17:43:09 +00:00
|
|
|
t.Errorf("%s\ngot %#v\nwant TotalFrames=%d", testName, got.Body.TotalFrames, wantTotalFrames)
|
2020-07-08 17:20:05 +00:00
|
|
|
}
|
|
|
|
if len(got.Body.StackFrames) != wantFrames {
|
2020-11-30 17:43:09 +00:00
|
|
|
t.Errorf("%s\ngot len(StackFrames)=%d\nwant %d", testName, len(got.Body.StackFrames), wantFrames)
|
2020-07-08 17:20:05 +00:00
|
|
|
} 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 {
|
2020-11-30 17:43:09 +00:00
|
|
|
t.Errorf("%s\ngot %#v\nwant Id=%d", testName, got.Body.StackFrames[i], wantStartID+i)
|
2020-07-08 17:20:05 +00:00
|
|
|
}
|
|
|
|
}
|
2020-11-30 17:43:09 +00:00
|
|
|
// Verify the name and line corresponding to the first returned frame (if any).
|
2020-07-08 17:20:05 +00:00
|
|
|
// This is useful when the first frame is the frame corresponding to the breakpoint at
|
2020-11-30 17:43:09 +00:00
|
|
|
// a predefined line. Line values < 0 are a signal to skip the check (which can be useful
|
2020-07-08 17:20:05 +00:00
|
|
|
// for frames in the third-party code, where we do not control the lines).
|
|
|
|
if wantFrames > 0 && wantStartLine > 0 && got.Body.StackFrames[0].Line != wantStartLine {
|
2020-11-30 17:43:09 +00:00
|
|
|
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)
|
2020-07-08 17:20:05 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-08-11 15:34:27 +00:00
|
|
|
// expectScope is a helper for verifying the values within a ScopesResponse.
|
|
|
|
// i - index of the scope within ScopesRespose.Body.Scopes array
|
|
|
|
// name - name of the scope
|
|
|
|
// varRef - reference to retrieve variables of this scope
|
|
|
|
func expectScope(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 || goti.VariablesReference != varRef || goti.Expensive {
|
|
|
|
t.Errorf("\ngot %#v\nwant Name=%q VariablesReference=%d Expensive=false", goti, name, varRef)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// expectChildren is a helper for verifying the number of variables within a VariablesResponse.
|
2020-09-15 20:14:55 +00:00
|
|
|
// parentName - pseudoname of the enclosing variable or scope (used for error message only)
|
2020-08-11 15:34:27 +00:00
|
|
|
// numChildren - number of variables/fields/elements of this variable
|
|
|
|
func expectChildren(t *testing.T, got *dap.VariablesResponse, parentName string, numChildren int) {
|
|
|
|
t.Helper()
|
|
|
|
if len(got.Body.Variables) != numChildren {
|
2020-09-15 20:14:55 +00:00
|
|
|
t.Errorf("\ngot len(%s)=%d (children=%#v)\nwant len=%d", parentName, len(got.Body.Variables), got.Body.Variables, numChildren)
|
2020-08-11 15:34:27 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// expectVar is a helper for verifying the values within a VariablesResponse.
|
|
|
|
// i - index of the variable within VariablesRespose.Body.Variables array (-1 will search all vars for a match)
|
|
|
|
// name - name of the variable
|
2021-01-14 18:53:12 +00:00
|
|
|
// evalName - fully qualified variable name or alternative expression to load this variable
|
2020-08-11 15:34:27 +00:00
|
|
|
// value - the value of the variable
|
2021-01-14 18:53:12 +00:00
|
|
|
// useExactMatch - true if name, evalName and value are to be compared to exactly, false if to be used as regex
|
2020-08-11 15:34:27 +00:00
|
|
|
// 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)
|
2021-05-10 18:34:42 +00:00
|
|
|
func expectVar(t *testing.T, got *dap.VariablesResponse, i int, name, evalName, value, typ string, useExactMatch, hasRef bool) (ref int) {
|
2020-08-11 15:34:27 +00:00
|
|
|
t.Helper()
|
|
|
|
if len(got.Body.Variables) <= i {
|
2020-09-15 20:14:55 +00:00
|
|
|
t.Errorf("\ngot len=%d (children=%#v)\nwant len>%d", len(got.Body.Variables), got.Body.Variables, i)
|
2020-08-11 15:34:27 +00:00
|
|
|
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", got, name)
|
|
|
|
return 0
|
|
|
|
}
|
|
|
|
|
|
|
|
goti := got.Body.Variables[i]
|
2021-01-08 17:17:54 +00:00
|
|
|
matchedName := false
|
|
|
|
if useExactMatch {
|
|
|
|
matchedName = (goti.Name == name)
|
|
|
|
} else {
|
|
|
|
matchedName, _ = regexp.MatchString(name, goti.Name)
|
|
|
|
}
|
|
|
|
if !matchedName || (goti.VariablesReference > 0) != hasRef {
|
2020-08-11 15:34:27 +00:00
|
|
|
t.Errorf("\ngot %#v\nwant Name=%q hasRef=%t", goti, name, hasRef)
|
|
|
|
}
|
2021-01-14 18:53:12 +00:00
|
|
|
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)
|
|
|
|
}
|
2021-01-08 17:17:54 +00:00
|
|
|
matchedValue := false
|
2020-08-11 15:34:27 +00:00
|
|
|
if useExactMatch {
|
2021-01-08 17:17:54 +00:00
|
|
|
matchedValue = (goti.Value == value)
|
2020-08-11 15:34:27 +00:00
|
|
|
} else {
|
2021-01-08 17:17:54 +00:00
|
|
|
matchedValue, _ = regexp.MatchString(value, goti.Value)
|
2020-08-11 15:34:27 +00:00
|
|
|
}
|
2021-01-08 17:17:54 +00:00
|
|
|
if !matchedValue {
|
2020-08-11 15:34:27 +00:00
|
|
|
t.Errorf("\ngot %s=%q\nwant %q", name, goti.Value, value)
|
|
|
|
}
|
2021-05-10 18:34:42 +00:00
|
|
|
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)
|
|
|
|
}
|
2020-08-11 15:34:27 +00:00
|
|
|
return goti.VariablesReference
|
|
|
|
}
|
|
|
|
|
|
|
|
// expectVarExact is a helper like expectVar that matches value exactly.
|
2021-05-10 18:34:42 +00:00
|
|
|
func expectVarExact(t *testing.T, got *dap.VariablesResponse, i int, name, evalName, value, typ string, hasRef bool) (ref int) {
|
2020-08-11 15:34:27 +00:00
|
|
|
t.Helper()
|
2021-05-10 18:34:42 +00:00
|
|
|
return expectVar(t, got, i, name, evalName, value, typ, true, hasRef)
|
2020-08-11 15:34:27 +00:00
|
|
|
}
|
|
|
|
|
2021-01-14 18:53:12 +00:00
|
|
|
// expectVarRegex is a helper like expectVar that treats value, evalName or name as a regex.
|
2021-05-10 18:34:42 +00:00
|
|
|
func expectVarRegex(t *testing.T, got *dap.VariablesResponse, i int, name, evalName, value, typ string, hasRef bool) (ref int) {
|
2020-08-11 15:34:27 +00:00
|
|
|
t.Helper()
|
2021-05-10 18:34:42 +00:00
|
|
|
return expectVar(t, got, i, name, evalName, value, typ, false, hasRef)
|
2021-01-14 18:53:12 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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)
|
|
|
|
}
|
2021-03-15 16:36:46 +00:00
|
|
|
// 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 {
|
2021-01-14 18:53:12 +00:00
|
|
|
t.Errorf("\ngot value=%q\nwant %q", validated.Body.Result, original.Value)
|
|
|
|
}
|
2020-08-11 15:34:27 +00:00
|
|
|
}
|
|
|
|
|
2020-10-02 16:18:33 +00:00
|
|
|
// TestStackTraceRequest executes to a breakpoint and tests different
|
2020-07-01 18:01:17 +00:00
|
|
|
// 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
|
2020-12-28 17:14:15 +00:00
|
|
|
runDebugSessionWithBPs(t, client, "launch",
|
2020-07-08 17:20:05 +00:00
|
|
|
// Launch
|
|
|
|
func() {
|
|
|
|
client.LaunchRequest("exec", fixture.Path, !stopOnEntry)
|
|
|
|
},
|
|
|
|
// Set breakpoints
|
|
|
|
fixture.Source, []int{8, 18},
|
2020-08-11 15:34:27 +00:00
|
|
|
[]onBreakpoint{{
|
2020-07-08 17:20:05 +00:00
|
|
|
// Stop at line 8
|
2020-08-11 15:34:27 +00:00
|
|
|
execute: func() {
|
2020-11-30 17:43:09 +00:00
|
|
|
// 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 := -1
|
|
|
|
frameID := func(frameIndex int) int {
|
|
|
|
reqIndex++
|
|
|
|
return startHandle + NumFrames*reqIndex + frameIndex
|
|
|
|
}
|
2020-07-08 17:20:05 +00:00
|
|
|
|
2020-11-30 17:43:09 +00:00
|
|
|
tests := map[string]struct {
|
|
|
|
startFrame int
|
|
|
|
levels int
|
|
|
|
wantStartName string
|
|
|
|
wantStartLine int
|
|
|
|
wantStartFrame int
|
|
|
|
wantFramesReturned int
|
|
|
|
wantFramesAvailable int
|
|
|
|
}{
|
|
|
|
"all frame levels from 0 to NumFrames": {0, NumFrames, "main.Increment", 8, 0, NumFrames, NumFrames},
|
|
|
|
"subset of frames from 1 to -1": {1, NumFrames - 1, "main.Increment", 11, 1, NumFrames - 1, NumFrames},
|
|
|
|
"load stack in pages: first half": {0, NumFrames / 2, "main.Increment", 8, 0, NumFrames / 2, NumFrames},
|
|
|
|
"load stack in pages: second half": {NumFrames / 2, NumFrames, "main.main", 17, NumFrames / 2, NumFrames / 2, NumFrames},
|
|
|
|
"zero levels means all levels": {0, 0, "main.Increment", 8, 0, NumFrames, NumFrames},
|
|
|
|
"zero levels means all remaining levels": {NumFrames / 2, 0, "main.main", 17, NumFrames / 2, NumFrames / 2, NumFrames},
|
|
|
|
"negative levels treated as 0 (all)": {0, -10, "main.Increment", 8, 0, NumFrames, NumFrames},
|
|
|
|
"OOB levels is capped at available len": {0, NumFrames + 1, "main.Increment", 8, 0, NumFrames, NumFrames},
|
|
|
|
"OOB levels is capped at available len 1": {1, NumFrames + 1, "main.Increment", 11, 1, NumFrames - 1, NumFrames},
|
|
|
|
"negative startFrame treated as 0": {-10, 0, "main.Increment", 8, 0, NumFrames, NumFrames},
|
|
|
|
"OOB startFrame returns empty trace": {NumFrames, 0, "main.Increment", -1, -1, 0, NumFrames},
|
|
|
|
}
|
|
|
|
for name, tc := range tests {
|
|
|
|
client.StackTraceRequest(1, tc.startFrame, tc.levels)
|
|
|
|
stResp = client.ExpectStackTraceResponse(t)
|
|
|
|
expectStackFramesNamed(name, t, stResp,
|
|
|
|
tc.wantStartName, tc.wantStartLine, frameID(tc.wantStartFrame), tc.wantFramesReturned, tc.wantFramesAvailable)
|
|
|
|
}
|
2020-07-08 17:20:05 +00:00
|
|
|
},
|
2020-08-11 15:34:27 +00:00
|
|
|
disconnect: false,
|
|
|
|
}, {
|
2020-07-08 17:20:05 +00:00
|
|
|
// Stop at line 18
|
2020-08-11 15:34:27 +00:00
|
|
|
execute: func() {
|
2020-07-08 17:20:05 +00:00
|
|
|
// Frame ids get reset at each breakpoint.
|
|
|
|
client.StackTraceRequest(1, 0, 0)
|
|
|
|
stResp = client.ExpectStackTraceResponse(t)
|
2020-11-30 17:43:09 +00:00
|
|
|
expectStackFrames(t, stResp, "main.main", 18, startHandle, 3, 3)
|
2020-08-11 15:34:27 +00:00
|
|
|
},
|
|
|
|
disconnect: false,
|
|
|
|
}})
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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) {
|
2020-12-28 17:14:15 +00:00
|
|
|
runDebugSessionWithBPs(t, client, "launch",
|
2020-08-11 15:34:27 +00:00
|
|
|
// Launch
|
|
|
|
func() {
|
2020-09-15 20:14:55 +00:00
|
|
|
client.LaunchRequestWithArgs(map[string]interface{}{
|
|
|
|
"mode": "exec", "program": fixture.Path, "showGlobalVariables": true,
|
|
|
|
})
|
2020-08-11 15:34:27 +00:00
|
|
|
},
|
|
|
|
// Breakpoints are set within the program
|
|
|
|
fixture.Source, []int{},
|
|
|
|
[]onBreakpoint{{
|
2020-11-12 23:24:31 +00:00
|
|
|
// Stop at first breakpoint
|
2020-08-11 15:34:27 +00:00
|
|
|
execute: func() {
|
|
|
|
client.StackTraceRequest(1, 0, 20)
|
|
|
|
stack := client.ExpectStackTraceResponse(t)
|
2020-08-17 07:07:53 +00:00
|
|
|
|
2021-03-08 17:41:47 +00:00
|
|
|
startLineno := 66
|
2020-08-17 07:07:53 +00:00
|
|
|
if runtime.GOOS == "windows" && goversion.VersionAfterOrEqual(runtime.Version(), 1, 15) {
|
|
|
|
// Go1.15 on windows inserts a NOP after the call to
|
2020-10-30 12:53:54 +00:00
|
|
|
// runtime.Breakpoint and marks it same line as the
|
|
|
|
// runtime.Breakpoint call, making this flaky, so skip the line check.
|
|
|
|
startLineno = -1
|
2020-08-17 07:07:53 +00:00
|
|
|
}
|
|
|
|
|
2020-11-30 17:43:09 +00:00
|
|
|
expectStackFrames(t, stack, "main.foobar", startLineno, 1000, 4, 4)
|
2020-08-11 15:34:27 +00:00
|
|
|
|
|
|
|
client.ScopesRequest(1000)
|
|
|
|
scopes := client.ExpectScopesResponse(t)
|
|
|
|
expectScope(t, scopes, 0, "Arguments", 1000)
|
|
|
|
expectScope(t, scopes, 1, "Locals", 1001)
|
2020-09-15 20:14:55 +00:00
|
|
|
expectScope(t, scopes, 2, "Globals (package main)", 1002)
|
2020-08-11 15:34:27 +00:00
|
|
|
|
|
|
|
// Arguments
|
|
|
|
|
|
|
|
client.VariablesRequest(1000)
|
|
|
|
args := client.ExpectVariablesResponse(t)
|
|
|
|
expectChildren(t, args, "Arguments", 2)
|
2021-05-10 18:34:42 +00:00
|
|
|
expectVarExact(t, args, 0, "baz", "baz", `"bazburzum"`, "string", noChildren)
|
|
|
|
ref := expectVarExact(t, args, 1, "bar", "bar", `main.FooBar {Baz: 10, Bur: "lorem"}`, "main.FooBar", hasChildren)
|
2020-08-11 15:34:27 +00:00
|
|
|
if ref > 0 {
|
|
|
|
client.VariablesRequest(ref)
|
|
|
|
bar := client.ExpectVariablesResponse(t)
|
|
|
|
expectChildren(t, bar, "bar", 2)
|
2021-05-10 18:34:42 +00:00
|
|
|
expectVarExact(t, bar, 0, "Baz", "bar.Baz", "10", "int", noChildren)
|
|
|
|
expectVarExact(t, bar, 1, "Bur", "bar.Bur", `"lorem"`, "string", noChildren)
|
2021-01-14 18:53:12 +00:00
|
|
|
validateEvaluateName(t, client, bar, 0)
|
|
|
|
validateEvaluateName(t, client, bar, 1)
|
2020-08-11 15:34:27 +00:00
|
|
|
}
|
|
|
|
|
2020-09-15 20:14:55 +00:00
|
|
|
// Globals
|
|
|
|
|
|
|
|
client.VariablesRequest(1002)
|
|
|
|
globals := client.ExpectVariablesResponse(t)
|
2021-05-10 18:34:42 +00:00
|
|
|
expectVarExact(t, globals, 0, "p1", "main.p1", "10", "int", noChildren)
|
2020-09-15 20:14:55 +00:00
|
|
|
|
2020-08-11 15:34:27 +00:00
|
|
|
// Locals
|
|
|
|
|
|
|
|
client.VariablesRequest(1001)
|
|
|
|
locals := client.ExpectVariablesResponse(t)
|
2021-03-08 17:41:47 +00:00
|
|
|
expectChildren(t, locals, "Locals", 31)
|
2020-08-11 15:34:27 +00:00
|
|
|
|
|
|
|
// reflect.Kind == Bool
|
2021-05-10 18:34:42 +00:00
|
|
|
expectVarExact(t, locals, -1, "b1", "b1", "true", "bool", noChildren)
|
|
|
|
expectVarExact(t, locals, -1, "b2", "b2", "false", "bool", noChildren)
|
2020-08-11 15:34:27 +00:00
|
|
|
// reflect.Kind == Int
|
2021-05-10 18:34:42 +00:00
|
|
|
expectVarExact(t, locals, -1, "a2", "a2", "6", "int", noChildren)
|
|
|
|
expectVarExact(t, locals, -1, "neg", "neg", "-1", "int", noChildren)
|
2020-08-11 15:34:27 +00:00
|
|
|
// reflect.Kind == Int8
|
2021-05-10 18:34:42 +00:00
|
|
|
expectVarExact(t, locals, -1, "i8", "i8", "1", "int8", noChildren)
|
2020-08-11 15:34:27 +00:00
|
|
|
// reflect.Kind == Int16 - see testvariables2
|
|
|
|
// reflect.Kind == Int32 - see testvariables2
|
|
|
|
// reflect.Kind == Int64 - see testvariables2
|
|
|
|
// reflect.Kind == Uint
|
|
|
|
// reflect.Kind == Uint8
|
2021-05-10 18:34:42 +00:00
|
|
|
expectVarExact(t, locals, -1, "u8", "u8", "255", "uint8", noChildren)
|
2020-08-11 15:34:27 +00:00
|
|
|
// reflect.Kind == Uint16
|
2021-05-10 18:34:42 +00:00
|
|
|
expectVarExact(t, locals, -1, "u16", "u16", "65535", "uint16", noChildren)
|
2020-08-11 15:34:27 +00:00
|
|
|
// reflect.Kind == Uint32
|
2021-05-10 18:34:42 +00:00
|
|
|
expectVarExact(t, locals, -1, "u32", "u32", "4294967295", "uint32", noChildren)
|
2020-08-11 15:34:27 +00:00
|
|
|
// reflect.Kind == Uint64
|
2021-05-10 18:34:42 +00:00
|
|
|
expectVarExact(t, locals, -1, "u64", "u64", "18446744073709551615", "uint64", noChildren)
|
2020-08-11 15:34:27 +00:00
|
|
|
// reflect.Kind == Uintptr
|
2021-05-10 18:34:42 +00:00
|
|
|
expectVarExact(t, locals, -1, "up", "up", "5", "uintptr", noChildren)
|
2020-08-11 15:34:27 +00:00
|
|
|
// reflect.Kind == Float32
|
2021-05-10 18:34:42 +00:00
|
|
|
expectVarExact(t, locals, -1, "f32", "f32", "1.2", "float32", noChildren)
|
2020-08-11 15:34:27 +00:00
|
|
|
// reflect.Kind == Float64
|
2021-05-10 18:34:42 +00:00
|
|
|
expectVarExact(t, locals, -1, "a3", "a3", "7.23", "float64", noChildren)
|
2020-08-11 15:34:27 +00:00
|
|
|
// reflect.Kind == Complex64
|
2021-05-10 18:34:42 +00:00
|
|
|
ref = expectVarExact(t, locals, -1, "c64", "c64", "(1 + 2i)", "complex64", hasChildren)
|
2020-08-11 15:34:27 +00:00
|
|
|
if ref > 0 {
|
|
|
|
client.VariablesRequest(ref)
|
|
|
|
c64 := client.ExpectVariablesResponse(t)
|
|
|
|
expectChildren(t, c64, "c64", 2)
|
2021-05-10 18:34:42 +00:00
|
|
|
expectVarExact(t, c64, 0, "real", "", "1", "float32", noChildren)
|
|
|
|
expectVarExact(t, c64, 1, "imaginary", "", "2", "float32", noChildren)
|
2020-08-11 15:34:27 +00:00
|
|
|
}
|
|
|
|
// reflect.Kind == Complex128
|
2021-05-10 18:34:42 +00:00
|
|
|
ref = expectVarExact(t, locals, -1, "c128", "c128", "(2 + 3i)", "complex128", hasChildren)
|
2020-08-11 15:34:27 +00:00
|
|
|
if ref > 0 {
|
|
|
|
client.VariablesRequest(ref)
|
|
|
|
c128 := client.ExpectVariablesResponse(t)
|
|
|
|
expectChildren(t, c128, "c128", 2)
|
2021-05-10 18:34:42 +00:00
|
|
|
expectVarExact(t, c128, 0, "real", "", "2", "float64", noChildren)
|
|
|
|
expectVarExact(t, c128, 1, "imaginary", "", "3", "float64", noChildren)
|
2020-08-11 15:34:27 +00:00
|
|
|
}
|
|
|
|
// reflect.Kind == Array
|
2021-05-10 18:34:42 +00:00
|
|
|
ref = expectVarExact(t, locals, -1, "a4", "a4", "[2]int [1,2]", "[2]int", hasChildren)
|
2020-08-11 15:34:27 +00:00
|
|
|
if ref > 0 {
|
|
|
|
client.VariablesRequest(ref)
|
|
|
|
a4 := client.ExpectVariablesResponse(t)
|
|
|
|
expectChildren(t, a4, "a4", 2)
|
2021-05-10 18:34:42 +00:00
|
|
|
expectVarExact(t, a4, 0, "[0]", "a4[0]", "1", "int", noChildren)
|
|
|
|
expectVarExact(t, a4, 1, "[1]", "a4[1]", "2", "int", noChildren)
|
2020-08-11 15:34:27 +00:00
|
|
|
}
|
2021-05-10 18:34:42 +00:00
|
|
|
ref = expectVarExact(t, locals, -1, "a11", "a11", `[3]main.FooBar [{Baz: 1, Bur: "a"},{Baz: 2, Bur: "b"},{Baz: 3, Bur: "c"}]`, "[3]main.FooBar", hasChildren)
|
2020-08-11 15:34:27 +00:00
|
|
|
if ref > 0 {
|
|
|
|
client.VariablesRequest(ref)
|
|
|
|
a11 := client.ExpectVariablesResponse(t)
|
|
|
|
expectChildren(t, a11, "a11", 3)
|
2021-05-10 18:34:42 +00:00
|
|
|
expectVarExact(t, a11, 0, "[0]", "a11[0]", `main.FooBar {Baz: 1, Bur: "a"}`, "main.FooBar", hasChildren)
|
|
|
|
ref = expectVarExact(t, a11, 1, "[1]", "a11[1]", `main.FooBar {Baz: 2, Bur: "b"}`, "main.FooBar", hasChildren)
|
2020-08-11 15:34:27 +00:00
|
|
|
if ref > 0 {
|
|
|
|
client.VariablesRequest(ref)
|
|
|
|
a11_1 := client.ExpectVariablesResponse(t)
|
|
|
|
expectChildren(t, a11_1, "a11[1]", 2)
|
2021-05-10 18:34:42 +00:00
|
|
|
expectVarExact(t, a11_1, 0, "Baz", "a11[1].Baz", "2", "int", noChildren)
|
|
|
|
expectVarExact(t, a11_1, 1, "Bur", "a11[1].Bur", `"b"`, "string", noChildren)
|
2021-01-14 18:53:12 +00:00
|
|
|
validateEvaluateName(t, client, a11_1, 0)
|
|
|
|
validateEvaluateName(t, client, a11_1, 1)
|
2020-08-11 15:34:27 +00:00
|
|
|
}
|
2021-05-10 18:34:42 +00:00
|
|
|
expectVarExact(t, a11, 2, "[2]", "a11[2]", `main.FooBar {Baz: 3, Bur: "c"}`, "main.FooBar", hasChildren)
|
2020-08-11 15:34:27 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// reflect.Kind == Chan - see testvariables2
|
|
|
|
// reflect.Kind == Func - see testvariables2
|
|
|
|
// reflect.Kind == Interface - see testvariables2
|
|
|
|
// reflect.Kind == Map - see testvariables2
|
|
|
|
// reflect.Kind == Ptr
|
2021-05-10 18:34:42 +00:00
|
|
|
ref = expectVarExact(t, locals, -1, "a7", "a7", `*main.FooBar {Baz: 5, Bur: "strum"}`, "*main.FooBar", hasChildren)
|
2020-08-11 15:34:27 +00:00
|
|
|
if ref > 0 {
|
|
|
|
client.VariablesRequest(ref)
|
|
|
|
a7 := client.ExpectVariablesResponse(t)
|
|
|
|
expectChildren(t, a7, "a7", 1)
|
2021-05-10 18:34:42 +00:00
|
|
|
ref = expectVarExact(t, a7, 0, "", "(*a7)", `main.FooBar {Baz: 5, Bur: "strum"}`, "main.FooBar", hasChildren)
|
2020-08-11 15:34:27 +00:00
|
|
|
if ref > 0 {
|
|
|
|
client.VariablesRequest(ref)
|
|
|
|
a7val := client.ExpectVariablesResponse(t)
|
|
|
|
expectChildren(t, a7val, "*a7", 2)
|
2021-05-10 18:34:42 +00:00
|
|
|
expectVarExact(t, a7val, 0, "Baz", "(*a7).Baz", "5", "int", noChildren)
|
|
|
|
expectVarExact(t, a7val, 1, "Bur", "(*a7).Bur", `"strum"`, "string", noChildren)
|
2021-01-14 18:53:12 +00:00
|
|
|
validateEvaluateName(t, client, a7val, 0)
|
|
|
|
validateEvaluateName(t, client, a7val, 1)
|
2020-08-11 15:34:27 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
// TODO(polina): how to test for "nil" (without type) and "void"?
|
2021-05-10 18:34:42 +00:00
|
|
|
expectVarExact(t, locals, -1, "a9", "a9", "*main.FooBar nil", "*main.FooBar", noChildren)
|
2020-08-11 15:34:27 +00:00
|
|
|
// reflect.Kind == Slice
|
2021-05-10 18:34:42 +00:00
|
|
|
ref = expectVarExact(t, locals, -1, "a5", "a5", "[]int len: 5, cap: 5, [1,2,3,4,5]", "[]int", hasChildren)
|
2020-08-11 15:34:27 +00:00
|
|
|
if ref > 0 {
|
|
|
|
client.VariablesRequest(ref)
|
|
|
|
a5 := client.ExpectVariablesResponse(t)
|
|
|
|
expectChildren(t, a5, "a5", 5)
|
2021-05-10 18:34:42 +00:00
|
|
|
expectVarExact(t, a5, 0, "[0]", "a5[0]", "1", "int", noChildren)
|
|
|
|
expectVarExact(t, a5, 4, "[4]", "a5[4]", "5", "int", noChildren)
|
2021-01-14 18:53:12 +00:00
|
|
|
validateEvaluateName(t, client, a5, 0)
|
|
|
|
validateEvaluateName(t, client, a5, 1)
|
2020-08-11 15:34:27 +00:00
|
|
|
}
|
2021-05-10 18:34:42 +00:00
|
|
|
ref = expectVarExact(t, locals, -1, "a12", "a12", `[]main.FooBar len: 2, cap: 2, [{Baz: 4, Bur: "d"},{Baz: 5, Bur: "e"}]`, "[]main.FooBar", hasChildren)
|
2020-08-11 15:34:27 +00:00
|
|
|
if ref > 0 {
|
|
|
|
client.VariablesRequest(ref)
|
|
|
|
a12 := client.ExpectVariablesResponse(t)
|
|
|
|
expectChildren(t, a12, "a12", 2)
|
2021-05-10 18:34:42 +00:00
|
|
|
expectVarExact(t, a12, 0, "[0]", "a12[0]", `main.FooBar {Baz: 4, Bur: "d"}`, "main.FooBar", hasChildren)
|
|
|
|
ref = expectVarExact(t, a12, 1, "[1]", "a12[1]", `main.FooBar {Baz: 5, Bur: "e"}`, "main.FooBar", hasChildren)
|
2020-08-11 15:34:27 +00:00
|
|
|
if ref > 0 {
|
|
|
|
client.VariablesRequest(ref)
|
|
|
|
a12_1 := client.ExpectVariablesResponse(t)
|
|
|
|
expectChildren(t, a12_1, "a12[1]", 2)
|
2021-05-10 18:34:42 +00:00
|
|
|
expectVarExact(t, a12_1, 0, "Baz", "a12[1].Baz", "5", "int", noChildren)
|
|
|
|
expectVarExact(t, a12_1, 1, "Bur", "a12[1].Bur", `"e"`, "string", noChildren)
|
2021-01-14 18:53:12 +00:00
|
|
|
validateEvaluateName(t, client, a12_1, 0)
|
|
|
|
validateEvaluateName(t, client, a12_1, 1)
|
2020-08-11 15:34:27 +00:00
|
|
|
}
|
|
|
|
}
|
2021-05-10 18:34:42 +00:00
|
|
|
ref = expectVarExact(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)
|
2020-08-11 15:34:27 +00:00
|
|
|
if ref > 0 {
|
|
|
|
client.VariablesRequest(ref)
|
|
|
|
a13 := client.ExpectVariablesResponse(t)
|
|
|
|
expectChildren(t, a13, "a13", 3)
|
2021-05-10 18:34:42 +00:00
|
|
|
expectVarExact(t, a13, 0, "[0]", "a13[0]", `*main.FooBar {Baz: 6, Bur: "f"}`, "*main.FooBar", hasChildren)
|
|
|
|
expectVarExact(t, a13, 1, "[1]", "a13[1]", `*main.FooBar {Baz: 7, Bur: "g"}`, "*main.FooBar", hasChildren)
|
|
|
|
ref = expectVarExact(t, a13, 2, "[2]", "a13[2]", `*main.FooBar {Baz: 8, Bur: "h"}`, "*main.FooBar", hasChildren)
|
2020-08-11 15:34:27 +00:00
|
|
|
if ref > 0 {
|
|
|
|
client.VariablesRequest(ref)
|
|
|
|
a13_2 := client.ExpectVariablesResponse(t)
|
|
|
|
expectChildren(t, a13_2, "a13[2]", 1)
|
2021-05-10 18:34:42 +00:00
|
|
|
ref = expectVarExact(t, a13_2, 0, "", "(*a13[2])", `main.FooBar {Baz: 8, Bur: "h"}`, "main.FooBar", hasChildren)
|
2021-01-14 18:53:12 +00:00
|
|
|
validateEvaluateName(t, client, a13_2, 0)
|
2020-08-11 15:34:27 +00:00
|
|
|
if ref > 0 {
|
|
|
|
client.VariablesRequest(ref)
|
|
|
|
val := client.ExpectVariablesResponse(t)
|
|
|
|
expectChildren(t, val, "*a13[2]", 2)
|
2021-05-10 18:34:42 +00:00
|
|
|
expectVarExact(t, val, 0, "Baz", "(*a13[2]).Baz", "8", "int", noChildren)
|
|
|
|
expectVarExact(t, val, 1, "Bur", "(*a13[2]).Bur", `"h"`, "string", noChildren)
|
2021-01-14 18:53:12 +00:00
|
|
|
validateEvaluateName(t, client, val, 0)
|
|
|
|
validateEvaluateName(t, client, val, 1)
|
2020-08-11 15:34:27 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// reflect.Kind == String
|
2021-05-10 18:34:42 +00:00
|
|
|
expectVarExact(t, locals, -1, "a1", "a1", `"foofoofoofoofoofoo"`, "string", noChildren)
|
|
|
|
expectVarExact(t, locals, -1, "a10", "a10", `"ofo"`, "string", noChildren)
|
2020-08-11 15:34:27 +00:00
|
|
|
// reflect.Kind == Struct
|
2021-05-10 18:34:42 +00:00
|
|
|
ref = expectVarExact(t, locals, -1, "a6", "a6", `main.FooBar {Baz: 8, Bur: "word"}`, "main.FooBar", hasChildren)
|
2020-08-11 15:34:27 +00:00
|
|
|
if ref > 0 {
|
|
|
|
client.VariablesRequest(ref)
|
|
|
|
a6 := client.ExpectVariablesResponse(t)
|
|
|
|
expectChildren(t, a6, "a6", 2)
|
2021-05-10 18:34:42 +00:00
|
|
|
expectVarExact(t, a6, 0, "Baz", "a6.Baz", "8", "int", noChildren)
|
|
|
|
expectVarExact(t, a6, 1, "Bur", "a6.Bur", `"word"`, "string", noChildren)
|
2020-08-11 15:34:27 +00:00
|
|
|
}
|
2021-05-10 18:34:42 +00:00
|
|
|
ref = expectVarExact(t, locals, -1, "a8", "a8", `main.FooBar2 {Bur: 10, Baz: "feh"}`, "main.FooBar2", hasChildren)
|
2020-08-11 15:34:27 +00:00
|
|
|
if ref > 0 {
|
|
|
|
client.VariablesRequest(ref)
|
|
|
|
a8 := client.ExpectVariablesResponse(t)
|
|
|
|
expectChildren(t, a8, "a8", 2)
|
2021-05-10 18:34:42 +00:00
|
|
|
expectVarExact(t, a8, 0, "Bur", "a8.Bur", "10", "int", noChildren)
|
|
|
|
expectVarExact(t, a8, 1, "Baz", "a8.Baz", `"feh"`, "string", noChildren)
|
2020-08-11 15:34:27 +00:00
|
|
|
}
|
|
|
|
// reflect.Kind == UnsafePointer - see testvariables2
|
|
|
|
},
|
|
|
|
disconnect: false,
|
|
|
|
}, {
|
2020-11-12 23:24:31 +00:00
|
|
|
// Stop at second breakpoint
|
2020-08-11 15:34:27 +00:00
|
|
|
execute: func() {
|
|
|
|
// Frame ids get reset at each breakpoint.
|
|
|
|
client.StackTraceRequest(1, 0, 20)
|
|
|
|
stack := client.ExpectStackTraceResponse(t)
|
2020-11-30 17:43:09 +00:00
|
|
|
expectStackFrames(t, stack, "main.barfoo", 27, 1000, 5, 5)
|
2020-08-11 15:34:27 +00:00
|
|
|
|
|
|
|
client.ScopesRequest(1000)
|
|
|
|
scopes := client.ExpectScopesResponse(t)
|
|
|
|
expectScope(t, scopes, 0, "Arguments", 1000)
|
|
|
|
expectScope(t, scopes, 1, "Locals", 1001)
|
2020-09-15 20:14:55 +00:00
|
|
|
expectScope(t, scopes, 2, "Globals (package main)", 1002)
|
2020-08-11 15:34:27 +00:00
|
|
|
|
|
|
|
client.ScopesRequest(1111)
|
2021-05-06 09:11:45 +00:00
|
|
|
erres := client.ExpectInvisibleErrorResponse(t)
|
2020-08-11 15:34:27 +00:00
|
|
|
if erres.Body.Error.Format != "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(1000) // Arguments
|
|
|
|
args := client.ExpectVariablesResponse(t)
|
|
|
|
expectChildren(t, args, "Arguments", 0)
|
|
|
|
|
|
|
|
client.VariablesRequest(1001) // Locals
|
|
|
|
locals := client.ExpectVariablesResponse(t)
|
|
|
|
expectChildren(t, locals, "Locals", 1)
|
2021-05-10 18:34:42 +00:00
|
|
|
expectVarExact(t, locals, -1, "a1", "a1", `"bur"`, "string", noChildren)
|
2020-08-11 15:34:27 +00:00
|
|
|
|
2020-09-15 20:14:55 +00:00
|
|
|
client.VariablesRequest(1002) // Globals
|
|
|
|
globals := client.ExpectVariablesResponse(t)
|
2021-05-10 18:34:42 +00:00
|
|
|
expectVarExact(t, globals, 0, "p1", "main.p1", "10", "int", noChildren)
|
2020-09-15 20:14:55 +00:00
|
|
|
|
2020-08-11 15:34:27 +00:00
|
|
|
client.VariablesRequest(7777)
|
2021-05-06 09:11:45 +00:00
|
|
|
erres = client.ExpectInvisibleErrorResponse(t)
|
2020-08-11 15:34:27 +00:00
|
|
|
if erres.Body.Error.Format != "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) {
|
2020-12-28 17:14:15 +00:00
|
|
|
runDebugSessionWithBPs(t, client, "launch",
|
2020-08-11 15:34:27 +00:00
|
|
|
// 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)
|
2021-01-29 17:25:31 +00:00
|
|
|
expectStackFrames(t, stack, "main.main", -1, 1000, 3, 3)
|
2020-08-11 15:34:27 +00:00
|
|
|
|
|
|
|
client.ScopesRequest(1000)
|
|
|
|
scopes := client.ExpectScopesResponse(t)
|
|
|
|
expectScope(t, scopes, 0, "Arguments", 1000)
|
|
|
|
expectScope(t, scopes, 1, "Locals", 1001)
|
|
|
|
},
|
|
|
|
disconnect: false,
|
|
|
|
}, {
|
|
|
|
execute: func() {
|
|
|
|
client.StackTraceRequest(1, 0, 20)
|
|
|
|
stack := client.ExpectStackTraceResponse(t)
|
2021-01-29 17:25:31 +00:00
|
|
|
expectStackFrames(t, stack, "main.main", -1, 1000, 3, 3)
|
2020-08-11 15:34:27 +00:00
|
|
|
|
|
|
|
client.ScopesRequest(1000)
|
|
|
|
scopes := client.ExpectScopesResponse(t)
|
2020-09-15 20:14:55 +00:00
|
|
|
if len(scopes.Body.Scopes) > 2 {
|
|
|
|
t.Errorf("\ngot %#v\nwant len(scopes)=2 (Argumes & Locals)", scopes)
|
|
|
|
}
|
2020-08-11 15:34:27 +00:00
|
|
|
expectScope(t, scopes, 0, "Arguments", 1000)
|
|
|
|
expectScope(t, scopes, 1, "Locals", 1001)
|
|
|
|
|
|
|
|
// Arguments
|
|
|
|
|
|
|
|
client.VariablesRequest(1000)
|
|
|
|
args := client.ExpectVariablesResponse(t)
|
|
|
|
expectChildren(t, args, "Arguments", 0)
|
|
|
|
|
|
|
|
// Locals
|
|
|
|
|
|
|
|
client.VariablesRequest(1001)
|
|
|
|
locals := client.ExpectVariablesResponse(t)
|
|
|
|
|
|
|
|
// reflect.Kind == Bool - see testvariables
|
|
|
|
// reflect.Kind == Int - see testvariables
|
|
|
|
// reflect.Kind == Int8
|
2021-05-10 18:34:42 +00:00
|
|
|
expectVarExact(t, locals, -1, "ni8", "ni8", "-5", "int8", noChildren)
|
2020-08-11 15:34:27 +00:00
|
|
|
// reflect.Kind == Int16
|
2021-05-10 18:34:42 +00:00
|
|
|
expectVarExact(t, locals, -1, "ni16", "ni16", "-5", "int16", noChildren)
|
2020-08-11 15:34:27 +00:00
|
|
|
// reflect.Kind == Int32
|
2021-05-10 18:34:42 +00:00
|
|
|
expectVarExact(t, locals, -1, "ni32", "ni32", "-5", "int32", noChildren)
|
2020-08-11 15:34:27 +00:00
|
|
|
// reflect.Kind == Int64
|
2021-05-10 18:34:42 +00:00
|
|
|
expectVarExact(t, locals, -1, "ni64", "ni64", "-5", "int64", noChildren)
|
2020-08-11 15:34:27 +00:00
|
|
|
// 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
|
2021-05-10 18:34:42 +00:00
|
|
|
expectVarExact(t, locals, -1, "pinf", "pinf", "+Inf", "float64", noChildren)
|
|
|
|
expectVarExact(t, locals, -1, "ninf", "ninf", "-Inf", "float64", noChildren)
|
|
|
|
expectVarExact(t, locals, -1, "nan", "nan", "NaN", "float64", noChildren)
|
2020-08-11 15:34:27 +00:00
|
|
|
// reflect.Kind == Complex64 - see testvariables
|
|
|
|
// reflect.Kind == Complex128 - see testvariables
|
|
|
|
// reflect.Kind == Array
|
2021-05-10 18:34:42 +00:00
|
|
|
expectVarExact(t, locals, -1, "a0", "a0", "[0]int []", "[0]int", noChildren)
|
2020-08-11 15:34:27 +00:00
|
|
|
// reflect.Kind == Chan
|
2021-05-10 18:34:42 +00:00
|
|
|
ref := expectVarExact(t, locals, -1, "ch1", "ch1", "chan int 4/11", "chan int", hasChildren)
|
2020-08-11 15:34:27 +00:00
|
|
|
if ref > 0 {
|
|
|
|
client.VariablesRequest(ref)
|
|
|
|
ch1 := client.ExpectVariablesResponse(t)
|
|
|
|
expectChildren(t, ch1, "ch1", 11)
|
2021-05-10 18:34:42 +00:00
|
|
|
expectVarExact(t, ch1, 0, "qcount", "ch1.qcount", "4", "uint", noChildren)
|
|
|
|
expectVarRegex(t, ch1, 10, "lock", "ch1.lock", `runtime\.mutex {.*key: 0.*}`, `runtime\.mutex`, hasChildren)
|
2021-01-14 18:53:12 +00:00
|
|
|
validateEvaluateName(t, client, ch1, 0)
|
|
|
|
validateEvaluateName(t, client, ch1, 10)
|
2020-08-11 15:34:27 +00:00
|
|
|
}
|
2021-05-10 18:34:42 +00:00
|
|
|
expectVarExact(t, locals, -1, "chnil", "chnil", "chan int nil", "chan int", noChildren)
|
2020-08-11 15:34:27 +00:00
|
|
|
// reflect.Kind == Func
|
2021-05-10 18:34:42 +00:00
|
|
|
expectVarExact(t, locals, -1, "fn1", "fn1", "main.afunc", "main.functype", noChildren)
|
|
|
|
expectVarExact(t, locals, -1, "fn2", "fn2", "nil", "main.functype", noChildren)
|
2020-08-11 15:34:27 +00:00
|
|
|
// reflect.Kind == Interface
|
2021-05-10 18:34:42 +00:00
|
|
|
expectVarExact(t, locals, -1, "ifacenil", "ifacenil", "interface {} nil", "interface {}", noChildren)
|
|
|
|
ref = expectVarExact(t, locals, -1, "iface2", "iface2", "interface {}(string) \"test\"", "interface {}", hasChildren)
|
2020-08-11 15:34:27 +00:00
|
|
|
if ref > 0 {
|
|
|
|
client.VariablesRequest(ref)
|
|
|
|
iface2 := client.ExpectVariablesResponse(t)
|
|
|
|
expectChildren(t, iface2, "iface2", 1)
|
2021-05-10 18:34:42 +00:00
|
|
|
expectVarExact(t, iface2, 0, "data", "iface2.(data)", `"test"`, "string", noChildren)
|
2021-01-14 18:53:12 +00:00
|
|
|
validateEvaluateName(t, client, iface2, 0)
|
2020-08-11 15:34:27 +00:00
|
|
|
}
|
2021-05-10 18:34:42 +00:00
|
|
|
ref = expectVarExact(t, locals, -1, "iface4", "iface4", "interface {}([]go/constant.Value) [4]", "interface {}", hasChildren)
|
2020-08-11 15:34:27 +00:00
|
|
|
if ref > 0 {
|
|
|
|
client.VariablesRequest(ref)
|
|
|
|
iface4 := client.ExpectVariablesResponse(t)
|
|
|
|
expectChildren(t, iface4, "iface4", 1)
|
2021-05-10 18:34:42 +00:00
|
|
|
ref = expectVarExact(t, iface4, 0, "data", "iface4.(data)", "[]go/constant.Value len: 1, cap: 1, [4]", "[]go/constant.Value", hasChildren)
|
2020-08-11 15:34:27 +00:00
|
|
|
if ref > 0 {
|
|
|
|
client.VariablesRequest(ref)
|
|
|
|
iface4data := client.ExpectVariablesResponse(t)
|
|
|
|
expectChildren(t, iface4data, "iface4.data", 1)
|
2021-05-10 18:34:42 +00:00
|
|
|
ref = expectVarExact(t, iface4data, 0, "[0]", "iface4.(data)[0]", "go/constant.Value(go/constant.int64Val) 4", "go/constant.Value", hasChildren)
|
2021-01-14 18:53:12 +00:00
|
|
|
if ref > 0 {
|
|
|
|
client.VariablesRequest(ref)
|
|
|
|
iface4data0 := client.ExpectVariablesResponse(t)
|
|
|
|
expectChildren(t, iface4data0, "iface4.data[0]", 1)
|
2021-05-10 18:34:42 +00:00
|
|
|
expectVarExact(t, iface4data0, 0, "data", "iface4.(data)[0].(data)", "4", "go/constant.int64Val", noChildren)
|
2021-01-14 18:53:12 +00:00
|
|
|
validateEvaluateName(t, client, iface4data0, 0)
|
|
|
|
}
|
2020-08-11 15:34:27 +00:00
|
|
|
}
|
|
|
|
}
|
2021-05-10 18:34:42 +00:00
|
|
|
expectVarExact(t, locals, -1, "errnil", "errnil", "error nil", "error", noChildren)
|
|
|
|
ref = expectVarExact(t, locals, -1, "err1", "err1", "error(*main.astruct) *{A: 1, B: 2}", "error", hasChildren)
|
2021-01-14 18:53:12 +00:00
|
|
|
if ref > 0 {
|
|
|
|
client.VariablesRequest(ref)
|
|
|
|
err1 := client.ExpectVariablesResponse(t)
|
|
|
|
expectChildren(t, err1, "err1", 1)
|
2021-05-10 18:34:42 +00:00
|
|
|
expectVarExact(t, err1, 0, "data", "err1.(data)", "*main.astruct {A: 1, B: 2}", "*main.astruct", hasChildren)
|
2021-01-14 18:53:12 +00:00
|
|
|
validateEvaluateName(t, client, err1, 0)
|
|
|
|
}
|
2021-05-10 18:34:42 +00:00
|
|
|
ref = expectVarExact(t, locals, -1, "ptrinf", "ptrinf", "*interface {}(**interface {}) **...", "*interface {}", hasChildren)
|
2021-02-21 16:00:05 +00:00
|
|
|
if ref > 0 {
|
|
|
|
client.VariablesRequest(ref)
|
|
|
|
ptrinf_val := client.ExpectVariablesResponse(t)
|
|
|
|
expectChildren(t, ptrinf_val, "*ptrinf", 1)
|
2021-05-10 18:34:42 +00:00
|
|
|
ref = expectVarExact(t, ptrinf_val, 0, "", "(*ptrinf)", "interface {}(**interface {}) **...", "interface {}", hasChildren)
|
2021-02-21 16:00:05 +00:00
|
|
|
if ref > 0 {
|
|
|
|
client.VariablesRequest(ref)
|
|
|
|
ptrinf_val_data := client.ExpectVariablesResponse(t)
|
|
|
|
expectChildren(t, ptrinf_val_data, "(*ptrinf).data", 1)
|
2021-05-10 18:34:42 +00:00
|
|
|
expectVarExact(t, ptrinf_val_data, 0, "data", "(*ptrinf).(data)", "**interface {}(**interface {}) ...", "**interface {}", hasChildren)
|
2021-02-21 16:00:05 +00:00
|
|
|
validateEvaluateName(t, client, ptrinf_val_data, 0)
|
|
|
|
}
|
|
|
|
}
|
2020-08-11 15:34:27 +00:00
|
|
|
// reflect.Kind == Map
|
2021-05-10 18:34:42 +00:00
|
|
|
expectVarExact(t, locals, -1, "mnil", "mnil", "map[string]main.astruct nil", "map[string]main.astruct", noChildren)
|
2021-01-14 18:53:12 +00:00
|
|
|
// key - scalar, value - compound
|
2021-05-10 18:34:42 +00:00
|
|
|
ref = expectVarExact(t, locals, -1, "m2", "m2", "map[int]*main.astruct [1: *{A: 10, B: 11}, ]", "map[int]*main.astruct", hasChildren)
|
2020-08-11 15:34:27 +00:00
|
|
|
if ref > 0 {
|
|
|
|
client.VariablesRequest(ref)
|
|
|
|
m2 := client.ExpectVariablesResponse(t)
|
2021-01-14 18:53:12 +00:00
|
|
|
expectChildren(t, m2, "m2", 1) // each key-value represented by a single child
|
2021-05-10 18:34:42 +00:00
|
|
|
ref = expectVarExact(t, m2, 0, "1", "m2[1]", "*main.astruct {A: 10, B: 11}", "int: *main.astruct", hasChildren)
|
2020-08-11 15:34:27 +00:00
|
|
|
if ref > 0 {
|
|
|
|
client.VariablesRequest(ref)
|
2021-01-14 18:53:12 +00:00
|
|
|
m2kv1 := client.ExpectVariablesResponse(t)
|
|
|
|
expectChildren(t, m2kv1, "m2[1]", 1)
|
2021-05-10 18:34:42 +00:00
|
|
|
ref = expectVarExact(t, m2kv1, 0, "", "(*m2[1])", "main.astruct {A: 10, B: 11}", "main.astruct", hasChildren)
|
2020-08-11 15:34:27 +00:00
|
|
|
if ref > 0 {
|
|
|
|
client.VariablesRequest(ref)
|
2021-01-14 18:53:12 +00:00
|
|
|
m2kv1deref := client.ExpectVariablesResponse(t)
|
|
|
|
expectChildren(t, m2kv1deref, "*m2[1]", 2)
|
2021-05-10 18:34:42 +00:00
|
|
|
expectVarExact(t, m2kv1deref, 0, "A", "(*m2[1]).A", "10", "int", noChildren)
|
|
|
|
expectVarExact(t, m2kv1deref, 1, "B", "(*m2[1]).B", "11", "int", noChildren)
|
2021-01-14 18:53:12 +00:00
|
|
|
validateEvaluateName(t, client, m2kv1deref, 0)
|
|
|
|
validateEvaluateName(t, client, m2kv1deref, 1)
|
2020-08-11 15:34:27 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2021-01-14 18:53:12 +00:00
|
|
|
// key - compound, value - scalar
|
2021-05-10 18:34:42 +00:00
|
|
|
ref = expectVarExact(t, locals, -1, "m3", "m3", "map[main.astruct]int [{A: 1, B: 1}: 42, {A: 2, B: 2}: 43, ]", "map[main.astruct]int", hasChildren)
|
2020-08-11 15:34:27 +00:00
|
|
|
if ref > 0 {
|
|
|
|
client.VariablesRequest(ref)
|
|
|
|
m3 := client.ExpectVariablesResponse(t)
|
2021-01-14 18:53:12 +00:00
|
|
|
expectChildren(t, m3, "m3", 2) // each key-value represented by a single child
|
2021-05-10 18:34:42 +00:00
|
|
|
ref = expectVarRegex(t, m3, 0, `main\.astruct {A: 1, B: 1}`, `m3\[\(\*\(\*"main.astruct"\)\(0x[0-9a-f]+\)\)\]`, "42", "int", hasChildren)
|
2020-08-11 15:34:27 +00:00
|
|
|
if ref > 0 {
|
|
|
|
client.VariablesRequest(ref)
|
2021-01-14 18:53:12 +00:00
|
|
|
m3kv0 := client.ExpectVariablesResponse(t)
|
|
|
|
expectChildren(t, m3kv0, "m3[0]", 2)
|
2021-05-10 18:34:42 +00:00
|
|
|
expectVarRegex(t, m3kv0, 0, "A", `\(*\(*"main\.astruct"\)\(0x[0-9a-f]+\)\)\.A`, "1", "int", noChildren)
|
2021-01-14 18:53:12 +00:00
|
|
|
validateEvaluateName(t, client, m3kv0, 0)
|
2020-08-11 15:34:27 +00:00
|
|
|
}
|
2021-05-10 18:34:42 +00:00
|
|
|
ref = expectVarRegex(t, m3, 1, `main\.astruct {A: 2, B: 2}`, `m3\[\(\*\(\*"main.astruct"\)\(0x[0-9a-f]+\)\)\]`, "43", "", hasChildren)
|
2021-01-14 18:53:12 +00:00
|
|
|
if ref > 0 { // inspect another key from another key-value child
|
2020-08-11 15:34:27 +00:00
|
|
|
client.VariablesRequest(ref)
|
2021-01-14 18:53:12 +00:00
|
|
|
m3kv1 := client.ExpectVariablesResponse(t)
|
|
|
|
expectChildren(t, m3kv1, "m3[1]", 2)
|
2021-05-10 18:34:42 +00:00
|
|
|
expectVarRegex(t, m3kv1, 1, "B", `\(*\(*"main\.astruct"\)\(0x[0-9a-f]+\)\)\.B`, "2", "int", noChildren)
|
2021-01-14 18:53:12 +00:00
|
|
|
validateEvaluateName(t, client, m3kv1, 1)
|
2020-08-11 15:34:27 +00:00
|
|
|
}
|
|
|
|
}
|
2021-03-25 16:44:32 +00:00
|
|
|
// key - compound + truncated, value - scalar
|
2021-05-10 18:34:42 +00:00
|
|
|
ref = expectVarExact(t, locals, -1, "m5", "m5", `map[main.C]int [{s: "very long string 0123456789a0123456789b0123456789c0123456789d012...+73 more"}: 1, ]`, "map[main.C]int", hasChildren)
|
2021-03-25 16:44:32 +00:00
|
|
|
if ref > 0 {
|
|
|
|
client.VariablesRequest(ref)
|
|
|
|
m5 := client.ExpectVariablesResponse(t)
|
|
|
|
expectChildren(t, m5, "m5", 1)
|
2021-05-10 18:34:42 +00:00
|
|
|
expectVarRegex(t, m5, 0, `main\.C {s: "very long string 0123456789a0123456789b0123456789c01\.\.\. @ 0x[0-9a-f]+`, `m5\[\(\*\(\*"main\.C"\)\(0x[0-9a-f]+\)\)\]`, "1", `int`, hasChildren)
|
2021-03-25 16:44:32 +00:00
|
|
|
}
|
2021-01-14 18:53:12 +00:00
|
|
|
// key - compound, value - compound
|
2021-05-10 18:34:42 +00:00
|
|
|
ref = expectVarExact(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)
|
2020-08-11 15:34:27 +00:00
|
|
|
if ref > 0 {
|
|
|
|
client.VariablesRequest(ref)
|
|
|
|
m4 := client.ExpectVariablesResponse(t)
|
2021-01-14 18:53:12 +00:00
|
|
|
expectChildren(t, m4, "m4", 4) // each key and value represented by a child, so double the key-value count
|
2021-05-10 18:34:42 +00:00
|
|
|
expectVarRegex(t, m4, 0, `\[key 0\]`, `\(\*\(\*"main\.astruct"\)\(0x[0-9a-f]+\)\)`, `main\.astruct {A: 1, B: 1}`, `main\.astruct`, hasChildren)
|
|
|
|
expectVarRegex(t, m4, 1, `\[val 0\]`, `m4\[\(\*\(\*"main\.astruct"\)\(0x[0-9a-f]+\)\)\]`, `main\.astruct {A: 11, B: 11}`, `main\.astruct`, hasChildren)
|
|
|
|
ref = expectVarRegex(t, m4, 2, `\[key 1\]`, `\(\*\(\*"main\.astruct"\)\(0x[0-9a-f]+\)\)`, `main\.astruct {A: 2, B: 2}`, `main\.astruct`, hasChildren)
|
2020-08-11 15:34:27 +00:00
|
|
|
if ref > 0 {
|
|
|
|
client.VariablesRequest(ref)
|
2020-09-15 20:14:55 +00:00
|
|
|
m4Key1 := client.ExpectVariablesResponse(t)
|
|
|
|
expectChildren(t, m4Key1, "m4Key1", 2)
|
2021-05-10 18:34:42 +00:00
|
|
|
expectVarRegex(t, m4Key1, 0, "A", `\(\*\(\*"main\.astruct"\)\(0x[0-9a-f]+\)\)\.A`, "2", "int", noChildren)
|
|
|
|
expectVarRegex(t, m4Key1, 1, "B", `\(\*\(\*"main\.astruct"\)\(0x[0-9a-f]+\)\)\.B`, "2", "int", noChildren)
|
2021-01-14 18:53:12 +00:00
|
|
|
validateEvaluateName(t, client, m4Key1, 0)
|
|
|
|
validateEvaluateName(t, client, m4Key1, 1)
|
2020-08-11 15:34:27 +00:00
|
|
|
}
|
2021-05-10 18:34:42 +00:00
|
|
|
ref = expectVarRegex(t, m4, 3, `\[val 1\]`, `m4\[\(\*\(\*"main\.astruct"\)\(0x[0-9a-f]+\)\)\]`, `main\.astruct {A: 22, B: 22}`, "main.astruct", hasChildren)
|
2020-08-11 15:34:27 +00:00
|
|
|
if ref > 0 {
|
|
|
|
client.VariablesRequest(ref)
|
2020-09-15 20:14:55 +00:00
|
|
|
m4Val1 := client.ExpectVariablesResponse(t)
|
|
|
|
expectChildren(t, m4Val1, "m4Val1", 2)
|
2021-05-10 18:34:42 +00:00
|
|
|
expectVarRegex(t, m4Val1, 0, "A", `m4\[\(\*\(\*"main\.astruct"\)\(0x[0-9a-f]+\)\)\]\.A`, "22", "int", noChildren)
|
|
|
|
expectVarRegex(t, m4Val1, 1, "B", `m4\[\(\*\(\*"main\.astruct"\)\(0x[0-9a-f]+\)\)\]\.B`, "22", "int", noChildren)
|
2021-01-14 18:53:12 +00:00
|
|
|
validateEvaluateName(t, client, m4Val1, 0)
|
|
|
|
validateEvaluateName(t, client, m4Val1, 1)
|
2020-08-11 15:34:27 +00:00
|
|
|
}
|
|
|
|
}
|
2021-05-10 18:34:42 +00:00
|
|
|
expectVarExact(t, locals, -1, "emptymap", "emptymap", "map[string]string []", "map[string]string", noChildren)
|
2021-02-21 16:00:05 +00:00
|
|
|
// reflect.Kind == Ptr
|
2021-05-10 18:34:42 +00:00
|
|
|
ref = expectVarExact(t, locals, -1, "pp1", "pp1", "**1", "**int", hasChildren)
|
2021-02-21 16:00:05 +00:00
|
|
|
if ref > 0 {
|
|
|
|
client.VariablesRequest(ref)
|
|
|
|
pp1val := client.ExpectVariablesResponse(t)
|
|
|
|
expectChildren(t, pp1val, "*pp1", 1)
|
2021-05-10 18:34:42 +00:00
|
|
|
ref = expectVarExact(t, pp1val, 0, "", "(*pp1)", "*1", "*int", hasChildren)
|
2021-02-21 16:00:05 +00:00
|
|
|
if ref > 0 {
|
|
|
|
client.VariablesRequest(ref)
|
|
|
|
pp1valval := client.ExpectVariablesResponse(t)
|
|
|
|
expectChildren(t, pp1valval, "*(*pp1)", 1)
|
2021-05-10 18:34:42 +00:00
|
|
|
expectVarExact(t, pp1valval, 0, "", "(*(*pp1))", "1", "int", noChildren)
|
2021-02-21 16:00:05 +00:00
|
|
|
validateEvaluateName(t, client, pp1valval, 0)
|
|
|
|
}
|
|
|
|
}
|
2020-08-11 15:34:27 +00:00
|
|
|
// reflect.Kind == Slice
|
2021-05-10 18:34:42 +00:00
|
|
|
ref = expectVarExact(t, locals, -1, "zsslice", "zsslice", "[]struct {} len: 3, cap: 3, [{},{},{}]", "[]struct {}", hasChildren)
|
2020-08-11 15:34:27 +00:00
|
|
|
if ref > 0 {
|
|
|
|
client.VariablesRequest(ref)
|
|
|
|
zsslice := client.ExpectVariablesResponse(t)
|
|
|
|
expectChildren(t, zsslice, "zsslice", 3)
|
2021-05-10 18:34:42 +00:00
|
|
|
expectVarExact(t, zsslice, 2, "[2]", "zsslice[2]", "struct {} {}", "struct {}", noChildren)
|
2021-01-14 18:53:12 +00:00
|
|
|
validateEvaluateName(t, client, zsslice, 2)
|
2020-08-11 15:34:27 +00:00
|
|
|
}
|
2021-05-10 18:34:42 +00:00
|
|
|
expectVarExact(t, locals, -1, "emptyslice", "emptyslice", "[]string len: 0, cap: 0, []", "[]string", noChildren)
|
|
|
|
expectVarExact(t, locals, -1, "nilslice", "nilslice", "[]int len: 0, cap: 0, nil", "[]int", noChildren)
|
2020-08-11 15:34:27 +00:00
|
|
|
// reflect.Kind == String
|
2021-05-10 18:34:42 +00:00
|
|
|
expectVarExact(t, locals, -1, "longstr", "longstr", "\"very long string 0123456789a0123456789b0123456789c0123456789d012...+73 more\"", "string", noChildren)
|
2020-08-11 15:34:27 +00:00
|
|
|
// reflect.Kind == Struct
|
2021-05-10 18:34:42 +00:00
|
|
|
expectVarExact(t, locals, -1, "zsvar", "zsvar", "struct {} {}", "struct {}", noChildren)
|
2020-08-11 15:34:27 +00:00
|
|
|
// reflect.Kind == UnsafePointer
|
|
|
|
// TODO(polina): how do I test for unsafe.Pointer(nil)?
|
2021-05-10 18:34:42 +00:00
|
|
|
expectVarRegex(t, locals, -1, "upnil", "upnil", `unsafe\.Pointer\(0x0\)`, "int", noChildren)
|
|
|
|
expectVarRegex(t, locals, -1, "up1", "up1", `unsafe\.Pointer\(0x[0-9a-f]+\)`, "int", noChildren)
|
2020-08-11 15:34:27 +00:00
|
|
|
|
|
|
|
// Test unreadable variable
|
2021-05-10 18:34:42 +00:00
|
|
|
ref = expectVarRegex(t, locals, -1, "unread", "unread", `\*\(unreadable .+\)`, "int", hasChildren)
|
2020-08-11 15:34:27 +00:00
|
|
|
if ref > 0 {
|
|
|
|
client.VariablesRequest(ref)
|
|
|
|
val := client.ExpectVariablesResponse(t)
|
|
|
|
expectChildren(t, val, "*unread", 1)
|
2021-05-10 18:34:42 +00:00
|
|
|
expectVarRegex(t, val, 0, "^$", `\(\*unread\)`, `\(unreadable .+\)`, "int", noChildren)
|
2021-01-14 18:53:12 +00:00
|
|
|
validateEvaluateName(t, client, val, 0)
|
2020-08-11 15:34:27 +00:00
|
|
|
}
|
2021-02-23 16:29:06 +00:00
|
|
|
},
|
|
|
|
disconnect: true,
|
|
|
|
}})
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
// TestVariablesLoading exposes test cases where variables might be partiall 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
|
|
|
|
DefaultLoadConfig.MaxStructFields = 5
|
|
|
|
defer func() { DefaultLoadConfig.MaxStructFields = -1 }()
|
|
|
|
|
|
|
|
client.StackTraceRequest(1, 0, 0)
|
|
|
|
client.ExpectStackTraceResponse(t)
|
2020-08-11 15:34:27 +00:00
|
|
|
|
2021-02-23 16:29:06 +00:00
|
|
|
client.ScopesRequest(1000)
|
|
|
|
client.ExpectScopesResponse(t)
|
|
|
|
|
|
|
|
client.VariablesRequest(1001) // Locals
|
|
|
|
locals := client.ExpectVariablesResponse(t)
|
|
|
|
|
|
|
|
// String partially missing based on LoadConfig.MaxStringLen
|
2021-05-10 18:34:42 +00:00
|
|
|
expectVarExact(t, locals, -1, "longstr", "longstr", "\"very long string 0123456789a0123456789b0123456789c0123456789d012...+73 more\"", "string", noChildren)
|
2021-02-23 16:29:06 +00:00
|
|
|
|
|
|
|
// Array partially missing based on LoadConfig.MaxArrayValues
|
2021-05-10 18:34:42 +00:00
|
|
|
ref := expectVarExact(t, locals, -1, "longarr", "longarr", "(loaded 64/100) [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,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,...+36 more]", "[100]int", hasChildren)
|
2021-02-23 16:29:06 +00:00
|
|
|
if ref > 0 {
|
|
|
|
client.VariablesRequest(ref)
|
|
|
|
longarr := client.ExpectVariablesResponse(t)
|
|
|
|
expectChildren(t, longarr, "longarr", 64)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Slice partially missing based on LoadConfig.MaxArrayValues
|
2021-05-10 18:34:42 +00:00
|
|
|
ref = expectVarExact(t, locals, -1, "longslice", "longslice", "(loaded 64/100) []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,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,...+36 more]", "[]int", hasChildren)
|
2021-02-23 16:29:06 +00:00
|
|
|
if ref > 0 {
|
|
|
|
client.VariablesRequest(ref)
|
|
|
|
longarr := client.ExpectVariablesResponse(t)
|
|
|
|
expectChildren(t, longarr, "longslice", 64)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Map partially missing based on LoadConfig.MaxArrayValues
|
2021-05-10 18:34:42 +00:00
|
|
|
ref = expectVarRegex(t, locals, -1, "m1", "m1", `\(loaded 64/66\) map\[string\]main\.astruct \[.+\.\.\.\+2 more\]`, `map\[string\]main\.astruct`, hasChildren)
|
2020-08-11 15:34:27 +00:00
|
|
|
if ref > 0 {
|
|
|
|
client.VariablesRequest(ref)
|
|
|
|
m1 := client.ExpectVariablesResponse(t)
|
2021-02-23 16:29:06 +00:00
|
|
|
expectChildren(t, m1, "m1", 64)
|
2020-08-11 15:34:27 +00:00
|
|
|
}
|
2021-02-21 16:00:05 +00:00
|
|
|
|
2021-02-23 16:29:06 +00:00
|
|
|
// Struct partially missing based on LoadConfig.MaxStructFields
|
2021-05-10 18:34:42 +00:00
|
|
|
ref = expectVarExact(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)
|
2021-02-23 16:29:06 +00:00
|
|
|
if ref > 0 {
|
|
|
|
client.VariablesRequest(ref)
|
|
|
|
sd := client.ExpectVariablesResponse(t)
|
|
|
|
expectChildren(t, sd, "sd", 5)
|
|
|
|
}
|
|
|
|
|
2021-05-04 19:53:42 +00:00
|
|
|
// Fully missing struct auto-loaded when reaching LoadConfig.MaxVariableRecurse (also tests evaluateName corner case)
|
2021-05-10 18:34:42 +00:00
|
|
|
ref = expectVarRegex(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)
|
2021-02-21 16:00:05 +00:00
|
|
|
if ref > 0 {
|
|
|
|
client.VariablesRequest(ref)
|
|
|
|
c1 := client.ExpectVariablesResponse(t)
|
|
|
|
expectChildren(t, c1, "c1", 2)
|
2021-05-10 18:34:42 +00:00
|
|
|
ref = expectVarRegex(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)
|
2021-02-21 16:00:05 +00:00
|
|
|
if ref > 0 {
|
|
|
|
client.VariablesRequest(ref)
|
|
|
|
c1sa := client.ExpectVariablesResponse(t)
|
|
|
|
expectChildren(t, c1sa, "c1.sa", 3)
|
2021-05-10 18:34:42 +00:00
|
|
|
ref = expectVarRegex(t, c1sa, 0, `\[0\]`, `c1\.sa\[0\]`, `\*\(\*main\.astruct\)\(0x[0-9a-f]+\)`, `\*main\.astruct`, hasChildren)
|
2021-02-21 16:00:05 +00:00
|
|
|
if ref > 0 {
|
2021-05-04 19:53:42 +00:00
|
|
|
// Auto-loading of fully missing struc children happens here
|
2021-02-21 16:00:05 +00:00
|
|
|
client.VariablesRequest(ref)
|
|
|
|
c1sa0 := client.ExpectVariablesResponse(t)
|
|
|
|
expectChildren(t, c1sa0, "c1.sa[0]", 1)
|
2021-05-10 18:34:42 +00:00
|
|
|
// TODO(polina): there should be children here once we support auto loading
|
|
|
|
expectVarExact(t, c1sa0, 0, "", "(*c1.sa[0])", "main.astruct {A: 1, B: 2}", "main.astruct", hasChildren)
|
2021-02-21 16:00:05 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-05-04 19:53:42 +00:00
|
|
|
// Fully missing struct auto-loaded when hitting LoadConfig.MaxVariableRecurse (also tests evaluteName corner case)
|
2021-05-10 18:34:42 +00:00
|
|
|
ref = expectVarRegex(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)
|
2021-02-21 16:00:05 +00:00
|
|
|
if ref > 0 {
|
|
|
|
client.VariablesRequest(ref)
|
|
|
|
aas := client.ExpectVariablesResponse(t)
|
|
|
|
expectChildren(t, aas, "aas", 1)
|
2021-05-10 18:34:42 +00:00
|
|
|
ref = expectVarRegex(t, aas, 0, "[0]", `aas\[0\]`, `main\.a {aas: \[\]main.a len: 1, cap: 1, \[\(\*main\.a\)\(0x[0-9a-f]+\)\]}`, `main\.a`, hasChildren)
|
2021-02-21 16:00:05 +00:00
|
|
|
if ref > 0 {
|
|
|
|
client.VariablesRequest(ref)
|
|
|
|
aas0 := client.ExpectVariablesResponse(t)
|
|
|
|
expectChildren(t, aas0, "aas[0]", 1)
|
2021-05-10 18:34:42 +00:00
|
|
|
ref = expectVarRegex(t, aas0, 0, "aas", `aas\[0\]\.aas`, `\[\]main\.a len: 1, cap: 1, \[\(\*main\.a\)\(0x[0-9a-f]+\)\]`, `\[\]main\.a`, hasChildren)
|
2021-02-21 16:00:05 +00:00
|
|
|
if ref > 0 {
|
2021-05-04 19:53:42 +00:00
|
|
|
// Auto-loading of fully missing struct children happens here
|
2021-02-21 16:00:05 +00:00
|
|
|
client.VariablesRequest(ref)
|
|
|
|
aas0aas := client.ExpectVariablesResponse(t)
|
|
|
|
expectChildren(t, aas0aas, "aas[0].aas", 1)
|
2021-05-10 18:34:42 +00:00
|
|
|
// TODO(polina): there should be a child here once we support auto loading - test for "aas[0].aas[0].aas"
|
|
|
|
ref = expectVarRegex(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)
|
2021-05-04 19:53:42 +00:00
|
|
|
if ref > 0 {
|
|
|
|
client.VariablesRequest(ref)
|
|
|
|
aas0aas0 := client.ExpectVariablesResponse(t)
|
|
|
|
expectChildren(t, aas0aas, "aas[0].aas[0]", 1)
|
2021-05-10 18:34:42 +00:00
|
|
|
expectVarRegex(t, aas0aas0, 0, "aas", `aas\[0\]\.aas\[0\]\.aas`, `\[\]main\.a len: 1, cap: 1, \[\(\*main\.a\)\(0x[0-9a-f]+\)\]`, `\[\]main\.a`, hasChildren)
|
2021-05-04 19:53:42 +00:00
|
|
|
}
|
2021-02-21 16:00:05 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-05-04 19:53:42 +00:00
|
|
|
// Fully missing map auto-loaded when hitting LoadConfig.MaxVariableRecurse (also tests evaluateName corner case)
|
2021-05-10 18:34:42 +00:00
|
|
|
ref = expectVarExact(t, locals, -1, "tm", "tm", "main.truncatedMap {v: []map[string]main.astruct len: 1, cap: 1, [[...]]}", "main.truncatedMap", hasChildren)
|
2021-02-21 16:00:05 +00:00
|
|
|
if ref > 0 {
|
|
|
|
client.VariablesRequest(ref)
|
|
|
|
tm := client.ExpectVariablesResponse(t)
|
|
|
|
expectChildren(t, tm, "tm", 1)
|
2021-05-10 18:34:42 +00:00
|
|
|
ref = expectVarExact(t, tm, 0, "v", "tm.v", "[]map[string]main.astruct len: 1, cap: 1, [[...]]", "[]map[string]main.astruct", hasChildren)
|
2021-02-21 16:00:05 +00:00
|
|
|
if ref > 0 {
|
2021-05-04 19:53:42 +00:00
|
|
|
// Auto-loading of fully missing map chidlren happens here, but they get trancated at MaxArrayValuess
|
2021-02-21 16:00:05 +00:00
|
|
|
client.VariablesRequest(ref)
|
2021-03-08 17:41:47 +00:00
|
|
|
tmV := client.ExpectVariablesResponse(t)
|
|
|
|
expectChildren(t, tmV, "tm.v", 1)
|
2021-05-10 18:34:42 +00:00
|
|
|
ref = expectVarRegex(t, tmV, 0, `\[0\]`, `tm\.v\[0\]`, `map\[string\]main\.astruct \[.+\.\.\.\+2 more\]`, `map\[string\]main\.astruct`, hasChildren)
|
2021-05-04 19:53:42 +00:00
|
|
|
if ref > 0 {
|
|
|
|
client.VariablesRequest(ref)
|
|
|
|
tmV0 := client.ExpectVariablesResponse(t)
|
|
|
|
expectChildren(t, tmV0, "tm.v[0]", 64)
|
|
|
|
}
|
2021-02-21 16:00:05 +00:00
|
|
|
}
|
|
|
|
}
|
2021-05-04 19:53:42 +00:00
|
|
|
|
|
|
|
// Auto-loading works with call return variables as well
|
|
|
|
protest.MustSupportFunctionCalls(t, testBackend)
|
|
|
|
client.EvaluateRequest("call rettm()", 1000, "repl")
|
|
|
|
got := client.ExpectEvaluateResponse(t)
|
|
|
|
ref = expectEval(t, got, "main.truncatedMap {v: []map[string]main.astruct len: 1, cap: 1, [[...]]}", hasChildren)
|
|
|
|
if ref > 0 {
|
|
|
|
client.VariablesRequest(ref)
|
|
|
|
rv := client.ExpectVariablesResponse(t)
|
|
|
|
expectChildren(t, rv, "rv", 1)
|
2021-05-10 18:34:42 +00:00
|
|
|
ref = expectVarExact(t, rv, 0, "~r0", "", "main.truncatedMap {v: []map[string]main.astruct len: 1, cap: 1, [[...]]}", "main.truncatedMap", hasChildren)
|
2021-05-04 19:53:42 +00:00
|
|
|
if ref > 0 {
|
|
|
|
client.VariablesRequest(ref)
|
|
|
|
tm := client.ExpectVariablesResponse(t)
|
|
|
|
expectChildren(t, tm, "tm", 1)
|
2021-05-10 18:34:42 +00:00
|
|
|
ref = expectVarExact(t, tm, 0, "v", "", "[]map[string]main.astruct len: 1, cap: 1, [[...]]", "[]map[string]main.astruct", hasChildren)
|
2021-05-04 19:53:42 +00:00
|
|
|
if ref > 0 {
|
|
|
|
// Auto-loading of fully missing map chidlren happens here, but they get trancated at MaxArrayValuess
|
|
|
|
client.VariablesRequest(ref)
|
|
|
|
tmV := client.ExpectVariablesResponse(t)
|
|
|
|
expectChildren(t, tmV, "tm.v", 1)
|
|
|
|
// TODO(polina): this evaluate name is not usable - it should be empty
|
2021-05-10 18:34:42 +00:00
|
|
|
ref = expectVarRegex(t, tmV, 0, `\[0\]`, `\[0\]`, `map\[string\]main\.astruct \[.+\.\.\.\+2 more\]`, `map\[string\]main\.astruct`, hasChildren)
|
2021-05-04 19:53:42 +00:00
|
|
|
if ref > 0 {
|
|
|
|
client.VariablesRequest(ref)
|
|
|
|
tmV0 := client.ExpectVariablesResponse(t)
|
|
|
|
expectChildren(t, tmV0, "tm.v[0]", 64)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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
|
2021-02-23 16:29:06 +00:00
|
|
|
},
|
|
|
|
disconnect: true,
|
|
|
|
}})
|
|
|
|
})
|
2021-03-08 17:41:47 +00:00
|
|
|
runTest(t, "testvariables", func(client *daptest.Client, fixture protest.Fixture) {
|
2021-02-23 16:29:06 +00:00
|
|
|
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)
|
|
|
|
|
2021-03-08 17:41:47 +00:00
|
|
|
var 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
|
|
|
|
}
|
|
|
|
}
|
2021-02-23 16:29:06 +00:00
|
|
|
|
2021-03-08 17:41:47 +00:00
|
|
|
client.VariablesRequest(localsRef)
|
|
|
|
locals := client.ExpectVariablesResponse(t)
|
|
|
|
|
|
|
|
// Interface auto-loaded when hitting LoadConfig.MaxVariableRecurse=1
|
2021-02-23 16:29:06 +00:00
|
|
|
|
2021-05-10 18:34:42 +00:00
|
|
|
ref := expectVarRegex(t, locals, -1, "ni", "ni", `\[\]interface {} len: 1, cap: 1, \[\[\]interface {} len: 1, cap: 1, \[\*\(\*interface {}\)\(0x[0-9a-f]+\)\]\]`, `\[\]interface {}`, hasChildren)
|
2021-03-08 17:41:47 +00:00
|
|
|
if ref > 0 {
|
|
|
|
client.VariablesRequest(ref)
|
|
|
|
ni := client.ExpectVariablesResponse(t)
|
2021-05-10 18:34:42 +00:00
|
|
|
ref = expectVarRegex(t, ni, 0, `\[0\]`, `ni\[0\]`, `interface \{\}\(\[\]interface \{\}\) \[\*\(\*interface \{\}\)\(0x[0-9a-f]+\)\]`, "interface {}", hasChildren)
|
2021-03-08 17:41:47 +00:00
|
|
|
if ref > 0 {
|
|
|
|
client.VariablesRequest(ref)
|
|
|
|
niI1 := client.ExpectVariablesResponse(t)
|
2021-05-10 18:34:42 +00:00
|
|
|
ref = expectVarRegex(t, niI1, 0, "data", `ni\[0\]\.\(data\)`, `\[\]interface {} len: 1, cap: 1, \[\*\(\*interface {}\)\(0x[0-9a-f]+\)`, `\[\]interface {}`, hasChildren)
|
2021-03-08 17:41:47 +00:00
|
|
|
if ref > 0 {
|
|
|
|
// Auto-loading happens here
|
|
|
|
client.VariablesRequest(ref)
|
|
|
|
niI1Data := client.ExpectVariablesResponse(t)
|
2021-05-10 18:34:42 +00:00
|
|
|
ref = expectVarExact(t, niI1Data, 0, "[0]", "ni[0].(data)[0]", "interface {}(int) 123", "interface {}", hasChildren)
|
2021-03-08 17:41:47 +00:00
|
|
|
if ref > 0 {
|
|
|
|
client.VariablesRequest(ref)
|
|
|
|
niI1DataI2 := client.ExpectVariablesResponse(t)
|
2021-05-10 18:34:42 +00:00
|
|
|
expectVarExact(t, niI1DataI2, 0, "data", "ni[0].(data)[0].(data)", "123", "int", noChildren)
|
2021-03-08 17:41:47 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-05-04 19:53:42 +00:00
|
|
|
// Pointer values loaded even with LoadConfig.FollowPointers=false
|
2021-05-10 18:34:42 +00:00
|
|
|
expectVarExact(t, locals, -1, "a7", "a7", "*main.FooBar {Baz: 5, Bur: \"strum\"}", "*main.FooBar", hasChildren)
|
2021-05-04 19:53:42 +00:00
|
|
|
|
|
|
|
// Auto-loading works on results of evaluate expressions as well
|
|
|
|
client.EvaluateRequest("a7", frame, "repl")
|
|
|
|
expectEval(t, client.ExpectEvaluateResponse(t), "*main.FooBar {Baz: 5, Bur: \"strum\"}", hasChildren)
|
|
|
|
|
|
|
|
client.EvaluateRequest("&a7", frame, "repl")
|
|
|
|
pa7 := client.ExpectEvaluateResponse(t)
|
|
|
|
ref = expectEvalRegex(t, pa7, `\*\(\*main\.FooBar\)\(0x[0-9a-f]+\)`, hasChildren)
|
|
|
|
if ref > 0 {
|
|
|
|
client.VariablesRequest(ref)
|
|
|
|
a7 := client.ExpectVariablesResponse(t)
|
2021-05-10 18:34:42 +00:00
|
|
|
expectVarExact(t, a7, 0, "a7", "(*(&a7))", "*main.FooBar {Baz: 5, Bur: \"strum\"}", "*main.FooBar", hasChildren)
|
2021-05-04 19:53:42 +00:00
|
|
|
}
|
2021-03-08 17:41:47 +00:00
|
|
|
}
|
2021-05-04 19:53:42 +00:00
|
|
|
|
|
|
|
// Frame-independent loading expressions allow us to auto-load
|
|
|
|
// variables in any frame, not just topmost.
|
2021-03-08 17:41:47 +00:00
|
|
|
loadvars(1000 /*first topmost frame*/)
|
|
|
|
// step into another function
|
|
|
|
client.StepInRequest(1)
|
|
|
|
client.ExpectStepInResponse(t)
|
|
|
|
client.ExpectStoppedEvent(t)
|
|
|
|
handleStop(t, client, 1, "main.barfoo", 24)
|
|
|
|
loadvars(1001 /*second frame here is same as topmost above*/)
|
2020-08-11 15:34:27 +00:00
|
|
|
},
|
|
|
|
disconnect: true,
|
|
|
|
}})
|
2020-07-08 17:20:05 +00:00
|
|
|
})
|
|
|
|
}
|
2020-02-15 19:52:53 +00:00
|
|
|
|
2020-09-15 20:14:55 +00:00
|
|
|
// 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) {
|
2020-12-28 17:14:15 +00:00
|
|
|
runDebugSessionWithBPs(t, client, "launch",
|
2020-09-15 20:14:55 +00:00
|
|
|
// 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 36
|
|
|
|
execute: func() {
|
|
|
|
client.StackTraceRequest(1, 0, 20)
|
|
|
|
stack := client.ExpectStackTraceResponse(t)
|
2020-11-30 17:43:09 +00:00
|
|
|
expectStackFrames(t, stack, "main.main", 36, 1000, 3, 3)
|
2020-09-15 20:14:55 +00:00
|
|
|
|
|
|
|
client.ScopesRequest(1000)
|
|
|
|
scopes := client.ExpectScopesResponse(t)
|
|
|
|
expectScope(t, scopes, 0, "Arguments", 1000)
|
|
|
|
expectScope(t, scopes, 1, "Locals", 1001)
|
|
|
|
expectScope(t, scopes, 2, "Globals (package main)", 1002)
|
|
|
|
|
|
|
|
client.VariablesRequest(1002)
|
|
|
|
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)
|
2020-12-28 17:14:15 +00:00
|
|
|
expectStackFrames(t, stack, "", 13, 1000, 4, 4)
|
2020-09-15 20:14:55 +00:00
|
|
|
|
|
|
|
client.ScopesRequest(1000)
|
|
|
|
scopes = client.ExpectScopesResponse(t)
|
|
|
|
expectScope(t, scopes, 0, "Arguments", 1000)
|
|
|
|
expectScope(t, scopes, 1, "Locals", 1001)
|
|
|
|
expectScope(t, scopes, 2, "Globals (package github.com/go-delve/delve/_fixtures/internal/dir0/pkg)", 1002)
|
|
|
|
|
|
|
|
client.VariablesRequest(1002)
|
|
|
|
globals := client.ExpectVariablesResponse(t)
|
|
|
|
expectChildren(t, globals, "Globals", 1)
|
2021-05-10 18:34:42 +00:00
|
|
|
ref := expectVarExact(t, globals, 0, "SomeVar", "github.com/go-delve/delve/_fixtures/internal/dir0/pkg.SomeVar", "github.com/go-delve/delve/_fixtures/internal/dir0/pkg.SomeType {X: 0}", "github.com/go-delve/delve/_fixtures/internal/dir0/pkg.SomeType", hasChildren)
|
2020-09-15 20:14:55 +00:00
|
|
|
|
|
|
|
if ref > 0 {
|
|
|
|
client.VariablesRequest(ref)
|
|
|
|
somevar := client.ExpectVariablesResponse(t)
|
|
|
|
expectChildren(t, somevar, "SomeVar", 1)
|
2021-01-14 18:53:12 +00:00
|
|
|
// TODO(polina): unlike main.p, this prefix won't work
|
2021-05-10 18:34:42 +00:00
|
|
|
expectVarExact(t, somevar, 0, "X", "github.com/go-delve/delve/_fixtures/internal/dir0/pkg.SomeVar.X", "0", "float64", noChildren)
|
2020-09-15 20:14:55 +00:00
|
|
|
}
|
|
|
|
},
|
|
|
|
disconnect: false,
|
|
|
|
}})
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2021-04-19 18:14:50 +00:00
|
|
|
// 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)
|
|
|
|
expectStackFrames(t, stack, "main.main", 13, 1000, 3, 3)
|
|
|
|
|
|
|
|
client.ScopesRequest(1000)
|
|
|
|
scopes := client.ExpectScopesResponse(t)
|
|
|
|
expectScope(t, scopes, 0, "Arguments", 1000)
|
|
|
|
expectScope(t, scopes, 1, "Locals", 1001)
|
|
|
|
expectScope(t, scopes, 2, "Globals (package main)", 1002)
|
|
|
|
|
|
|
|
client.VariablesRequest(1001)
|
|
|
|
locals := client.ExpectVariablesResponse(t)
|
|
|
|
|
2021-05-10 18:34:42 +00:00
|
|
|
expectVarExact(t, locals, 0, "(a)", "a", "0", "int", !hasChildren)
|
|
|
|
expectVarExact(t, locals, 1, "a", "a", "1", "int", !hasChildren)
|
2021-04-19 18:14:50 +00:00
|
|
|
|
|
|
|
// Check that the non-shadowed of "a" is returned from evaluate request.
|
|
|
|
validateEvaluateName(t, client, locals, 1)
|
|
|
|
},
|
|
|
|
disconnect: false,
|
|
|
|
}})
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2020-07-08 17:20:05 +00:00
|
|
|
// 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
|
2020-12-28 17:14:15 +00:00
|
|
|
runDebugSessionWithBPs(t, client, "launch",
|
2020-07-08 17:20:05 +00:00
|
|
|
// Launch
|
|
|
|
func() {
|
|
|
|
client.LaunchRequestWithArgs(map[string]interface{}{
|
|
|
|
"mode": "exec", "program": fixture.Path, "stackTraceDepth": 1,
|
|
|
|
})
|
|
|
|
},
|
|
|
|
// Set breakpoints
|
|
|
|
fixture.Source, []int{8},
|
2020-08-11 15:34:27 +00:00
|
|
|
[]onBreakpoint{{ // Stop at line 8
|
|
|
|
execute: func() {
|
2020-07-08 17:20:05 +00:00
|
|
|
client.StackTraceRequest(1, 0, 0)
|
|
|
|
stResp = client.ExpectStackTraceResponse(t)
|
2020-11-30 17:43:09 +00:00
|
|
|
expectStackFrames(t, stResp, "main.Increment", 8, 1000, 2 /*returned*/, 2 /*available*/)
|
2020-08-11 15:34:27 +00:00
|
|
|
},
|
|
|
|
disconnect: false,
|
|
|
|
}})
|
2020-02-15 19:52:53 +00:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2021-05-17 16:17:00 +00:00
|
|
|
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, client, bps, client.ExpectSetBreakpointsResponse(t))
|
|
|
|
}
|
|
|
|
|
|
|
|
func checkSetBreakpointsResponse(t *testing.T, client *daptest.Client, bps []Breakpoint, got *dap.SetBreakpointsResponse) {
|
|
|
|
t.Helper()
|
|
|
|
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 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])
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-10-02 16:18:33 +00:00
|
|
|
// 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) {
|
2020-12-28 17:14:15 +00:00
|
|
|
runDebugSessionWithBPs(t, client, "launch",
|
2020-10-02 16:18:33 +00:00
|
|
|
// Launch
|
|
|
|
func() {
|
|
|
|
client.LaunchRequest("exec", fixture.Path, !stopOnEntry)
|
|
|
|
},
|
|
|
|
// Set breakpoints
|
|
|
|
fixture.Source, []int{16}, // b main.main
|
|
|
|
[]onBreakpoint{{
|
|
|
|
execute: func() {
|
2020-11-12 23:24:31 +00:00
|
|
|
handleStop(t, client, 1, "main.main", 16)
|
2020-10-02 16:18:33 +00:00
|
|
|
|
|
|
|
// Set two breakpoints at the next two lines in main
|
|
|
|
client.SetBreakpointsRequest(fixture.Source, []int{17, 18})
|
2021-05-17 16:17:00 +00:00
|
|
|
expectSetBreakpointsResponse(t, client, []Breakpoint{{17, fixture.Source, true, ""}, {18, fixture.Source, true, ""}})
|
2020-10-02 16:18:33 +00:00
|
|
|
|
|
|
|
// Clear 17, reset 18
|
|
|
|
client.SetBreakpointsRequest(fixture.Source, []int{18})
|
2021-05-17 16:17:00 +00:00
|
|
|
expectSetBreakpointsResponse(t, client, []Breakpoint{{18, fixture.Source, true, ""}})
|
2020-10-02 16:18:33 +00:00
|
|
|
|
|
|
|
// Skip 17, continue to 18
|
|
|
|
client.ContinueRequest(1)
|
|
|
|
client.ExpectContinueResponse(t)
|
|
|
|
client.ExpectStoppedEvent(t)
|
2020-11-12 23:24:31 +00:00
|
|
|
handleStop(t, client, 1, "main.main", 18)
|
2020-10-02 16:18:33 +00:00
|
|
|
|
|
|
|
// Set another breakpoint inside the loop in loop(), twice to trigger error
|
|
|
|
client.SetBreakpointsRequest(fixture.Source, []int{8, 8})
|
2021-05-17 16:17:00 +00:00
|
|
|
expectSetBreakpointsResponse(t, client, []Breakpoint{{8, fixture.Source, true, ""}, {8, "", false, "Breakpoint exists"}})
|
2020-10-02 16:18:33 +00:00
|
|
|
|
|
|
|
// Continue into the loop
|
|
|
|
client.ContinueRequest(1)
|
|
|
|
client.ExpectContinueResponse(t)
|
|
|
|
client.ExpectStoppedEvent(t)
|
2020-11-12 23:24:31 +00:00
|
|
|
handleStop(t, client, 1, "main.loop", 8)
|
2020-10-02 16:18:33 +00:00
|
|
|
client.VariablesRequest(1001) // Locals
|
|
|
|
locals := client.ExpectVariablesResponse(t)
|
2021-05-10 18:34:42 +00:00
|
|
|
expectVarExact(t, locals, 0, "i", "i", "0", "int", noChildren) // i == 0
|
2020-10-02 16:18:33 +00:00
|
|
|
|
|
|
|
// Edit the breakpoint to add a condition
|
|
|
|
client.SetConditionalBreakpointsRequest(fixture.Source, []int{8}, map[int]string{8: "i == 3"})
|
2021-05-17 16:17:00 +00:00
|
|
|
expectSetBreakpointsResponse(t, client, []Breakpoint{{8, fixture.Source, true, ""}})
|
2020-10-02 16:18:33 +00:00
|
|
|
|
|
|
|
// Continue until condition is hit
|
|
|
|
client.ContinueRequest(1)
|
|
|
|
client.ExpectContinueResponse(t)
|
|
|
|
client.ExpectStoppedEvent(t)
|
2020-11-12 23:24:31 +00:00
|
|
|
handleStop(t, client, 1, "main.loop", 8)
|
2020-10-02 16:18:33 +00:00
|
|
|
client.VariablesRequest(1001) // Locals
|
|
|
|
locals = client.ExpectVariablesResponse(t)
|
2021-05-10 18:34:42 +00:00
|
|
|
expectVarExact(t, locals, 0, "i", "i", "3", "int", noChildren) // i == 3
|
2020-10-02 16:18:33 +00:00
|
|
|
|
|
|
|
// Edit the breakpoint to remove a condition
|
|
|
|
client.SetConditionalBreakpointsRequest(fixture.Source, []int{8}, map[int]string{8: ""})
|
2021-05-17 16:17:00 +00:00
|
|
|
expectSetBreakpointsResponse(t, client, []Breakpoint{{8, fixture.Source, true, ""}})
|
2020-10-02 16:18:33 +00:00
|
|
|
|
|
|
|
// Continue for one more loop iteration
|
|
|
|
client.ContinueRequest(1)
|
|
|
|
client.ExpectContinueResponse(t)
|
|
|
|
client.ExpectStoppedEvent(t)
|
2020-11-12 23:24:31 +00:00
|
|
|
handleStop(t, client, 1, "main.loop", 8)
|
2020-10-02 16:18:33 +00:00
|
|
|
client.VariablesRequest(1001) // Locals
|
|
|
|
locals = client.ExpectVariablesResponse(t)
|
2021-05-10 18:34:42 +00:00
|
|
|
expectVarExact(t, locals, 0, "i", "i", "4", "int", noChildren) // i == 4
|
2020-10-02 16:18:33 +00:00
|
|
|
|
|
|
|
// Set at a line without a statement
|
|
|
|
client.SetBreakpointsRequest(fixture.Source, []int{1000})
|
2021-05-17 16:17:00 +00:00
|
|
|
expectSetBreakpointsResponse(t, client, []Breakpoint{{1000, "", false, "could not find statement"}}) // all cleared, none set
|
2020-10-02 16:18:33 +00:00
|
|
|
},
|
|
|
|
// The program has an infinite loop, so we must kill it by disconnecting.
|
|
|
|
disconnect: true,
|
|
|
|
}})
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2021-05-17 16:17:00 +00:00
|
|
|
func expectSetBreakpointsResponseAndStoppedEvent(t *testing.T, client *daptest.Client) (se *dap.StoppedEvent, br *dap.SetBreakpointsResponse) {
|
|
|
|
for i := 0; i < 2; i++ {
|
|
|
|
switch m := client.ExpectMessage(t).(type) {
|
|
|
|
case *dap.StoppedEvent:
|
|
|
|
se = m
|
|
|
|
case *dap.SetBreakpointsResponse:
|
|
|
|
br = m
|
|
|
|
default:
|
|
|
|
t.Fatalf("Unexpected message type: expect StoppedEvent or SetBreakpointsResponse, got %#v", m)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if se == nil || br == nil {
|
|
|
|
t.Fatal("Expected StoppedEvent and SetBreakpointsResponse")
|
|
|
|
}
|
|
|
|
return se, br
|
|
|
|
}
|
|
|
|
|
|
|
|
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
|
|
|
|
handleStop(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,]
|
|
|
|
se, br := expectSetBreakpointsResponseAndStoppedEvent(t, client)
|
|
|
|
if se.Body.Reason != "pause" || !se.Body.AllThreadsStopped || se.Body.ThreadId != 0 && se.Body.ThreadId != 1 {
|
|
|
|
t.Errorf("\ngot %#v\nwant Reason='pause' AllThreadsStopped=true ThreadId=0/1", se)
|
|
|
|
}
|
|
|
|
checkSetBreakpointsResponse(t, client, []Breakpoint{{15, fixture.Source, true, ""}}, br)
|
|
|
|
// Halt cancelled next, so if we continue we will not stop at line 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)
|
|
|
|
}
|
|
|
|
handleStop(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,]
|
|
|
|
se, br = expectSetBreakpointsResponseAndStoppedEvent(t, client)
|
|
|
|
if se.Body.Reason != "pause" || !se.Body.AllThreadsStopped || se.Body.ThreadId != 0 && se.Body.ThreadId != 1 {
|
|
|
|
t.Errorf("\ngot %#v\nwant Reason='pause' AllThreadsStopped=true ThreadId=0/1", se)
|
|
|
|
}
|
|
|
|
checkSetBreakpointsResponse(t, client, []Breakpoint{{9, fixture.Source, true, ""}}, br)
|
|
|
|
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)
|
|
|
|
}
|
|
|
|
handleStop(t, client, 1, "main.sayhi", 9)
|
|
|
|
|
|
|
|
},
|
|
|
|
disconnect: true,
|
|
|
|
}})
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2021-04-15 23:35:37 +00:00
|
|
|
// 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 == "freebsd" {
|
|
|
|
t.SkipNow()
|
|
|
|
}
|
|
|
|
if runtime.GOOS == "windows" {
|
|
|
|
t.Skip("test skipped on windows, see https://delve.beta.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)
|
|
|
|
case "launch":
|
|
|
|
client.LaunchRequestWithArgs(launchAttachConfig)
|
|
|
|
default:
|
|
|
|
t.Fatalf("invalid request: %s", request)
|
|
|
|
}
|
|
|
|
},
|
|
|
|
// Set breakpoints
|
|
|
|
filepath.Join(nonexistentDir, "loopprog.go"), []int{8},
|
|
|
|
[]onBreakpoint{{
|
|
|
|
|
|
|
|
execute: func() {
|
|
|
|
handleStop(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
|
|
|
|
}
|
|
|
|
|
2021-03-23 03:06:09 +00:00
|
|
|
// 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,
|
2021-04-20 07:40:34 +00:00
|
|
|
"cwd": wd,
|
2021-03-23 03:06:09 +00:00
|
|
|
})
|
|
|
|
},
|
|
|
|
// Set breakpoints
|
|
|
|
fixture.Source, []int{10}, // b main.main
|
|
|
|
[]onBreakpoint{{
|
|
|
|
execute: func() {
|
|
|
|
handleStop(t, client, 1, "main.main", 10)
|
|
|
|
client.VariablesRequest(1001) // Locals
|
|
|
|
locals := client.ExpectVariablesResponse(t)
|
|
|
|
expectChildren(t, locals, "Locals", 2)
|
2021-05-10 18:34:42 +00:00
|
|
|
expectVarExact(t, locals, 0, "pwd", "pwd", fmt.Sprintf("%q", wd), "string", noChildren)
|
|
|
|
expectVarExact(t, locals, 1, "err", "err", "error nil", "error", noChildren)
|
2021-03-23 03:06:09 +00:00
|
|
|
|
|
|
|
},
|
|
|
|
disconnect: false,
|
|
|
|
}})
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2020-11-12 23:24:31 +00:00
|
|
|
// expectEval 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 expectEval(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
|
|
|
|
}
|
|
|
|
|
2021-03-15 16:36:46 +00:00
|
|
|
func expectEvalRegex(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
|
|
|
|
}
|
|
|
|
|
2020-11-12 23:24:31 +00:00
|
|
|
func TestEvaluateRequest(t *testing.T) {
|
|
|
|
runTest(t, "testvariables", func(client *daptest.Client, fixture protest.Fixture) {
|
2020-12-28 17:14:15 +00:00
|
|
|
runDebugSessionWithBPs(t, client, "launch",
|
2020-11-12 23:24:31 +00:00
|
|
|
// Launch
|
|
|
|
func() {
|
|
|
|
client.LaunchRequest("exec", fixture.Path, !stopOnEntry)
|
|
|
|
},
|
|
|
|
fixture.Source, []int{}, // Breakpoint set in the program
|
|
|
|
[]onBreakpoint{{ // Stop at first breakpoint
|
|
|
|
execute: func() {
|
2021-03-08 17:41:47 +00:00
|
|
|
handleStop(t, client, 1, "main.foobar", 66)
|
2020-11-12 23:24:31 +00:00
|
|
|
|
|
|
|
// Variable lookup
|
|
|
|
client.EvaluateRequest("a2", 1000, "this context will be ignored")
|
|
|
|
got := client.ExpectEvaluateResponse(t)
|
|
|
|
expectEval(t, got, "6", noChildren)
|
|
|
|
|
|
|
|
client.EvaluateRequest("a5", 1000, "this context will be ignored")
|
|
|
|
got = client.ExpectEvaluateResponse(t)
|
2021-03-15 16:36:46 +00:00
|
|
|
ref := expectEval(t, got, "[]int len: 5, cap: 5, [1,2,3,4,5]", hasChildren)
|
2020-11-12 23:24:31 +00:00
|
|
|
if ref > 0 {
|
|
|
|
client.VariablesRequest(ref)
|
|
|
|
a5 := client.ExpectVariablesResponse(t)
|
|
|
|
expectChildren(t, a5, "a5", 5)
|
2021-05-10 18:34:42 +00:00
|
|
|
expectVarExact(t, a5, 0, "[0]", "(a5)[0]", "1", "int", noChildren)
|
|
|
|
expectVarExact(t, a5, 4, "[4]", "(a5)[4]", "5", "int", noChildren)
|
2021-01-14 18:53:12 +00:00
|
|
|
validateEvaluateName(t, client, a5, 0)
|
|
|
|
validateEvaluateName(t, client, a5, 4)
|
2020-11-12 23:24:31 +00:00
|
|
|
}
|
|
|
|
|
2021-02-23 16:29:06 +00:00
|
|
|
// Variable lookup that's not fully loaded
|
|
|
|
client.EvaluateRequest("ba", 1000, "this context will be ignored")
|
|
|
|
got = client.ExpectEvaluateResponse(t)
|
2021-03-15 16:36:46 +00:00
|
|
|
expectEval(t, got, "(loaded 64/200) []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)
|
2021-02-23 16:29:06 +00:00
|
|
|
|
2020-11-12 23:24:31 +00:00
|
|
|
// All (binary and unary) on basic types except <-, ++ and --
|
|
|
|
client.EvaluateRequest("1+1", 1000, "this context will be ignored")
|
|
|
|
got = client.ExpectEvaluateResponse(t)
|
|
|
|
expectEval(t, got, "2", noChildren)
|
|
|
|
|
|
|
|
// Comparison operators on any type
|
|
|
|
client.EvaluateRequest("1<2", 1000, "this context will be ignored")
|
|
|
|
got = client.ExpectEvaluateResponse(t)
|
|
|
|
expectEval(t, got, "true", noChildren)
|
|
|
|
|
|
|
|
// Type casts between numeric types
|
|
|
|
client.EvaluateRequest("int(2.3)", 1000, "this context will be ignored")
|
|
|
|
got = client.ExpectEvaluateResponse(t)
|
|
|
|
expectEval(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)
|
2021-05-04 19:53:42 +00:00
|
|
|
ref = expectEvalRegex(t, got, `\*\(unreadable .+\)`, hasChildren)
|
|
|
|
if ref > 0 {
|
|
|
|
client.VariablesRequest(ref)
|
|
|
|
val := client.ExpectVariablesResponse(t)
|
|
|
|
expectChildren(t, val, "*(*int)(2)", 1)
|
2021-05-10 18:34:42 +00:00
|
|
|
expectVarRegex(t, val, 0, "^$", `\(\*\(\(\*int\)\(2\)\)\)`, `\(unreadable .+\)`, "int", noChildren)
|
2021-05-04 19:53:42 +00:00
|
|
|
validateEvaluateName(t, client, val, 0)
|
|
|
|
}
|
2021-02-23 16:29:06 +00:00
|
|
|
|
2020-11-12 23:24:31 +00:00
|
|
|
// Type casts between string, []byte and []rune
|
|
|
|
client.EvaluateRequest("[]byte(\"ABC€\")", 1000, "this context will be ignored")
|
|
|
|
got = client.ExpectEvaluateResponse(t)
|
2021-03-15 16:36:46 +00:00
|
|
|
expectEval(t, got, "[]uint8 len: 6, cap: 6, [65,66,67,226,130,172]", noChildren)
|
2020-11-12 23:24:31 +00:00
|
|
|
|
|
|
|
// Struct member access (i.e. somevar.memberfield)
|
|
|
|
client.EvaluateRequest("ms.Nest.Level", 1000, "this context will be ignored")
|
|
|
|
got = client.ExpectEvaluateResponse(t)
|
|
|
|
expectEval(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)
|
|
|
|
expectEval(t, got, "5", noChildren)
|
|
|
|
|
|
|
|
// Map access
|
|
|
|
client.EvaluateRequest("mp[1]", 1000, "this context will be ignored")
|
|
|
|
got = client.ExpectEvaluateResponse(t)
|
2021-03-15 16:36:46 +00:00
|
|
|
ref = expectEval(t, got, "interface {}(int) 42", hasChildren)
|
2020-11-12 23:24:31 +00:00
|
|
|
if ref > 0 {
|
|
|
|
client.VariablesRequest(ref)
|
|
|
|
expr := client.ExpectVariablesResponse(t)
|
|
|
|
expectChildren(t, expr, "mp[1]", 1)
|
2021-05-10 18:34:42 +00:00
|
|
|
expectVarExact(t, expr, 0, "data", "(mp[1]).(data)", "42", "int", noChildren)
|
2021-01-14 18:53:12 +00:00
|
|
|
validateEvaluateName(t, client, expr, 0)
|
2020-11-12 23:24:31 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Pointer dereference
|
|
|
|
client.EvaluateRequest("*ms.Nest", 1000, "this context will be ignored")
|
|
|
|
got = client.ExpectEvaluateResponse(t)
|
2021-03-15 16:36:46 +00:00
|
|
|
ref = expectEvalRegex(t, got, `main\.Nest {Level: 1, Nest: \*main.Nest {Level: 2, Nest: \*\(\*main\.Nest\)\(0x[0-9a-f]+\)}}`, hasChildren)
|
2020-11-12 23:24:31 +00:00
|
|
|
if ref > 0 {
|
|
|
|
client.VariablesRequest(ref)
|
|
|
|
expr := client.ExpectVariablesResponse(t)
|
|
|
|
expectChildren(t, expr, "*ms.Nest", 2)
|
2021-05-10 18:34:42 +00:00
|
|
|
expectVarExact(t, expr, 0, "Level", "(*ms.Nest).Level", "1", "int", noChildren)
|
2021-01-14 18:53:12 +00:00
|
|
|
validateEvaluateName(t, client, expr, 0)
|
2020-11-12 23:24:31 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Calls to builtin functions: cap, len, complex, imag and real
|
|
|
|
client.EvaluateRequest("len(a5)", 1000, "this context will be ignored")
|
|
|
|
got = client.ExpectEvaluateResponse(t)
|
|
|
|
expectEval(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)
|
|
|
|
expectEval(t, got, "42", noChildren)
|
|
|
|
},
|
|
|
|
disconnect: false,
|
|
|
|
}, { // Stop at second breakpoint
|
|
|
|
execute: func() {
|
|
|
|
handleStop(t, client, 1, "main.barfoo", 27)
|
|
|
|
|
|
|
|
// Top-most frame
|
|
|
|
client.EvaluateRequest("a1", 1000, "this context will be ignored")
|
|
|
|
got := client.ExpectEvaluateResponse(t)
|
|
|
|
expectEval(t, got, "\"bur\"", noChildren)
|
|
|
|
// No frame defaults to top-most frame
|
|
|
|
client.EvaluateRequest("a1", 0, "this context will be ignored")
|
|
|
|
got = client.ExpectEvaluateResponse(t)
|
|
|
|
expectEval(t, got, "\"bur\"", noChildren)
|
|
|
|
// Next frame
|
|
|
|
client.EvaluateRequest("a1", 1001, "this context will be ignored")
|
|
|
|
got = client.ExpectEvaluateResponse(t)
|
|
|
|
expectEval(t, got, "\"foofoofoofoofoofoo\"", noChildren)
|
|
|
|
// Next frame
|
|
|
|
client.EvaluateRequest("a1", 1002, "any context but watch")
|
|
|
|
erres := client.ExpectVisibleErrorResponse(t)
|
|
|
|
if erres.Body.Error.Format != "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")
|
2021-05-06 09:11:45 +00:00
|
|
|
erres = client.ExpectInvisibleErrorResponse(t)
|
2020-11-12 23:24:31 +00:00
|
|
|
if erres.Body.Error.Format != "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 TestEvaluateCallRequest(t *testing.T) {
|
|
|
|
protest.MustSupportFunctionCalls(t, testBackend)
|
|
|
|
runTest(t, "fncall", func(client *daptest.Client, fixture protest.Fixture) {
|
2020-12-28 17:14:15 +00:00
|
|
|
runDebugSessionWithBPs(t, client, "launch",
|
2020-11-12 23:24:31 +00:00
|
|
|
// Launch
|
|
|
|
func() {
|
|
|
|
client.LaunchRequest("exec", fixture.Path, !stopOnEntry)
|
|
|
|
},
|
|
|
|
fixture.Source, []int{88},
|
|
|
|
[]onBreakpoint{{ // Stop in makeclos()
|
|
|
|
execute: func() {
|
|
|
|
handleStop(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 erres.Body.Error.Format != "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 erres.Body.Error.Format != "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 erres.Body.Error.Format != "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)
|
|
|
|
handleStop(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")
|
2020-12-27 23:11:02 +00:00
|
|
|
stopped := client.ExpectStoppedEvent(t)
|
2020-11-12 23:24:31 +00:00
|
|
|
erres = client.ExpectVisibleErrorResponse(t)
|
|
|
|
if erres.Body.Error.Format != "Unable to evaluate expression: call stopped" {
|
|
|
|
t.Errorf("\ngot %#v\nwant Format=\"Unable to evaluate expression: call stopped\"", erres)
|
|
|
|
}
|
2020-12-27 23:11:02 +00:00
|
|
|
handleStop(t, client, stopped.Body.ThreadId, "main.makeclos", 88)
|
2020-11-12 23:24:31 +00:00
|
|
|
|
|
|
|
// Complete the call and get back to original breakpoint in makeclos()
|
|
|
|
client.ContinueRequest(1)
|
|
|
|
client.ExpectContinueResponse(t)
|
|
|
|
client.ExpectStoppedEvent(t)
|
|
|
|
handleStop(t, client, 1, "main.makeclos", 88)
|
|
|
|
},
|
|
|
|
disconnect: false,
|
|
|
|
}, { // Stop at runtime breakpoint
|
|
|
|
execute: func() {
|
|
|
|
handleStop(t, client, 1, "main.main", 197)
|
|
|
|
|
|
|
|
// No return values
|
|
|
|
client.EvaluateRequest("call call0(1, 2)", 1000, "this context will be ignored")
|
|
|
|
got := client.ExpectEvaluateResponse(t)
|
|
|
|
expectEval(t, got, "", noChildren)
|
|
|
|
// One unnamed return value
|
|
|
|
client.EvaluateRequest("call call1(one, two)", 1000, "this context will be ignored")
|
|
|
|
got = client.ExpectEvaluateResponse(t)
|
|
|
|
ref := expectEval(t, got, "3", hasChildren)
|
|
|
|
if ref > 0 {
|
|
|
|
client.VariablesRequest(ref)
|
|
|
|
rv := client.ExpectVariablesResponse(t)
|
|
|
|
expectChildren(t, rv, "rv", 1)
|
2021-05-10 18:34:42 +00:00
|
|
|
expectVarExact(t, rv, 0, "~r2", "", "3", "int", noChildren)
|
2020-11-12 23:24:31 +00:00
|
|
|
}
|
|
|
|
// 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)
|
2021-03-15 16:36:46 +00:00
|
|
|
ref = expectEval(t, got, `interface {}(string) "callpanic panicked"`, hasChildren)
|
2020-11-12 23:24:31 +00:00
|
|
|
if ref > 0 {
|
|
|
|
client.VariablesRequest(ref)
|
|
|
|
rv := client.ExpectVariablesResponse(t)
|
|
|
|
expectChildren(t, rv, "rv", 1)
|
2021-05-10 18:34:42 +00:00
|
|
|
ref = expectVarExact(t, rv, 0, "~panic", "", `interface {}(string) "callpanic panicked"`, "interface {}", hasChildren)
|
2020-11-12 23:24:31 +00:00
|
|
|
if ref > 0 {
|
|
|
|
client.VariablesRequest(ref)
|
|
|
|
p := client.ExpectVariablesResponse(t)
|
|
|
|
expectChildren(t, p, "~panic", 1)
|
2021-05-10 18:34:42 +00:00
|
|
|
expectVarExact(t, p, 0, "data", "", "\"callpanic panicked\"", "string", noChildren)
|
2020-11-12 23:24:31 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
// Multiple return values
|
|
|
|
client.EvaluateRequest("call call2(one, two)", 1000, "this context will be ignored")
|
|
|
|
got = client.ExpectEvaluateResponse(t)
|
|
|
|
ref = expectEval(t, got, "1, 2", hasChildren)
|
|
|
|
if ref > 0 {
|
|
|
|
client.VariablesRequest(ref)
|
|
|
|
rvs := client.ExpectVariablesResponse(t)
|
|
|
|
expectChildren(t, rvs, "rvs", 2)
|
2021-05-10 18:34:42 +00:00
|
|
|
expectVarExact(t, rvs, 0, "~r2", "", "1", "int", noChildren)
|
|
|
|
expectVarExact(t, rvs, 1, "~r3", "", "2", "int", noChildren)
|
2020-11-12 23:24:31 +00:00
|
|
|
}
|
|
|
|
// No frame defaults to top-most frame
|
|
|
|
client.EvaluateRequest("call call1(one, two)", 0, "this context will be ignored")
|
|
|
|
got = client.ExpectEvaluateResponse(t)
|
|
|
|
expectEval(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)
|
|
|
|
expectEval(t, got, "2", hasChildren)
|
|
|
|
// Just 'call', even with extra space, is treated as {expression}
|
|
|
|
client.EvaluateRequest("call ", 1000, "watch")
|
|
|
|
got = client.ExpectEvaluateResponse(t)
|
|
|
|
expectEval(t, got, "\"this is a variable named `call`\"", noChildren)
|
|
|
|
// Call error
|
|
|
|
client.EvaluateRequest("call call1(one)", 1000, "watch")
|
2021-05-06 09:11:45 +00:00
|
|
|
erres := client.ExpectInvisibleErrorResponse(t)
|
2020-11-12 23:24:31 +00:00
|
|
|
if erres.Body.Error.Format != "Unable to evaluate expression: not enough arguments" {
|
|
|
|
t.Errorf("\ngot %#v\nwant Format=\"Unable to evaluate expression: not enough arguments\"", erres)
|
|
|
|
}
|
|
|
|
// Call can exit
|
|
|
|
client.EvaluateRequest("call callexit()", 1000, "this context will be ignored")
|
|
|
|
},
|
2021-04-12 21:50:15 +00:00
|
|
|
terminated: true,
|
2020-11-12 23:24:31 +00:00
|
|
|
disconnect: true,
|
|
|
|
}})
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2020-08-24 17:21:51 +00:00
|
|
|
func TestNextAndStep(t *testing.T) {
|
|
|
|
runTest(t, "testinline", func(client *daptest.Client, fixture protest.Fixture) {
|
2020-12-28 17:14:15 +00:00
|
|
|
runDebugSessionWithBPs(t, client, "launch",
|
2020-08-24 17:21:51 +00:00
|
|
|
// Launch
|
|
|
|
func() {
|
|
|
|
client.LaunchRequest("exec", fixture.Path, !stopOnEntry)
|
|
|
|
},
|
|
|
|
// Set breakpoints
|
|
|
|
fixture.Source, []int{11},
|
|
|
|
[]onBreakpoint{{ // Stop at line 11
|
|
|
|
execute: func() {
|
2020-11-12 23:24:31 +00:00
|
|
|
handleStop(t, client, 1, "main.initialize", 11)
|
2020-08-24 17:21:51 +00:00
|
|
|
|
2020-11-12 23:24:31 +00:00
|
|
|
expectStop := func(fun string, line int) {
|
2020-08-24 17:21:51 +00:00
|
|
|
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)
|
|
|
|
}
|
2020-11-12 23:24:31 +00:00
|
|
|
handleStop(t, client, 1, fun, line)
|
2020-08-24 17:21:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
client.StepOutRequest(1)
|
|
|
|
client.ExpectStepOutResponse(t)
|
2020-11-12 23:24:31 +00:00
|
|
|
expectStop("main.main", 18)
|
2020-08-24 17:21:51 +00:00
|
|
|
|
|
|
|
client.NextRequest(1)
|
|
|
|
client.ExpectNextResponse(t)
|
2020-11-12 23:24:31 +00:00
|
|
|
expectStop("main.main", 19)
|
2020-08-24 17:21:51 +00:00
|
|
|
|
|
|
|
client.StepInRequest(1)
|
|
|
|
client.ExpectStepInResponse(t)
|
2020-11-12 23:24:31 +00:00
|
|
|
expectStop("main.inlineThis", 5)
|
2020-08-24 17:21:51 +00:00
|
|
|
|
2021-04-21 20:39:19 +00:00
|
|
|
client.NextRequest(-1000)
|
2020-08-24 17:21:51 +00:00
|
|
|
client.ExpectNextResponse(t)
|
2021-04-21 20:39:19 +00:00
|
|
|
if se := client.ExpectStoppedEvent(t); se.Body.Reason != "error" || se.Body.Text != "unknown goroutine -1000" {
|
|
|
|
t.Errorf("got %#v, want Reaspon=\"error\", Text=\"unknown goroutine -1000\"", se)
|
|
|
|
}
|
|
|
|
handleStop(t, client, 1, "main.inlineThis", 5)
|
2020-08-24 17:21:51 +00:00
|
|
|
},
|
|
|
|
disconnect: false,
|
|
|
|
}})
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2021-04-02 16:19:16 +00:00
|
|
|
func TestNextParked(t *testing.T) {
|
|
|
|
if runtime.GOOS == "freebsd" {
|
|
|
|
t.SkipNow()
|
|
|
|
}
|
|
|
|
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() {
|
|
|
|
goroutineId := testStepParkedHelper(t, client, fixture)
|
|
|
|
|
|
|
|
client.NextRequest(goroutineId)
|
|
|
|
client.ExpectNextResponse(t)
|
|
|
|
|
|
|
|
se := client.ExpectStoppedEvent(t)
|
|
|
|
if se.Body.ThreadId != goroutineId {
|
|
|
|
t.Fatalf("Next did not continue on the selected goroutine, expected %d got %d", goroutineId, se.Body.ThreadId)
|
|
|
|
}
|
|
|
|
},
|
|
|
|
disconnect: false,
|
|
|
|
}})
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestStepInParked(t *testing.T) {
|
|
|
|
if runtime.GOOS == "freebsd" {
|
|
|
|
t.SkipNow()
|
|
|
|
}
|
|
|
|
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() {
|
|
|
|
goroutineId := testStepParkedHelper(t, client, fixture)
|
|
|
|
|
|
|
|
client.StepInRequest(goroutineId)
|
|
|
|
client.ExpectStepInResponse(t)
|
|
|
|
|
|
|
|
se := client.ExpectStoppedEvent(t)
|
|
|
|
if se.Body.ThreadId != goroutineId {
|
|
|
|
t.Fatalf("StepIn did not continue on the selected goroutine, expected %d got %d", goroutineId, se.Body.ThreadId)
|
|
|
|
}
|
|
|
|
},
|
|
|
|
disconnect: false,
|
|
|
|
}})
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
func testStepParkedHelper(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)
|
|
|
|
|
|
|
|
var goroutineId = -1
|
|
|
|
for goroutineId < 0 {
|
|
|
|
client.ContinueRequest(1)
|
|
|
|
client.ExpectContinueResponse(t)
|
|
|
|
|
|
|
|
se := client.ExpectStoppedEvent(t)
|
|
|
|
|
|
|
|
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 {
|
|
|
|
// 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, 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 {
|
|
|
|
goroutineId = g.Id
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if goroutineId >= 0 {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Clear all breakpoints.
|
|
|
|
client.SetBreakpointsRequest(fixture.Source, []int{})
|
|
|
|
client.ExpectSetBreakpointsResponse(t)
|
|
|
|
|
|
|
|
return goroutineId
|
|
|
|
}
|
|
|
|
|
2021-04-07 08:00:14 +00:00
|
|
|
// TestStepOutPreservesGoroutine is inspired by proc_test.TestStepOutPreservesGoroutine
|
|
|
|
// and checks that StepOut preserves the currently selected goroutine.
|
2021-04-02 16:19:16 +00:00
|
|
|
func TestStepOutPreservesGoroutine(t *testing.T) {
|
|
|
|
// Checks that StepOut preserves the currently selected goroutine.
|
|
|
|
if runtime.GOOS == "freebsd" {
|
|
|
|
t.SkipNow()
|
|
|
|
}
|
|
|
|
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 {
|
|
|
|
goroutineId = candg[rand.Intn(len(candg))]
|
|
|
|
t.Logf("selected goroutine %d\n", goroutineId)
|
|
|
|
|
|
|
|
}
|
|
|
|
client.StepOutRequest(goroutineId)
|
|
|
|
client.ExpectStepOutResponse(t)
|
|
|
|
|
2021-04-08 09:09:41 +00:00
|
|
|
switch e := client.ExpectMessage(t).(type) {
|
2021-04-07 08:00:14 +00:00
|
|
|
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)
|
2021-04-02 16:19:16 +00:00
|
|
|
}
|
|
|
|
},
|
|
|
|
disconnect: false,
|
|
|
|
}})
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2020-08-24 17:21:51 +00:00
|
|
|
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) {
|
2020-12-28 17:14:15 +00:00
|
|
|
runDebugSessionWithBPs(t, client, "launch",
|
2020-08-24 17:21:51 +00:00
|
|
|
// Launch
|
|
|
|
func() {
|
|
|
|
client.LaunchRequest("exec", fixture.Path, !stopOnEntry)
|
|
|
|
},
|
|
|
|
// Set breakpoints
|
|
|
|
fixture.Source, []int{4},
|
|
|
|
[]onBreakpoint{{ // Stop at line 4
|
|
|
|
execute: func() {
|
2020-11-12 23:24:31 +00:00
|
|
|
handleStop(t, client, 1, "main.main", 4)
|
2020-08-24 17:21:51 +00:00
|
|
|
|
|
|
|
expectStoppedOnError := func(errorPrefix string) {
|
|
|
|
t.Helper()
|
|
|
|
oe := client.ExpectOutputEvent(t)
|
|
|
|
if oe.Body.Category != "stderr" || !strings.HasPrefix(oe.Body.Output, "ERROR: "+errorPrefix) {
|
|
|
|
t.Errorf("\ngot %#v\nwant Category=\"stderr\" Output=\"%s ...\"", oe, errorPrefix)
|
|
|
|
}
|
2021-05-04 19:49:52 +00:00
|
|
|
se := client.ExpectStoppedEvent(t)
|
|
|
|
if se.Body.ThreadId != 1 || se.Body.Reason != "runtime error" || !strings.HasPrefix(se.Body.Text, errorPrefix) {
|
|
|
|
t.Errorf("\ngot %#v\nwant ThreadId=1 Reason=\"runtime error\" Text=\"%s\"", se, errorPrefix)
|
|
|
|
}
|
2020-08-24 17:21:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
client.ContinueRequest(1)
|
|
|
|
client.ExpectContinueResponse(t)
|
|
|
|
expectStoppedOnError("invalid memory address or nil pointer dereference")
|
|
|
|
|
|
|
|
client.NextRequest(1)
|
|
|
|
client.ExpectNextResponse(t)
|
|
|
|
expectStoppedOnError("invalid memory address or nil pointer dereference")
|
|
|
|
|
|
|
|
client.NextRequest(1)
|
|
|
|
client.ExpectNextResponse(t)
|
|
|
|
expectStoppedOnError("next while nexting")
|
|
|
|
|
|
|
|
client.StepInRequest(1)
|
|
|
|
client.ExpectStepInResponse(t)
|
|
|
|
expectStoppedOnError("next while nexting")
|
|
|
|
|
|
|
|
client.StepOutRequest(1)
|
|
|
|
client.ExpectStepOutResponse(t)
|
|
|
|
expectStoppedOnError("next while nexting")
|
|
|
|
},
|
|
|
|
disconnect: true,
|
|
|
|
}})
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2020-10-07 15:24:40 +00:00
|
|
|
func TestPanicBreakpointOnContinue(t *testing.T) {
|
|
|
|
runTest(t, "panic", func(client *daptest.Client, fixture protest.Fixture) {
|
2020-12-28 17:14:15 +00:00
|
|
|
runDebugSessionWithBPs(t, client, "launch",
|
2020-10-07 15:24:40 +00:00
|
|
|
// Launch
|
|
|
|
func() {
|
|
|
|
client.LaunchRequest("exec", fixture.Path, !stopOnEntry)
|
|
|
|
},
|
|
|
|
// Set breakpoints
|
|
|
|
fixture.Source, []int{5},
|
|
|
|
[]onBreakpoint{{
|
|
|
|
execute: func() {
|
2020-11-12 23:24:31 +00:00
|
|
|
handleStop(t, client, 1, "main.main", 5)
|
2020-10-07 15:24:40 +00:00
|
|
|
|
|
|
|
client.ContinueRequest(1)
|
|
|
|
client.ExpectContinueResponse(t)
|
|
|
|
|
|
|
|
se := client.ExpectStoppedEvent(t)
|
|
|
|
if se.Body.ThreadId != 1 || se.Body.Reason != "panic" {
|
|
|
|
t.Errorf("\ngot %#v\nwant ThreadId=1 Reason=\"panic\"", se)
|
|
|
|
}
|
|
|
|
},
|
|
|
|
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) {
|
2020-12-28 17:14:15 +00:00
|
|
|
runDebugSessionWithBPs(t, client, "launch",
|
2020-10-07 15:24:40 +00:00
|
|
|
// Launch
|
|
|
|
func() {
|
|
|
|
client.LaunchRequest("exec", fixture.Path, !stopOnEntry)
|
|
|
|
},
|
|
|
|
// Set breakpoints
|
|
|
|
fixture.Source, []int{5},
|
|
|
|
[]onBreakpoint{{
|
|
|
|
execute: func() {
|
2020-11-12 23:24:31 +00:00
|
|
|
handleStop(t, client, 1, "main.main", 5)
|
2020-10-07 15:24:40 +00:00
|
|
|
|
|
|
|
client.NextRequest(1)
|
|
|
|
client.ExpectNextResponse(t)
|
|
|
|
|
|
|
|
se := client.ExpectStoppedEvent(t)
|
|
|
|
|
|
|
|
if se.Body.ThreadId != 1 || se.Body.Reason != "panic" {
|
|
|
|
t.Errorf("\ngot %#v\nexpected ThreadId=1 Reason=\"panic\"", se)
|
|
|
|
}
|
|
|
|
},
|
|
|
|
disconnect: true,
|
|
|
|
}})
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestFatalThrowBreakpoint(t *testing.T) {
|
|
|
|
runTest(t, "testdeadlock", func(client *daptest.Client, fixture protest.Fixture) {
|
2020-12-28 17:14:15 +00:00
|
|
|
runDebugSessionWithBPs(t, client, "launch",
|
2020-10-07 15:24:40 +00:00
|
|
|
// Launch
|
|
|
|
func() {
|
|
|
|
client.LaunchRequest("exec", fixture.Path, !stopOnEntry)
|
|
|
|
},
|
|
|
|
// Set breakpoints
|
|
|
|
fixture.Source, []int{3},
|
|
|
|
[]onBreakpoint{{
|
|
|
|
execute: func() {
|
2020-11-12 23:24:31 +00:00
|
|
|
handleStop(t, client, 1, "main.main", 3)
|
2020-10-07 15:24:40 +00:00
|
|
|
|
|
|
|
client.ContinueRequest(1)
|
|
|
|
client.ExpectContinueResponse(t)
|
|
|
|
|
|
|
|
se := client.ExpectStoppedEvent(t)
|
|
|
|
if se.Body.Reason != "fatal error" {
|
|
|
|
t.Errorf("\ngot %#v\nwant Reason=\"fatal error\"", se)
|
|
|
|
}
|
|
|
|
},
|
|
|
|
disconnect: true,
|
|
|
|
}})
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2020-08-24 17:21:51 +00:00
|
|
|
// handleStop covers the standard sequence of reqeusts 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.
|
2020-11-12 23:24:31 +00:00
|
|
|
// Skips line check if line is -1.
|
|
|
|
func handleStop(t *testing.T, client *daptest.Client, thread int, name string, line int) {
|
2020-08-24 17:21:51 +00:00
|
|
|
t.Helper()
|
|
|
|
client.ThreadsRequest()
|
|
|
|
client.ExpectThreadsResponse(t)
|
|
|
|
|
|
|
|
client.StackTraceRequest(thread, 0, 20)
|
|
|
|
st := client.ExpectStackTraceResponse(t)
|
2020-11-12 23:24:31 +00:00
|
|
|
if len(st.Body.StackFrames) < 1 {
|
|
|
|
t.Errorf("\ngot %#v\nwant len(stackframes) => 1", st)
|
|
|
|
} else {
|
|
|
|
if line != -1 && st.Body.StackFrames[0].Line != line {
|
|
|
|
t.Errorf("\ngot %#v\nwant Line=%d", st, line)
|
|
|
|
}
|
|
|
|
if st.Body.StackFrames[0].Name != name {
|
|
|
|
t.Errorf("\ngot %#v\nwant Name=%q", st, name)
|
|
|
|
}
|
2020-08-24 17:21:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
client.ScopesRequest(1000)
|
|
|
|
client.ExpectScopesResponse(t)
|
|
|
|
|
|
|
|
client.VariablesRequest(1000) // Arguments
|
|
|
|
client.ExpectVariablesResponse(t)
|
|
|
|
client.VariablesRequest(1001) // Locals
|
|
|
|
client.ExpectVariablesResponse(t)
|
|
|
|
}
|
|
|
|
|
2020-08-11 15:34:27 +00:00
|
|
|
// 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.
|
2021-04-12 21:50:15 +00:00
|
|
|
// If terminated is true, we expect requests at this breakpoint
|
|
|
|
// to result in termination.
|
2020-08-11 15:34:27 +00:00
|
|
|
type onBreakpoint struct {
|
|
|
|
execute func()
|
|
|
|
disconnect bool
|
2021-04-12 21:50:15 +00:00
|
|
|
terminated bool
|
2020-08-11 15:34:27 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// runDebugSessionWithBPs is a helper for executing the common init and shutdown
|
2020-03-10 19:29:06 +00:00
|
|
|
// sequences for a program that does not stop on entry
|
2020-12-28 17:14:15 +00:00
|
|
|
// 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.
|
2020-07-08 17:20:05 +00:00
|
|
|
// breakpoints - list of lines, where breakpoints are to be set
|
2020-08-11 15:34:27 +00:00
|
|
|
// onBreakpoints - list of test sequences to execute at each of the set breakpoints.
|
2020-12-28 17:14:15 +00:00
|
|
|
func runDebugSessionWithBPs(t *testing.T, client *daptest.Client, cmd string, cmdRequest func(), source string, breakpoints []int, onBPs []onBreakpoint) {
|
2020-03-04 17:22:51 +00:00
|
|
|
client.InitializeRequest()
|
2021-05-06 09:11:45 +00:00
|
|
|
client.ExpectInitializeResponseAndCapabilities(t)
|
2020-03-04 17:22:51 +00:00
|
|
|
|
2020-12-28 17:14:15 +00:00
|
|
|
cmdRequest()
|
2020-03-04 17:22:51 +00:00
|
|
|
client.ExpectInitializedEvent(t)
|
2020-12-28 17:14:15 +00:00
|
|
|
if cmd == "launch" {
|
|
|
|
client.ExpectLaunchResponse(t)
|
|
|
|
} else if cmd == "attach" {
|
|
|
|
client.ExpectAttachResponse(t)
|
|
|
|
} else {
|
|
|
|
panic("expected launch or attach command")
|
|
|
|
}
|
2020-03-04 17:22:51 +00:00
|
|
|
|
2020-07-08 17:20:05 +00:00
|
|
|
client.SetBreakpointsRequest(source, breakpoints)
|
|
|
|
client.ExpectSetBreakpointsResponse(t)
|
|
|
|
|
2020-03-10 19:29:06 +00:00
|
|
|
// Skip no-op setExceptionBreakpoints
|
|
|
|
|
2020-03-04 17:22:51 +00:00
|
|
|
client.ConfigurationDoneRequest()
|
|
|
|
client.ExpectConfigurationDoneResponse(t)
|
|
|
|
|
2020-07-08 17:20:05 +00:00
|
|
|
// Program automatically continues to breakpoint or completion
|
|
|
|
|
2020-08-11 15:34:27 +00:00
|
|
|
// 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 {
|
2020-07-08 17:20:05 +00:00
|
|
|
client.ExpectStoppedEvent(t)
|
2020-08-11 15:34:27 +00:00
|
|
|
onBP.execute()
|
|
|
|
if onBP.disconnect {
|
2021-04-12 21:50:15 +00:00
|
|
|
if onBP.terminated {
|
|
|
|
client.ExpectTerminatedEvent(t)
|
|
|
|
}
|
2020-12-28 17:14:15 +00:00
|
|
|
client.DisconnectRequestWithKillOption(true)
|
2021-04-12 21:50:15 +00:00
|
|
|
if onBP.terminated {
|
|
|
|
client.ExpectOutputEventProcessExited(t, 0)
|
|
|
|
client.ExpectOutputEventDetaching(t)
|
|
|
|
} else {
|
|
|
|
client.ExpectOutputEventDetachingKill(t)
|
|
|
|
}
|
2020-08-11 15:34:27 +00:00
|
|
|
client.ExpectDisconnectResponse(t)
|
|
|
|
return
|
|
|
|
}
|
2020-07-08 17:20:05 +00:00
|
|
|
client.ContinueRequest(1)
|
|
|
|
client.ExpectContinueResponse(t)
|
|
|
|
// "Continue" is triggered after the response is sent
|
|
|
|
}
|
2020-03-10 19:29:06 +00:00
|
|
|
|
2020-12-28 17:14:15 +00:00
|
|
|
if cmd == "launch" { // Let the program run to completion
|
|
|
|
client.ExpectTerminatedEvent(t)
|
|
|
|
}
|
|
|
|
client.DisconnectRequestWithKillOption(true)
|
2021-04-12 21:50:15 +00:00
|
|
|
if cmd == "launch" {
|
|
|
|
client.ExpectOutputEventProcessExited(t, 0)
|
|
|
|
client.ExpectOutputEventDetaching(t)
|
|
|
|
} else if cmd == "attach" {
|
|
|
|
client.ExpectOutputEventDetachingKill(t)
|
|
|
|
}
|
2020-03-04 17:22:51 +00:00
|
|
|
client.ExpectDisconnectResponse(t)
|
|
|
|
}
|
|
|
|
|
2020-10-02 16:18:33 +00:00
|
|
|
// runDebugSession is a helper for executing the standard init and shutdown
|
2020-07-08 17:20:05 +00:00
|
|
|
// sequences for a program that does not stop on entry
|
|
|
|
// while specifying unique launch criteria via parameters.
|
2020-12-28 17:14:15 +00:00
|
|
|
func runDebugSession(t *testing.T, client *daptest.Client, cmd string, cmdRequest func(), source string) {
|
|
|
|
runDebugSessionWithBPs(t, client, cmd, cmdRequest, source, nil, nil)
|
2020-07-08 17:20:05 +00:00
|
|
|
}
|
|
|
|
|
2020-03-04 17:22:51 +00:00
|
|
|
func TestLaunchDebugRequest(t *testing.T) {
|
2021-02-24 16:19:07 +00:00
|
|
|
rescueStderr := os.Stderr
|
|
|
|
r, w, _ := os.Pipe()
|
|
|
|
os.Stderr = w
|
|
|
|
|
2021-04-14 07:22:40 +00:00
|
|
|
tmpBin := "__tmpBin"
|
2020-02-15 19:52:53 +00:00
|
|
|
runTest(t, "increment", func(client *daptest.Client, fixture protest.Fixture) {
|
2020-05-28 21:01:51 +00:00
|
|
|
// We reuse the harness that builds, but ignore the built binary,
|
|
|
|
// only relying on the source to be built in response to LaunchRequest.
|
2020-12-28 17:14:15 +00:00
|
|
|
runDebugSession(t, client, "launch", func() {
|
2021-03-24 18:02:22 +00:00
|
|
|
wd, _ := os.Getwd()
|
2020-03-04 17:22:51 +00:00
|
|
|
client.LaunchRequestWithArgs(map[string]interface{}{
|
2021-04-14 07:22:40 +00:00
|
|
|
"mode": "debug", "program": fixture.Source, "output": filepath.Join(wd, tmpBin)})
|
2020-10-02 16:18:33 +00:00
|
|
|
}, fixture.Source)
|
2020-03-04 17:22:51 +00:00
|
|
|
})
|
2021-02-24 16:19:07 +00:00
|
|
|
// Wait for the test to finish to capture all stderr
|
|
|
|
time.Sleep(100 * time.Millisecond)
|
|
|
|
|
|
|
|
w.Close()
|
|
|
|
err, _ := ioutil.ReadAll(r)
|
|
|
|
t.Log(string(err))
|
|
|
|
os.Stderr = rescueStderr
|
|
|
|
|
|
|
|
rmErrRe, _ := regexp.Compile(`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?)
|
2021-03-15 16:34:26 +00:00
|
|
|
// 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" || !strings.Contains(rmErr, "Access is denied") {
|
|
|
|
t.Fatalf("Binary removal failure:\n%s\n", rmErr)
|
|
|
|
}
|
2021-04-14 07:22:40 +00:00
|
|
|
} else {
|
|
|
|
// 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)
|
|
|
|
}
|
2021-02-24 16:19:07 +00:00
|
|
|
}
|
2020-03-04 17:22:51 +00:00
|
|
|
}
|
2020-02-24 17:36:34 +00:00
|
|
|
|
2021-03-08 17:42:54 +00:00
|
|
|
// 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{}{
|
2021-03-24 18:02:22 +00:00
|
|
|
"mode": "" /*"debug" by default*/, "program": fixture.Source, "output": "__mybin"})
|
2021-03-08 17:42:54 +00:00
|
|
|
}, fixture.Source)
|
|
|
|
})
|
|
|
|
runTest(t, "increment", func(client *daptest.Client, fixture protest.Fixture) {
|
|
|
|
runDebugSession(t, client, "launch", func() {
|
|
|
|
// Use the default output directory.
|
|
|
|
client.LaunchRequestWithArgs(map[string]interface{}{
|
2021-03-24 18:02:22 +00:00
|
|
|
/*"mode":"debug" by default*/ "program": fixture.Source, "output": "__mybin"})
|
2021-03-08 17:42:54 +00:00
|
|
|
}, fixture.Source)
|
|
|
|
})
|
|
|
|
runTest(t, "increment", func(client *daptest.Client, fixture protest.Fixture) {
|
|
|
|
runDebugSession(t, client, "launch", func() {
|
|
|
|
// Use the default output directory.
|
|
|
|
client.LaunchRequestWithArgs(map[string]interface{}{
|
|
|
|
"mode": "debug", "program": fixture.Source})
|
|
|
|
// writes to default output dir __debug_bin
|
|
|
|
}, fixture.Source)
|
|
|
|
})
|
|
|
|
|
dap: change how noDebug launch request is served (#2407)
* dap: change how noDebug launch request is served
PR #2400 added support for noDebug launch requests - that was done
by directly starting the target program using os/exec.Cmd and blocking
the launch request indefinitely until the program terminates or
is stopped with a disconnect request (when dlv dap can support it).
Even though the approach seemed to work for user, that adds an extra
control flow and complexity to the codebase.
This change takes a different approach to implement the noDebug
launch feature. Instead of using os/exec.Cmd, this uses the existing
debug launch path, but avoids setting breakpoints. Finally, the program
will start upon receiving onConfigurationDoneRequest and blocks there
until the program terminates. This simplifies the code.
The DAP spec does not explicitly specify what to do about other
requests. It seems like VSCode can issue evaluate requests or other
requests after the configuration done request. Currently they are
blocked because the program is in running state. We can consider checking
s.noDebug and responding with an error in the future if we want/need to.
See the log below for a typical DAP request/response sequence:
2021-03-29T01:42:53-04:00 debug layer=dap building binary at /Users/hakim/projects/s/__debug_bin
2021-03-29T01:42:55-04:00 info layer=debugger launching process with args: [/Users/hakim/projects/s/__debug_bin]
2021-03-29T01:42:55-04:00 debug layer=dap [-> to client]{"seq":0,"type":"event","event":"initialized"}
2021-03-29T01:42:55-04:00 debug layer=dap [-> to client]{"seq":0,"type":"response","request_seq":2,"success":true,"command":"launch"}
2021-03-29T01:42:55-04:00 debug layer=dap [<- from client]{"seq":3,"type":"request","command":"setBreakpoints","arguments":{"source":{"name":"main.go","path":"/Users/hakim/projects/s/main.go"},"breakpoints":[{"line":9}],"lines":[9]}}
2021-03-29T01:42:55-04:00 error layer=dap Unable to set or clear breakpoints: running in noDebug mode
2021-03-29T01:42:55-04:00 debug layer=dap [-> to client]{"seq":0,"type":"response","request_seq":3,"success":false,"command":"setBreakpoints","message":"Unable to set or clear breakpoints","body":{"error":{"id":2002,"format":"Unable to set or clear breakpoints: running in noDebug mode"}}}
2021-03-29T01:42:55-04:00 debug layer=dap [<- from client]{"seq":4,"type":"request","command":"configurationDone","arguments":{}}
2021-03-29T01:42:55-04:00 debug layer=dap [-> to client]{"seq":0,"type":"response","request_seq":4,"success":true,"command":"configurationDone"}
2021-03-29T01:42:55-04:00 debug layer=debugger continuing
Hello
2021-03-29T01:43:00-04:00 debug layer=dap [-> to client]{"seq":0,"type":"event","event":"terminated","body":{}}
2021-03-29T01:43:00-04:00 debug layer=dap [<- from client]{"seq":5,"type":"request","command":"threads"}
2021-03-29T01:43:00-04:00 debug layer=dap [-> to client]{"seq":0,"type":"response","request_seq":5,"success":true,"command":"threads","body":{"threads":null}}
2021-03-29T01:43:00-04:00 debug layer=dap [<- from client]{"seq":6,"type":"request","command":"disconnect","arguments":{}}
2021-03-29T01:43:00-04:00 debug layer=dap [-> to client]{"seq":0,"type":"response","request_seq":6,"success":true,"command":"disconnect"}
2021-03-29T01:43:00-04:00 debug layer=debugger halting
2021-03-29T01:43:00-04:00 error layer=dap Process 27219 has exited with status 0
2021-03-29T01:43:00-04:00 debug layer=debugger detaching
Updates https://github.com/golang/vscode-go/issues/1111
* service/dap: address polina's comments for noDebug logic
Reworked so the noDebug launch request again blocks launch request
handler after responding with the launch response. By skipping
the initialized event, it should prevent well-behaving clients from
sending any further requests. Currently, doesn't matter because
the handler goroutine will be blocked until the program termination
anyway.
Placed mutex back, since I noticed that's the only way to prevent
racing between Stop and the handler goroutine. The synchronization
lotic (in particular, during teardown) should be revisited after
asynchronous request support work is done.
* dap: address more comments
2021-04-05 18:44:02 +00:00
|
|
|
// if noDebug is not a bool, behave as if it is the default value (false).
|
2021-03-23 19:10:21 +00:00
|
|
|
runTest(t, "increment", func(client *daptest.Client, fixture protest.Fixture) {
|
dap: change how noDebug launch request is served (#2407)
* dap: change how noDebug launch request is served
PR #2400 added support for noDebug launch requests - that was done
by directly starting the target program using os/exec.Cmd and blocking
the launch request indefinitely until the program terminates or
is stopped with a disconnect request (when dlv dap can support it).
Even though the approach seemed to work for user, that adds an extra
control flow and complexity to the codebase.
This change takes a different approach to implement the noDebug
launch feature. Instead of using os/exec.Cmd, this uses the existing
debug launch path, but avoids setting breakpoints. Finally, the program
will start upon receiving onConfigurationDoneRequest and blocks there
until the program terminates. This simplifies the code.
The DAP spec does not explicitly specify what to do about other
requests. It seems like VSCode can issue evaluate requests or other
requests after the configuration done request. Currently they are
blocked because the program is in running state. We can consider checking
s.noDebug and responding with an error in the future if we want/need to.
See the log below for a typical DAP request/response sequence:
2021-03-29T01:42:53-04:00 debug layer=dap building binary at /Users/hakim/projects/s/__debug_bin
2021-03-29T01:42:55-04:00 info layer=debugger launching process with args: [/Users/hakim/projects/s/__debug_bin]
2021-03-29T01:42:55-04:00 debug layer=dap [-> to client]{"seq":0,"type":"event","event":"initialized"}
2021-03-29T01:42:55-04:00 debug layer=dap [-> to client]{"seq":0,"type":"response","request_seq":2,"success":true,"command":"launch"}
2021-03-29T01:42:55-04:00 debug layer=dap [<- from client]{"seq":3,"type":"request","command":"setBreakpoints","arguments":{"source":{"name":"main.go","path":"/Users/hakim/projects/s/main.go"},"breakpoints":[{"line":9}],"lines":[9]}}
2021-03-29T01:42:55-04:00 error layer=dap Unable to set or clear breakpoints: running in noDebug mode
2021-03-29T01:42:55-04:00 debug layer=dap [-> to client]{"seq":0,"type":"response","request_seq":3,"success":false,"command":"setBreakpoints","message":"Unable to set or clear breakpoints","body":{"error":{"id":2002,"format":"Unable to set or clear breakpoints: running in noDebug mode"}}}
2021-03-29T01:42:55-04:00 debug layer=dap [<- from client]{"seq":4,"type":"request","command":"configurationDone","arguments":{}}
2021-03-29T01:42:55-04:00 debug layer=dap [-> to client]{"seq":0,"type":"response","request_seq":4,"success":true,"command":"configurationDone"}
2021-03-29T01:42:55-04:00 debug layer=debugger continuing
Hello
2021-03-29T01:43:00-04:00 debug layer=dap [-> to client]{"seq":0,"type":"event","event":"terminated","body":{}}
2021-03-29T01:43:00-04:00 debug layer=dap [<- from client]{"seq":5,"type":"request","command":"threads"}
2021-03-29T01:43:00-04:00 debug layer=dap [-> to client]{"seq":0,"type":"response","request_seq":5,"success":true,"command":"threads","body":{"threads":null}}
2021-03-29T01:43:00-04:00 debug layer=dap [<- from client]{"seq":6,"type":"request","command":"disconnect","arguments":{}}
2021-03-29T01:43:00-04:00 debug layer=dap [-> to client]{"seq":0,"type":"response","request_seq":6,"success":true,"command":"disconnect"}
2021-03-29T01:43:00-04:00 debug layer=debugger halting
2021-03-29T01:43:00-04:00 error layer=dap Process 27219 has exited with status 0
2021-03-29T01:43:00-04:00 debug layer=debugger detaching
Updates https://github.com/golang/vscode-go/issues/1111
* service/dap: address polina's comments for noDebug logic
Reworked so the noDebug launch request again blocks launch request
handler after responding with the launch response. By skipping
the initialized event, it should prevent well-behaving clients from
sending any further requests. Currently, doesn't matter because
the handler goroutine will be blocked until the program termination
anyway.
Placed mutex back, since I noticed that's the only way to prevent
racing between Stop and the handler goroutine. The synchronization
lotic (in particular, during teardown) should be revisited after
asynchronous request support work is done.
* dap: address more comments
2021-04-05 18:44:02 +00:00
|
|
|
runDebugSession(t, client, "launch", func() {
|
2021-03-23 19:10:21 +00:00
|
|
|
client.LaunchRequestWithArgs(map[string]interface{}{
|
dap: change how noDebug launch request is served (#2407)
* dap: change how noDebug launch request is served
PR #2400 added support for noDebug launch requests - that was done
by directly starting the target program using os/exec.Cmd and blocking
the launch request indefinitely until the program terminates or
is stopped with a disconnect request (when dlv dap can support it).
Even though the approach seemed to work for user, that adds an extra
control flow and complexity to the codebase.
This change takes a different approach to implement the noDebug
launch feature. Instead of using os/exec.Cmd, this uses the existing
debug launch path, but avoids setting breakpoints. Finally, the program
will start upon receiving onConfigurationDoneRequest and blocks there
until the program terminates. This simplifies the code.
The DAP spec does not explicitly specify what to do about other
requests. It seems like VSCode can issue evaluate requests or other
requests after the configuration done request. Currently they are
blocked because the program is in running state. We can consider checking
s.noDebug and responding with an error in the future if we want/need to.
See the log below for a typical DAP request/response sequence:
2021-03-29T01:42:53-04:00 debug layer=dap building binary at /Users/hakim/projects/s/__debug_bin
2021-03-29T01:42:55-04:00 info layer=debugger launching process with args: [/Users/hakim/projects/s/__debug_bin]
2021-03-29T01:42:55-04:00 debug layer=dap [-> to client]{"seq":0,"type":"event","event":"initialized"}
2021-03-29T01:42:55-04:00 debug layer=dap [-> to client]{"seq":0,"type":"response","request_seq":2,"success":true,"command":"launch"}
2021-03-29T01:42:55-04:00 debug layer=dap [<- from client]{"seq":3,"type":"request","command":"setBreakpoints","arguments":{"source":{"name":"main.go","path":"/Users/hakim/projects/s/main.go"},"breakpoints":[{"line":9}],"lines":[9]}}
2021-03-29T01:42:55-04:00 error layer=dap Unable to set or clear breakpoints: running in noDebug mode
2021-03-29T01:42:55-04:00 debug layer=dap [-> to client]{"seq":0,"type":"response","request_seq":3,"success":false,"command":"setBreakpoints","message":"Unable to set or clear breakpoints","body":{"error":{"id":2002,"format":"Unable to set or clear breakpoints: running in noDebug mode"}}}
2021-03-29T01:42:55-04:00 debug layer=dap [<- from client]{"seq":4,"type":"request","command":"configurationDone","arguments":{}}
2021-03-29T01:42:55-04:00 debug layer=dap [-> to client]{"seq":0,"type":"response","request_seq":4,"success":true,"command":"configurationDone"}
2021-03-29T01:42:55-04:00 debug layer=debugger continuing
Hello
2021-03-29T01:43:00-04:00 debug layer=dap [-> to client]{"seq":0,"type":"event","event":"terminated","body":{}}
2021-03-29T01:43:00-04:00 debug layer=dap [<- from client]{"seq":5,"type":"request","command":"threads"}
2021-03-29T01:43:00-04:00 debug layer=dap [-> to client]{"seq":0,"type":"response","request_seq":5,"success":true,"command":"threads","body":{"threads":null}}
2021-03-29T01:43:00-04:00 debug layer=dap [<- from client]{"seq":6,"type":"request","command":"disconnect","arguments":{}}
2021-03-29T01:43:00-04:00 debug layer=dap [-> to client]{"seq":0,"type":"response","request_seq":6,"success":true,"command":"disconnect"}
2021-03-29T01:43:00-04:00 debug layer=debugger halting
2021-03-29T01:43:00-04:00 error layer=dap Process 27219 has exited with status 0
2021-03-29T01:43:00-04:00 debug layer=debugger detaching
Updates https://github.com/golang/vscode-go/issues/1111
* service/dap: address polina's comments for noDebug logic
Reworked so the noDebug launch request again blocks launch request
handler after responding with the launch response. By skipping
the initialized event, it should prevent well-behaving clients from
sending any further requests. Currently, doesn't matter because
the handler goroutine will be blocked until the program termination
anyway.
Placed mutex back, since I noticed that's the only way to prevent
racing between Stop and the handler goroutine. The synchronization
lotic (in particular, during teardown) should be revisited after
asynchronous request support work is done.
* dap: address more comments
2021-04-05 18:44:02 +00:00
|
|
|
"mode": "debug", "program": fixture.Source, "noDebug": "true"})
|
2021-03-23 19:10:21 +00:00
|
|
|
}, fixture.Source)
|
|
|
|
})
|
dap: change how noDebug launch request is served (#2407)
* dap: change how noDebug launch request is served
PR #2400 added support for noDebug launch requests - that was done
by directly starting the target program using os/exec.Cmd and blocking
the launch request indefinitely until the program terminates or
is stopped with a disconnect request (when dlv dap can support it).
Even though the approach seemed to work for user, that adds an extra
control flow and complexity to the codebase.
This change takes a different approach to implement the noDebug
launch feature. Instead of using os/exec.Cmd, this uses the existing
debug launch path, but avoids setting breakpoints. Finally, the program
will start upon receiving onConfigurationDoneRequest and blocks there
until the program terminates. This simplifies the code.
The DAP spec does not explicitly specify what to do about other
requests. It seems like VSCode can issue evaluate requests or other
requests after the configuration done request. Currently they are
blocked because the program is in running state. We can consider checking
s.noDebug and responding with an error in the future if we want/need to.
See the log below for a typical DAP request/response sequence:
2021-03-29T01:42:53-04:00 debug layer=dap building binary at /Users/hakim/projects/s/__debug_bin
2021-03-29T01:42:55-04:00 info layer=debugger launching process with args: [/Users/hakim/projects/s/__debug_bin]
2021-03-29T01:42:55-04:00 debug layer=dap [-> to client]{"seq":0,"type":"event","event":"initialized"}
2021-03-29T01:42:55-04:00 debug layer=dap [-> to client]{"seq":0,"type":"response","request_seq":2,"success":true,"command":"launch"}
2021-03-29T01:42:55-04:00 debug layer=dap [<- from client]{"seq":3,"type":"request","command":"setBreakpoints","arguments":{"source":{"name":"main.go","path":"/Users/hakim/projects/s/main.go"},"breakpoints":[{"line":9}],"lines":[9]}}
2021-03-29T01:42:55-04:00 error layer=dap Unable to set or clear breakpoints: running in noDebug mode
2021-03-29T01:42:55-04:00 debug layer=dap [-> to client]{"seq":0,"type":"response","request_seq":3,"success":false,"command":"setBreakpoints","message":"Unable to set or clear breakpoints","body":{"error":{"id":2002,"format":"Unable to set or clear breakpoints: running in noDebug mode"}}}
2021-03-29T01:42:55-04:00 debug layer=dap [<- from client]{"seq":4,"type":"request","command":"configurationDone","arguments":{}}
2021-03-29T01:42:55-04:00 debug layer=dap [-> to client]{"seq":0,"type":"response","request_seq":4,"success":true,"command":"configurationDone"}
2021-03-29T01:42:55-04:00 debug layer=debugger continuing
Hello
2021-03-29T01:43:00-04:00 debug layer=dap [-> to client]{"seq":0,"type":"event","event":"terminated","body":{}}
2021-03-29T01:43:00-04:00 debug layer=dap [<- from client]{"seq":5,"type":"request","command":"threads"}
2021-03-29T01:43:00-04:00 debug layer=dap [-> to client]{"seq":0,"type":"response","request_seq":5,"success":true,"command":"threads","body":{"threads":null}}
2021-03-29T01:43:00-04:00 debug layer=dap [<- from client]{"seq":6,"type":"request","command":"disconnect","arguments":{}}
2021-03-29T01:43:00-04:00 debug layer=dap [-> to client]{"seq":0,"type":"response","request_seq":6,"success":true,"command":"disconnect"}
2021-03-29T01:43:00-04:00 debug layer=debugger halting
2021-03-29T01:43:00-04:00 error layer=dap Process 27219 has exited with status 0
2021-03-29T01:43:00-04:00 debug layer=debugger detaching
Updates https://github.com/golang/vscode-go/issues/1111
* service/dap: address polina's comments for noDebug logic
Reworked so the noDebug launch request again blocks launch request
handler after responding with the launch response. By skipping
the initialized event, it should prevent well-behaving clients from
sending any further requests. Currently, doesn't matter because
the handler goroutine will be blocked until the program termination
anyway.
Placed mutex back, since I noticed that's the only way to prevent
racing between Stop and the handler goroutine. The synchronization
lotic (in particular, during teardown) should be revisited after
asynchronous request support work is done.
* dap: address more comments
2021-04-05 18:44:02 +00:00
|
|
|
}
|
|
|
|
|
2021-04-12 21:50:15 +00:00
|
|
|
func TestLaunchRequestNoDebug_GoodStatus(t *testing.T) {
|
2021-03-23 19:10:21 +00:00
|
|
|
runTest(t, "increment", func(client *daptest.Client, fixture protest.Fixture) {
|
dap: change how noDebug launch request is served (#2407)
* dap: change how noDebug launch request is served
PR #2400 added support for noDebug launch requests - that was done
by directly starting the target program using os/exec.Cmd and blocking
the launch request indefinitely until the program terminates or
is stopped with a disconnect request (when dlv dap can support it).
Even though the approach seemed to work for user, that adds an extra
control flow and complexity to the codebase.
This change takes a different approach to implement the noDebug
launch feature. Instead of using os/exec.Cmd, this uses the existing
debug launch path, but avoids setting breakpoints. Finally, the program
will start upon receiving onConfigurationDoneRequest and blocks there
until the program terminates. This simplifies the code.
The DAP spec does not explicitly specify what to do about other
requests. It seems like VSCode can issue evaluate requests or other
requests after the configuration done request. Currently they are
blocked because the program is in running state. We can consider checking
s.noDebug and responding with an error in the future if we want/need to.
See the log below for a typical DAP request/response sequence:
2021-03-29T01:42:53-04:00 debug layer=dap building binary at /Users/hakim/projects/s/__debug_bin
2021-03-29T01:42:55-04:00 info layer=debugger launching process with args: [/Users/hakim/projects/s/__debug_bin]
2021-03-29T01:42:55-04:00 debug layer=dap [-> to client]{"seq":0,"type":"event","event":"initialized"}
2021-03-29T01:42:55-04:00 debug layer=dap [-> to client]{"seq":0,"type":"response","request_seq":2,"success":true,"command":"launch"}
2021-03-29T01:42:55-04:00 debug layer=dap [<- from client]{"seq":3,"type":"request","command":"setBreakpoints","arguments":{"source":{"name":"main.go","path":"/Users/hakim/projects/s/main.go"},"breakpoints":[{"line":9}],"lines":[9]}}
2021-03-29T01:42:55-04:00 error layer=dap Unable to set or clear breakpoints: running in noDebug mode
2021-03-29T01:42:55-04:00 debug layer=dap [-> to client]{"seq":0,"type":"response","request_seq":3,"success":false,"command":"setBreakpoints","message":"Unable to set or clear breakpoints","body":{"error":{"id":2002,"format":"Unable to set or clear breakpoints: running in noDebug mode"}}}
2021-03-29T01:42:55-04:00 debug layer=dap [<- from client]{"seq":4,"type":"request","command":"configurationDone","arguments":{}}
2021-03-29T01:42:55-04:00 debug layer=dap [-> to client]{"seq":0,"type":"response","request_seq":4,"success":true,"command":"configurationDone"}
2021-03-29T01:42:55-04:00 debug layer=debugger continuing
Hello
2021-03-29T01:43:00-04:00 debug layer=dap [-> to client]{"seq":0,"type":"event","event":"terminated","body":{}}
2021-03-29T01:43:00-04:00 debug layer=dap [<- from client]{"seq":5,"type":"request","command":"threads"}
2021-03-29T01:43:00-04:00 debug layer=dap [-> to client]{"seq":0,"type":"response","request_seq":5,"success":true,"command":"threads","body":{"threads":null}}
2021-03-29T01:43:00-04:00 debug layer=dap [<- from client]{"seq":6,"type":"request","command":"disconnect","arguments":{}}
2021-03-29T01:43:00-04:00 debug layer=dap [-> to client]{"seq":0,"type":"response","request_seq":6,"success":true,"command":"disconnect"}
2021-03-29T01:43:00-04:00 debug layer=debugger halting
2021-03-29T01:43:00-04:00 error layer=dap Process 27219 has exited with status 0
2021-03-29T01:43:00-04:00 debug layer=debugger detaching
Updates https://github.com/golang/vscode-go/issues/1111
* service/dap: address polina's comments for noDebug logic
Reworked so the noDebug launch request again blocks launch request
handler after responding with the launch response. By skipping
the initialized event, it should prevent well-behaving clients from
sending any further requests. Currently, doesn't matter because
the handler goroutine will be blocked until the program termination
anyway.
Placed mutex back, since I noticed that's the only way to prevent
racing between Stop and the handler goroutine. The synchronization
lotic (in particular, during teardown) should be revisited after
asynchronous request support work is done.
* dap: address more comments
2021-04-05 18:44:02 +00:00
|
|
|
runNoDebugDebugSession(t, client, func() {
|
2021-03-23 19:10:21 +00:00
|
|
|
client.LaunchRequestWithArgs(map[string]interface{}{
|
|
|
|
"noDebug": true,
|
2021-04-12 21:50:15 +00:00
|
|
|
"mode": "debug",
|
|
|
|
"program": fixture.Source,
|
|
|
|
"output": cleanExeName("__mybin")})
|
|
|
|
}, fixture.Source, []int{8}, 0)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestLaunchRequestNoDebug_BadStatus(t *testing.T) {
|
|
|
|
runTest(t, "issue1101", func(client *daptest.Client, fixture protest.Fixture) {
|
|
|
|
runNoDebugDebugSession(t, client, func() {
|
|
|
|
client.LaunchRequestWithArgs(map[string]interface{}{
|
|
|
|
"noDebug": true,
|
|
|
|
"mode": "debug",
|
2021-03-23 19:10:21 +00:00
|
|
|
"program": fixture.Source,
|
2021-03-24 18:02:22 +00:00
|
|
|
"output": cleanExeName("__mybin")})
|
2021-04-12 21:50:15 +00:00
|
|
|
}, fixture.Source, []int{8}, 2)
|
2021-03-23 19:10:21 +00:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
dap: change how noDebug launch request is served (#2407)
* dap: change how noDebug launch request is served
PR #2400 added support for noDebug launch requests - that was done
by directly starting the target program using os/exec.Cmd and blocking
the launch request indefinitely until the program terminates or
is stopped with a disconnect request (when dlv dap can support it).
Even though the approach seemed to work for user, that adds an extra
control flow and complexity to the codebase.
This change takes a different approach to implement the noDebug
launch feature. Instead of using os/exec.Cmd, this uses the existing
debug launch path, but avoids setting breakpoints. Finally, the program
will start upon receiving onConfigurationDoneRequest and blocks there
until the program terminates. This simplifies the code.
The DAP spec does not explicitly specify what to do about other
requests. It seems like VSCode can issue evaluate requests or other
requests after the configuration done request. Currently they are
blocked because the program is in running state. We can consider checking
s.noDebug and responding with an error in the future if we want/need to.
See the log below for a typical DAP request/response sequence:
2021-03-29T01:42:53-04:00 debug layer=dap building binary at /Users/hakim/projects/s/__debug_bin
2021-03-29T01:42:55-04:00 info layer=debugger launching process with args: [/Users/hakim/projects/s/__debug_bin]
2021-03-29T01:42:55-04:00 debug layer=dap [-> to client]{"seq":0,"type":"event","event":"initialized"}
2021-03-29T01:42:55-04:00 debug layer=dap [-> to client]{"seq":0,"type":"response","request_seq":2,"success":true,"command":"launch"}
2021-03-29T01:42:55-04:00 debug layer=dap [<- from client]{"seq":3,"type":"request","command":"setBreakpoints","arguments":{"source":{"name":"main.go","path":"/Users/hakim/projects/s/main.go"},"breakpoints":[{"line":9}],"lines":[9]}}
2021-03-29T01:42:55-04:00 error layer=dap Unable to set or clear breakpoints: running in noDebug mode
2021-03-29T01:42:55-04:00 debug layer=dap [-> to client]{"seq":0,"type":"response","request_seq":3,"success":false,"command":"setBreakpoints","message":"Unable to set or clear breakpoints","body":{"error":{"id":2002,"format":"Unable to set or clear breakpoints: running in noDebug mode"}}}
2021-03-29T01:42:55-04:00 debug layer=dap [<- from client]{"seq":4,"type":"request","command":"configurationDone","arguments":{}}
2021-03-29T01:42:55-04:00 debug layer=dap [-> to client]{"seq":0,"type":"response","request_seq":4,"success":true,"command":"configurationDone"}
2021-03-29T01:42:55-04:00 debug layer=debugger continuing
Hello
2021-03-29T01:43:00-04:00 debug layer=dap [-> to client]{"seq":0,"type":"event","event":"terminated","body":{}}
2021-03-29T01:43:00-04:00 debug layer=dap [<- from client]{"seq":5,"type":"request","command":"threads"}
2021-03-29T01:43:00-04:00 debug layer=dap [-> to client]{"seq":0,"type":"response","request_seq":5,"success":true,"command":"threads","body":{"threads":null}}
2021-03-29T01:43:00-04:00 debug layer=dap [<- from client]{"seq":6,"type":"request","command":"disconnect","arguments":{}}
2021-03-29T01:43:00-04:00 debug layer=dap [-> to client]{"seq":0,"type":"response","request_seq":6,"success":true,"command":"disconnect"}
2021-03-29T01:43:00-04:00 debug layer=debugger halting
2021-03-29T01:43:00-04:00 error layer=dap Process 27219 has exited with status 0
2021-03-29T01:43:00-04:00 debug layer=debugger detaching
Updates https://github.com/golang/vscode-go/issues/1111
* service/dap: address polina's comments for noDebug logic
Reworked so the noDebug launch request again blocks launch request
handler after responding with the launch response. By skipping
the initialized event, it should prevent well-behaving clients from
sending any further requests. Currently, doesn't matter because
the handler goroutine will be blocked until the program termination
anyway.
Placed mutex back, since I noticed that's the only way to prevent
racing between Stop and the handler goroutine. The synchronization
lotic (in particular, during teardown) should be revisited after
asynchronous request support work is done.
* dap: address more comments
2021-04-05 18:44:02 +00:00
|
|
|
// runNoDebugDebugSession tests the session started with noDebug=true runs uninterrupted
|
|
|
|
// even when breakpoint is set.
|
2021-04-12 21:50:15 +00:00
|
|
|
func runNoDebugDebugSession(t *testing.T, client *daptest.Client, cmdRequest func(), source string, breakpoints []int, status int) {
|
2021-03-23 19:10:21 +00:00
|
|
|
client.InitializeRequest()
|
2021-05-06 09:11:45 +00:00
|
|
|
client.ExpectInitializeResponseAndCapabilities(t)
|
2021-03-23 19:10:21 +00:00
|
|
|
|
|
|
|
cmdRequest()
|
dap: change how noDebug launch request is served (#2407)
* dap: change how noDebug launch request is served
PR #2400 added support for noDebug launch requests - that was done
by directly starting the target program using os/exec.Cmd and blocking
the launch request indefinitely until the program terminates or
is stopped with a disconnect request (when dlv dap can support it).
Even though the approach seemed to work for user, that adds an extra
control flow and complexity to the codebase.
This change takes a different approach to implement the noDebug
launch feature. Instead of using os/exec.Cmd, this uses the existing
debug launch path, but avoids setting breakpoints. Finally, the program
will start upon receiving onConfigurationDoneRequest and blocks there
until the program terminates. This simplifies the code.
The DAP spec does not explicitly specify what to do about other
requests. It seems like VSCode can issue evaluate requests or other
requests after the configuration done request. Currently they are
blocked because the program is in running state. We can consider checking
s.noDebug and responding with an error in the future if we want/need to.
See the log below for a typical DAP request/response sequence:
2021-03-29T01:42:53-04:00 debug layer=dap building binary at /Users/hakim/projects/s/__debug_bin
2021-03-29T01:42:55-04:00 info layer=debugger launching process with args: [/Users/hakim/projects/s/__debug_bin]
2021-03-29T01:42:55-04:00 debug layer=dap [-> to client]{"seq":0,"type":"event","event":"initialized"}
2021-03-29T01:42:55-04:00 debug layer=dap [-> to client]{"seq":0,"type":"response","request_seq":2,"success":true,"command":"launch"}
2021-03-29T01:42:55-04:00 debug layer=dap [<- from client]{"seq":3,"type":"request","command":"setBreakpoints","arguments":{"source":{"name":"main.go","path":"/Users/hakim/projects/s/main.go"},"breakpoints":[{"line":9}],"lines":[9]}}
2021-03-29T01:42:55-04:00 error layer=dap Unable to set or clear breakpoints: running in noDebug mode
2021-03-29T01:42:55-04:00 debug layer=dap [-> to client]{"seq":0,"type":"response","request_seq":3,"success":false,"command":"setBreakpoints","message":"Unable to set or clear breakpoints","body":{"error":{"id":2002,"format":"Unable to set or clear breakpoints: running in noDebug mode"}}}
2021-03-29T01:42:55-04:00 debug layer=dap [<- from client]{"seq":4,"type":"request","command":"configurationDone","arguments":{}}
2021-03-29T01:42:55-04:00 debug layer=dap [-> to client]{"seq":0,"type":"response","request_seq":4,"success":true,"command":"configurationDone"}
2021-03-29T01:42:55-04:00 debug layer=debugger continuing
Hello
2021-03-29T01:43:00-04:00 debug layer=dap [-> to client]{"seq":0,"type":"event","event":"terminated","body":{}}
2021-03-29T01:43:00-04:00 debug layer=dap [<- from client]{"seq":5,"type":"request","command":"threads"}
2021-03-29T01:43:00-04:00 debug layer=dap [-> to client]{"seq":0,"type":"response","request_seq":5,"success":true,"command":"threads","body":{"threads":null}}
2021-03-29T01:43:00-04:00 debug layer=dap [<- from client]{"seq":6,"type":"request","command":"disconnect","arguments":{}}
2021-03-29T01:43:00-04:00 debug layer=dap [-> to client]{"seq":0,"type":"response","request_seq":6,"success":true,"command":"disconnect"}
2021-03-29T01:43:00-04:00 debug layer=debugger halting
2021-03-29T01:43:00-04:00 error layer=dap Process 27219 has exited with status 0
2021-03-29T01:43:00-04:00 debug layer=debugger detaching
Updates https://github.com/golang/vscode-go/issues/1111
* service/dap: address polina's comments for noDebug logic
Reworked so the noDebug launch request again blocks launch request
handler after responding with the launch response. By skipping
the initialized event, it should prevent well-behaving clients from
sending any further requests. Currently, doesn't matter because
the handler goroutine will be blocked until the program termination
anyway.
Placed mutex back, since I noticed that's the only way to prevent
racing between Stop and the handler goroutine. The synchronization
lotic (in particular, during teardown) should be revisited after
asynchronous request support work is done.
* dap: address more comments
2021-04-05 18:44:02 +00:00
|
|
|
// no initialized event.
|
|
|
|
// noDebug mode applies only to "launch" requests.
|
|
|
|
client.ExpectLaunchResponse(t)
|
|
|
|
|
2021-04-12 21:50:15 +00:00
|
|
|
client.ExpectOutputEventProcessExited(t, status)
|
2021-03-23 19:10:21 +00:00
|
|
|
client.ExpectTerminatedEvent(t)
|
dap: change how noDebug launch request is served (#2407)
* dap: change how noDebug launch request is served
PR #2400 added support for noDebug launch requests - that was done
by directly starting the target program using os/exec.Cmd and blocking
the launch request indefinitely until the program terminates or
is stopped with a disconnect request (when dlv dap can support it).
Even though the approach seemed to work for user, that adds an extra
control flow and complexity to the codebase.
This change takes a different approach to implement the noDebug
launch feature. Instead of using os/exec.Cmd, this uses the existing
debug launch path, but avoids setting breakpoints. Finally, the program
will start upon receiving onConfigurationDoneRequest and blocks there
until the program terminates. This simplifies the code.
The DAP spec does not explicitly specify what to do about other
requests. It seems like VSCode can issue evaluate requests or other
requests after the configuration done request. Currently they are
blocked because the program is in running state. We can consider checking
s.noDebug and responding with an error in the future if we want/need to.
See the log below for a typical DAP request/response sequence:
2021-03-29T01:42:53-04:00 debug layer=dap building binary at /Users/hakim/projects/s/__debug_bin
2021-03-29T01:42:55-04:00 info layer=debugger launching process with args: [/Users/hakim/projects/s/__debug_bin]
2021-03-29T01:42:55-04:00 debug layer=dap [-> to client]{"seq":0,"type":"event","event":"initialized"}
2021-03-29T01:42:55-04:00 debug layer=dap [-> to client]{"seq":0,"type":"response","request_seq":2,"success":true,"command":"launch"}
2021-03-29T01:42:55-04:00 debug layer=dap [<- from client]{"seq":3,"type":"request","command":"setBreakpoints","arguments":{"source":{"name":"main.go","path":"/Users/hakim/projects/s/main.go"},"breakpoints":[{"line":9}],"lines":[9]}}
2021-03-29T01:42:55-04:00 error layer=dap Unable to set or clear breakpoints: running in noDebug mode
2021-03-29T01:42:55-04:00 debug layer=dap [-> to client]{"seq":0,"type":"response","request_seq":3,"success":false,"command":"setBreakpoints","message":"Unable to set or clear breakpoints","body":{"error":{"id":2002,"format":"Unable to set or clear breakpoints: running in noDebug mode"}}}
2021-03-29T01:42:55-04:00 debug layer=dap [<- from client]{"seq":4,"type":"request","command":"configurationDone","arguments":{}}
2021-03-29T01:42:55-04:00 debug layer=dap [-> to client]{"seq":0,"type":"response","request_seq":4,"success":true,"command":"configurationDone"}
2021-03-29T01:42:55-04:00 debug layer=debugger continuing
Hello
2021-03-29T01:43:00-04:00 debug layer=dap [-> to client]{"seq":0,"type":"event","event":"terminated","body":{}}
2021-03-29T01:43:00-04:00 debug layer=dap [<- from client]{"seq":5,"type":"request","command":"threads"}
2021-03-29T01:43:00-04:00 debug layer=dap [-> to client]{"seq":0,"type":"response","request_seq":5,"success":true,"command":"threads","body":{"threads":null}}
2021-03-29T01:43:00-04:00 debug layer=dap [<- from client]{"seq":6,"type":"request","command":"disconnect","arguments":{}}
2021-03-29T01:43:00-04:00 debug layer=dap [-> to client]{"seq":0,"type":"response","request_seq":6,"success":true,"command":"disconnect"}
2021-03-29T01:43:00-04:00 debug layer=debugger halting
2021-03-29T01:43:00-04:00 error layer=dap Process 27219 has exited with status 0
2021-03-29T01:43:00-04:00 debug layer=debugger detaching
Updates https://github.com/golang/vscode-go/issues/1111
* service/dap: address polina's comments for noDebug logic
Reworked so the noDebug launch request again blocks launch request
handler after responding with the launch response. By skipping
the initialized event, it should prevent well-behaving clients from
sending any further requests. Currently, doesn't matter because
the handler goroutine will be blocked until the program termination
anyway.
Placed mutex back, since I noticed that's the only way to prevent
racing between Stop and the handler goroutine. The synchronization
lotic (in particular, during teardown) should be revisited after
asynchronous request support work is done.
* dap: address more comments
2021-04-05 18:44:02 +00:00
|
|
|
client.DisconnectRequestWithKillOption(true)
|
|
|
|
client.ExpectDisconnectResponse(t)
|
2021-03-23 19:10:21 +00:00
|
|
|
}
|
|
|
|
|
2020-03-04 17:22:51 +00:00
|
|
|
func TestLaunchTestRequest(t *testing.T) {
|
|
|
|
runTest(t, "increment", func(client *daptest.Client, fixture protest.Fixture) {
|
2020-12-28 17:14:15 +00:00
|
|
|
runDebugSession(t, client, "launch", func() {
|
2020-05-28 21:01:51 +00:00
|
|
|
// We reuse the harness that builds, but ignore the built binary,
|
|
|
|
// only relying on the source to be built in response to LaunchRequest.
|
2020-03-04 17:22:51 +00:00
|
|
|
fixtures := protest.FindFixturesDir()
|
|
|
|
testdir, _ := filepath.Abs(filepath.Join(fixtures, "buildtest"))
|
|
|
|
client.LaunchRequestWithArgs(map[string]interface{}{
|
|
|
|
"mode": "test", "program": testdir, "output": "__mytestdir"})
|
2020-10-02 16:18:33 +00:00
|
|
|
}, fixture.Source)
|
2020-03-04 17:22:51 +00:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2020-05-28 21:01:51 +00:00
|
|
|
// 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.
|
2020-05-11 16:52:26 +00:00
|
|
|
func TestLaunchRequestWithArgs(t *testing.T) {
|
|
|
|
runTest(t, "testargs", func(client *daptest.Client, fixture protest.Fixture) {
|
2020-12-28 17:14:15 +00:00
|
|
|
runDebugSession(t, client, "launch", func() {
|
2020-05-11 16:52:26 +00:00
|
|
|
client.LaunchRequestWithArgs(map[string]interface{}{
|
|
|
|
"mode": "exec", "program": fixture.Path,
|
|
|
|
"args": []string{"test", "pass flag"}})
|
2020-10-02 16:18:33 +00:00
|
|
|
}, fixture.Source)
|
2020-05-11 16:52:26 +00:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2020-05-28 21:01:51 +00:00
|
|
|
// 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) {
|
2020-12-28 17:14:15 +00:00
|
|
|
runDebugSession(t, client, "launch", func() {
|
2020-05-28 21:01:51 +00:00
|
|
|
// 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{}{
|
2021-03-24 18:02:22 +00:00
|
|
|
"mode": "debug", "program": fixture.Source, "output": "__mybin",
|
2020-05-28 21:01:51 +00:00
|
|
|
"buildFlags": "-ldflags '-X main.Hello=World'"})
|
2020-10-02 16:18:33 +00:00
|
|
|
}, fixture.Source)
|
2020-05-28 21:01:51 +00:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2020-12-28 17:14:15 +00:00
|
|
|
func TestAttachRequest(t *testing.T) {
|
|
|
|
if runtime.GOOS == "freebsd" {
|
|
|
|
t.SkipNow()
|
|
|
|
}
|
2021-01-18 15:48:06 +00:00
|
|
|
if runtime.GOOS == "windows" {
|
|
|
|
t.Skip("test skipped on windows, see https://delve.beta.teamcity.com/project/Delve_windows for details")
|
|
|
|
}
|
2020-12-28 17:14:15 +00:00
|
|
|
runTest(t, "loopprog", func(client *daptest.Client, fixture protest.Fixture) {
|
|
|
|
// Start the program to attach to
|
2021-04-15 23:35:37 +00:00
|
|
|
cmd := execFixture(t, fixture)
|
2020-12-28 17:14:15 +00:00
|
|
|
|
|
|
|
runDebugSessionWithBPs(t, client, "attach",
|
|
|
|
// Attach
|
|
|
|
func() {
|
2021-03-08 17:42:54 +00:00
|
|
|
client.AttachRequest(map[string]interface{}{
|
|
|
|
/*"mode": "local" by default*/ "processId": cmd.Process.Pid, "stopOnEntry": false})
|
2020-12-28 17:14:15 +00:00
|
|
|
},
|
|
|
|
// Set breakpoints
|
|
|
|
fixture.Source, []int{8},
|
|
|
|
[]onBreakpoint{{
|
|
|
|
// Stop at line 8
|
|
|
|
execute: func() {
|
|
|
|
handleStop(t, client, 1, "main.loop", 8)
|
|
|
|
client.VariablesRequest(1001) // Locals
|
|
|
|
locals := client.ExpectVariablesResponse(t)
|
|
|
|
expectChildren(t, locals, "Locals", 1)
|
2021-05-10 18:34:42 +00:00
|
|
|
expectVarRegex(t, locals, 0, "i", "i", "[0-9]+", "int", noChildren)
|
2020-12-28 17:14:15 +00:00
|
|
|
},
|
|
|
|
disconnect: true,
|
|
|
|
}})
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2020-05-01 19:24:44 +00:00
|
|
|
func TestUnupportedCommandResponses(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.ExceptionInfoRequest()
|
|
|
|
expectUnsupportedCommand("exceptionInfo")
|
|
|
|
|
|
|
|
client.DataBreakpointInfoRequest()
|
|
|
|
expectUnsupportedCommand("dataBreakpointInfo")
|
|
|
|
|
|
|
|
client.SetDataBreakpointsRequest()
|
|
|
|
expectUnsupportedCommand("setDataBreakpoints")
|
|
|
|
|
|
|
|
client.BreakpointLocationsRequest()
|
|
|
|
expectUnsupportedCommand("breakpointLocations")
|
|
|
|
|
|
|
|
client.ModulesRequest()
|
|
|
|
expectUnsupportedCommand("modules")
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestRequiredNotYetImplementedResponses(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.PauseRequest()
|
|
|
|
expectNotYetImplemented("pause")
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
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.SetFunctionBreakpointsRequest()
|
|
|
|
expectNotYetImplemented("setFunctionBreakpoints")
|
|
|
|
|
|
|
|
client.StepBackRequest()
|
|
|
|
expectNotYetImplemented("stepBack")
|
|
|
|
|
|
|
|
client.ReverseContinueRequest()
|
|
|
|
expectNotYetImplemented("reverseContinue")
|
|
|
|
|
|
|
|
client.SetVariableRequest()
|
|
|
|
expectNotYetImplemented("setVariable")
|
|
|
|
|
|
|
|
client.SetExpressionRequest()
|
|
|
|
expectNotYetImplemented("setExpression")
|
|
|
|
|
|
|
|
client.LoadedSourcesRequest()
|
|
|
|
expectNotYetImplemented("loadedSources")
|
|
|
|
|
|
|
|
client.ReadMemoryRequest()
|
|
|
|
expectNotYetImplemented("readMemory")
|
|
|
|
|
|
|
|
client.DisassembleRequest()
|
|
|
|
expectNotYetImplemented("disassemble")
|
|
|
|
|
|
|
|
client.CancelRequest()
|
|
|
|
expectNotYetImplemented("cancel")
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2020-03-04 17:22:51 +00:00
|
|
|
func TestBadLaunchRequests(t *testing.T) {
|
|
|
|
runTest(t, "increment", func(client *daptest.Client, fixture protest.Fixture) {
|
2020-03-10 19:29:06 +00:00
|
|
|
seqCnt := 1
|
2020-03-04 17:22:51 +00:00
|
|
|
expectFailedToLaunch := func(response *dap.ErrorResponse) {
|
2020-02-24 17:36:34 +00:00
|
|
|
t.Helper()
|
2020-03-04 17:22:51 +00:00
|
|
|
if response.RequestSeq != seqCnt {
|
|
|
|
t.Errorf("RequestSeq got %d, want %d", seqCnt, response.RequestSeq)
|
2020-02-24 17:36:34 +00:00
|
|
|
}
|
|
|
|
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 response.Body.Error.Id != 3000 {
|
|
|
|
t.Errorf("Id got %d, want 3000", response.Body.Error.Id)
|
|
|
|
}
|
2020-03-04 17:22:51 +00:00
|
|
|
seqCnt++
|
2020-02-24 17:36:34 +00:00
|
|
|
}
|
|
|
|
|
2020-03-04 17:22:51 +00:00
|
|
|
expectFailedToLaunchWithMessage := func(response *dap.ErrorResponse, errmsg string) {
|
|
|
|
t.Helper()
|
|
|
|
expectFailedToLaunch(response)
|
|
|
|
if response.Body.Error.Format != errmsg {
|
|
|
|
t.Errorf("\ngot %q\nwant %q", response.Body.Error.Format, errmsg)
|
|
|
|
}
|
2020-02-15 19:52:53 +00:00
|
|
|
}
|
|
|
|
|
2020-03-04 17:22:51 +00:00
|
|
|
// Test for the DAP-specific detailed error message.
|
|
|
|
client.LaunchRequest("exec", "", stopOnEntry)
|
2021-05-06 09:11:45 +00:00
|
|
|
expectFailedToLaunchWithMessage(client.ExpectInvisibleErrorResponse(t),
|
2020-03-04 17:22:51 +00:00
|
|
|
"Failed to launch: The program attribute is missing in debug configuration.")
|
|
|
|
|
2021-03-08 17:42:54 +00:00
|
|
|
// Bad "program"
|
|
|
|
client.LaunchRequestWithArgs(map[string]interface{}{"mode": "debug", "program": 12345})
|
2021-05-06 09:11:45 +00:00
|
|
|
expectFailedToLaunchWithMessage(client.ExpectInvisibleErrorResponse(t),
|
2020-03-04 17:22:51 +00:00
|
|
|
"Failed to launch: The program attribute is missing in debug configuration.")
|
|
|
|
|
2021-03-08 17:42:54 +00:00
|
|
|
client.LaunchRequestWithArgs(map[string]interface{}{"mode": "debug", "program": nil})
|
2021-05-06 09:11:45 +00:00
|
|
|
expectFailedToLaunchWithMessage(client.ExpectInvisibleErrorResponse(t),
|
2020-03-04 17:22:51 +00:00
|
|
|
"Failed to launch: The program attribute is missing in debug configuration.")
|
|
|
|
|
2021-03-08 17:42:54 +00:00
|
|
|
client.LaunchRequestWithArgs(map[string]interface{}{"mode": "debug"})
|
2021-05-06 09:11:45 +00:00
|
|
|
expectFailedToLaunchWithMessage(client.ExpectInvisibleErrorResponse(t),
|
2020-03-04 17:22:51 +00:00
|
|
|
"Failed to launch: The program attribute is missing in debug configuration.")
|
|
|
|
|
2021-03-08 17:42:54 +00:00
|
|
|
// Bad "mode"
|
2020-03-04 17:22:51 +00:00
|
|
|
client.LaunchRequest("remote", fixture.Path, stopOnEntry)
|
2021-05-06 09:11:45 +00:00
|
|
|
expectFailedToLaunchWithMessage(client.ExpectInvisibleErrorResponse(t),
|
2020-03-04 17:22:51 +00:00
|
|
|
"Failed to launch: Unsupported 'mode' value \"remote\" in debug configuration.")
|
|
|
|
|
|
|
|
client.LaunchRequest("notamode", fixture.Path, stopOnEntry)
|
2021-05-06 09:11:45 +00:00
|
|
|
expectFailedToLaunchWithMessage(client.ExpectInvisibleErrorResponse(t),
|
2020-03-04 17:22:51 +00:00
|
|
|
"Failed to launch: Unsupported 'mode' value \"notamode\" in debug configuration.")
|
|
|
|
|
|
|
|
client.LaunchRequestWithArgs(map[string]interface{}{"mode": 12345, "program": fixture.Path})
|
2021-05-06 09:11:45 +00:00
|
|
|
expectFailedToLaunchWithMessage(client.ExpectInvisibleErrorResponse(t),
|
2020-03-04 17:22:51 +00:00
|
|
|
"Failed to launch: Unsupported 'mode' value %!q(float64=12345) in debug configuration.")
|
|
|
|
|
2021-03-08 17:42:54 +00:00
|
|
|
client.LaunchRequestWithArgs(map[string]interface{}{"mode": ""}) // empty mode defaults to "debug" (not an error)
|
2021-05-06 09:11:45 +00:00
|
|
|
expectFailedToLaunchWithMessage(client.ExpectInvisibleErrorResponse(t),
|
2021-03-08 17:42:54 +00:00
|
|
|
"Failed to launch: The program attribute is missing in debug configuration.")
|
|
|
|
|
|
|
|
client.LaunchRequestWithArgs(map[string]interface{}{}) // missing mode defaults to "debug" (not an error)
|
2021-05-06 09:11:45 +00:00
|
|
|
expectFailedToLaunchWithMessage(client.ExpectInvisibleErrorResponse(t),
|
2021-03-08 17:42:54 +00:00
|
|
|
"Failed to launch: The program attribute is missing in debug configuration.")
|
|
|
|
|
|
|
|
// Bad "args"
|
2020-05-11 16:52:26 +00:00
|
|
|
client.LaunchRequestWithArgs(map[string]interface{}{"mode": "exec", "program": fixture.Path, "args": nil})
|
2021-05-06 09:11:45 +00:00
|
|
|
expectFailedToLaunchWithMessage(client.ExpectInvisibleErrorResponse(t),
|
2020-05-11 16:52:26 +00:00
|
|
|
"Failed to launch: 'args' attribute '<nil>' in debug configuration is not an array.")
|
|
|
|
|
|
|
|
client.LaunchRequestWithArgs(map[string]interface{}{"mode": "exec", "program": fixture.Path, "args": 12345})
|
2021-05-06 09:11:45 +00:00
|
|
|
expectFailedToLaunchWithMessage(client.ExpectInvisibleErrorResponse(t),
|
2020-05-11 16:52:26 +00:00
|
|
|
"Failed to launch: 'args' attribute '12345' in debug configuration is not an array.")
|
|
|
|
|
|
|
|
client.LaunchRequestWithArgs(map[string]interface{}{"mode": "exec", "program": fixture.Path, "args": []int{1, 2}})
|
2021-05-06 09:11:45 +00:00
|
|
|
expectFailedToLaunchWithMessage(client.ExpectInvisibleErrorResponse(t),
|
2020-05-11 16:52:26 +00:00
|
|
|
"Failed to launch: value '1' in 'args' attribute in debug configuration is not a string.")
|
|
|
|
|
2021-03-08 17:42:54 +00:00
|
|
|
// Bad "buildFlags"
|
2020-05-28 21:01:51 +00:00
|
|
|
client.LaunchRequestWithArgs(map[string]interface{}{"mode": "debug", "program": fixture.Source, "buildFlags": 123})
|
2021-05-06 09:11:45 +00:00
|
|
|
expectFailedToLaunchWithMessage(client.ExpectInvisibleErrorResponse(t),
|
2020-05-28 21:01:51 +00:00
|
|
|
"Failed to launch: 'buildFlags' attribute '123' in debug configuration is not a string.")
|
|
|
|
|
2021-04-15 23:35:37 +00:00
|
|
|
client.LaunchRequestWithArgs(map[string]interface{}{"mode": "debug", "program": fixture.Source, "substitutePath": 123})
|
2021-05-06 09:11:45 +00:00
|
|
|
expectFailedToLaunchWithMessage(client.ExpectInvisibleErrorResponse(t),
|
2021-04-15 23:35:37 +00:00
|
|
|
"Failed to launch: 'substitutePath' attribute '123' in debug configuration is not a []{'from': string, 'to': string}")
|
|
|
|
|
|
|
|
client.LaunchRequestWithArgs(map[string]interface{}{"mode": "debug", "program": fixture.Source, "substitutePath": []interface{}{123}})
|
2021-05-06 09:11:45 +00:00
|
|
|
expectFailedToLaunchWithMessage(client.ExpectInvisibleErrorResponse(t),
|
2021-04-15 23:35:37 +00:00
|
|
|
"Failed to launch: 'substitutePath' attribute '[123]' in debug configuration is not a []{'from': string, 'to': string}")
|
|
|
|
|
|
|
|
client.LaunchRequestWithArgs(map[string]interface{}{"mode": "debug", "program": fixture.Source, "substitutePath": []interface{}{map[string]interface{}{"to": "path2"}}})
|
2021-05-06 09:11:45 +00:00
|
|
|
expectFailedToLaunchWithMessage(client.ExpectInvisibleErrorResponse(t),
|
2021-04-15 23:35:37 +00:00
|
|
|
"Failed to launch: 'substitutePath' attribute '[map[to:path2]]' in debug configuration is not a []{'from': string, 'to': string}")
|
|
|
|
|
|
|
|
client.LaunchRequestWithArgs(map[string]interface{}{"mode": "debug", "program": fixture.Source, "substitutePath": []interface{}{map[string]interface{}{"from": "path1", "to": 123}}})
|
2021-05-06 09:11:45 +00:00
|
|
|
expectFailedToLaunchWithMessage(client.ExpectInvisibleErrorResponse(t),
|
2021-04-15 23:35:37 +00:00
|
|
|
"Failed to launch: 'substitutePath' attribute '[map[from:path1 to:123]]' in debug configuration is not a []{'from': string, 'to': string}")
|
2021-04-20 07:40:34 +00:00
|
|
|
client.LaunchRequestWithArgs(map[string]interface{}{"mode": "debug", "program": fixture.Source, "cwd": 123})
|
2021-03-23 03:06:09 +00:00
|
|
|
expectFailedToLaunchWithMessage(client.ExpectErrorResponse(t),
|
2021-04-20 07:40:34 +00:00
|
|
|
"Failed to launch: 'cwd' attribute '123' in debug configuration is not a string.")
|
2021-03-23 03:06:09 +00:00
|
|
|
|
2020-02-15 19:52:53 +00:00
|
|
|
// Skip detailed message checks for potentially different OS-specific errors.
|
2020-03-04 17:22:51 +00:00
|
|
|
client.LaunchRequest("exec", fixture.Path+"_does_not_exist", stopOnEntry)
|
2021-05-06 09:11:45 +00:00
|
|
|
expectFailedToLaunch(client.ExpectInvisibleErrorResponse(t)) // No such file or directory
|
2020-03-04 17:22:51 +00:00
|
|
|
|
|
|
|
client.LaunchRequest("debug", fixture.Path+"_does_not_exist", stopOnEntry)
|
2021-05-17 16:13:25 +00:00
|
|
|
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)
|
|
|
|
}
|
2021-05-06 09:11:45 +00:00
|
|
|
expectFailedToLaunch(client.ExpectInvisibleErrorResponse(t))
|
2021-03-08 17:42:54 +00:00
|
|
|
|
|
|
|
client.LaunchRequest("" /*debug by default*/, fixture.Path+"_does_not_exist", stopOnEntry)
|
2021-05-17 16:13:25 +00:00
|
|
|
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)
|
|
|
|
}
|
2021-05-06 09:11:45 +00:00
|
|
|
expectFailedToLaunch(client.ExpectInvisibleErrorResponse(t))
|
2020-02-15 19:52:53 +00:00
|
|
|
|
2020-03-04 17:22:51 +00:00
|
|
|
client.LaunchRequest("exec", fixture.Source, stopOnEntry)
|
2021-05-06 09:11:45 +00:00
|
|
|
expectFailedToLaunch(client.ExpectInvisibleErrorResponse(t)) // Not an executable
|
2020-02-15 19:52:53 +00:00
|
|
|
|
2021-04-26 17:31:59 +00:00
|
|
|
client.LaunchRequestWithArgs(map[string]interface{}{"mode": "debug", "program": fixture.Source, "buildFlags": "-bad -flags"})
|
2021-05-17 16:13:25 +00:00
|
|
|
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)
|
|
|
|
}
|
|
|
|
expectFailedToLaunchWithMessage(client.ExpectInvisibleErrorResponse(t), "Failed to launch: Build error: Check the debug console for details.")
|
2021-04-26 17:31:59 +00:00
|
|
|
client.LaunchRequestWithArgs(map[string]interface{}{"mode": "debug", "program": fixture.Source, "noDebug": true, "buildFlags": "-bad -flags"})
|
2021-05-17 16:13:25 +00:00
|
|
|
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)
|
|
|
|
}
|
|
|
|
expectFailedToLaunchWithMessage(client.ExpectInvisibleErrorResponse(t), "Failed to launch: Build error: Check the debug console for details.")
|
dap: change how noDebug launch request is served (#2407)
* dap: change how noDebug launch request is served
PR #2400 added support for noDebug launch requests - that was done
by directly starting the target program using os/exec.Cmd and blocking
the launch request indefinitely until the program terminates or
is stopped with a disconnect request (when dlv dap can support it).
Even though the approach seemed to work for user, that adds an extra
control flow and complexity to the codebase.
This change takes a different approach to implement the noDebug
launch feature. Instead of using os/exec.Cmd, this uses the existing
debug launch path, but avoids setting breakpoints. Finally, the program
will start upon receiving onConfigurationDoneRequest and blocks there
until the program terminates. This simplifies the code.
The DAP spec does not explicitly specify what to do about other
requests. It seems like VSCode can issue evaluate requests or other
requests after the configuration done request. Currently they are
blocked because the program is in running state. We can consider checking
s.noDebug and responding with an error in the future if we want/need to.
See the log below for a typical DAP request/response sequence:
2021-03-29T01:42:53-04:00 debug layer=dap building binary at /Users/hakim/projects/s/__debug_bin
2021-03-29T01:42:55-04:00 info layer=debugger launching process with args: [/Users/hakim/projects/s/__debug_bin]
2021-03-29T01:42:55-04:00 debug layer=dap [-> to client]{"seq":0,"type":"event","event":"initialized"}
2021-03-29T01:42:55-04:00 debug layer=dap [-> to client]{"seq":0,"type":"response","request_seq":2,"success":true,"command":"launch"}
2021-03-29T01:42:55-04:00 debug layer=dap [<- from client]{"seq":3,"type":"request","command":"setBreakpoints","arguments":{"source":{"name":"main.go","path":"/Users/hakim/projects/s/main.go"},"breakpoints":[{"line":9}],"lines":[9]}}
2021-03-29T01:42:55-04:00 error layer=dap Unable to set or clear breakpoints: running in noDebug mode
2021-03-29T01:42:55-04:00 debug layer=dap [-> to client]{"seq":0,"type":"response","request_seq":3,"success":false,"command":"setBreakpoints","message":"Unable to set or clear breakpoints","body":{"error":{"id":2002,"format":"Unable to set or clear breakpoints: running in noDebug mode"}}}
2021-03-29T01:42:55-04:00 debug layer=dap [<- from client]{"seq":4,"type":"request","command":"configurationDone","arguments":{}}
2021-03-29T01:42:55-04:00 debug layer=dap [-> to client]{"seq":0,"type":"response","request_seq":4,"success":true,"command":"configurationDone"}
2021-03-29T01:42:55-04:00 debug layer=debugger continuing
Hello
2021-03-29T01:43:00-04:00 debug layer=dap [-> to client]{"seq":0,"type":"event","event":"terminated","body":{}}
2021-03-29T01:43:00-04:00 debug layer=dap [<- from client]{"seq":5,"type":"request","command":"threads"}
2021-03-29T01:43:00-04:00 debug layer=dap [-> to client]{"seq":0,"type":"response","request_seq":5,"success":true,"command":"threads","body":{"threads":null}}
2021-03-29T01:43:00-04:00 debug layer=dap [<- from client]{"seq":6,"type":"request","command":"disconnect","arguments":{}}
2021-03-29T01:43:00-04:00 debug layer=dap [-> to client]{"seq":0,"type":"response","request_seq":6,"success":true,"command":"disconnect"}
2021-03-29T01:43:00-04:00 debug layer=debugger halting
2021-03-29T01:43:00-04:00 error layer=dap Process 27219 has exited with status 0
2021-03-29T01:43:00-04:00 debug layer=debugger detaching
Updates https://github.com/golang/vscode-go/issues/1111
* service/dap: address polina's comments for noDebug logic
Reworked so the noDebug launch request again blocks launch request
handler after responding with the launch response. By skipping
the initialized event, it should prevent well-behaving clients from
sending any further requests. Currently, doesn't matter because
the handler goroutine will be blocked until the program termination
anyway.
Placed mutex back, since I noticed that's the only way to prevent
racing between Stop and the handler goroutine. The synchronization
lotic (in particular, during teardown) should be revisited after
asynchronous request support work is done.
* dap: address more comments
2021-04-05 18:44:02 +00:00
|
|
|
|
|
|
|
// Bad "wd".
|
2021-04-20 07:40:34 +00:00
|
|
|
client.LaunchRequestWithArgs(map[string]interface{}{"mode": "debug", "program": fixture.Source, "noDebug": false, "cwd": "dir/invalid"})
|
dap: change how noDebug launch request is served (#2407)
* dap: change how noDebug launch request is served
PR #2400 added support for noDebug launch requests - that was done
by directly starting the target program using os/exec.Cmd and blocking
the launch request indefinitely until the program terminates or
is stopped with a disconnect request (when dlv dap can support it).
Even though the approach seemed to work for user, that adds an extra
control flow and complexity to the codebase.
This change takes a different approach to implement the noDebug
launch feature. Instead of using os/exec.Cmd, this uses the existing
debug launch path, but avoids setting breakpoints. Finally, the program
will start upon receiving onConfigurationDoneRequest and blocks there
until the program terminates. This simplifies the code.
The DAP spec does not explicitly specify what to do about other
requests. It seems like VSCode can issue evaluate requests or other
requests after the configuration done request. Currently they are
blocked because the program is in running state. We can consider checking
s.noDebug and responding with an error in the future if we want/need to.
See the log below for a typical DAP request/response sequence:
2021-03-29T01:42:53-04:00 debug layer=dap building binary at /Users/hakim/projects/s/__debug_bin
2021-03-29T01:42:55-04:00 info layer=debugger launching process with args: [/Users/hakim/projects/s/__debug_bin]
2021-03-29T01:42:55-04:00 debug layer=dap [-> to client]{"seq":0,"type":"event","event":"initialized"}
2021-03-29T01:42:55-04:00 debug layer=dap [-> to client]{"seq":0,"type":"response","request_seq":2,"success":true,"command":"launch"}
2021-03-29T01:42:55-04:00 debug layer=dap [<- from client]{"seq":3,"type":"request","command":"setBreakpoints","arguments":{"source":{"name":"main.go","path":"/Users/hakim/projects/s/main.go"},"breakpoints":[{"line":9}],"lines":[9]}}
2021-03-29T01:42:55-04:00 error layer=dap Unable to set or clear breakpoints: running in noDebug mode
2021-03-29T01:42:55-04:00 debug layer=dap [-> to client]{"seq":0,"type":"response","request_seq":3,"success":false,"command":"setBreakpoints","message":"Unable to set or clear breakpoints","body":{"error":{"id":2002,"format":"Unable to set or clear breakpoints: running in noDebug mode"}}}
2021-03-29T01:42:55-04:00 debug layer=dap [<- from client]{"seq":4,"type":"request","command":"configurationDone","arguments":{}}
2021-03-29T01:42:55-04:00 debug layer=dap [-> to client]{"seq":0,"type":"response","request_seq":4,"success":true,"command":"configurationDone"}
2021-03-29T01:42:55-04:00 debug layer=debugger continuing
Hello
2021-03-29T01:43:00-04:00 debug layer=dap [-> to client]{"seq":0,"type":"event","event":"terminated","body":{}}
2021-03-29T01:43:00-04:00 debug layer=dap [<- from client]{"seq":5,"type":"request","command":"threads"}
2021-03-29T01:43:00-04:00 debug layer=dap [-> to client]{"seq":0,"type":"response","request_seq":5,"success":true,"command":"threads","body":{"threads":null}}
2021-03-29T01:43:00-04:00 debug layer=dap [<- from client]{"seq":6,"type":"request","command":"disconnect","arguments":{}}
2021-03-29T01:43:00-04:00 debug layer=dap [-> to client]{"seq":0,"type":"response","request_seq":6,"success":true,"command":"disconnect"}
2021-03-29T01:43:00-04:00 debug layer=debugger halting
2021-03-29T01:43:00-04:00 error layer=dap Process 27219 has exited with status 0
2021-03-29T01:43:00-04:00 debug layer=debugger detaching
Updates https://github.com/golang/vscode-go/issues/1111
* service/dap: address polina's comments for noDebug logic
Reworked so the noDebug launch request again blocks launch request
handler after responding with the launch response. By skipping
the initialized event, it should prevent well-behaving clients from
sending any further requests. Currently, doesn't matter because
the handler goroutine will be blocked until the program termination
anyway.
Placed mutex back, since I noticed that's the only way to prevent
racing between Stop and the handler goroutine. The synchronization
lotic (in particular, during teardown) should be revisited after
asynchronous request support work is done.
* dap: address more comments
2021-04-05 18:44:02 +00:00
|
|
|
expectFailedToLaunch(client.ExpectErrorResponse(t)) // invalid directory, the error message is system-dependent.
|
2021-04-20 07:40:34 +00:00
|
|
|
client.LaunchRequestWithArgs(map[string]interface{}{"mode": "debug", "program": fixture.Source, "noDebug": true, "cwd": "dir/invalid"})
|
dap: change how noDebug launch request is served (#2407)
* dap: change how noDebug launch request is served
PR #2400 added support for noDebug launch requests - that was done
by directly starting the target program using os/exec.Cmd and blocking
the launch request indefinitely until the program terminates or
is stopped with a disconnect request (when dlv dap can support it).
Even though the approach seemed to work for user, that adds an extra
control flow and complexity to the codebase.
This change takes a different approach to implement the noDebug
launch feature. Instead of using os/exec.Cmd, this uses the existing
debug launch path, but avoids setting breakpoints. Finally, the program
will start upon receiving onConfigurationDoneRequest and blocks there
until the program terminates. This simplifies the code.
The DAP spec does not explicitly specify what to do about other
requests. It seems like VSCode can issue evaluate requests or other
requests after the configuration done request. Currently they are
blocked because the program is in running state. We can consider checking
s.noDebug and responding with an error in the future if we want/need to.
See the log below for a typical DAP request/response sequence:
2021-03-29T01:42:53-04:00 debug layer=dap building binary at /Users/hakim/projects/s/__debug_bin
2021-03-29T01:42:55-04:00 info layer=debugger launching process with args: [/Users/hakim/projects/s/__debug_bin]
2021-03-29T01:42:55-04:00 debug layer=dap [-> to client]{"seq":0,"type":"event","event":"initialized"}
2021-03-29T01:42:55-04:00 debug layer=dap [-> to client]{"seq":0,"type":"response","request_seq":2,"success":true,"command":"launch"}
2021-03-29T01:42:55-04:00 debug layer=dap [<- from client]{"seq":3,"type":"request","command":"setBreakpoints","arguments":{"source":{"name":"main.go","path":"/Users/hakim/projects/s/main.go"},"breakpoints":[{"line":9}],"lines":[9]}}
2021-03-29T01:42:55-04:00 error layer=dap Unable to set or clear breakpoints: running in noDebug mode
2021-03-29T01:42:55-04:00 debug layer=dap [-> to client]{"seq":0,"type":"response","request_seq":3,"success":false,"command":"setBreakpoints","message":"Unable to set or clear breakpoints","body":{"error":{"id":2002,"format":"Unable to set or clear breakpoints: running in noDebug mode"}}}
2021-03-29T01:42:55-04:00 debug layer=dap [<- from client]{"seq":4,"type":"request","command":"configurationDone","arguments":{}}
2021-03-29T01:42:55-04:00 debug layer=dap [-> to client]{"seq":0,"type":"response","request_seq":4,"success":true,"command":"configurationDone"}
2021-03-29T01:42:55-04:00 debug layer=debugger continuing
Hello
2021-03-29T01:43:00-04:00 debug layer=dap [-> to client]{"seq":0,"type":"event","event":"terminated","body":{}}
2021-03-29T01:43:00-04:00 debug layer=dap [<- from client]{"seq":5,"type":"request","command":"threads"}
2021-03-29T01:43:00-04:00 debug layer=dap [-> to client]{"seq":0,"type":"response","request_seq":5,"success":true,"command":"threads","body":{"threads":null}}
2021-03-29T01:43:00-04:00 debug layer=dap [<- from client]{"seq":6,"type":"request","command":"disconnect","arguments":{}}
2021-03-29T01:43:00-04:00 debug layer=dap [-> to client]{"seq":0,"type":"response","request_seq":6,"success":true,"command":"disconnect"}
2021-03-29T01:43:00-04:00 debug layer=debugger halting
2021-03-29T01:43:00-04:00 error layer=dap Process 27219 has exited with status 0
2021-03-29T01:43:00-04:00 debug layer=debugger detaching
Updates https://github.com/golang/vscode-go/issues/1111
* service/dap: address polina's comments for noDebug logic
Reworked so the noDebug launch request again blocks launch request
handler after responding with the launch response. By skipping
the initialized event, it should prevent well-behaving clients from
sending any further requests. Currently, doesn't matter because
the handler goroutine will be blocked until the program termination
anyway.
Placed mutex back, since I noticed that's the only way to prevent
racing between Stop and the handler goroutine. The synchronization
lotic (in particular, during teardown) should be revisited after
asynchronous request support work is done.
* dap: address more comments
2021-04-05 18:44:02 +00:00
|
|
|
expectFailedToLaunch(client.ExpectErrorResponse(t)) // invalid directory, the error message is system-dependent.
|
2021-03-05 09:07:23 +00:00
|
|
|
|
2020-02-15 19:52:53 +00:00
|
|
|
// We failed to launch the program. Make sure shutdown still works.
|
|
|
|
client.DisconnectRequest()
|
2020-02-24 17:36:34 +00:00
|
|
|
dresp := client.ExpectDisconnectResponse(t)
|
2020-03-04 17:22:51 +00:00
|
|
|
if dresp.RequestSeq != seqCnt {
|
|
|
|
t.Errorf("got %#v, want RequestSeq=%d", dresp, seqCnt)
|
2020-02-24 17:36:34 +00:00
|
|
|
}
|
2020-02-15 19:52:53 +00:00
|
|
|
})
|
|
|
|
}
|
2020-02-27 04:45:48 +00:00
|
|
|
|
2020-12-28 17:14:15 +00:00
|
|
|
func TestBadAttachRequest(t *testing.T) {
|
|
|
|
runTest(t, "loopprog", func(client *daptest.Client, fixture protest.Fixture) {
|
|
|
|
seqCnt := 1
|
|
|
|
expectFailedToAttach := 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 response.Body.Error.Id != 3001 {
|
|
|
|
t.Errorf("Id got %d, want 3001", response.Body.Error.Id)
|
|
|
|
}
|
|
|
|
seqCnt++
|
|
|
|
}
|
|
|
|
|
|
|
|
expectFailedToAttachWithMessage := func(response *dap.ErrorResponse, errmsg string) {
|
|
|
|
t.Helper()
|
|
|
|
expectFailedToAttach(response)
|
|
|
|
if response.Body.Error.Format != errmsg {
|
|
|
|
t.Errorf("\ngot %q\nwant %q", response.Body.Error.Format, errmsg)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-03-08 17:42:54 +00:00
|
|
|
// Bad "mode"
|
2020-12-28 17:14:15 +00:00
|
|
|
client.AttachRequest(map[string]interface{}{"mode": "remote"})
|
2021-05-06 09:11:45 +00:00
|
|
|
expectFailedToAttachWithMessage(client.ExpectInvisibleErrorResponse(t),
|
2020-12-28 17:14:15 +00:00
|
|
|
"Failed to attach: Unsupported 'mode' value \"remote\" in debug configuration")
|
|
|
|
|
|
|
|
client.AttachRequest(map[string]interface{}{"mode": "blah blah blah"})
|
2021-05-06 09:11:45 +00:00
|
|
|
expectFailedToAttachWithMessage(client.ExpectInvisibleErrorResponse(t),
|
2020-12-28 17:14:15 +00:00
|
|
|
"Failed to attach: Unsupported 'mode' value \"blah blah blah\" in debug configuration")
|
|
|
|
|
|
|
|
client.AttachRequest(map[string]interface{}{"mode": 123})
|
2021-05-06 09:11:45 +00:00
|
|
|
expectFailedToAttachWithMessage(client.ExpectInvisibleErrorResponse(t),
|
2020-12-28 17:14:15 +00:00
|
|
|
"Failed to attach: Unsupported 'mode' value %!q(float64=123) in debug configuration")
|
|
|
|
|
2021-03-08 17:42:54 +00:00
|
|
|
client.AttachRequest(map[string]interface{}{"mode": ""}) // empty mode defaults to "local" (not an error)
|
2021-05-06 09:11:45 +00:00
|
|
|
expectFailedToAttachWithMessage(client.ExpectInvisibleErrorResponse(t),
|
2021-03-08 17:42:54 +00:00
|
|
|
"Failed to attach: The 'processId' attribute is missing in debug configuration")
|
|
|
|
|
|
|
|
client.AttachRequest(map[string]interface{}{}) // no mode defaults to "local" (not an error)
|
2021-05-06 09:11:45 +00:00
|
|
|
expectFailedToAttachWithMessage(client.ExpectInvisibleErrorResponse(t),
|
2021-03-08 17:42:54 +00:00
|
|
|
"Failed to attach: The 'processId' attribute is missing in debug configuration")
|
|
|
|
|
|
|
|
// Bad "processId"
|
|
|
|
client.AttachRequest(map[string]interface{}{"mode": "local"})
|
2021-05-06 09:11:45 +00:00
|
|
|
expectFailedToAttachWithMessage(client.ExpectInvisibleErrorResponse(t),
|
2020-12-28 17:14:15 +00:00
|
|
|
"Failed to attach: The 'processId' attribute is missing in debug configuration")
|
|
|
|
|
2021-03-08 17:42:54 +00:00
|
|
|
client.AttachRequest(map[string]interface{}{"mode": "local", "processId": nil})
|
2021-05-06 09:11:45 +00:00
|
|
|
expectFailedToAttachWithMessage(client.ExpectInvisibleErrorResponse(t),
|
2020-12-28 17:14:15 +00:00
|
|
|
"Failed to attach: The 'processId' attribute is missing in debug configuration")
|
|
|
|
|
2021-03-08 17:42:54 +00:00
|
|
|
client.AttachRequest(map[string]interface{}{"mode": "local", "processId": 0})
|
2021-05-06 09:11:45 +00:00
|
|
|
expectFailedToAttachWithMessage(client.ExpectInvisibleErrorResponse(t),
|
2020-12-28 17:14:15 +00:00
|
|
|
"Failed to attach: The 'processId' attribute is missing in debug configuration")
|
|
|
|
|
2021-03-08 17:42:54 +00:00
|
|
|
client.AttachRequest(map[string]interface{}{"mode": "local", "processId": "1"})
|
2021-05-06 09:11:45 +00:00
|
|
|
expectFailedToAttachWithMessage(client.ExpectInvisibleErrorResponse(t),
|
2020-12-28 17:14:15 +00:00
|
|
|
"Failed to attach: The 'processId' attribute is missing in debug configuration")
|
|
|
|
|
2021-03-08 17:42:54 +00:00
|
|
|
client.AttachRequest(map[string]interface{}{"mode": "local", "processId": 1})
|
2020-12-28 17:14:15 +00:00
|
|
|
// The exact message varies on different systems, so skip that check
|
2021-05-06 09:11:45 +00:00
|
|
|
expectFailedToAttach(client.ExpectInvisibleErrorResponse(t)) // could not attach to pid 1
|
2020-12-28 17:14:15 +00:00
|
|
|
|
|
|
|
// This will make debugger.(*Debugger) panic, which we will catch as an internal error.
|
2021-03-08 17:42:54 +00:00
|
|
|
client.AttachRequest(map[string]interface{}{"mode": "local", "processId": -1})
|
2021-05-06 09:11:45 +00:00
|
|
|
er := client.ExpectInvisibleErrorResponse(t)
|
2020-12-28 17:14:15 +00:00
|
|
|
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 er.Body.Error.Format != "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 er.Body.Error.Id != 8888 {
|
|
|
|
t.Errorf("Id got %d, want 8888", er.Body.Error.Id)
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2021-05-06 07:56:29 +00:00
|
|
|
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.
|
|
|
|
client := startDapServer(t)
|
|
|
|
// client.Close will close the client connectinon, which will cause a connection error
|
|
|
|
// on the server side and signal disconnect to unblock Stop() above.
|
|
|
|
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 response.Body.Error.Id != 3002 {
|
|
|
|
t.Errorf("Id got %d, want 3002", response.Body.Error.Id)
|
|
|
|
}
|
|
|
|
if response.Body.Error.Format != err {
|
|
|
|
t.Errorf("\ngot %q\nwant %q", response.Body.Error.Format, err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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.",
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2020-02-27 04:45:48 +00:00
|
|
|
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.UnknownRequest()
|
|
|
|
time.Sleep(100 * time.Millisecond)
|
|
|
|
|
|
|
|
_, err := client.ReadMessage()
|
|
|
|
|
|
|
|
if err != io.EOF {
|
|
|
|
t.Errorf("got err=%v, want io.EOF", err)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|