internal/lsp: add more flexible completion tests

Add a new @completePartial note that does not require you to specify
the full list of completions. This gets rid of a lot of noise when you
just want to test the relative order of some completion candidates but
don't care about all the other candidates in scope.

I changed one existing test to use @completePartial as an example.

Change-Id: I56005405477e562803f094c0cac05ef2b854ad1a
Reviewed-on: https://go-review.googlesource.com/c/tools/+/192657
Run-TryBot: Rebecca Stambler <rstambler@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
This commit is contained in:
Muir Manders 2019-06-17 20:56:06 -07:00 committed by Rebecca Stambler
parent 0673112484
commit 5797d2e298
4 changed files with 146 additions and 37 deletions

View File

@ -117,9 +117,9 @@ func (r *runner) Completion(t *testing.T, data tests.Completions, snippets tests
// Set this as a default.
modified.Completion.Documentation = true
for src, itemList := range data {
for src, test := range data {
var want []source.CompletionItem
for _, pos := range itemList {
for _, pos := range test.CompletionItems {
want = append(want, *items[pos])
}
@ -138,8 +138,16 @@ func (r *runner) Completion(t *testing.T, data tests.Completions, snippets tests
}
got = append(got, item)
}
if diff := diffCompletionItems(t, src, want, got); diff != "" {
t.Errorf("%s: %s", src, diff)
switch test.Type {
case tests.CompletionFull:
if diff := diffCompletionItems(want, got); diff != "" {
t.Errorf("%s: %s", src, diff)
}
case tests.CompletionPartial:
if msg := checkCompletionOrder(want, got); msg != "" {
t.Errorf("%s: %s", src, msg)
}
}
}
@ -219,7 +227,7 @@ func isBuiltin(item protocol.CompletionItem) bool {
// diffCompletionItems prints the diff between expected and actual completion
// test results.
func diffCompletionItems(t *testing.T, spn span.Span, want []source.CompletionItem, got []protocol.CompletionItem) string {
func diffCompletionItems(want []source.CompletionItem, got []protocol.CompletionItem) string {
if len(got) != len(want) {
return summarizeCompletionItems(-1, want, got, "different lengths got %v want %v", len(got), len(want))
}
@ -243,6 +251,43 @@ func diffCompletionItems(t *testing.T, spn span.Span, want []source.CompletionIt
return ""
}
func checkCompletionOrder(want []source.CompletionItem, got []protocol.CompletionItem) string {
var (
matchedIdxs []int
lastGotIdx int
inOrder = true
)
for _, w := range want {
var found bool
for i, g := range got {
if w.Label == g.Label && w.Detail == g.Detail && toProtocolCompletionItemKind(w.Kind) == g.Kind {
matchedIdxs = append(matchedIdxs, i)
found = true
if i < lastGotIdx {
inOrder = false
}
lastGotIdx = i
break
}
}
if !found {
return summarizeCompletionItems(-1, []source.CompletionItem{w}, got, "didn't find expected completion")
}
}
sort.Ints(matchedIdxs)
matched := make([]protocol.CompletionItem, 0, len(matchedIdxs))
for _, idx := range matchedIdxs {
matched = append(matched, got[idx])
}
if !inOrder {
return summarizeCompletionItems(-1, want, matched, "completions out of order")
}
return ""
}
func summarizeCompletionItems(i int, want []source.CompletionItem, got []protocol.CompletionItem, reason string, args ...interface{}) string {
msg := &bytes.Buffer{}
fmt.Fprint(msg, "completion failed")

View File

@ -86,9 +86,9 @@ func (r *runner) Diagnostics(t *testing.T, data tests.Diagnostics) {
func (r *runner) Completion(t *testing.T, data tests.Completions, snippets tests.CompletionSnippets, items tests.CompletionItems) {
ctx := r.ctx
for src, itemList := range data {
for src, test := range data {
var want []source.CompletionItem
for _, pos := range itemList {
for _, pos := range test.CompletionItems {
want = append(want, *items[pos])
}
f, err := r.view.GetFile(ctx, src.URI())
@ -142,8 +142,15 @@ func (r *runner) Completion(t *testing.T, data tests.Completions, snippets tests
}
got = append(got, item)
}
if diff := diffCompletionItems(t, src, want, got); diff != "" {
t.Errorf("%s: %s", src, diff)
switch test.Type {
case tests.CompletionFull:
if diff := diffCompletionItems(want, got); diff != "" {
t.Errorf("%s: %s", src, diff)
}
case tests.CompletionPartial:
if msg := checkCompletionOrder(want, got); msg != "" {
t.Errorf("%s: %s", src, msg)
}
}
}
for _, usePlaceholders := range []bool{true, false} {
@ -207,7 +214,7 @@ func isBuiltin(item source.CompletionItem) bool {
// diffCompletionItems prints the diff between expected and actual completion
// test results.
func diffCompletionItems(t *testing.T, spn span.Span, want []source.CompletionItem, got []source.CompletionItem) string {
func diffCompletionItems(want []source.CompletionItem, got []source.CompletionItem) string {
sort.SliceStable(got, func(i, j int) bool {
return got[i].Score > got[j].Score
})
@ -250,6 +257,43 @@ func diffCompletionItems(t *testing.T, spn span.Span, want []source.CompletionIt
return ""
}
func checkCompletionOrder(want []source.CompletionItem, got []source.CompletionItem) string {
var (
matchedIdxs []int
lastGotIdx int
inOrder = true
)
for _, w := range want {
var found bool
for i, g := range got {
if w.Label == g.Label && w.Detail == g.Detail && w.Kind == g.Kind {
matchedIdxs = append(matchedIdxs, i)
found = true
if i < lastGotIdx {
inOrder = false
}
lastGotIdx = i
break
}
}
if !found {
return summarizeCompletionItems(-1, []source.CompletionItem{w}, got, "didn't find expected completion")
}
}
sort.Ints(matchedIdxs)
matched := make([]source.CompletionItem, 0, len(matchedIdxs))
for _, idx := range matchedIdxs {
matched = append(matched, got[idx])
}
if !inOrder {
return summarizeCompletionItems(-1, want, matched, "completions out of order")
}
return ""
}
func summarizeCompletionItems(i int, want []source.CompletionItem, got []source.CompletionItem, reason string, args ...interface{}) string {
msg := &bytes.Buffer{}
fmt.Fprint(msg, "completion failed")

View File

@ -4,7 +4,7 @@
package deepcomplete
import "context" //@item(ctxPackage, "context", "\"context\"", "package")
import "context"
type deepA struct {
b deepB //@item(deepBField, "b", "deepB", "field")
@ -26,15 +26,14 @@ func _() {
func wantsContext(context.Context) {}
func _() {
context.Background() //@item(ctxBackground, "context.Background", "func() context.Context", "func", "Background returns a non-nil, empty Context.")
context.TODO() //@item(ctxTODO, "context.TODO", "func() context.Context", "func", "TODO returns a non-nil, empty Context.")
context.WithValue(nil, nil, nil) //@item(ctxWithValue, "context.WithValue", "func(parent context.Context, key interface{}, val interface{}) context.Context", "func", "WithValue returns a copy of parent in which the value associated with key is val.")
context.Background() //@item(ctxBackground, "context.Background", "func() context.Context", "func", "Background returns a non-nil, empty Context.")
context.TODO() //@item(ctxTODO, "context.TODO", "func() context.Context", "func", "TODO returns a non-nil, empty Context.")
wantsContext(c) //@complete(")", ctxBackground, ctxTODO, ctxWithValue, ctxPackage)
wantsContext(c) //@completePartial(")", ctxBackground, ctxTODO)
}
func _() {
type deepCircle struct { //@item(deepCircleStruct, "deepCircle", "struct{...}", "struct")
type deepCircle struct {
*deepCircle
}
var circle deepCircle //@item(deepCircle, "circle", "deepCircle", "var")

View File

@ -58,7 +58,7 @@ var updateGolden = flag.Bool("golden", false, "Update golden files")
type Diagnostics map[span.URI][]source.Diagnostic
type CompletionItems map[token.Pos]*source.CompletionItem
type Completions map[span.Span][]token.Pos
type Completions map[span.Span]Completion
type CompletionSnippets map[span.Span]CompletionSnippet
type FoldingRanges []span.Span
type Formats []span.Span
@ -125,6 +125,21 @@ type Definition struct {
Src, Def span.Span
}
type CompletionTestType int
const (
// Full means candidates in test must match full list of candidates.
CompletionFull CompletionTestType = iota
// Partial means candidates in test must be valid and in the right relative order.
CompletionPartial
)
type Completion struct {
CompletionItems []token.Pos
Type CompletionTestType
}
type CompletionSnippet struct {
CompletionItem token.Pos
PlainSnippet string
@ -232,24 +247,25 @@ func Load(t testing.TB, exporter packagestest.Exporter, dir string) *Data {
// Collect any data that needs to be used by subsequent tests.
if err := data.Exported.Expect(map[string]interface{}{
"diag": data.collectDiagnostics,
"item": data.collectCompletionItems,
"complete": data.collectCompletions,
"fold": data.collectFoldingRanges,
"format": data.collectFormats,
"import": data.collectImports,
"godef": data.collectDefinitions,
"typdef": data.collectTypeDefinitions,
"hover": data.collectHoverDefinitions,
"highlight": data.collectHighlights,
"refs": data.collectReferences,
"rename": data.collectRenames,
"prepare": data.collectPrepareRenames,
"symbol": data.collectSymbols,
"signature": data.collectSignatures,
"snippet": data.collectCompletionSnippets,
"link": data.collectLinks,
"suggestedfix": data.collectSuggestedFixes,
"diag": data.collectDiagnostics,
"item": data.collectCompletionItems,
"complete": data.collectCompletions(CompletionFull),
"completePartial": data.collectCompletions(CompletionPartial),
"fold": data.collectFoldingRanges,
"format": data.collectFormats,
"import": data.collectImports,
"godef": data.collectDefinitions,
"typdef": data.collectTypeDefinitions,
"hover": data.collectHoverDefinitions,
"highlight": data.collectHighlights,
"refs": data.collectReferences,
"rename": data.collectRenames,
"prepare": data.collectPrepareRenames,
"symbol": data.collectSymbols,
"signature": data.collectSignatures,
"snippet": data.collectCompletionSnippets,
"link": data.collectLinks,
"suggestedfix": data.collectSuggestedFixes,
}); err != nil {
t.Fatal(err)
}
@ -567,8 +583,13 @@ func summarizeDiagnostics(i int, want []source.Diagnostic, got []source.Diagnost
return msg.String()
}
func (data *Data) collectCompletions(src span.Span, expected []token.Pos) {
data.Completions[src] = expected
func (data *Data) collectCompletions(typ CompletionTestType) func(span.Span, []token.Pos) {
return func(src span.Span, expected []token.Pos) {
data.Completions[src] = Completion{
CompletionItems: expected,
Type: typ,
}
}
}
func (data *Data) collectCompletionItems(pos token.Pos, args []string) {