github.com/GoogleContainerTools/skaffold@v1.39.18/pkg/skaffold/lsp/handler_test.go (about) 1 /* 2 Copyright 2021 The Skaffold Authors 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package lsp 18 19 import ( 20 "context" 21 "encoding/json" 22 "io" 23 "net" 24 "os" 25 "reflect" 26 "testing" 27 28 "go.lsp.dev/jsonrpc2" 29 "go.lsp.dev/protocol" 30 31 "github.com/GoogleContainerTools/skaffold/pkg/skaffold/config" 32 "github.com/GoogleContainerTools/skaffold/pkg/skaffold/runner" 33 "github.com/GoogleContainerTools/skaffold/pkg/skaffold/runner/runcontext" 34 "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/util" 35 "github.com/GoogleContainerTools/skaffold/testutil" 36 ) 37 38 type callTest struct { 39 description string 40 method string 41 params interface{} 42 expected interface{} 43 } 44 45 var callTests = []callTest{ 46 { 47 description: "verify lsp 'initialize' method returns expected results", 48 method: protocol.MethodInitialize, 49 params: protocol.InitializeParams{ 50 WorkspaceFolders: []protocol.WorkspaceFolder{ 51 { 52 URI: "overwritten by test", 53 Name: "test name", 54 }, 55 }, 56 Capabilities: protocol.ClientCapabilities{ 57 TextDocument: &protocol.TextDocumentClientCapabilities{ 58 // TODO(aaron-prindle) make sure the values here make sense (similar to VSCode, hit more edge cases (missing capability, versioning, old fields), etc.) 59 PublishDiagnostics: &protocol.PublishDiagnosticsClientCapabilities{ 60 RelatedInformation: true, 61 TagSupport: &protocol.PublishDiagnosticsClientCapabilitiesTagSupport{ 62 ValueSet: []protocol.DiagnosticTag{protocol.DiagnosticTagDeprecated}, 63 }, 64 VersionSupport: true, 65 CodeDescriptionSupport: true, 66 DataSupport: true, 67 }, 68 }, 69 }, 70 }, 71 expected: protocol.InitializeResult{ 72 Capabilities: protocol.ServerCapabilities{ 73 TextDocumentSync: protocol.TextDocumentSyncOptions{ 74 Change: protocol.TextDocumentSyncKindFull, 75 OpenClose: true, 76 Save: &protocol.SaveOptions{ 77 IncludeText: true, 78 }, 79 }, 80 }, 81 }, 82 }, 83 // TODO(aaron-prindle) add error cases and full set of functionality (textDocument/* reqs, etc.) 84 } 85 86 func TestRequest(t *testing.T) { 87 ctx := context.Background() 88 a, b, done := prepare(ctx, t) 89 defer done() 90 workdir, err := os.Getwd() 91 if err != nil { 92 t.Fatalf("error getting working directory: %v", err) 93 } 94 for _, test := range callTests { 95 testutil.Run(t, test.description, func(t *testutil.T) { 96 // need to marshal and unmarshal to get proper formatting for matching as structs not represented identically w/o this for verification 97 expectedData, err := json.Marshal(test.expected) 98 if err != nil { 99 t.Fatalf("marshalling expected response to json failed: %v", err) 100 } 101 var expected protocol.InitializeResult 102 json.Unmarshal(expectedData, &expected) 103 test.expected = expected 104 105 params := test.params 106 if v, ok := test.params.(protocol.InitializeParams); ok { 107 v.WorkspaceFolders[0].URI = "file://" + workdir 108 params = v 109 } 110 111 results := test.newResults() 112 if _, err := a.Call(ctx, test.method, params, results); err != nil { 113 t.Fatalf("%v call failed: %v", test.method, err) 114 } 115 116 test.verifyResults(t.T, results) 117 118 if _, err := b.Call(ctx, test.method, params, results); err != nil { 119 t.Fatalf("%v call failed: %v", test.method, err) 120 } 121 test.verifyResults(t.T, results) 122 }) 123 } 124 } 125 126 func (test *callTest) newResults() interface{} { 127 switch e := test.expected.(type) { 128 case []interface{}: 129 var r []interface{} 130 for _, v := range e { 131 r = append(r, reflect.New(reflect.TypeOf(v)).Interface()) 132 } 133 return r 134 135 case nil: 136 return nil 137 138 default: 139 return reflect.New(reflect.TypeOf(test.expected)).Interface() 140 } 141 } 142 143 func (test *callTest) verifyResults(t *testing.T, results interface{}) { 144 t.Helper() 145 146 if results == nil { 147 return 148 } 149 150 val := reflect.Indirect(reflect.ValueOf(results)).Interface() 151 if !reflect.DeepEqual(val, test.expected) { 152 t.Errorf("%v results are incorrect, got %+v expect %+v", test.method, val, test.expected) 153 } 154 } 155 156 func prepare(ctx context.Context, t *testing.T) (a, b jsonrpc2.Conn, done func()) { 157 t.Helper() 158 159 // make a wait group that can be used to wait for the system to shut down 160 aPipe, bPipe := net.Pipe() 161 a = run(ctx, aPipe) 162 b = run(ctx, bPipe) 163 done = func() { 164 a.Close() 165 b.Close() 166 <-a.Done() 167 <-b.Done() 168 } 169 170 return a, b, done 171 } 172 173 func run(ctx context.Context, nc io.ReadWriteCloser) jsonrpc2.Conn { 174 stream := jsonrpc2.NewStream(nc) 175 conn := jsonrpc2.NewConn(stream) 176 conn.Go(ctx, GetHandler(conn, nil, config.SkaffoldOptions{}, func(ctx context.Context, out io.Writer, opts config.SkaffoldOptions) (runner.Runner, []util.VersionedConfig, *runcontext.RunContext, error) { 177 return nil, nil, nil, nil 178 })) 179 180 return conn 181 }