// 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 dap
import (
"bufio"
"bytes"
"encoding/json"
"io"
"io/ioutil"
"reflect"
"strings"
"testing"
"time"
)
func Test_WriteBaseMessage(t *testing.T) {
tests := []struct {
input string
wantWritten string
wantErr error
}{
{``, "Content-Length: 0\r\n\r\n", nil},
{`a`, "Content-Length: 1\r\n\r\na", nil},
{`{}`, "Content-Length: 2\r\n\r\n{}", nil},
{`{"a":0 "b":"blah"}`, "Content-Length: 18\r\n\r\n{\"a\":0 \"b\":\"blah\"}", nil},
}
for _, test := range tests {
t.Run(test.input, func(t *testing.T) {
var buf bytes.Buffer
gotErr := WriteBaseMessage(&buf, []byte(test.input))
gotWritten := buf.String()
if gotErr != test.wantErr {
t.Errorf("got err=%#v, want %#v", gotErr, test.wantErr)
}
if gotErr == nil && gotWritten != test.wantWritten {
t.Errorf("got written=%q, want %q", gotWritten, test.wantWritten)
}
})
}
}
func Test_ReadBaseMessage(t *testing.T) {
tests := []struct {
input string
wantBytesRead []byte
wantBytesLeft []byte
wantErr error
}{
{"", nil, []byte(""), io.EOF},
{"random stuff\r\nabc", nil, []byte("c"), ErrHeaderDelimiterNotCrLfCrLf},
{"Cache-Control: no-cache\r\n\r\n", nil, []byte(""), ErrHeaderNotContentLength},
{"Content-Length 1\r\n\r\nabc", nil, []byte("abc"), ErrHeaderNotContentLength},
{"Content-Length: 10\r\n\r\nabc", nil, []byte(""), io.ErrUnexpectedEOF},
{"Content-Length: 3\r\n\r\nabc", []byte("abc"), []byte(""), nil},
{"Content-Length: 4194305\r\n\r\nabc", nil, []byte("abc"), ErrHeaderContentTooLong},
{"Content-Length: 6506440440440\r\n\r\nabc", nil, []byte("abc"), ErrHeaderContentTooLong},
}
for _, test := range tests {
t.Run(test.input, func(t *testing.T) {
reader := bufio.NewReader(strings.NewReader(test.input))
gotBytes, gotErr := ReadBaseMessage(reader)
if gotErr != test.wantErr {
t.Errorf("got err=%#v, want %#v", gotErr, test.wantErr)
}
if gotErr == nil && !bytes.Equal(gotBytes, test.wantBytesRead) {
t.Errorf("got bytes=%q, want %q", gotBytes, test.wantBytesRead)
}
bytesLeft, _ := ioutil.ReadAll(reader)
if !bytes.Equal(bytesLeft, test.wantBytesLeft) {
t.Errorf("got bytesLeft=%q, want %q", bytesLeft, test.wantBytesLeft)
}
})
}
}
func Test_readContentLengthHeader(t *testing.T) {
tests := []struct {
input string
wantBytesLeft string // Bytes left in the reader after header reading
wantLen int64 // Extracted content length value
wantErr error
}{
{"", "", 0, io.EOF},
{"Cache-Control: no-cache", "", 0, io.EOF},
{"Cache-Control: no-cache\r", "", 0, io.EOF},
{"Cache-Control: no-cache\rabc", "", 0, ErrHeaderDelimiterNotCrLfCrLf},
{"Cache-Control: no-cache\r\n", "", 0, io.ErrUnexpectedEOF},
{"Cache-Control: no-cache\r\n\r", "", 0, io.ErrUnexpectedEOF},
{"Cache-Control: no-cache\r\n\r\n", "", 0, ErrHeaderNotContentLength},
{"Cache-Control: no-cache\r\n\r\nabc", "abc", 0, ErrHeaderNotContentLength},
{"Content-Length: 3 abc", "", 0, io.EOF},
{"Content-Length: 3\nabc", "", 0, io.EOF},
{"Content-Length: 3\rabc", "", 0, ErrHeaderDelimiterNotCrLfCrLf},
{"Content-Length: 3\r\nabc", "c", 0, ErrHeaderDelimiterNotCrLfCrLf},
{"Content-Length: 3\r\n\rabc", "bc", 0, ErrHeaderDelimiterNotCrLfCrLf},
{"Content-Length: 3\r \n\r\nabc", "\nabc", 0, ErrHeaderDelimiterNotCrLfCrLf},
{"Content-Length: 3\r\n \r\nabc", "\nabc", 0, ErrHeaderDelimiterNotCrLfCrLf},
{"Content-Length: 3\r\n\r \nabc", "\nabc", 0, ErrHeaderDelimiterNotCrLfCrLf},
{"Content-Length 3\r\n\r\nabc", "abc", 0, ErrHeaderNotContentLength},
{"_Content-Length: 3\r\n\r\nabc", "abc", 0, ErrHeaderNotContentLength},
{"Content-Length: 3_\r\n\r\nabc", "abc", 0, ErrHeaderNotContentLength},
{"Content-Length: x\r\n\r\nabc", "abc", 0, ErrHeaderNotContentLength},
{"Content-Length: 3.0\r\n\r\nabc", "abc", 0, ErrHeaderNotContentLength},
{"Content-Length: -3\r\n\r\nabc", "abc", 0, ErrHeaderNotContentLength},
{"Content-Length: 0\r\n\r\nabc", "abc", 0, nil},
{"Content-Length: 3\r\n\r\nabc", "abc", 3, nil},
{"Content-Length: 9223372036854775807\r\n\r\nabc", "abc", 9223372036854775807, nil},
}
for _, test := range tests {
t.Run(test.input, func(t *testing.T) {
reader := bufio.NewReader(strings.NewReader(test.input))
gotLen, gotErr := readContentLengthHeader(reader)
if gotErr != test.wantErr {
t.Errorf("got err=%#v, want %#v", gotErr, test.wantErr)
}
if gotErr == nil && gotLen != test.wantLen {
t.Errorf("got len=%d, want %d", gotLen, test.wantLen)
}
bytesLeft, _ := ioutil.ReadAll(reader)
if string(bytesLeft) != test.wantBytesLeft {
t.Errorf("got bytesLeft=%q, want %q", bytesLeft, test.wantBytesLeft)
}
})
}
}
func TestWriteRead(t *testing.T) {
writeContent := [][]byte{
[]byte("this is"),
[]byte("a read write"),
[]byte("test"),
}
var buf bytes.Buffer
for _, wc := range writeContent {
if err := WriteBaseMessage(&buf, wc); err != nil {
t.Fatal(err)
}
}
reader := bufio.NewReader(&buf)
for _, wc := range writeContent {
rc, err := ReadBaseMessage(reader)
if err != nil {
t.Fatal(err)
}
if !bytes.Equal(rc, wc) {
t.Fatalf("got %q, want %q", rc, wc)
}
}
}
// readMessagesAndNotify reads messages one by one until EOF.
// Notifies of a read via messagesRead channel.
func readMessagesAndNotify(t *testing.T, r io.Reader, messagesRead chan<- []byte) {
reader := bufio.NewReader(r)
for {
msg, err := ReadBaseMessage(reader)
if err == io.EOF {
close(messagesRead)
break
}
// On error, this will send "" as the content read
messagesRead <- msg
}
}
func writeOrFail(t *testing.T, w io.Writer, data string) {
if n, err := w.Write([]byte(data)); err != nil || n < len(data) {
t.Fatal(err)
}
}
func TestReadMessageInParts(t *testing.T) {
// This test uses separate goroutines to write and read messages
// and relies on blocking channel operations between them to ensure that
// the expected number of messages is read for what is written.
// Otherwise, the test will time out.
messagesRead := make(chan []byte)
r, w := io.Pipe()
header := "Content-Length: 11"
baddelim := "\r\r\r\r"
content1 := "message one"
content2 := "message two"
nocontent := ""
// This will keep blocking to read a full message or EOF.
go readMessagesAndNotify(t, r, messagesRead)
checkNoMessageRead := func() {
time.Sleep(100 * time.Millisecond) // Let read goroutine run
select {
case msg := <-messagesRead:
t.Errorf("got %q, want none", msg)
default:
}
}
checkOneMessageRead := func(want []byte) {
got := <-messagesRead
if !bytes.Equal(got, want) {
t.Errorf("got %q, want %q", got, want)
}
}
// Good message written in full
writeOrFail(t, w, header+crLfcrLf+content1)
checkOneMessageRead([]byte(content1))
// Good message written in parts
writeOrFail(t, w, header)
checkNoMessageRead()
writeOrFail(t, w, crLfcrLf)
checkNoMessageRead()
writeOrFail(t, w, content2)
checkOneMessageRead([]byte(content2))
// Bad message written in full
writeOrFail(t, w, header+baddelim)
checkOneMessageRead([]byte(nocontent))
// Bad meassage written in parts
writeOrFail(t, w, header)
checkNoMessageRead()
writeOrFail(t, w, baddelim)
checkOneMessageRead([]byte(nocontent))
w.Close() // "sends" EOF
}
func TestReadWriteWithCodec(t *testing.T) {
// Tests end-to-end write and read from a buffer using the DAP codec.
req := InitializeRequest{
Request: Request{
ProtocolMessage: ProtocolMessage{
Type: "request",
Seq: 121,
},
Command: "initialize",
},
Arguments: InitializeRequestArguments{
ClientID: "vscode",
ClientName: "Visual Studio Code",
AdapterID: "go",
PathFormat: "path",
LinesStartAt1: true,
ColumnsStartAt1: false,
SupportsVariableType: true,
SupportsVariablePaging: true,
SupportsRunInTerminalRequest: false,
Locale: "en-us",
},
}
baseReq, err := json.Marshal(req)
if err != nil {
t.Error(err)
}
buf := new(bytes.Buffer)
err = WriteBaseMessage(buf, baseReq)
if err != nil {
t.Error(err)
}
reader := bufio.NewReader(buf)
msg, err := ReadBaseMessage(reader)
if err != nil {
t.Error(err)
}
readReqPtr, err := DecodeProtocolMessage(msg)
if err != nil {
t.Error(err)
}
if !reflect.DeepEqual(readReqPtr, &req) {
t.Errorf("got req=%#v, want %#v", readReqPtr, req)
}
}
const cancelReqString = "Content-Length: 75\r\n\r\n{\"seq\":25,\"type\":\"request\",\"command\":\"cancel\",\"arguments\":{\"requestId\":24}}"
var cancelReqStruct = CancelRequest{
Request: Request{
ProtocolMessage: ProtocolMessage{
Type: "request",
Seq: 25,
},
Command: "cancel",
},
Arguments: CancelArguments{RequestId: 24},
}
func TestWriteProtocolMessage(t *testing.T) {
buf := new(bytes.Buffer)
err := WriteProtocolMessage(buf, &cancelReqStruct)
if err != nil {
t.Error(err)
}
if buf.String() != cancelReqString {
t.Errorf("got %#v, want %#v", buf.String(), cancelReqString)
}
}
func TestReadProtocolMessage(t *testing.T) {
reader := bufio.NewReader(strings.NewReader(cancelReqString))
msg, err := ReadProtocolMessage(reader)
if err != nil {
t.Error(err)
}
if !reflect.DeepEqual(msg, &cancelReqStruct) {
t.Errorf("got req=%#v, want %#v", msg, &cancelReqStruct)
}
}