server_test.go 6.5 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
package dap

import (
	"bytes"
	"flag"
	"fmt"
	"net"
	"os"
	"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"
)

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))
}

func startDAPServer(t *testing.T) (server *Server, addr string) {
	listener, err := net.Listen("tcp", ":0")
	if err != nil {
		t.Fatal(err)
	}
	server = NewServer(&service.Config{
		Listener:       listener,
		Backend:        "default",
		DisconnectChan: nil,
	})
	server.Run()
	// Give server time to start listening for clients
	time.Sleep(100 * time.Millisecond)
	return server, listener.Addr().String()
}

func expectMessage(t *testing.T, client *daptest.Client, want []byte) {
44
	t.Helper()
45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
	got, err := client.ReadBaseMessage()
	if err != nil {
		t.Error(err)
	}
	if !bytes.Equal(got, want) {
		t.Errorf("\ngot  %q\nwant %q", got, want)
	}
}

// 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)

	server, addr := startDAPServer(t)
60
	defer server.Stop()
61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126
	client := daptest.NewClient(addr)
	defer client.Close()

	test(client, fixture)
}

// TODO(polina): instead of hardcoding message bytes,
// add methods to client to receive, decode and verify responses.

func TestStopOnEntry(t *testing.T) {
	runTest(t, "increment", func(client *daptest.Client, fixture protest.Fixture) {
		client.InitializeRequest()
		expectMessage(t, client, []byte(`{"seq":0,"type":"response","request_seq":0,"success":true,"command":"initialize","body":{"supportsConfigurationDoneRequest":true}}`))

		client.LaunchRequest(fixture.Path, true /*stopOnEntry*/)
		expectMessage(t, client, []byte(`{"seq":0,"type":"event","event":"initialized"}`))
		expectMessage(t, client, []byte(`{"seq":0,"type":"response","request_seq":1,"success":true,"command":"launch"}`))

		client.SetExceptionBreakpointsRequest()
		expectMessage(t, client, []byte(`{"seq":0,"type":"response","request_seq":2,"success":true,"command":"setExceptionBreakpoints"}`))

		client.ConfigurationDoneRequest()
		expectMessage(t, client, []byte(`{"seq":0,"type":"event","event":"stopped","body":{"reason":"breakpoint","threadId":1,"allThreadsStopped":true}}`))
		expectMessage(t, client, []byte(`{"seq":0,"type":"response","request_seq":3,"success":true,"command":"configurationDone"}`))

		client.ContinueRequest(1)
		expectMessage(t, client, []byte(`{"seq":0,"type":"response","request_seq":4,"success":true,"command":"continue","body":{}}`))
		expectMessage(t, client, []byte(`{"seq":0,"type":"event","event":"terminated","body":{}}`))

		client.DisconnectRequest()
		expectMessage(t, client, []byte(`{"seq":0,"type":"response","request_seq":5,"success":true,"command":"disconnect"}`))
	})
}

func TestSetBreakpoint(t *testing.T) {
	runTest(t, "increment", func(client *daptest.Client, fixture protest.Fixture) {
		client.InitializeRequest()
		expectMessage(t, client, []byte(`{"seq":0,"type":"response","request_seq":0,"success":true,"command":"initialize","body":{"supportsConfigurationDoneRequest":true}}`))

		client.LaunchRequest(fixture.Path, false /*stopOnEntry*/)
		expectMessage(t, client, []byte(`{"seq":0,"type":"event","event":"initialized"}`))
		expectMessage(t, client, []byte(`{"seq":0,"type":"response","request_seq":1,"success":true,"command":"launch"}`))

		client.SetBreakpointsRequest(fixture.Source, []int{8, 100})
		expectMessage(t, client, []byte(`{"seq":0,"type":"response","request_seq":2,"success":true,"command":"setBreakpoints","body":{"breakpoints":[{"verified":true,"source":{},"line":8}]}}`))

		client.SetExceptionBreakpointsRequest()
		expectMessage(t, client, []byte(`{"seq":0,"type":"response","request_seq":3,"success":true,"command":"setExceptionBreakpoints"}`))

		client.ConfigurationDoneRequest()
		expectMessage(t, client, []byte(`{"seq":0,"type":"response","request_seq":4,"success":true,"command":"configurationDone"}`))

		client.ContinueRequest(1)
		expectMessage(t, client, []byte(`{"seq":0,"type":"event","event":"stopped","body":{"reason":"breakpoint","threadId":1,"allThreadsStopped":true}}`))
		expectMessage(t, client, []byte(`{"seq":0,"type":"response","request_seq":5,"success":true,"command":"continue","body":{}}`))

		client.ContinueRequest(1)
		expectMessage(t, client, []byte(`{"seq":0,"type":"event","event":"terminated","body":{}}`))
		expectMessage(t, client, []byte(`{"seq":0,"type":"response","request_seq":6,"success":true,"command":"continue","body":{}}`))

		client.DisconnectRequest()
		expectMessage(t, client, []byte(`{"seq":0,"type":"response","request_seq":7,"success":true,"command":"disconnect"}`))
	})
}

func expectErrorResponse(t *testing.T, client *daptest.Client, requestSeq int, command string, message string, id int) *dap.ErrorResponse {
127
	t.Helper()
128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162
	response, err := client.ReadErrorResponse()
	if err != nil {
		t.Error(err)
		return nil
	}
	got := response.(*dap.ErrorResponse)
	if got.RequestSeq != requestSeq || got.Command != command || got.Message != message || got.Body.Error.Id != id {
		want := fmt.Sprintf("{RequestSeq: %d, Command: %q, Message: %q, Id: %d}", requestSeq, command, message, id)
		t.Errorf("\ngot  %#v\nwant %s", got, want)
		return nil
	}
	return got
}

func TestBadLaunchRequests(t *testing.T) {
	runTest(t, "increment", func(client *daptest.Client, fixture protest.Fixture) {
		client.LaunchRequest("", true)
		// Test for the DAP-specific detailed error message.
		want := "Failed to launch: The program attribute is missing in debug configuration."
		if got := expectErrorResponse(t, client, 0, "launch", "Failed to launch", 3000); got != nil && got.Body.Error.Format != want {
			t.Errorf("got %q, want %q", got.Body.Error.Format, want)
		}

		// Skip detailed message checks for potentially different OS-specific errors.
		client.LaunchRequest(fixture.Path+"_does_not_exist", false)
		expectErrorResponse(t, client, 1, "launch", "Failed to launch", 3000)

		client.LaunchRequest(fixture.Source, true) // Not an executable
		expectErrorResponse(t, client, 2, "launch", "Failed to launch", 3000)

		// We failed to launch the program. Make sure shutdown still works.
		client.DisconnectRequest()
		expectMessage(t, client, []byte(`{"seq":0,"type":"response","request_seq":3,"success":true,"command":"disconnect"}`))
	})
}