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  }