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