2020-02-15 19:52:53 +00:00
|
|
|
package dap
|
|
|
|
|
|
|
|
import (
|
|
|
|
"flag"
|
2020-02-27 04:45:48 +00:00
|
|
|
"io"
|
2020-02-15 19:52:53 +00:00
|
|
|
"net"
|
|
|
|
"os"
|
2020-03-04 17:22:51 +00:00
|
|
|
"path/filepath"
|
2020-02-27 04:45:48 +00:00
|
|
|
"sync"
|
2020-02-15 19:52:53 +00:00
|
|
|
"testing"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"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/google/go-dap"
|
|
|
|
)
|
|
|
|
|
2020-03-04 17:22:51 +00:00
|
|
|
const stopOnEntry bool = true
|
|
|
|
|
2020-02-15 19:52:53 +00:00
|
|
|
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))
|
|
|
|
}
|
|
|
|
|
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)) {
|
|
|
|
var buildFlags protest.BuildFlags
|
|
|
|
fixture := protest.BuildFixture(name, buildFlags)
|
|
|
|
|
|
|
|
// 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,
|
|
|
|
Backend: "default",
|
2020-02-27 04:45:48 +00:00
|
|
|
DisconnectChan: disconnectChan,
|
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
|
|
|
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() })
|
|
|
|
}()
|
2020-02-15 19:52:53 +00:00
|
|
|
|
2020-02-27 04:45:48 +00:00
|
|
|
client := daptest.NewClient(listener.Addr().String())
|
2020-02-15 19:52:53 +00:00
|
|
|
defer client.Close()
|
|
|
|
|
2020-02-27 04:45:48 +00:00
|
|
|
defer func() {
|
|
|
|
stopOnce.Do(func() { server.Stop() })
|
|
|
|
}()
|
|
|
|
|
2020-02-15 19:52:53 +00:00
|
|
|
test(client, fixture)
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestStopOnEntry(t *testing.T) {
|
|
|
|
runTest(t, "increment", func(client *daptest.Client, fixture protest.Fixture) {
|
2020-02-24 17:36:34 +00:00
|
|
|
// This test exhaustively tests Seq and RequestSeq on all messages from the
|
|
|
|
// server. Other tests shouldn't necessarily repeat these checks.
|
2020-02-15 19:52:53 +00:00
|
|
|
client.InitializeRequest()
|
2020-02-24 17:36:34 +00:00
|
|
|
initResp := client.ExpectInitializeResponse(t)
|
|
|
|
if initResp.Seq != 0 || initResp.RequestSeq != 0 {
|
|
|
|
t.Errorf("got %#v, want Seq=0, RequestSeq=0", initResp)
|
|
|
|
}
|
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
|
|
|
initEv := client.ExpectInitializedEvent(t)
|
|
|
|
if initEv.Seq != 0 {
|
|
|
|
t.Errorf("got %#v, want Seq=0", initEv)
|
|
|
|
}
|
|
|
|
|
|
|
|
launchResp := client.ExpectLaunchResponse(t)
|
|
|
|
if launchResp.Seq != 0 || launchResp.RequestSeq != 1 {
|
|
|
|
t.Errorf("got %#v, want Seq=0, RequestSeq=1", launchResp)
|
|
|
|
}
|
2020-02-15 19:52:53 +00:00
|
|
|
|
|
|
|
client.SetExceptionBreakpointsRequest()
|
2020-02-24 17:36:34 +00:00
|
|
|
sResp := client.ExpectSetExceptionBreakpointsResponse(t)
|
|
|
|
if sResp.Seq != 0 || sResp.RequestSeq != 2 {
|
|
|
|
t.Errorf("got %#v, want Seq=0, RequestSeq=2", sResp)
|
|
|
|
}
|
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 ||
|
|
|
|
stopEvent.Body.Reason != "breakpoint" ||
|
|
|
|
stopEvent.Body.ThreadId != 1 ||
|
|
|
|
!stopEvent.Body.AllThreadsStopped {
|
|
|
|
t.Errorf("got %#v, want Seq=0, Body={Reason=\"breakpoint\", ThreadId=1, AllThreadsStopped=true}", stopEvent)
|
|
|
|
}
|
|
|
|
|
|
|
|
cdResp := client.ExpectConfigurationDoneResponse(t)
|
|
|
|
if cdResp.Seq != 0 || cdResp.RequestSeq != 3 {
|
|
|
|
t.Errorf("got %#v, want Seq=0, RequestSeq=3", cdResp)
|
|
|
|
}
|
2020-02-15 19:52:53 +00:00
|
|
|
|
|
|
|
client.ContinueRequest(1)
|
2020-02-24 17:36:34 +00:00
|
|
|
contResp := client.ExpectContinueResponse(t)
|
|
|
|
if contResp.Seq != 0 || contResp.RequestSeq != 4 {
|
|
|
|
t.Errorf("got %#v, want Seq=0, RequestSeq=4", contResp)
|
|
|
|
}
|
|
|
|
|
|
|
|
termEv := client.ExpectTerminatedEvent(t)
|
|
|
|
if termEv.Seq != 0 {
|
|
|
|
t.Errorf("got %#v, want Seq=0", termEv)
|
|
|
|
}
|
2020-02-15 19:52:53 +00:00
|
|
|
|
|
|
|
client.DisconnectRequest()
|
2020-02-24 17:36:34 +00:00
|
|
|
dResp := client.ExpectDisconnectResponse(t)
|
|
|
|
if dResp.Seq != 0 || dResp.RequestSeq != 5 {
|
|
|
|
t.Errorf("got %#v, want Seq=0, RequestSeq=5", dResp)
|
|
|
|
}
|
2020-02-15 19:52:53 +00:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestSetBreakpoint(t *testing.T) {
|
|
|
|
runTest(t, "increment", func(client *daptest.Client, fixture protest.Fixture) {
|
|
|
|
client.InitializeRequest()
|
2020-02-24 17:36:34 +00:00
|
|
|
client.ExpectInitializeResponse(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)
|
|
|
|
launchResp := client.ExpectLaunchResponse(t)
|
|
|
|
if launchResp.RequestSeq != 1 {
|
|
|
|
t.Errorf("got %#v, want RequestSeq=1", launchResp)
|
|
|
|
}
|
2020-02-15 19:52:53 +00:00
|
|
|
|
|
|
|
client.SetBreakpointsRequest(fixture.Source, []int{8, 100})
|
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]
|
|
|
|
if !bkpt0.Verified || bkpt0.Line != 8 {
|
|
|
|
t.Errorf("got breakpoints[0] = %#v, want Verified=true, Line=8", bkpt0)
|
|
|
|
}
|
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-02-24 17:36:34 +00:00
|
|
|
cdResp := client.ExpectConfigurationDoneResponse(t)
|
|
|
|
if cdResp.RequestSeq != 4 {
|
|
|
|
t.Errorf("got %#v, want RequestSeq=4", cdResp)
|
|
|
|
}
|
2020-02-15 19:52:53 +00:00
|
|
|
|
|
|
|
client.ContinueRequest(1)
|
2020-02-24 17:36:34 +00:00
|
|
|
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.ExpectContinueResponse(t)
|
2020-02-15 19:52:53 +00:00
|
|
|
|
|
|
|
client.ContinueRequest(1)
|
2020-02-24 17:36:34 +00:00
|
|
|
client.ExpectTerminatedEvent(t)
|
|
|
|
client.ExpectContinueResponse(t)
|
2020-02-15 19:52:53 +00:00
|
|
|
|
|
|
|
client.DisconnectRequest()
|
2020-02-24 17:36:34 +00:00
|
|
|
client.ExpectDisconnectResponse(t)
|
2020-02-15 19:52:53 +00:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2020-03-04 17:22:51 +00:00
|
|
|
// runDebugSesion is a helper for executing the standard init and shutdown
|
|
|
|
// sequences while specifying unique launch criteria via parameters.
|
|
|
|
func runDebugSession(t *testing.T, client *daptest.Client, launchRequest func()) {
|
|
|
|
client.InitializeRequest()
|
|
|
|
client.ExpectInitializeResponse(t)
|
|
|
|
|
|
|
|
launchRequest()
|
|
|
|
client.ExpectInitializedEvent(t)
|
|
|
|
client.ExpectLaunchResponse(t)
|
|
|
|
|
|
|
|
client.ConfigurationDoneRequest()
|
|
|
|
client.ExpectConfigurationDoneResponse(t)
|
|
|
|
|
|
|
|
client.ExpectTerminatedEvent(t)
|
|
|
|
client.DisconnectRequest()
|
|
|
|
client.ExpectDisconnectResponse(t)
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestLaunchDebugRequest(t *testing.T) {
|
2020-02-15 19:52:53 +00:00
|
|
|
runTest(t, "increment", func(client *daptest.Client, fixture protest.Fixture) {
|
2020-03-04 17:22:51 +00:00
|
|
|
// We reuse the harness that builds, but ignore the actual binary.
|
|
|
|
runDebugSession(t, client, func() {
|
|
|
|
// Use the default output directory.
|
|
|
|
client.LaunchRequestWithArgs(map[string]interface{}{
|
|
|
|
"mode": "debug", "program": fixture.Source})
|
|
|
|
})
|
|
|
|
})
|
|
|
|
}
|
2020-02-24 17:36:34 +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) {
|
|
|
|
runDebugSession(t, client, func() {
|
|
|
|
// We reuse the harness that builds, but ignore the actual binary.
|
|
|
|
fixtures := protest.FindFixturesDir()
|
|
|
|
testdir, _ := filepath.Abs(filepath.Join(fixtures, "buildtest"))
|
|
|
|
client.LaunchRequestWithArgs(map[string]interface{}{
|
|
|
|
"mode": "test", "program": testdir, "output": "__mytestdir"})
|
|
|
|
})
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestBadLaunchRequests(t *testing.T) {
|
|
|
|
runTest(t, "increment", func(client *daptest.Client, fixture protest.Fixture) {
|
|
|
|
seqCnt := 0
|
|
|
|
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)
|
|
|
|
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.")
|
|
|
|
|
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)
|
|
|
|
expectFailedToLaunch(client.ExpectErrorResponse(t))
|
|
|
|
|
|
|
|
client.LaunchRequest("debug", fixture.Path+"_does_not_exist", stopOnEntry)
|
|
|
|
expectFailedToLaunch(client.ExpectErrorResponse(t)) // Build error
|
2020-02-15 19:52:53 +00:00
|
|
|
|
2020-03-04 17:22:51 +00:00
|
|
|
client.LaunchRequest("exec", fixture.Source, stopOnEntry)
|
|
|
|
expectFailedToLaunch(client.ExpectErrorResponse(t)) // Not an executable
|
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
|
|
|
|
|
|
|
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)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|