whatcanGOwrong
This commit is contained in:
@@ -0,0 +1,32 @@
|
||||
// Copyright 2020 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// This starts a mock DAP server that runs indefinitely, accepts DAP
|
||||
// requests and responds with dummy or error responses.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"log"
|
||||
)
|
||||
|
||||
func main() {
|
||||
port := flag.String("port", "54321", "TCP port to listen on")
|
||||
flag.Parse()
|
||||
err := server(*port)
|
||||
if err != nil {
|
||||
log.Fatal("Could not start server: ", err)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,594 @@
|
||||
// Copyright 2020 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// This file defines helpers and request handlers for a dummy server
|
||||
// that accepts DAP requests and responds with dummy or error responses.
|
||||
// Fake-supported requests:
|
||||
// - initialize
|
||||
// - launch
|
||||
// - setBreakpoints
|
||||
// - setExceptionBreakpoints
|
||||
// - configurationDone
|
||||
// - threads
|
||||
// - stackTrace
|
||||
// - scopes
|
||||
// - variables
|
||||
// - continue
|
||||
// - disconnect
|
||||
// All other requests result in ErrorResponse's.
|
||||
//
|
||||
// The server uses the following goroutines:
|
||||
// - "main" goroutine accepts client connections one by one and
|
||||
// handles them serially by reading and decoding incoming requests
|
||||
// and dispatching each one to a new goroutine for further
|
||||
// processing.
|
||||
// - per-request goroutines process each request as if
|
||||
// letting fake debugger take over. They send events and responses
|
||||
// via the sender goroutine.
|
||||
// - sender goroutine listens for messages to send and
|
||||
// writes them to the client connection.
|
||||
//
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"io"
|
||||
"log"
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/google/go-dap"
|
||||
)
|
||||
|
||||
// server starts a server that listens on a specified port
|
||||
// and blocks indefinitely. This server can accept multiple
|
||||
// client connections at the same time.
|
||||
func server(port string) error {
|
||||
listener, err := net.Listen("tcp", ":"+port)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer listener.Close()
|
||||
log.Println("Started server at", listener.Addr())
|
||||
|
||||
for {
|
||||
conn, err := listener.Accept()
|
||||
if err != nil {
|
||||
log.Println("Connection failed:", err)
|
||||
continue
|
||||
}
|
||||
log.Println("Accepted connection from", conn.RemoteAddr())
|
||||
// Handle multiple client connections concurrently
|
||||
go handleConnection(conn)
|
||||
}
|
||||
}
|
||||
|
||||
// handleConnection handles a connection from a single client.
|
||||
// It reads and decodes the incoming data and dispatches it
|
||||
// to per-request processing goroutines. It also launches the
|
||||
// sender goroutine to send resulting messages over the connection
|
||||
// back to the client.
|
||||
func handleConnection(conn net.Conn) {
|
||||
debugSession := fakeDebugSession{
|
||||
rw: bufio.NewReadWriter(bufio.NewReader(conn), bufio.NewWriter(conn)),
|
||||
sendQueue: make(chan dap.Message),
|
||||
stopDebug: make(chan struct{}),
|
||||
}
|
||||
go debugSession.sendFromQueue()
|
||||
|
||||
for {
|
||||
err := debugSession.handleRequest()
|
||||
// TODO(polina): check for connection vs decoding error?
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
log.Println("No more data to read:", err)
|
||||
break
|
||||
}
|
||||
// There maybe more messages to process, but
|
||||
// we will start with the strict behavior of only accepting
|
||||
// expected inputs.
|
||||
log.Fatal("Server error: ", err)
|
||||
}
|
||||
}
|
||||
|
||||
log.Println("Closing connection from", conn.RemoteAddr())
|
||||
close(debugSession.stopDebug)
|
||||
debugSession.sendWg.Wait()
|
||||
close(debugSession.sendQueue)
|
||||
conn.Close()
|
||||
}
|
||||
|
||||
func (ds *fakeDebugSession) handleRequest() error {
|
||||
log.Println("Reading request...")
|
||||
request, err := dap.ReadProtocolMessage(ds.rw.Reader)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Printf("Received request\n\t%#v\n", request)
|
||||
ds.sendWg.Add(1)
|
||||
go func() {
|
||||
ds.dispatchRequest(request)
|
||||
ds.sendWg.Done()
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
|
||||
// dispatchRequest launches a new goroutine to process each request
|
||||
// and send back events and responses.
|
||||
func (ds *fakeDebugSession) dispatchRequest(request dap.Message) {
|
||||
switch request := request.(type) {
|
||||
case *dap.InitializeRequest:
|
||||
ds.onInitializeRequest(request)
|
||||
case *dap.LaunchRequest:
|
||||
ds.onLaunchRequest(request)
|
||||
case *dap.AttachRequest:
|
||||
ds.onAttachRequest(request)
|
||||
case *dap.DisconnectRequest:
|
||||
ds.onDisconnectRequest(request)
|
||||
case *dap.TerminateRequest:
|
||||
ds.onTerminateRequest(request)
|
||||
case *dap.RestartRequest:
|
||||
ds.onRestartRequest(request)
|
||||
case *dap.SetBreakpointsRequest:
|
||||
ds.onSetBreakpointsRequest(request)
|
||||
case *dap.SetFunctionBreakpointsRequest:
|
||||
ds.onSetFunctionBreakpointsRequest(request)
|
||||
case *dap.SetExceptionBreakpointsRequest:
|
||||
ds.onSetExceptionBreakpointsRequest(request)
|
||||
case *dap.ConfigurationDoneRequest:
|
||||
ds.onConfigurationDoneRequest(request)
|
||||
case *dap.ContinueRequest:
|
||||
ds.onContinueRequest(request)
|
||||
case *dap.NextRequest:
|
||||
ds.onNextRequest(request)
|
||||
case *dap.StepInRequest:
|
||||
ds.onStepInRequest(request)
|
||||
case *dap.StepOutRequest:
|
||||
ds.onStepOutRequest(request)
|
||||
case *dap.StepBackRequest:
|
||||
ds.onStepBackRequest(request)
|
||||
case *dap.ReverseContinueRequest:
|
||||
ds.onReverseContinueRequest(request)
|
||||
case *dap.RestartFrameRequest:
|
||||
ds.onRestartFrameRequest(request)
|
||||
case *dap.GotoRequest:
|
||||
ds.onGotoRequest(request)
|
||||
case *dap.PauseRequest:
|
||||
ds.onPauseRequest(request)
|
||||
case *dap.StackTraceRequest:
|
||||
ds.onStackTraceRequest(request)
|
||||
case *dap.ScopesRequest:
|
||||
ds.onScopesRequest(request)
|
||||
case *dap.VariablesRequest:
|
||||
ds.onVariablesRequest(request)
|
||||
case *dap.SetVariableRequest:
|
||||
ds.onSetVariableRequest(request)
|
||||
case *dap.SetExpressionRequest:
|
||||
ds.onSetExpressionRequest(request)
|
||||
case *dap.SourceRequest:
|
||||
ds.onSourceRequest(request)
|
||||
case *dap.ThreadsRequest:
|
||||
ds.onThreadsRequest(request)
|
||||
case *dap.TerminateThreadsRequest:
|
||||
ds.onTerminateThreadsRequest(request)
|
||||
case *dap.EvaluateRequest:
|
||||
ds.onEvaluateRequest(request)
|
||||
case *dap.StepInTargetsRequest:
|
||||
ds.onStepInTargetsRequest(request)
|
||||
case *dap.GotoTargetsRequest:
|
||||
ds.onGotoTargetsRequest(request)
|
||||
case *dap.CompletionsRequest:
|
||||
ds.onCompletionsRequest(request)
|
||||
case *dap.ExceptionInfoRequest:
|
||||
ds.onExceptionInfoRequest(request)
|
||||
case *dap.LoadedSourcesRequest:
|
||||
ds.onLoadedSourcesRequest(request)
|
||||
case *dap.DataBreakpointInfoRequest:
|
||||
ds.onDataBreakpointInfoRequest(request)
|
||||
case *dap.SetDataBreakpointsRequest:
|
||||
ds.onSetDataBreakpointsRequest(request)
|
||||
case *dap.ReadMemoryRequest:
|
||||
ds.onReadMemoryRequest(request)
|
||||
case *dap.DisassembleRequest:
|
||||
ds.onDisassembleRequest(request)
|
||||
case *dap.CancelRequest:
|
||||
ds.onCancelRequest(request)
|
||||
case *dap.BreakpointLocationsRequest:
|
||||
ds.onBreakpointLocationsRequest(request)
|
||||
default:
|
||||
log.Fatalf("Unable to process %#v", request)
|
||||
}
|
||||
}
|
||||
|
||||
// send lets the sender goroutine know via a channel that there is
|
||||
// a message to be sent to client. This is called by per-request
|
||||
// goroutines to send events and responses for each request and
|
||||
// to notify of events triggered by the fake debugger.
|
||||
func (ds *fakeDebugSession) send(message dap.Message) {
|
||||
ds.sendQueue <- message
|
||||
}
|
||||
|
||||
// sendFromQueue is to be run in a separate goroutine to listen on a
|
||||
// channel for messages to send back to the client. It will
|
||||
// return once the channel is closed.
|
||||
func (ds *fakeDebugSession) sendFromQueue() {
|
||||
for message := range ds.sendQueue {
|
||||
dap.WriteProtocolMessage(ds.rw.Writer, message)
|
||||
log.Printf("Message sent\n\t%#v\n", message)
|
||||
ds.rw.Flush()
|
||||
}
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Very Fake Debugger
|
||||
//
|
||||
|
||||
// The debugging session will keep track of how many breakpoints
|
||||
// have been set. Once start-up is done (i.e. configurationDone
|
||||
// request is processed), it will "stop" at each breakpoint one by
|
||||
// one, and once there are no more, it will trigger a terminated event.
|
||||
type fakeDebugSession struct {
|
||||
// rw is used to read requests and write events/responses
|
||||
rw *bufio.ReadWriter
|
||||
|
||||
// sendQueue is used to capture messages from multiple request
|
||||
// processing goroutines while writing them to the client connection
|
||||
// from a single goroutine via sendFromQueue. We must keep track of
|
||||
// the multiple channel senders with a wait group to make sure we do
|
||||
// not close this channel prematurely. Closing this channel will signal
|
||||
// the sendFromQueue goroutine that it can exit.
|
||||
sendQueue chan dap.Message
|
||||
sendWg sync.WaitGroup
|
||||
|
||||
// stopDebug is used to notify long-running handlers to stop processing.
|
||||
stopDebug chan struct{}
|
||||
|
||||
// bpSet is a counter of the remaining breakpoints that the debug
|
||||
// session is yet to stop at before the program terminates.
|
||||
bpSet int
|
||||
bpSetMux sync.Mutex
|
||||
}
|
||||
|
||||
// doContinue allows fake program execution to continue when the program
|
||||
// is started or unpaused. It simulates events from the debug session
|
||||
// by "stopping" on a breakpoint or terminating if there are no more
|
||||
// breakpoints. Safe to use concurrently.
|
||||
func (ds *fakeDebugSession) doContinue() {
|
||||
var e dap.Message
|
||||
ds.bpSetMux.Lock()
|
||||
if ds.bpSet == 0 {
|
||||
// Pretend that the program is running.
|
||||
// The delay will allow for all in-flight responses
|
||||
// to be sent before termination.
|
||||
time.Sleep(1000 * time.Millisecond)
|
||||
e = &dap.TerminatedEvent{
|
||||
Event: *newEvent("terminated"),
|
||||
}
|
||||
} else {
|
||||
e = &dap.StoppedEvent{
|
||||
Event: *newEvent("stopped"),
|
||||
Body: dap.StoppedEventBody{Reason: "breakpoint", ThreadId: 1, AllThreadsStopped: true},
|
||||
}
|
||||
ds.bpSet--
|
||||
}
|
||||
ds.bpSetMux.Unlock()
|
||||
ds.send(e)
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Request Handlers
|
||||
//
|
||||
// Below is a dummy implementation of the request handlers.
|
||||
// They take no action, but just return dummy responses.
|
||||
// A real debug adaptor would call the debugger methods here
|
||||
// and use their results to populate each response.
|
||||
|
||||
func (ds *fakeDebugSession) onInitializeRequest(request *dap.InitializeRequest) {
|
||||
response := &dap.InitializeResponse{}
|
||||
response.Response = *newResponse(request.Seq, request.Command)
|
||||
response.Body.SupportsConfigurationDoneRequest = true
|
||||
response.Body.SupportsFunctionBreakpoints = false
|
||||
response.Body.SupportsConditionalBreakpoints = false
|
||||
response.Body.SupportsHitConditionalBreakpoints = false
|
||||
response.Body.SupportsEvaluateForHovers = false
|
||||
response.Body.ExceptionBreakpointFilters = []dap.ExceptionBreakpointsFilter{}
|
||||
response.Body.SupportsStepBack = false
|
||||
response.Body.SupportsSetVariable = false
|
||||
response.Body.SupportsRestartFrame = false
|
||||
response.Body.SupportsGotoTargetsRequest = false
|
||||
response.Body.SupportsStepInTargetsRequest = false
|
||||
response.Body.SupportsCompletionsRequest = false
|
||||
response.Body.CompletionTriggerCharacters = []string{}
|
||||
response.Body.SupportsModulesRequest = false
|
||||
response.Body.AdditionalModuleColumns = []dap.ColumnDescriptor{}
|
||||
response.Body.SupportedChecksumAlgorithms = []dap.ChecksumAlgorithm{}
|
||||
response.Body.SupportsRestartRequest = false
|
||||
response.Body.SupportsExceptionOptions = false
|
||||
response.Body.SupportsValueFormattingOptions = false
|
||||
response.Body.SupportsExceptionInfoRequest = false
|
||||
response.Body.SupportTerminateDebuggee = false
|
||||
response.Body.SupportsDelayedStackTraceLoading = false
|
||||
response.Body.SupportsLoadedSourcesRequest = false
|
||||
response.Body.SupportsLogPoints = false
|
||||
response.Body.SupportsTerminateThreadsRequest = false
|
||||
response.Body.SupportsSetExpression = false
|
||||
response.Body.SupportsTerminateRequest = false
|
||||
response.Body.SupportsDataBreakpoints = false
|
||||
response.Body.SupportsReadMemoryRequest = false
|
||||
response.Body.SupportsDisassembleRequest = false
|
||||
response.Body.SupportsCancelRequest = false
|
||||
response.Body.SupportsBreakpointLocationsRequest = false
|
||||
// This is a fake set up, so we can start "accepting" configuration
|
||||
// requests for setting breakpoints, etc from the client at any time.
|
||||
// Notify the client with an 'initialized' event. The client will end
|
||||
// the configuration sequence with 'configurationDone' request.
|
||||
e := &dap.InitializedEvent{Event: *newEvent("initialized")}
|
||||
ds.send(e)
|
||||
ds.send(response)
|
||||
}
|
||||
|
||||
func (ds *fakeDebugSession) onLaunchRequest(request *dap.LaunchRequest) {
|
||||
// This is where a real debug adaptor would check the soundness of the
|
||||
// arguments (e.g. program from launch.json) and then use them to launch the
|
||||
// debugger and attach to the program.
|
||||
response := &dap.LaunchResponse{}
|
||||
response.Response = *newResponse(request.Seq, request.Command)
|
||||
ds.send(response)
|
||||
}
|
||||
|
||||
func (ds *fakeDebugSession) onAttachRequest(request *dap.AttachRequest) {
|
||||
ds.send(newErrorResponse(request.Seq, request.Command, "AttachRequest is not yet supported"))
|
||||
}
|
||||
|
||||
func (ds *fakeDebugSession) onDisconnectRequest(request *dap.DisconnectRequest) {
|
||||
response := &dap.DisconnectResponse{}
|
||||
response.Response = *newResponse(request.Seq, request.Command)
|
||||
ds.send(response)
|
||||
}
|
||||
|
||||
func (ds *fakeDebugSession) onTerminateRequest(request *dap.TerminateRequest) {
|
||||
ds.send(newErrorResponse(request.Seq, request.Command, "TerminateRequest is not yet supported"))
|
||||
}
|
||||
|
||||
func (ds *fakeDebugSession) onRestartRequest(request *dap.RestartRequest) {
|
||||
ds.send(newErrorResponse(request.Seq, request.Command, "RestartRequest is not yet supported"))
|
||||
}
|
||||
|
||||
func (ds *fakeDebugSession) onSetBreakpointsRequest(request *dap.SetBreakpointsRequest) {
|
||||
response := &dap.SetBreakpointsResponse{}
|
||||
response.Response = *newResponse(request.Seq, request.Command)
|
||||
response.Body.Breakpoints = make([]dap.Breakpoint, len(request.Arguments.Breakpoints))
|
||||
for i, b := range request.Arguments.Breakpoints {
|
||||
response.Body.Breakpoints[i].Line = b.Line
|
||||
response.Body.Breakpoints[i].Verified = true
|
||||
ds.bpSetMux.Lock()
|
||||
ds.bpSet++
|
||||
ds.bpSetMux.Unlock()
|
||||
}
|
||||
ds.send(response)
|
||||
}
|
||||
|
||||
func (ds *fakeDebugSession) onSetFunctionBreakpointsRequest(request *dap.SetFunctionBreakpointsRequest) {
|
||||
ds.send(newErrorResponse(request.Seq, request.Command, "SetFunctionBreakpointsRequest is not yet supported"))
|
||||
}
|
||||
|
||||
func (ds *fakeDebugSession) onSetExceptionBreakpointsRequest(request *dap.SetExceptionBreakpointsRequest) {
|
||||
response := &dap.SetExceptionBreakpointsResponse{}
|
||||
response.Response = *newResponse(request.Seq, request.Command)
|
||||
ds.send(response)
|
||||
}
|
||||
|
||||
func (ds *fakeDebugSession) onConfigurationDoneRequest(request *dap.ConfigurationDoneRequest) {
|
||||
// This would be the place to check if the session was configured to
|
||||
// stop on entry and if that is the case, to issue a
|
||||
// stopped-on-breakpoint event. This being a mock implementation,
|
||||
// we "let" the program continue after sending a successful response.
|
||||
e := &dap.ThreadEvent{Event: *newEvent("thread"), Body: dap.ThreadEventBody{Reason: "started", ThreadId: 1}}
|
||||
ds.send(e)
|
||||
response := &dap.ConfigurationDoneResponse{}
|
||||
response.Response = *newResponse(request.Seq, request.Command)
|
||||
ds.send(response)
|
||||
ds.doContinue()
|
||||
}
|
||||
|
||||
func (ds *fakeDebugSession) onContinueRequest(request *dap.ContinueRequest) {
|
||||
response := &dap.ContinueResponse{}
|
||||
response.Response = *newResponse(request.Seq, request.Command)
|
||||
ds.send(response)
|
||||
ds.doContinue()
|
||||
}
|
||||
|
||||
func (ds *fakeDebugSession) onNextRequest(request *dap.NextRequest) {
|
||||
ds.send(newErrorResponse(request.Seq, request.Command, "NextRequest is not yet supported"))
|
||||
}
|
||||
|
||||
func (ds *fakeDebugSession) onStepInRequest(request *dap.StepInRequest) {
|
||||
ds.send(newErrorResponse(request.Seq, request.Command, "StepInRequest is not yet supported"))
|
||||
}
|
||||
|
||||
func (ds *fakeDebugSession) onStepOutRequest(request *dap.StepOutRequest) {
|
||||
ds.send(newErrorResponse(request.Seq, request.Command, "StepOutRequest is not yet supported"))
|
||||
}
|
||||
|
||||
func (ds *fakeDebugSession) onStepBackRequest(request *dap.StepBackRequest) {
|
||||
ds.send(newErrorResponse(request.Seq, request.Command, "StepBackRequest is not yet supported"))
|
||||
}
|
||||
|
||||
func (ds *fakeDebugSession) onReverseContinueRequest(request *dap.ReverseContinueRequest) {
|
||||
ds.send(newErrorResponse(request.Seq, request.Command, "ReverseContinueRequest is not yet supported"))
|
||||
}
|
||||
|
||||
func (ds *fakeDebugSession) onRestartFrameRequest(request *dap.RestartFrameRequest) {
|
||||
ds.send(newErrorResponse(request.Seq, request.Command, "RestartFrameRequest is not yet supported"))
|
||||
}
|
||||
|
||||
func (ds *fakeDebugSession) onGotoRequest(request *dap.GotoRequest) {
|
||||
ds.send(newErrorResponse(request.Seq, request.Command, "GotoRequest is not yet supported"))
|
||||
}
|
||||
|
||||
func (ds *fakeDebugSession) onPauseRequest(request *dap.PauseRequest) {
|
||||
ds.send(newErrorResponse(request.Seq, request.Command, "PauseRequest is not yet supported"))
|
||||
}
|
||||
|
||||
func (ds *fakeDebugSession) onStackTraceRequest(request *dap.StackTraceRequest) {
|
||||
response := &dap.StackTraceResponse{}
|
||||
response.Response = *newResponse(request.Seq, request.Command)
|
||||
response.Body = dap.StackTraceResponseBody{
|
||||
StackFrames: []dap.StackFrame{
|
||||
{
|
||||
Id: 1000,
|
||||
Source: &dap.Source{Name: "hello.go", Path: "/Users/foo/go/src/hello/hello.go", SourceReference: 0},
|
||||
Line: 5,
|
||||
Column: 0,
|
||||
Name: "main.main",
|
||||
},
|
||||
},
|
||||
TotalFrames: 1,
|
||||
}
|
||||
ds.send(response)
|
||||
}
|
||||
|
||||
func (ds *fakeDebugSession) onScopesRequest(request *dap.ScopesRequest) {
|
||||
response := &dap.ScopesResponse{}
|
||||
response.Response = *newResponse(request.Seq, request.Command)
|
||||
response.Body = dap.ScopesResponseBody{
|
||||
Scopes: []dap.Scope{
|
||||
{Name: "Local", VariablesReference: 1000, Expensive: false},
|
||||
{Name: "Global", VariablesReference: 1001, Expensive: true},
|
||||
},
|
||||
}
|
||||
ds.send(response)
|
||||
}
|
||||
|
||||
func (ds *fakeDebugSession) onVariablesRequest(request *dap.VariablesRequest) {
|
||||
select {
|
||||
case <-ds.stopDebug:
|
||||
return
|
||||
// simulate long-running processing to make this handler
|
||||
// respond to this request after the next request is received
|
||||
case <-time.After(100 * time.Millisecond):
|
||||
response := &dap.VariablesResponse{}
|
||||
response.Response = *newResponse(request.Seq, request.Command)
|
||||
response.Body = dap.VariablesResponseBody{
|
||||
Variables: []dap.Variable{{Name: "i", Value: "18434528", EvaluateName: "i", VariablesReference: 0}},
|
||||
}
|
||||
ds.send(response)
|
||||
}
|
||||
}
|
||||
|
||||
func (ds *fakeDebugSession) onSetVariableRequest(request *dap.SetVariableRequest) {
|
||||
ds.send(newErrorResponse(request.Seq, request.Command, "setVariableRequest is not yet supported"))
|
||||
}
|
||||
|
||||
func (ds *fakeDebugSession) onSetExpressionRequest(request *dap.SetExpressionRequest) {
|
||||
ds.send(newErrorResponse(request.Seq, request.Command, "SetExpressionRequest is not yet supported"))
|
||||
}
|
||||
|
||||
func (ds *fakeDebugSession) onSourceRequest(request *dap.SourceRequest) {
|
||||
ds.send(newErrorResponse(request.Seq, request.Command, "SourceRequest is not yet supported"))
|
||||
}
|
||||
|
||||
func (ds *fakeDebugSession) onThreadsRequest(request *dap.ThreadsRequest) {
|
||||
response := &dap.ThreadsResponse{}
|
||||
response.Response = *newResponse(request.Seq, request.Command)
|
||||
response.Body = dap.ThreadsResponseBody{Threads: []dap.Thread{{Id: 1, Name: "main"}}}
|
||||
ds.send(response)
|
||||
|
||||
}
|
||||
|
||||
func (ds *fakeDebugSession) onTerminateThreadsRequest(request *dap.TerminateThreadsRequest) {
|
||||
ds.send(newErrorResponse(request.Seq, request.Command, "TerminateRequest is not yet supported"))
|
||||
}
|
||||
|
||||
func (ds *fakeDebugSession) onEvaluateRequest(request *dap.EvaluateRequest) {
|
||||
ds.send(newErrorResponse(request.Seq, request.Command, "EvaluateRequest is not yet supported"))
|
||||
}
|
||||
|
||||
func (ds *fakeDebugSession) onStepInTargetsRequest(request *dap.StepInTargetsRequest) {
|
||||
ds.send(newErrorResponse(request.Seq, request.Command, "StepInTargetRequest is not yet supported"))
|
||||
}
|
||||
|
||||
func (ds *fakeDebugSession) onGotoTargetsRequest(request *dap.GotoTargetsRequest) {
|
||||
ds.send(newErrorResponse(request.Seq, request.Command, "GotoTargetRequest is not yet supported"))
|
||||
}
|
||||
|
||||
func (ds *fakeDebugSession) onCompletionsRequest(request *dap.CompletionsRequest) {
|
||||
ds.send(newErrorResponse(request.Seq, request.Command, "CompletionRequest is not yet supported"))
|
||||
}
|
||||
|
||||
func (ds *fakeDebugSession) onExceptionInfoRequest(request *dap.ExceptionInfoRequest) {
|
||||
ds.send(newErrorResponse(request.Seq, request.Command, "ExceptionRequest is not yet supported"))
|
||||
}
|
||||
|
||||
func (ds *fakeDebugSession) onLoadedSourcesRequest(request *dap.LoadedSourcesRequest) {
|
||||
ds.send(newErrorResponse(request.Seq, request.Command, "LoadedRequest is not yet supported"))
|
||||
}
|
||||
|
||||
func (ds *fakeDebugSession) onDataBreakpointInfoRequest(request *dap.DataBreakpointInfoRequest) {
|
||||
ds.send(newErrorResponse(request.Seq, request.Command, "DataBreakpointInfoRequest is not yet supported"))
|
||||
}
|
||||
|
||||
func (ds *fakeDebugSession) onSetDataBreakpointsRequest(request *dap.SetDataBreakpointsRequest) {
|
||||
ds.send(newErrorResponse(request.Seq, request.Command, "SetDataBreakpointsRequest is not yet supported"))
|
||||
}
|
||||
|
||||
func (ds *fakeDebugSession) onReadMemoryRequest(request *dap.ReadMemoryRequest) {
|
||||
ds.send(newErrorResponse(request.Seq, request.Command, "ReadMemoryRequest is not yet supported"))
|
||||
}
|
||||
|
||||
func (ds *fakeDebugSession) onDisassembleRequest(request *dap.DisassembleRequest) {
|
||||
ds.send(newErrorResponse(request.Seq, request.Command, "DisassembleRequest is not yet supported"))
|
||||
}
|
||||
|
||||
func (ds *fakeDebugSession) onCancelRequest(request *dap.CancelRequest) {
|
||||
ds.send(newErrorResponse(request.Seq, request.Command, "CancelRequest is not yet supported"))
|
||||
}
|
||||
|
||||
func (ds *fakeDebugSession) onBreakpointLocationsRequest(request *dap.BreakpointLocationsRequest) {
|
||||
ds.send(newErrorResponse(request.Seq, request.Command, "BreakpointLocationsRequest is not yet supported"))
|
||||
}
|
||||
|
||||
func newEvent(event string) *dap.Event {
|
||||
return &dap.Event{
|
||||
ProtocolMessage: dap.ProtocolMessage{
|
||||
Seq: 0,
|
||||
Type: "event",
|
||||
},
|
||||
Event: event,
|
||||
}
|
||||
}
|
||||
|
||||
func newResponse(requestSeq int, command string) *dap.Response {
|
||||
return &dap.Response{
|
||||
ProtocolMessage: dap.ProtocolMessage{
|
||||
Seq: 0,
|
||||
Type: "response",
|
||||
},
|
||||
Command: command,
|
||||
RequestSeq: requestSeq,
|
||||
Success: true,
|
||||
}
|
||||
}
|
||||
|
||||
func newErrorResponse(requestSeq int, command string, message string) *dap.ErrorResponse {
|
||||
er := &dap.ErrorResponse{}
|
||||
er.Response = *newResponse(requestSeq, command)
|
||||
er.Success = false
|
||||
er.Message = "unsupported"
|
||||
er.Body.Error.Format = message
|
||||
er.Body.Error.Id = 12345
|
||||
return er
|
||||
}
|
||||
@@ -0,0 +1,156 @@
|
||||
// Copyright 2020 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/google/go-dap"
|
||||
)
|
||||
|
||||
var initializeRequest = []byte(`{"seq":1,"type":"request","command":"initialize","arguments":{"clientID":"vscode","clientName":"Visual Studio Code","adapterID":"go","pathFormat":"path","linesStartAt1":true,"columnsStartAt1":true,"supportsVariableType":true,"supportsVariablePaging":true,"supportsRunInTerminalRequest":true,"locale":"en-us"}}`)
|
||||
var initializedEvent = []byte(`{"seq":0,"type":"event","event":"initialized"}`)
|
||||
var initializeResponse = []byte(`{"seq":0,"type":"response","request_seq":1,"success":true,"command":"initialize","body":{"supportsConfigurationDoneRequest":true}}`)
|
||||
|
||||
var launchRequest = []byte(`{"seq":2,"type":"request","command":"launch","arguments":{"noDebug": true,"name":"Launch","type":"go","request":"launch","mode":"debug","program":"/Users/foo/go/src/hello","__sessionId":"4c88179f-1202-4f75-9e67-5bf535cde30a","args":["somearg"],"env":{"GOPATH":"/Users/foo/go","HOME":"/Users/foo","SHELL":"/bin/bash"}}}`)
|
||||
var launchResponse = []byte(`{"seq":0,"type":"response","request_seq":2,"success":true,"command":"launch"}`)
|
||||
|
||||
var setBreakpointsRequest = []byte(`{"seq":3,"type":"request","command":"setBreakpoints","arguments":{"source":{"name":"hello.go","path":"/Users/foo/go/src/hello/hello.go"},"lines":[7],"breakpoints":[{"line":7}],"sourceModified":false}}`)
|
||||
var setBreakpointsResponse = []byte(`{"seq":0,"type":"response","request_seq":3,"success":true,"command":"setBreakpoints","body":{"breakpoints":[{"verified":true,"line":7}]}}`)
|
||||
|
||||
var setExceptionBreakpointsRequest = []byte(`{"seq":4,"type":"request","command":"setExceptionBreakpoints","arguments":{"filters":[]}}`)
|
||||
var setExceptionBreakpointsResponse = []byte(`{"seq":0,"type":"response","request_seq":4,"success":true,"command":"setExceptionBreakpoints","body":{}}`)
|
||||
|
||||
var configurationDoneRequest = []byte(`{"seq":5,"type":"request","command":"configurationDone"}`)
|
||||
var threadEvent = []byte(`{"seq":0,"type":"event","event":"thread","body":{"reason":"started","threadId":1}}`)
|
||||
var configurationDoneResponse = []byte(`{"seq":0,"type":"response","request_seq":5,"success":true,"command":"configurationDone"}`)
|
||||
|
||||
var stoppedEvent = []byte(`{"seq":0,"type":"event","event":"stopped","body":{"reason":"breakpoint","threadId":1,"allThreadsStopped":true}}`)
|
||||
|
||||
var threadsRequest = []byte(`{"seq":6,"type":"request","command":"threads"}`)
|
||||
var threadsResponse = []byte(`{"seq":0,"type":"response","request_seq":6,"success":true,"command":"threads","body":{"threads":[{"id":1,"name":"main"}]}}`)
|
||||
|
||||
var stackTraceRequest = []byte(`{"seq":7,"type":"request","command":"stackTrace","arguments":{"threadId":1,"startFrame":0,"levels":20}}`)
|
||||
var stackTraceResponse = []byte(`{"seq":0,"type":"response","request_seq":7,"success":true,"command":"stackTrace","body":{"stackFrames":[{"id":1000,"name":"main.main","source":{"name":"hello.go","path":"/Users/foo/go/src/hello/hello.go"},"line":5,"column":0}],"totalFrames":1}}`)
|
||||
|
||||
var scopesRequest = []byte(`{"seq":8,"type":"request","command":"scopes","arguments":{"frameId":1000}}`)
|
||||
var scopesResponse = []byte(`{"seq":0,"type":"response","request_seq":8,"success":true,"command":"scopes","body":{"scopes":[{"name":"Local","variablesReference":1000,"expensive":false},{"name":"Global","variablesReference":1001,"expensive":true}]}}`)
|
||||
|
||||
var variablesRequest = []byte(`{"seq":9,"type":"request","command":"variables","arguments":{"variablesReference":1000}}`)
|
||||
var variablesResponse = []byte(`{"seq":0,"type":"response","request_seq":9,"success":true,"command":"variables","body":{"variables":[{"name":"i","value":"18434528","evaluateName":"i","variablesReference":0}]}}`)
|
||||
|
||||
var continueRequest = []byte(`{"seq":10,"type":"request","command":"continue","arguments":{"threadId":1}}`)
|
||||
var continueResponse = []byte(`{"seq":0,"type":"response","request_seq":10,"success":true,"command":"continue","body":{"allThreadsContinued":false}}`)
|
||||
|
||||
var terminatedEvent = []byte(`{"seq":0,"type":"event","event":"terminated","body":{}}`)
|
||||
var disconnectRequest = []byte(`{"seq":11,"type":"request","command":"disconnect","arguments":{"restart":false}}`)
|
||||
var disconnectResponse = []byte(`{"seq":0,"type":"response","request_seq":11,"success":true,"command":"disconnect"}`)
|
||||
|
||||
func expectMessage(t *testing.T, r *bufio.Reader, want []byte) {
|
||||
got, err := dap.ReadBaseMessage(r)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if !bytes.Equal(got, want) {
|
||||
t.Errorf("\ngot %q\nwant %q", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestServer(t *testing.T) {
|
||||
log.SetOutput(ioutil.Discard)
|
||||
port := "54321"
|
||||
go func() {
|
||||
err := server(port)
|
||||
if err != nil {
|
||||
log.Fatal("Could not start server:", err)
|
||||
}
|
||||
}()
|
||||
// Give server time to start listening before clients connect
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(2)
|
||||
go client(t, port, &wg)
|
||||
go client(t, port, &wg)
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func client(t *testing.T, port string, wg *sync.WaitGroup) {
|
||||
defer wg.Done()
|
||||
conn, err := net.Dial("tcp", ":"+port)
|
||||
if err != nil {
|
||||
log.Fatal("Could not connect to server:", err)
|
||||
}
|
||||
defer func() {
|
||||
t.Log("Closing connection to server at", conn.RemoteAddr())
|
||||
conn.Close()
|
||||
}()
|
||||
t.Log("Connected to server at", conn.RemoteAddr())
|
||||
|
||||
r := bufio.NewReader(conn)
|
||||
|
||||
// Start up
|
||||
|
||||
dap.WriteBaseMessage(conn, initializeRequest)
|
||||
expectMessage(t, r, initializedEvent)
|
||||
expectMessage(t, r, initializeResponse)
|
||||
|
||||
dap.WriteBaseMessage(conn, launchRequest)
|
||||
expectMessage(t, r, launchResponse)
|
||||
|
||||
dap.WriteBaseMessage(conn, setBreakpointsRequest)
|
||||
expectMessage(t, r, setBreakpointsResponse)
|
||||
dap.WriteBaseMessage(conn, setExceptionBreakpointsRequest)
|
||||
expectMessage(t, r, setExceptionBreakpointsResponse)
|
||||
|
||||
dap.WriteBaseMessage(conn, configurationDoneRequest)
|
||||
expectMessage(t, r, threadEvent)
|
||||
expectMessage(t, r, configurationDoneResponse)
|
||||
|
||||
// Stop on preconfigured breakpoint & Continue
|
||||
|
||||
expectMessage(t, r, stoppedEvent)
|
||||
|
||||
dap.WriteBaseMessage(conn, threadsRequest)
|
||||
expectMessage(t, r, threadsResponse)
|
||||
|
||||
dap.WriteBaseMessage(conn, stackTraceRequest)
|
||||
expectMessage(t, r, stackTraceResponse)
|
||||
|
||||
dap.WriteBaseMessage(conn, scopesRequest)
|
||||
expectMessage(t, r, scopesResponse)
|
||||
|
||||
// Processing of this request will be slow due to a fake delay.
|
||||
// Send the next request right away and confirm that processing
|
||||
// happens concurrently and the two responses are received
|
||||
// out of order.
|
||||
dap.WriteBaseMessage(conn, variablesRequest)
|
||||
dap.WriteBaseMessage(conn, continueRequest)
|
||||
expectMessage(t, r, continueResponse)
|
||||
expectMessage(t, r, variablesResponse)
|
||||
|
||||
// Shut down
|
||||
|
||||
expectMessage(t, r, terminatedEvent)
|
||||
dap.WriteBaseMessage(conn, disconnectRequest)
|
||||
expectMessage(t, r, disconnectResponse)
|
||||
}
|
||||
Reference in New Issue
Block a user