package dap import ( "flag" "io" "net" "os" "path/filepath" "reflect" "regexp" "runtime" "strings" "sync" "testing" "time" "github.com/go-delve/delve/pkg/goversion" "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" "github.com/go-delve/delve/service/debugger" "github.com/google/go-dap" ) const stopOnEntry bool = true const hasChildren bool = true const noChildren bool = false func TestMain(m *testing.M) { var logOutput string flag.StringVar(&logOutput, "log-output", "", "configures log output") flag.Parse() logflags.Setup(logOutput != "", logOutput, "") os.Exit(protest.RunTestsWithFixtures(m)) } // name is for _fixtures/.go func runTest(t *testing.T, name string, test func(c *daptest.Client, f protest.Fixture)) { var buildFlags protest.BuildFlags fixture := protest.BuildFixture(name, buildFlags) // Start the DAP server. listener, err := net.Listen("tcp", ":0") if err != nil { t.Fatal(err) } disconnectChan := make(chan struct{}) server := NewServer(&service.Config{ Listener: listener, DisconnectChan: disconnectChan, Debugger: debugger.Config{ Backend: "default", }, }) server.Run() // Give server time to start listening for clients time.Sleep(100 * time.Millisecond) var stopOnce sync.Once // 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 stopOnce.Do(func() { server.Stop() }) }() client := daptest.NewClient(listener.Addr().String()) defer client.Close() defer func() { stopOnce.Do(func() { server.Stop() }) }() test(client, fixture) } // TestStopOnEntry emulates the message exchange that can be observed with // VS Code for the most basic debug session with "stopOnEntry" enabled: // - User selects "Start Debugging": 1 >> initialize // : 1 << initialize // : 2 >> launch // : << initialized event // : 2 << launch // : 3 >> setBreakpoints (empty) // : 3 << setBreakpoints // : 4 >> setExceptionBreakpoints (empty) // : 4 << setExceptionBreakpoints // : 5 >> configurationDone // - Program stops upon launching : << stopped event // : 5 << configurationDone // : 6 >> threads // : 6 << threads (Dummy) // : 7 >> threads // : 7 << threads (Dummy) // : 8 >> stackTrace // : 8 << stackTrace (Unable to produce stack trace) // : 9 >> stackTrace // : 9 << stackTrace (Unable to produce stack trace) // - User selects "Continue" : 10 >> continue // : 10 << continue // - Program runs to completion : << terminated event // : 11 >> disconnect // : 11 << disconnect // This test exhaustively tests Seq and RequestSeq on all messages from the // server. Other tests do not necessarily need to repeat all these checks. func TestStopOnEntry(t *testing.T) { runTest(t, "increment", func(client *daptest.Client, fixture protest.Fixture) { // 1 >> initialize, << initialize client.InitializeRequest() initResp := client.ExpectInitializeResponse(t) if initResp.Seq != 0 || initResp.RequestSeq != 1 { t.Errorf("\ngot %#v\nwant Seq=0, RequestSeq=1", initResp) } // 2 >> launch, << initialized, << launch client.LaunchRequest("exec", fixture.Path, stopOnEntry) initEvent := client.ExpectInitializedEvent(t) if initEvent.Seq != 0 { t.Errorf("\ngot %#v\nwant Seq=0", initEvent) } launchResp := client.ExpectLaunchResponse(t) if launchResp.Seq != 0 || launchResp.RequestSeq != 2 { t.Errorf("\ngot %#v\nwant Seq=0, RequestSeq=2", launchResp) } // 3 >> setBreakpoints, << setBreakpoints client.SetBreakpointsRequest(fixture.Source, nil) sbpResp := client.ExpectSetBreakpointsResponse(t) if sbpResp.Seq != 0 || sbpResp.RequestSeq != 3 || len(sbpResp.Body.Breakpoints) != 0 { t.Errorf("\ngot %#v\nwant Seq=0, RequestSeq=3, len(Breakpoints)=0", sbpResp) } // 4 >> setExceptionBreakpoints, << setExceptionBreakpoints client.SetExceptionBreakpointsRequest() sebpResp := client.ExpectSetExceptionBreakpointsResponse(t) if sebpResp.Seq != 0 || sebpResp.RequestSeq != 4 { t.Errorf("\ngot %#v\nwant Seq=0, RequestSeq=4", sebpResp) } // 5 >> configurationDone, << stopped, << configurationDone client.ConfigurationDoneRequest() stopEvent := client.ExpectStoppedEvent(t) if stopEvent.Seq != 0 || stopEvent.Body.Reason != "entry" || stopEvent.Body.ThreadId != 1 || !stopEvent.Body.AllThreadsStopped { t.Errorf("\ngot %#v\nwant Seq=0, Body={Reason=\"entry\", ThreadId=1, AllThreadsStopped=true}", stopEvent) } cdResp := client.ExpectConfigurationDoneResponse(t) if cdResp.Seq != 0 || cdResp.RequestSeq != 5 { t.Errorf("\ngot %#v\nwant Seq=0, RequestSeq=5", cdResp) } // 6 >> threads, << threads client.ThreadsRequest() tResp := client.ExpectThreadsResponse(t) if tResp.Seq != 0 || tResp.RequestSeq != 6 || len(tResp.Body.Threads) != 1 { t.Errorf("\ngot %#v\nwant Seq=0, RequestSeq=6 len(Threads)=1", tResp) } if tResp.Body.Threads[0].Id != 1 || tResp.Body.Threads[0].Name != "Dummy" { t.Errorf("\ngot %#v\nwant Id=1, Name=\"Dummy\"", tResp) } // 7 >> threads, << threads client.ThreadsRequest() tResp = client.ExpectThreadsResponse(t) if tResp.Seq != 0 || tResp.RequestSeq != 7 || len(tResp.Body.Threads) != 1 { t.Errorf("\ngot %#v\nwant Seq=0, RequestSeq=7 len(Threads)=1", tResp) } // 8 >> stackTrace, << stackTrace client.StackTraceRequest(1, 0, 20) stResp := client.ExpectErrorResponse(t) 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) } // 9 >> stackTrace, << stackTrace client.StackTraceRequest(1, 0, 20) stResp = client.ExpectErrorResponse(t) if stResp.Seq != 0 || stResp.RequestSeq != 9 || stResp.Body.Error.Id != 2004 { t.Errorf("\ngot %#v\nwant Seq=0, RequestSeq=9 Id=2004", stResp) } // 10 >> continue, << continue, << terminated client.ContinueRequest(1) contResp := client.ExpectContinueResponse(t) if contResp.Seq != 0 || contResp.RequestSeq != 10 { t.Errorf("\ngot %#v\nwant Seq=0, RequestSeq=10", contResp) } termEvent := client.ExpectTerminatedEvent(t) if termEvent.Seq != 0 { t.Errorf("\ngot %#v\nwant Seq=0", termEvent) } // 11 >> disconnect, << disconnect client.DisconnectRequest() dResp := client.ExpectDisconnectResponse(t) if dResp.Seq != 0 || dResp.RequestSeq != 11 { t.Errorf("\ngot %#v\nwant Seq=0, RequestSeq=11", dResp) } }) } // Like the test above, except the program is configured to continue on entry. func TestContinueOnEntry(t *testing.T) { runTest(t, "increment", func(client *daptest.Client, fixture protest.Fixture) { // 1 >> initialize, << initialize client.InitializeRequest() client.ExpectInitializeResponse(t) // 2 >> launch, << initialized, << launch client.LaunchRequest("exec", fixture.Path, !stopOnEntry) client.ExpectInitializedEvent(t) client.ExpectLaunchResponse(t) // 3 >> setBreakpoints, << setBreakpoints client.SetBreakpointsRequest(fixture.Source, nil) client.ExpectSetBreakpointsResponse(t) // 4 >> setExceptionBreakpoints, << setExceptionBreakpoints client.SetExceptionBreakpointsRequest() client.ExpectSetExceptionBreakpointsResponse(t) // 5 >> configurationDone, << configurationDone client.ConfigurationDoneRequest() client.ExpectConfigurationDoneResponse(t) // "Continue" happens behind the scenes // For now continue is blocking and runs until a stop or // termination. But once we upgrade the server to be async, // a simultaneous threads request can be made while continue // is running. Note that vscode-go just keeps track of the // continue state and would just return a dummy response // without talking to debugger if continue was in progress. // TODO(polina): test this once it is possible client.ExpectTerminatedEvent(t) // It is possible for the program to terminate before the initial // threads request is processed. // 6 >> threads, << threads client.ThreadsRequest() tResp := client.ExpectThreadsResponse(t) if tResp.Seq != 0 || tResp.RequestSeq != 6 || len(tResp.Body.Threads) != 0 { t.Errorf("\ngot %#v\nwant Seq=0, RequestSeq=6 len(Threads)=0", tResp) } // 7 >> disconnect, << disconnect client.DisconnectRequest() dResp := client.ExpectDisconnectResponse(t) if dResp.Seq != 0 || dResp.RequestSeq != 7 { t.Errorf("\ngot %#v\nwant Seq=0, RequestSeq=7", dResp) } }) } // TestSetBreakpoint corresponds to a debug session that is configured to // continue on entry with a pre-set breakpoint. func TestSetBreakpoint(t *testing.T) { runTest(t, "increment", func(client *daptest.Client, fixture protest.Fixture) { client.InitializeRequest() client.ExpectInitializeResponse(t) client.LaunchRequest("exec", fixture.Path, !stopOnEntry) client.ExpectInitializedEvent(t) client.ExpectLaunchResponse(t) client.SetBreakpointsRequest(fixture.Source, []int{8, 100}) sResp := client.ExpectSetBreakpointsResponse(t) if len(sResp.Body.Breakpoints) != 1 { t.Errorf("got %#v, want len(Breakpoints)=1", sResp) } bkpt0 := sResp.Body.Breakpoints[0] if !bkpt0.Verified || bkpt0.Line != 8 { t.Errorf("got breakpoints[0] = %#v, want Verified=true, Line=8", bkpt0) } client.SetExceptionBreakpointsRequest() client.ExpectSetExceptionBreakpointsResponse(t) client.ConfigurationDoneRequest() client.ExpectConfigurationDoneResponse(t) // This triggers "continue" // TODO(polina): add a no-op threads request // with dummy response here once server becomes async // to match what happens in VS Code. stopEvent1 := client.ExpectStoppedEvent(t) if stopEvent1.Body.Reason != "breakpoint" || stopEvent1.Body.ThreadId != 1 || !stopEvent1.Body.AllThreadsStopped { t.Errorf("got %#v, want Body={Reason=\"breakpoint\", ThreadId=1, AllThreadsStopped=true}", stopEvent1) } 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) } // TODO(polina): can we reliably test for these values? wantMain := dap.Thread{Id: 1, Name: "main.Increment"} wantRuntime := dap.Thread{Id: 2, Name: "runtime.gopark"} for _, got := range tResp.Body.Threads { if !reflect.DeepEqual(got, wantMain) && !strings.HasPrefix(got.Name, "runtime") { t.Errorf("\ngot %#v\nwant []dap.Thread{%#v, %#v, ...}", tResp.Body.Threads, wantMain, wantRuntime) } } client.StackTraceRequest(1, 0, 20) stResp := client.ExpectStackTraceResponse(t) if stResp.Body.TotalFrames != 6 { t.Errorf("\ngot %#v\nwant TotalFrames=6", stResp.Body.TotalFrames) } if len(stResp.Body.StackFrames) != 6 { t.Errorf("\ngot %#v\nwant len(StackFrames)=6", stResp.Body.StackFrames) } else { expectFrame := func(got dap.StackFrame, id int, name string, sourceName string, line int) { t.Helper() if got.Id != id || got.Name != name { t.Errorf("\ngot %#v\nwant Id=%d Name=%s", got, id, name) } if (sourceName != "" && got.Source.Name != sourceName) || (line > 0 && got.Line != line) { t.Errorf("\ngot %#v\nwant Source.Name=%s Line=%d", got, sourceName, line) } } 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) } client.ScopesRequest(1000) scopes := client.ExpectScopesResponse(t) 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) expectVarExact(t, args, 0, "y", "0", noChildren) expectVarExact(t, args, 1, "~r1", "0", noChildren) client.VariablesRequest(1001) // Locals locals := client.ExpectVariablesResponse(t) expectChildren(t, locals, "Locals", 0) client.ContinueRequest(1) client.ExpectContinueResponse(t) // "Continue" is triggered after the response is sent client.ExpectTerminatedEvent(t) client.DisconnectRequest() client.ExpectDisconnectResponse(t) }) } // expectStackFrames is a helper for verifying the values within StackTraceResponse. // wantStartLine - file line of the first returned frame (non-positive values are ignored). // wantStartID - id of the first frame returned (ignored if wantFrames is 0). // wantFrames - number of frames returned. // wantTotalFrames - total number of stack frames (StackTraceResponse.Body.TotalFrames). func expectStackFrames(t *testing.T, got *dap.StackTraceResponse, wantStartLine, wantStartID, wantFrames, wantTotalFrames int) { t.Helper() if got.Body.TotalFrames != wantTotalFrames { t.Errorf("\ngot %#v\nwant TotalFrames=%d", got.Body.TotalFrames, wantTotalFrames) } if len(got.Body.StackFrames) != wantFrames { t.Errorf("\ngot len(StackFrames)=%d\nwant %d", len(got.Body.StackFrames), wantFrames) } else { // Verify that frame ids are consecutive numbers starting at wantStartID for i := 0; i < wantFrames; i++ { if got.Body.StackFrames[i].Id != wantStartID+i { t.Errorf("\ngot %#v\nwant Id=%d", got.Body.StackFrames[i], wantStartID+i) } } // Verify the line corresponding to the first returned frame (if any). // This is useful when the first frame is the frame corresponding to the breakpoint at // a predefined line. Values < 0 are a signal to skip the check (which can be useful // for frames in the third-party code, where we do not control the lines). if wantFrames > 0 && wantStartLine > 0 && got.Body.StackFrames[0].Line != wantStartLine { t.Errorf("\ngot Line=%d\nwant %d", got.Body.StackFrames[0].Line, wantStartLine) } } } // 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. // parentName - name of the enclosing variable or scope // 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 { t.Errorf("\ngot len(%s)=%d\nwant %d", parentName, len(got.Body.Variables), numChildren) } } // 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 // value - the value of the variable // useExactMatch - true if value is to be compared to exactly, false if to be used as regex // hasRef - true if the variable should have children and therefore a non-0 variable reference // ref - reference to retrieve children of this variable (0 if none) func expectVar(t *testing.T, got *dap.VariablesResponse, i int, name, value string, useExactMatch, hasRef bool) (ref int) { t.Helper() if len(got.Body.Variables) <= i { t.Errorf("\ngot len=%d\nwant len>%d", len(got.Body.Variables), i) return } if i < 0 { for vi, v := range got.Body.Variables { if v.Name == name { i = vi break } } } if i < 0 { t.Errorf("\ngot %#v\nwant Variables[i].Name=%q", got, name) return 0 } goti := got.Body.Variables[i] if goti.Name != name || (goti.VariablesReference > 0) != hasRef { t.Errorf("\ngot %#v\nwant Name=%q hasRef=%t", goti, name, hasRef) } matched := false if useExactMatch { matched = (goti.Value == value) } else { matched, _ = regexp.MatchString(value, goti.Value) } if !matched { t.Errorf("\ngot %s=%q\nwant %q", name, goti.Value, value) } return goti.VariablesReference } // expectVarExact is a helper like expectVar that matches value exactly. func expectVarExact(t *testing.T, got *dap.VariablesResponse, i int, name, value string, hasRef bool) (ref int) { t.Helper() return expectVar(t, got, i, name, value, true, hasRef) } // expectVarRegex is a helper like expectVar that treats value as a regex. func expectVarRegex(t *testing.T, got *dap.VariablesResponse, i int, name, value string, hasRef bool) (ref int) { t.Helper() return expectVar(t, got, i, name, value, false, hasRef) } // TestStackTraceRequest executes to a breakpoint (similarly to TestSetBreakpoint // that includes more thorough checking of that sequence) and tests different // good and bad configurations of 'stackTrace' requests. func TestStackTraceRequest(t *testing.T) { runTest(t, "increment", func(client *daptest.Client, fixture protest.Fixture) { var stResp *dap.StackTraceResponse runDebugSessionWithBPs(t, client, // Launch func() { client.LaunchRequest("exec", fixture.Path, !stopOnEntry) }, // Set breakpoints fixture.Source, []int{8, 18}, []onBreakpoint{{ // Stop at line 8 execute: func() { client.StackTraceRequest(1, 0, 0) stResp = client.ExpectStackTraceResponse(t) expectStackFrames(t, stResp, 8, 1000, 6, 6) // Even though the stack frames are the same, // repeated requests at the same breakpoint, // would assign unique ids to them each time. client.StackTraceRequest(1, -100, 0) // Negative startFrame is treated as 0 stResp = client.ExpectStackTraceResponse(t) expectStackFrames(t, stResp, 8, 1006, 6, 6) client.StackTraceRequest(1, 3, 0) stResp = client.ExpectStackTraceResponse(t) expectStackFrames(t, stResp, 17, 1015, 3, 6) client.StackTraceRequest(1, 6, 0) stResp = client.ExpectStackTraceResponse(t) expectStackFrames(t, stResp, -1, -1, 0, 6) client.StackTraceRequest(1, 7, 0) // Out of bounds startFrame is capped at len stResp = client.ExpectStackTraceResponse(t) expectStackFrames(t, stResp, -1, -1, 0, 6) }, disconnect: false, }, { // Stop at line 18 execute: func() { // Frame ids get reset at each breakpoint. client.StackTraceRequest(1, 0, 0) stResp = client.ExpectStackTraceResponse(t) expectStackFrames(t, stResp, 18, 1000, 3, 3) client.StackTraceRequest(1, 0, -100) // Negative levels is treated as 0 stResp = client.ExpectStackTraceResponse(t) expectStackFrames(t, stResp, 18, 1003, 3, 3) client.StackTraceRequest(1, 0, 2) stResp = client.ExpectStackTraceResponse(t) expectStackFrames(t, stResp, 18, 1006, 2, 3) client.StackTraceRequest(1, 0, 3) stResp = client.ExpectStackTraceResponse(t) expectStackFrames(t, stResp, 18, 1009, 3, 3) client.StackTraceRequest(1, 0, 4) // Out of bounds levels is capped at len stResp = client.ExpectStackTraceResponse(t) expectStackFrames(t, stResp, 18, 1012, 3, 3) client.StackTraceRequest(1, 1, 2) stResp = client.ExpectStackTraceResponse(t) expectStackFrames(t, stResp, -1, 1016, 2, 3) // Don't test for runtime line we don't control }, 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) { runDebugSessionWithBPs(t, client, // Launch func() { client.LaunchRequest("exec", fixture.Path, !stopOnEntry) }, // Breakpoints are set within the program fixture.Source, []int{}, []onBreakpoint{{ // Stop at line 62 execute: func() { client.StackTraceRequest(1, 0, 20) stack := client.ExpectStackTraceResponse(t) startLineno := 62 if runtime.GOOS == "windows" && goversion.VersionAfterOrEqual(runtime.Version(), 1, 15) { // Go1.15 on windows inserts a NOP after the call to // runtime.Breakpoint and marks it as line 61 (same as the // runtime.Breakpoint call). startLineno = 61 } expectStackFrames(t, stack, startLineno, 1000, 4, 4) client.ScopesRequest(1000) scopes := client.ExpectScopesResponse(t) expectScope(t, scopes, 0, "Arguments", 1000) expectScope(t, scopes, 1, "Locals", 1001) // Arguments client.VariablesRequest(1000) args := client.ExpectVariablesResponse(t) expectChildren(t, args, "Arguments", 2) expectVarExact(t, args, 0, "baz", `"bazburzum"`, noChildren) ref := expectVarExact(t, args, 1, "bar", ``, hasChildren) if ref > 0 { client.VariablesRequest(ref) bar := client.ExpectVariablesResponse(t) expectChildren(t, bar, "bar", 2) expectVarExact(t, bar, 0, "Baz", "10", noChildren) expectVarExact(t, bar, 1, "Bur", `"lorem"`, noChildren) } // Locals client.VariablesRequest(1001) locals := client.ExpectVariablesResponse(t) expectChildren(t, locals, "Locals", 29) // reflect.Kind == Bool expectVarExact(t, locals, -1, "b1", "true", noChildren) expectVarExact(t, locals, -1, "b2", "false", noChildren) // reflect.Kind == Int expectVarExact(t, locals, -1, "a2", "6", noChildren) expectVarExact(t, locals, -1, "neg", "-1", noChildren) // reflect.Kind == Int8 expectVarExact(t, locals, -1, "i8", "1", noChildren) // reflect.Kind == Int16 - see testvariables2 // reflect.Kind == Int32 - see testvariables2 // reflect.Kind == Int64 - see testvariables2 // reflect.Kind == Uint // reflect.Kind == Uint8 expectVarExact(t, locals, -1, "u8", "255", noChildren) // reflect.Kind == Uint16 expectVarExact(t, locals, -1, "u16", "65535", noChildren) // reflect.Kind == Uint32 expectVarExact(t, locals, -1, "u32", "4294967295", noChildren) // reflect.Kind == Uint64 expectVarExact(t, locals, -1, "u64", "18446744073709551615", noChildren) // reflect.Kind == Uintptr expectVarExact(t, locals, -1, "up", "5", noChildren) // reflect.Kind == Float32 expectVarExact(t, locals, -1, "f32", "1.2", noChildren) // reflect.Kind == Float64 expectVarExact(t, locals, -1, "a3", "7.23", noChildren) // reflect.Kind == Complex64 ref = expectVarExact(t, locals, -1, "c64", "(1 + 2i)", hasChildren) if ref > 0 { client.VariablesRequest(ref) c64 := client.ExpectVariablesResponse(t) expectChildren(t, c64, "c64", 2) expectVarExact(t, c64, 0, "real", "1", noChildren) expectVarExact(t, c64, 1, "imaginary", "2", noChildren) } // reflect.Kind == Complex128 ref = expectVarExact(t, locals, -1, "c128", "(2 + 3i)", hasChildren) if ref > 0 { client.VariablesRequest(ref) c128 := client.ExpectVariablesResponse(t) expectChildren(t, c128, "c128", 2) expectVarExact(t, c128, 0, "real", "2", noChildren) expectVarExact(t, c128, 1, "imaginary", "3", noChildren) } // reflect.Kind == Array ref = expectVarExact(t, locals, -1, "a4", "<[2]int>", hasChildren) if ref > 0 { client.VariablesRequest(ref) a4 := client.ExpectVariablesResponse(t) expectChildren(t, a4, "a4", 2) expectVarExact(t, a4, 0, "[0]", "1", noChildren) expectVarExact(t, a4, 1, "[1]", "2", noChildren) } ref = expectVarExact(t, locals, -1, "a11", "<[3]main.FooBar>", hasChildren) if ref > 0 { client.VariablesRequest(ref) a11 := client.ExpectVariablesResponse(t) expectChildren(t, a11, "a11", 3) expectVarExact(t, a11, 0, "[0]", "", hasChildren) ref = expectVarExact(t, a11, 1, "[1]", "", hasChildren) if ref > 0 { client.VariablesRequest(ref) a11_1 := client.ExpectVariablesResponse(t) expectChildren(t, a11_1, "a11[1]", 2) expectVarExact(t, a11_1, 0, "Baz", "2", noChildren) expectVarExact(t, a11_1, 1, "Bur", `"b"`, noChildren) } expectVarExact(t, a11, 2, "[2]", "", hasChildren) } // reflect.Kind == Chan - see testvariables2 // reflect.Kind == Func - see testvariables2 // reflect.Kind == Interface - see testvariables2 // reflect.Kind == Map - see testvariables2 // reflect.Kind == Ptr ref = expectVarRegex(t, locals, -1, "a7", "<\\*main\\.FooBar>\\(0x[0-9a-f]+\\)", hasChildren) if ref > 0 { client.VariablesRequest(ref) a7 := client.ExpectVariablesResponse(t) expectChildren(t, a7, "a7", 1) ref = expectVarExact(t, a7, 0, "", "", hasChildren) if ref > 0 { client.VariablesRequest(ref) a7val := client.ExpectVariablesResponse(t) expectChildren(t, a7val, "*a7", 2) expectVarExact(t, a7val, 0, "Baz", "5", noChildren) expectVarExact(t, a7val, 1, "Bur", `"strum"`, noChildren) } } // TODO(polina): how to test for "nil" (without type) and "void"? expectVarExact(t, locals, -1, "a9", "nil <*main.FooBar>", noChildren) // reflect.Kind == Slice ref = expectVarExact(t, locals, -1, "a5", "<[]int> (length: 5, cap: 5)", hasChildren) if ref > 0 { client.VariablesRequest(ref) a5 := client.ExpectVariablesResponse(t) expectChildren(t, a5, "a5", 5) expectVarExact(t, a5, 0, "[0]", "1", noChildren) expectVarExact(t, a5, 4, "[4]", "5", noChildren) } ref = expectVarExact(t, locals, -1, "a12", "<[]main.FooBar> (length: 2, cap: 2)", hasChildren) if ref > 0 { client.VariablesRequest(ref) a12 := client.ExpectVariablesResponse(t) expectChildren(t, a12, "a12", 2) expectVarExact(t, a12, 0, "[0]", "", hasChildren) ref = expectVarExact(t, a12, 1, "[1]", "", hasChildren) if ref > 0 { client.VariablesRequest(ref) a12_1 := client.ExpectVariablesResponse(t) expectChildren(t, a12_1, "a12[1]", 2) expectVarExact(t, a12_1, 0, "Baz", "5", noChildren) expectVarExact(t, a12_1, 1, "Bur", `"e"`, noChildren) } } ref = expectVarExact(t, locals, -1, "a13", "<[]*main.FooBar> (length: 3, cap: 3)", hasChildren) if ref > 0 { client.VariablesRequest(ref) a13 := client.ExpectVariablesResponse(t) expectChildren(t, a13, "a13", 3) expectVarRegex(t, a13, 0, "[0]", "<\\*main\\.FooBar>\\(0x[0-9a-f]+\\)", hasChildren) expectVarRegex(t, a13, 1, "[1]", "<\\*main\\.FooBar>\\(0x[0-9a-f]+\\)", hasChildren) ref = expectVarRegex(t, a13, 2, "[2]", "<\\*main\\.FooBar>\\(0x[0-9a-f]+\\)", hasChildren) if ref > 0 { client.VariablesRequest(ref) a13_2 := client.ExpectVariablesResponse(t) expectChildren(t, a13_2, "a13[2]", 1) ref = expectVarExact(t, a13_2, 0, "", "", hasChildren) if ref > 0 { client.VariablesRequest(ref) val := client.ExpectVariablesResponse(t) expectChildren(t, val, "*a13[2]", 2) expectVarExact(t, val, 0, "Baz", "8", noChildren) expectVarExact(t, val, 1, "Bur", `"h"`, noChildren) } } } // reflect.Kind == String expectVarExact(t, locals, -1, "a1", `"foofoofoofoofoofoo"`, noChildren) expectVarExact(t, locals, -1, "a10", `"ofo"`, noChildren) // reflect.Kind == Struct ref = expectVarExact(t, locals, -1, "a6", "", hasChildren) if ref > 0 { client.VariablesRequest(ref) a6 := client.ExpectVariablesResponse(t) expectChildren(t, a6, "a6", 2) expectVarExact(t, a6, 0, "Baz", "8", noChildren) expectVarExact(t, a6, 1, "Bur", `"word"`, noChildren) } ref = expectVarExact(t, locals, -1, "a8", "", hasChildren) if ref > 0 { client.VariablesRequest(ref) a8 := client.ExpectVariablesResponse(t) expectChildren(t, a8, "a8", 2) expectVarExact(t, a8, 0, "Bur", "10", noChildren) expectVarExact(t, a8, 1, "Baz", `"feh"`, noChildren) } // reflect.Kind == UnsafePointer - see testvariables2 }, disconnect: false, }, { // Stop at line 25 execute: func() { // Frame ids get reset at each breakpoint. client.StackTraceRequest(1, 0, 20) stack := client.ExpectStackTraceResponse(t) expectStackFrames(t, stack, 25, 1000, 5, 5) client.ScopesRequest(1000) scopes := client.ExpectScopesResponse(t) expectScope(t, scopes, 0, "Arguments", 1000) expectScope(t, scopes, 1, "Locals", 1001) client.ScopesRequest(1111) erres := client.ExpectErrorResponse(t) 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) expectVarExact(t, locals, -1, "a1", `"bur"`, noChildren) client.VariablesRequest(7777) erres = client.ExpectErrorResponse(t) 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) { runDebugSessionWithBPs(t, client, // Launch func() { client.LaunchRequest("exec", fixture.Path, !stopOnEntry) }, // Breakpoints are set within the program fixture.Source, []int{}, []onBreakpoint{{ // Stop at line 317 execute: func() { client.StackTraceRequest(1, 0, 20) stack := client.ExpectStackTraceResponse(t) expectStackFrames(t, stack, 317, 1000, 3, 3) client.ScopesRequest(1000) scopes := client.ExpectScopesResponse(t) expectScope(t, scopes, 0, "Arguments", 1000) expectScope(t, scopes, 1, "Locals", 1001) }, disconnect: false, }, { // Stop at line 322 execute: func() { client.StackTraceRequest(1, 0, 20) stack := client.ExpectStackTraceResponse(t) expectStackFrames(t, stack, 322, 1000, 3, 3) client.ScopesRequest(1000) scopes := client.ExpectScopesResponse(t) 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 expectVarExact(t, locals, -1, "ni8", "-5", noChildren) // reflect.Kind == Int16 expectVarExact(t, locals, -1, "ni16", "-5", noChildren) // reflect.Kind == Int32 expectVarExact(t, locals, -1, "ni32", "-5", noChildren) // reflect.Kind == Int64 expectVarExact(t, locals, -1, "ni64", "-5", noChildren) // reflect.Kind == Uint // reflect.Kind == Uint8 - see testvariables // reflect.Kind == Uint16 - see testvariables // reflect.Kind == Uint32 - see testvariables // reflect.Kind == Uint64 - see testvariables // reflect.Kind == Uintptr - see testvariables // reflect.Kind == Float32 - see testvariables // reflect.Kind == Float64 expectVarExact(t, locals, -1, "pinf", "+Inf", noChildren) expectVarExact(t, locals, -1, "ninf", "-Inf", noChildren) expectVarExact(t, locals, -1, "nan", "NaN", noChildren) // reflect.Kind == Complex64 - see testvariables // reflect.Kind == Complex128 - see testvariables // reflect.Kind == Array expectVarExact(t, locals, -1, "a0", "<[0]int>", noChildren) // reflect.Kind == Chan ref := expectVarExact(t, locals, -1, "ch1", "", hasChildren) if ref > 0 { client.VariablesRequest(ref) ch1 := client.ExpectVariablesResponse(t) expectChildren(t, ch1, "ch1", 11) expectVarExact(t, ch1, 0, "qcount", "4", noChildren) expectVarExact(t, ch1, 10, "lock", "", hasChildren) } expectVarExact(t, locals, -1, "chnil", "nil ", noChildren) // reflect.Kind == Func expectVarExact(t, locals, -1, "fn1", "main.afunc", noChildren) expectVarExact(t, locals, -1, "fn2", "", noChildren) // reflect.Kind == Interface expectVarExact(t, locals, -1, "ifacenil", "nil ", noChildren) ref = expectVarExact(t, locals, -1, "iface2", "", hasChildren) if ref > 0 { client.VariablesRequest(ref) iface2 := client.ExpectVariablesResponse(t) expectChildren(t, iface2, "iface2", 1) expectVarExact(t, iface2, 0, "data", `"test"`, noChildren) } ref = expectVarExact(t, locals, -1, "iface4", "", hasChildren) if ref > 0 { client.VariablesRequest(ref) iface4 := client.ExpectVariablesResponse(t) expectChildren(t, iface4, "iface4", 1) ref = expectVarExact(t, iface4, 0, "data", "<[]go/constant.Value> (length: 1, cap: 1)", hasChildren) if ref > 0 { client.VariablesRequest(ref) iface4data := client.ExpectVariablesResponse(t) expectChildren(t, iface4data, "iface4.data", 1) expectVarExact(t, iface4data, 0, "[0]", "", hasChildren) } } // reflect.Kind == Map expectVarExact(t, locals, -1, "mnil", "nil ", noChildren) ref = expectVarExact(t, locals, -1, "m2", " (length: 1)", hasChildren) if ref > 0 { client.VariablesRequest(ref) m2 := client.ExpectVariablesResponse(t) expectChildren(t, m2, "m2", 1) ref = expectVarRegex(t, m2, 0, "1", "<\\*main\\.astruct>\\(0x[0-9a-f]+\\)", hasChildren) if ref > 0 { client.VariablesRequest(ref) m2_1 := client.ExpectVariablesResponse(t) expectChildren(t, m2_1, "m2[1]", 1) ref = expectVarExact(t, m2_1, 0, "", "", hasChildren) if ref > 0 { client.VariablesRequest(ref) m2_1val := client.ExpectVariablesResponse(t) expectChildren(t, m2_1val, "*m2[1]", 2) expectVarExact(t, m2_1val, 0, "A", "10", noChildren) expectVarExact(t, m2_1val, 1, "B", "11", noChildren) } } } ref = expectVarExact(t, locals, -1, "m3", " (length: 2)", hasChildren) if ref > 0 { client.VariablesRequest(ref) m3 := client.ExpectVariablesResponse(t) expectChildren(t, m3, "m3", 2) ref = expectVarExact(t, m3, 0, "[0]", "42", hasChildren) if ref > 0 { client.VariablesRequest(ref) m3_0 := client.ExpectVariablesResponse(t) expectChildren(t, m3_0, "m3[0]", 2) expectVarExact(t, m3_0, 0, "A", "1", noChildren) expectVarExact(t, m3_0, 1, "B", "1", noChildren) } ref = expectVarExact(t, m3, 1, "[1]", "43", hasChildren) if ref > 0 { client.VariablesRequest(ref) m3_1 := client.ExpectVariablesResponse(t) expectChildren(t, m3_1, "m3[1]", 2) expectVarExact(t, m3_1, 0, "A", "2", noChildren) expectVarExact(t, m3_1, 1, "B", "2", noChildren) } } ref = expectVarExact(t, locals, -1, "m4", " (length: 2)", hasChildren) if ref > 0 { client.VariablesRequest(ref) m4 := client.ExpectVariablesResponse(t) expectChildren(t, m4, "m4", 4) expectVarExact(t, m4, 0, "[key 0]", "", hasChildren) expectVarExact(t, m4, 1, "[val 0]", "", hasChildren) ref = expectVarExact(t, m4, 2, "[key 1]", "", hasChildren) if ref > 0 { client.VariablesRequest(ref) m4_key1 := client.ExpectVariablesResponse(t) expectChildren(t, m4_key1, "m4_key1", 2) expectVarExact(t, m4_key1, 0, "A", "2", noChildren) expectVarExact(t, m4_key1, 1, "B", "2", noChildren) } ref = expectVarExact(t, m4, 3, "[val 1]", "", hasChildren) if ref > 0 { client.VariablesRequest(ref) m4_val1 := client.ExpectVariablesResponse(t) expectChildren(t, m4_val1, "m4_val1", 2) expectVarExact(t, m4_val1, 0, "A", "22", noChildren) expectVarExact(t, m4_val1, 1, "B", "22", noChildren) } } expectVarExact(t, locals, -1, "emptymap", " (length: 0)", noChildren) // reflect.Kind == Ptr - see testvariables // reflect.Kind == Slice ref = expectVarExact(t, locals, -1, "zsslice", "<[]struct {}> (length: 3, cap: 3)", hasChildren) if ref > 0 { client.VariablesRequest(ref) zsslice := client.ExpectVariablesResponse(t) expectChildren(t, zsslice, "zsslice", 3) } expectVarExact(t, locals, -1, "emptyslice", "<[]string> (length: 0, cap: 0)", noChildren) expectVarExact(t, locals, -1, "nilslice", "nil <[]int>", noChildren) // reflect.Kind == String expectVarExact(t, locals, -1, "longstr", "\"very long string 0123456789a0123456789b0123456789c0123456789d012...+73 more\"", noChildren) // reflect.Kind == Struct expectVarExact(t, locals, -1, "zsvar", "", noChildren) // reflect.Kind == UnsafePointer // TODO(polina): how do I test for unsafe.Pointer(nil)? expectVarRegex(t, locals, -1, "upnil", "unsafe\\.Pointer\\(0x0\\)", noChildren) expectVarRegex(t, locals, -1, "up1", "unsafe\\.Pointer\\(0x[0-9a-f]+\\)", noChildren) // Test unreadable variable ref = expectVarExact(t, locals, -1, "unread", "<*int>(0x3039)", hasChildren) if ref > 0 { client.VariablesRequest(ref) val := client.ExpectVariablesResponse(t) expectChildren(t, val, "*unread", 1) expectVarRegex(t, val, 0, "", "unreadable <.+>", noChildren) } // Test that variables are not yet loaded completely. ref = expectVarExact(t, locals, -1, "m1", " (length: 66)", hasChildren) if ref > 0 { client.VariablesRequest(ref) m1 := client.ExpectVariablesResponse(t) expectChildren(t, m1, "m1", 64) // TODO(polina): should be 66. } }, disconnect: true, }}) }) } // Tests that 'stackTraceDepth' from LaunchRequest is parsed and passed to // stacktrace requests handlers. func TestLaunchRequestWithStackTraceDepth(t *testing.T) { runTest(t, "increment", func(client *daptest.Client, fixture protest.Fixture) { var stResp *dap.StackTraceResponse runDebugSessionWithBPs(t, client, // Launch func() { client.LaunchRequestWithArgs(map[string]interface{}{ "mode": "exec", "program": fixture.Path, "stackTraceDepth": 1, }) }, // Set breakpoints fixture.Source, []int{8}, []onBreakpoint{{ // Stop at line 8 execute: func() { client.StackTraceRequest(1, 0, 0) stResp = client.ExpectStackTraceResponse(t) expectStackFrames(t, stResp, 8, 1000, 2, 2) }, disconnect: false, }}) }) } // 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. type onBreakpoint struct { execute func() disconnect bool } // runDebugSessionWithBPs is a helper for executing the common init and shutdown // sequences for a program that does not stop on entry // while specifying breakpoints and unique launch criteria via parameters. // launchRequest - a function that sends a launch request, so the test author // has full control of its arguments. Note that he rest of // the test sequence assumes that stopOneEntry is false. // breakpoints - list of lines, where breakpoints are to be set // onBreakpoints - list of test sequences to execute at each of the set breakpoints. func runDebugSessionWithBPs(t *testing.T, client *daptest.Client, launchRequest func(), source string, breakpoints []int, onBPs []onBreakpoint) { client.InitializeRequest() client.ExpectInitializeResponse(t) launchRequest() client.ExpectInitializedEvent(t) client.ExpectLaunchResponse(t) client.SetBreakpointsRequest(source, breakpoints) client.ExpectSetBreakpointsResponse(t) // Skip no-op setExceptionBreakpoints client.ConfigurationDoneRequest() client.ExpectConfigurationDoneResponse(t) // Program automatically continues to breakpoint or completion // TODO(polina): See if we can make this more like withTestProcessArgs in proc_test: // a single function pointer gets called here and then if it wants to continue it calls // client.ContinueRequest/client.ExpectContinueResponse/client.ExpectStoppedEvent // (possibly using a helper function). for _, onBP := range onBPs { client.ExpectStoppedEvent(t) onBP.execute() if onBP.disconnect { client.DisconnectRequest() client.ExpectDisconnectResponse(t) return } client.ContinueRequest(1) client.ExpectContinueResponse(t) // "Continue" is triggered after the response is sent } client.ExpectTerminatedEvent(t) client.DisconnectRequest() client.ExpectDisconnectResponse(t) } // runDebugSesion is a helper for executing the standard init and shutdown // sequences for a program that does not stop on entry // while specifying unique launch criteria via parameters. func runDebugSession(t *testing.T, client *daptest.Client, launchRequest func()) { runDebugSessionWithBPs(t, client, launchRequest, "", nil, nil) } func TestLaunchDebugRequest(t *testing.T) { runTest(t, "increment", func(client *daptest.Client, fixture protest.Fixture) { // We reuse the harness that builds, but ignore the built binary, // only relying on the source to be built in response to LaunchRequest. runDebugSession(t, client, func() { // Use the default output directory. client.LaunchRequestWithArgs(map[string]interface{}{ "mode": "debug", "program": fixture.Source}) }) }) } func TestLaunchTestRequest(t *testing.T) { runTest(t, "increment", func(client *daptest.Client, fixture protest.Fixture) { runDebugSession(t, client, func() { // We reuse the harness that builds, but ignore the built binary, // only relying on the source to be built in response to LaunchRequest. fixtures := protest.FindFixturesDir() testdir, _ := filepath.Abs(filepath.Join(fixtures, "buildtest")) client.LaunchRequestWithArgs(map[string]interface{}{ "mode": "test", "program": testdir, "output": "__mytestdir"}) }) }) } // Tests that 'args' from LaunchRequest are parsed and passed to the target // program. The target program exits without an error on success, and // panics on error, causing an unexpected StoppedEvent instead of // Terminated Event. func TestLaunchRequestWithArgs(t *testing.T) { runTest(t, "testargs", func(client *daptest.Client, fixture protest.Fixture) { runDebugSession(t, client, func() { client.LaunchRequestWithArgs(map[string]interface{}{ "mode": "exec", "program": fixture.Path, "args": []string{"test", "pass flag"}}) }) }) } // Tests that 'buildFlags' from LaunchRequest are parsed and passed to the // compiler. The target program exits without an error on success, and // panics on error, causing an unexpected StoppedEvent instead of // TerminatedEvent. func TestLaunchRequestWithBuildFlags(t *testing.T) { runTest(t, "buildflagtest", func(client *daptest.Client, fixture protest.Fixture) { runDebugSession(t, client, func() { // We reuse the harness that builds, but ignore the built binary, // only relying on the source to be built in response to LaunchRequest. client.LaunchRequestWithArgs(map[string]interface{}{ "mode": "debug", "program": fixture.Source, "buildFlags": "-ldflags '-X main.Hello=World'"}) }) }) } 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.AttachRequest() expectNotYetImplemented("attach") client.NextRequest() expectNotYetImplemented("next") client.StepInRequest() expectNotYetImplemented("stepIn") client.StepOutRequest() expectNotYetImplemented("stepOut") client.PauseRequest() expectNotYetImplemented("pause") client.EvaluateRequest() expectNotYetImplemented("evaluate") }) } 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") }) } func TestBadLaunchRequests(t *testing.T) { runTest(t, "increment", func(client *daptest.Client, fixture protest.Fixture) { seqCnt := 1 expectFailedToLaunch := func(response *dap.ErrorResponse) { t.Helper() if response.RequestSeq != seqCnt { t.Errorf("RequestSeq got %d, want %d", seqCnt, response.RequestSeq) } if response.Command != "launch" { t.Errorf("Command got %q, want \"launch\"", response.Command) } if response.Message != "Failed to launch" { t.Errorf("Message got %q, want \"Failed to launch\"", response.Message) } if response.Body.Error.Id != 3000 { t.Errorf("Id got %d, want 3000", response.Body.Error.Id) } seqCnt++ } 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) } } // Test for the DAP-specific detailed error message. client.LaunchRequest("exec", "", stopOnEntry) expectFailedToLaunchWithMessage(client.ExpectErrorResponse(t), "Failed to launch: The program attribute is missing in debug configuration.") client.LaunchRequestWithArgs(map[string]interface{}{"program": 12345}) expectFailedToLaunchWithMessage(client.ExpectErrorResponse(t), "Failed to launch: The program attribute is missing in debug configuration.") client.LaunchRequestWithArgs(map[string]interface{}{"program": nil}) expectFailedToLaunchWithMessage(client.ExpectErrorResponse(t), "Failed to launch: The program attribute is missing in debug configuration.") client.LaunchRequestWithArgs(map[string]interface{}{}) expectFailedToLaunchWithMessage(client.ExpectErrorResponse(t), "Failed to launch: The program attribute is missing in debug configuration.") client.LaunchRequest("remote", fixture.Path, stopOnEntry) expectFailedToLaunchWithMessage(client.ExpectErrorResponse(t), "Failed to launch: Unsupported 'mode' value \"remote\" in debug configuration.") client.LaunchRequest("notamode", fixture.Path, stopOnEntry) expectFailedToLaunchWithMessage(client.ExpectErrorResponse(t), "Failed to launch: Unsupported 'mode' value \"notamode\" in debug configuration.") client.LaunchRequestWithArgs(map[string]interface{}{"mode": 12345, "program": fixture.Path}) expectFailedToLaunchWithMessage(client.ExpectErrorResponse(t), "Failed to launch: Unsupported 'mode' value %!q(float64=12345) in debug configuration.") client.LaunchRequestWithArgs(map[string]interface{}{"mode": "exec", "program": fixture.Path, "args": nil}) expectFailedToLaunchWithMessage(client.ExpectErrorResponse(t), "Failed to launch: 'args' attribute '' in debug configuration is not an array.") client.LaunchRequestWithArgs(map[string]interface{}{"mode": "exec", "program": fixture.Path, "args": 12345}) expectFailedToLaunchWithMessage(client.ExpectErrorResponse(t), "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}}) expectFailedToLaunchWithMessage(client.ExpectErrorResponse(t), "Failed to launch: value '1' in 'args' attribute in debug configuration is not a string.") client.LaunchRequestWithArgs(map[string]interface{}{"mode": "debug", "program": fixture.Source, "buildFlags": 123}) expectFailedToLaunchWithMessage(client.ExpectErrorResponse(t), "Failed to launch: 'buildFlags' attribute '123' in debug configuration is not a string.") // Skip detailed message checks for potentially different OS-specific errors. client.LaunchRequest("exec", fixture.Path+"_does_not_exist", stopOnEntry) expectFailedToLaunch(client.ExpectErrorResponse(t)) client.LaunchRequest("debug", fixture.Path+"_does_not_exist", stopOnEntry) expectFailedToLaunch(client.ExpectErrorResponse(t)) // Build error client.LaunchRequest("exec", fixture.Source, stopOnEntry) expectFailedToLaunch(client.ExpectErrorResponse(t)) // Not an executable client.LaunchRequestWithArgs(map[string]interface{}{"mode": "debug", "program": fixture.Source, "buildFlags": "123"}) expectFailedToLaunch(client.ExpectErrorResponse(t)) // Build error // We failed to launch the program. Make sure shutdown still works. client.DisconnectRequest() dresp := client.ExpectDisconnectResponse(t) if dresp.RequestSeq != seqCnt { t.Errorf("got %#v, want RequestSeq=%d", dresp, seqCnt) } }) } func 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) } }) }