github.com/april1989/origin-go-tools@v0.0.32/internal/lsp/tests/util.go (about)

     1  // Copyright 2020 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package tests
     6  
     7  import (
     8  	"bytes"
     9  	"fmt"
    10  	"go/token"
    11  	"path/filepath"
    12  	"sort"
    13  	"strconv"
    14  	"strings"
    15  	"testing"
    16  
    17  	"github.com/april1989/origin-go-tools/internal/lsp/diff"
    18  	"github.com/april1989/origin-go-tools/internal/lsp/diff/myers"
    19  	"github.com/april1989/origin-go-tools/internal/lsp/protocol"
    20  	"github.com/april1989/origin-go-tools/internal/lsp/source"
    21  	"github.com/april1989/origin-go-tools/internal/span"
    22  )
    23  
    24  // DiffLinks takes the links we got and checks if they are located within the source or a Note.
    25  // If the link is within a Note, the link is removed.
    26  // Returns an diff comment if there are differences and empty string if no diffs.
    27  func DiffLinks(mapper *protocol.ColumnMapper, wantLinks []Link, gotLinks []protocol.DocumentLink) string {
    28  	var notePositions []token.Position
    29  	links := make(map[span.Span]string, len(wantLinks))
    30  	for _, link := range wantLinks {
    31  		links[link.Src] = link.Target
    32  		notePositions = append(notePositions, link.NotePosition)
    33  	}
    34  	for _, link := range gotLinks {
    35  		spn, err := mapper.RangeSpan(link.Range)
    36  		if err != nil {
    37  			return fmt.Sprintf("%v", err)
    38  		}
    39  		linkInNote := false
    40  		for _, notePosition := range notePositions {
    41  			// Drop the links found inside expectation notes arguments as this links are not collected by expect package.
    42  			if notePosition.Line == spn.Start().Line() &&
    43  				notePosition.Column <= spn.Start().Column() {
    44  				delete(links, spn)
    45  				linkInNote = true
    46  			}
    47  		}
    48  		if linkInNote {
    49  			continue
    50  		}
    51  		if target, ok := links[spn]; ok {
    52  			delete(links, spn)
    53  			if target != link.Target {
    54  				return fmt.Sprintf("for %v want %v, got %v\n", spn, target, link.Target)
    55  			}
    56  		} else {
    57  			return fmt.Sprintf("unexpected link %v:%v\n", spn, link.Target)
    58  		}
    59  	}
    60  	for spn, target := range links {
    61  		return fmt.Sprintf("missing link %v:%v\n", spn, target)
    62  	}
    63  	return ""
    64  }
    65  
    66  // DiffSymbols prints the diff between expected and actual symbols test results.
    67  func DiffSymbols(t *testing.T, uri span.URI, want, got []protocol.DocumentSymbol) string {
    68  	sort.Slice(want, func(i, j int) bool { return want[i].Name < want[j].Name })
    69  	sort.Slice(got, func(i, j int) bool { return got[i].Name < got[j].Name })
    70  	if len(got) != len(want) {
    71  		return summarizeSymbols(-1, want, got, "different lengths got %v want %v", len(got), len(want))
    72  	}
    73  	for i, w := range want {
    74  		g := got[i]
    75  		if w.Name != g.Name {
    76  			return summarizeSymbols(i, want, got, "incorrect name got %v want %v", g.Name, w.Name)
    77  		}
    78  		if w.Kind != g.Kind {
    79  			return summarizeSymbols(i, want, got, "incorrect kind got %v want %v", g.Kind, w.Kind)
    80  		}
    81  		if protocol.CompareRange(w.SelectionRange, g.SelectionRange) != 0 {
    82  			return summarizeSymbols(i, want, got, "incorrect span got %v want %v", g.SelectionRange, w.SelectionRange)
    83  		}
    84  		if msg := DiffSymbols(t, uri, w.Children, g.Children); msg != "" {
    85  			return fmt.Sprintf("children of %s: %s", w.Name, msg)
    86  		}
    87  	}
    88  	return ""
    89  }
    90  
    91  func summarizeSymbols(i int, want, got []protocol.DocumentSymbol, reason string, args ...interface{}) string {
    92  	msg := &bytes.Buffer{}
    93  	fmt.Fprint(msg, "document symbols failed")
    94  	if i >= 0 {
    95  		fmt.Fprintf(msg, " at %d", i)
    96  	}
    97  	fmt.Fprint(msg, " because of ")
    98  	fmt.Fprintf(msg, reason, args...)
    99  	fmt.Fprint(msg, ":\nexpected:\n")
   100  	for _, s := range want {
   101  		fmt.Fprintf(msg, "  %v %v %v\n", s.Name, s.Kind, s.SelectionRange)
   102  	}
   103  	fmt.Fprintf(msg, "got:\n")
   104  	for _, s := range got {
   105  		fmt.Fprintf(msg, "  %v %v %v\n", s.Name, s.Kind, s.SelectionRange)
   106  	}
   107  	return msg.String()
   108  }
   109  
   110  // FilterWorkspaceSymbols filters to got contained in the given dirs.
   111  func FilterWorkspaceSymbols(got []protocol.SymbolInformation, dirs map[string]struct{}) []protocol.SymbolInformation {
   112  	var result []protocol.SymbolInformation
   113  	for _, si := range got {
   114  		if _, ok := dirs[filepath.Dir(si.Location.URI.SpanURI().Filename())]; ok {
   115  			result = append(result, si)
   116  		}
   117  	}
   118  	return result
   119  }
   120  
   121  // DiffWorkspaceSymbols prints the diff between expected and actual workspace
   122  // symbols test results.
   123  func DiffWorkspaceSymbols(want, got []protocol.SymbolInformation) string {
   124  	sort.Slice(want, func(i, j int) bool { return fmt.Sprintf("%v", want[i]) < fmt.Sprintf("%v", want[j]) })
   125  	sort.Slice(got, func(i, j int) bool { return fmt.Sprintf("%v", got[i]) < fmt.Sprintf("%v", got[j]) })
   126  	if len(got) != len(want) {
   127  		return summarizeWorkspaceSymbols(-1, want, got, "different lengths got %v want %v", len(got), len(want))
   128  	}
   129  	for i, w := range want {
   130  		g := got[i]
   131  		if w.Name != g.Name {
   132  			return summarizeWorkspaceSymbols(i, want, got, "incorrect name got %v want %v", g.Name, w.Name)
   133  		}
   134  		if w.Kind != g.Kind {
   135  			return summarizeWorkspaceSymbols(i, want, got, "incorrect kind got %v want %v", g.Kind, w.Kind)
   136  		}
   137  		if w.Location.URI != g.Location.URI {
   138  			return summarizeWorkspaceSymbols(i, want, got, "incorrect uri got %v want %v", g.Location.URI, w.Location.URI)
   139  		}
   140  		if protocol.CompareRange(w.Location.Range, g.Location.Range) != 0 {
   141  			return summarizeWorkspaceSymbols(i, want, got, "incorrect range got %v want %v", g.Location.Range, w.Location.Range)
   142  		}
   143  	}
   144  	return ""
   145  }
   146  
   147  func summarizeWorkspaceSymbols(i int, want, got []protocol.SymbolInformation, reason string, args ...interface{}) string {
   148  	msg := &bytes.Buffer{}
   149  	fmt.Fprint(msg, "workspace symbols failed")
   150  	if i >= 0 {
   151  		fmt.Fprintf(msg, " at %d", i)
   152  	}
   153  	fmt.Fprint(msg, " because of ")
   154  	fmt.Fprintf(msg, reason, args...)
   155  	fmt.Fprint(msg, ":\nexpected:\n")
   156  	for _, s := range want {
   157  		fmt.Fprintf(msg, "  %v %v %v:%v\n", s.Name, s.Kind, s.Location.URI, s.Location.Range)
   158  	}
   159  	fmt.Fprintf(msg, "got:\n")
   160  	for _, s := range got {
   161  		fmt.Fprintf(msg, "  %v %v %v:%v\n", s.Name, s.Kind, s.Location.URI, s.Location.Range)
   162  	}
   163  	return msg.String()
   164  }
   165  
   166  // DiffDiagnostics prints the diff between expected and actual diagnostics test
   167  // results.
   168  func DiffDiagnostics(uri span.URI, want, got []*source.Diagnostic) string {
   169  	source.SortDiagnostics(want)
   170  	source.SortDiagnostics(got)
   171  
   172  	if len(got) != len(want) {
   173  		return summarizeDiagnostics(-1, uri, want, got, "different lengths got %v want %v", len(got), len(want))
   174  	}
   175  	for i, w := range want {
   176  		g := got[i]
   177  		if w.Message != g.Message {
   178  			return summarizeDiagnostics(i, uri, want, got, "incorrect Message got %v want %v", g.Message, w.Message)
   179  		}
   180  		if w.Severity != g.Severity {
   181  			return summarizeDiagnostics(i, uri, want, got, "incorrect Severity got %v want %v", g.Severity, w.Severity)
   182  		}
   183  		if w.Source != g.Source {
   184  			return summarizeDiagnostics(i, uri, want, got, "incorrect Source got %v want %v", g.Source, w.Source)
   185  		}
   186  		// Don't check the range on the badimport test.
   187  		if strings.Contains(uri.Filename(), "badimport") {
   188  			continue
   189  		}
   190  		if protocol.ComparePosition(w.Range.Start, g.Range.Start) != 0 {
   191  			return summarizeDiagnostics(i, uri, want, got, "incorrect Start got %v want %v", g.Range.Start, w.Range.Start)
   192  		}
   193  		if !protocol.IsPoint(g.Range) { // Accept any 'want' range if the diagnostic returns a zero-length range.
   194  			if protocol.ComparePosition(w.Range.End, g.Range.End) != 0 {
   195  				return summarizeDiagnostics(i, uri, want, got, "incorrect End got %v want %v", g.Range.End, w.Range.End)
   196  			}
   197  		}
   198  	}
   199  	return ""
   200  }
   201  
   202  func summarizeDiagnostics(i int, uri span.URI, want, got []*source.Diagnostic, reason string, args ...interface{}) string {
   203  	msg := &bytes.Buffer{}
   204  	fmt.Fprint(msg, "diagnostics failed")
   205  	if i >= 0 {
   206  		fmt.Fprintf(msg, " at %d", i)
   207  	}
   208  	fmt.Fprint(msg, " because of ")
   209  	fmt.Fprintf(msg, reason, args...)
   210  	fmt.Fprint(msg, ":\nexpected:\n")
   211  	for _, d := range want {
   212  		fmt.Fprintf(msg, "  %s:%v: %s\n", uri, d.Range, d.Message)
   213  	}
   214  	fmt.Fprintf(msg, "got:\n")
   215  	for _, d := range got {
   216  		fmt.Fprintf(msg, "  %s:%v: %s\n", uri, d.Range, d.Message)
   217  	}
   218  	return msg.String()
   219  }
   220  
   221  func DiffCodeLens(uri span.URI, want, got []protocol.CodeLens) string {
   222  	sortCodeLens(want)
   223  	sortCodeLens(got)
   224  
   225  	if len(got) != len(want) {
   226  		return summarizeCodeLens(-1, uri, want, got, "different lengths got %v want %v", len(got), len(want))
   227  	}
   228  	for i, w := range want {
   229  		g := got[i]
   230  		if w.Command.Command != g.Command.Command {
   231  			return summarizeCodeLens(i, uri, want, got, "incorrect Command Name got %v want %v", g.Command.Command, w.Command.Command)
   232  		}
   233  		if w.Command.Title != g.Command.Title {
   234  			return summarizeCodeLens(i, uri, want, got, "incorrect Command Title got %v want %v", g.Command.Title, w.Command.Title)
   235  		}
   236  		if protocol.ComparePosition(w.Range.Start, g.Range.Start) != 0 {
   237  			return summarizeCodeLens(i, uri, want, got, "incorrect Start got %v want %v", g.Range.Start, w.Range.Start)
   238  		}
   239  		if !protocol.IsPoint(g.Range) { // Accept any 'want' range if the codelens returns a zero-length range.
   240  			if protocol.ComparePosition(w.Range.End, g.Range.End) != 0 {
   241  				return summarizeCodeLens(i, uri, want, got, "incorrect End got %v want %v", g.Range.End, w.Range.End)
   242  			}
   243  		}
   244  	}
   245  	return ""
   246  }
   247  
   248  func sortCodeLens(c []protocol.CodeLens) {
   249  	sort.Slice(c, func(i int, j int) bool {
   250  		if r := protocol.CompareRange(c[i].Range, c[j].Range); r != 0 {
   251  			return r < 0
   252  		}
   253  		if c[i].Command.Command < c[j].Command.Command {
   254  			return true
   255  		} else if c[i].Command.Command == c[j].Command.Command {
   256  			return c[i].Command.Title < c[j].Command.Title
   257  		} else {
   258  			return false
   259  		}
   260  	})
   261  }
   262  
   263  func summarizeCodeLens(i int, uri span.URI, want, got []protocol.CodeLens, reason string, args ...interface{}) string {
   264  	msg := &bytes.Buffer{}
   265  	fmt.Fprint(msg, "codelens failed")
   266  	if i >= 0 {
   267  		fmt.Fprintf(msg, " at %d", i)
   268  	}
   269  	fmt.Fprint(msg, " because of ")
   270  	fmt.Fprintf(msg, reason, args...)
   271  	fmt.Fprint(msg, ":\nexpected:\n")
   272  	for _, d := range want {
   273  		fmt.Fprintf(msg, "  %s:%v: %s | %s\n", uri, d.Range, d.Command.Command, d.Command.Title)
   274  	}
   275  	fmt.Fprintf(msg, "got:\n")
   276  	for _, d := range got {
   277  		fmt.Fprintf(msg, "  %s:%v: %s | %s\n", uri, d.Range, d.Command.Command, d.Command.Title)
   278  	}
   279  	return msg.String()
   280  }
   281  
   282  func DiffSignatures(spn span.Span, want, got *protocol.SignatureHelp) string {
   283  	decorate := func(f string, args ...interface{}) string {
   284  		return fmt.Sprintf("invalid signature at %s: %s", spn, fmt.Sprintf(f, args...))
   285  	}
   286  	if len(got.Signatures) != 1 {
   287  		return decorate("wanted 1 signature, got %d", len(got.Signatures))
   288  	}
   289  	if got.ActiveSignature != 0 {
   290  		return decorate("wanted active signature of 0, got %d", int(got.ActiveSignature))
   291  	}
   292  	if want.ActiveParameter != got.ActiveParameter {
   293  		return decorate("wanted active parameter of %d, got %d", want.ActiveParameter, int(got.ActiveParameter))
   294  	}
   295  	g := got.Signatures[0]
   296  	w := want.Signatures[0]
   297  	if w.Label != g.Label {
   298  		wLabel := w.Label + "\n"
   299  		d := myers.ComputeEdits("", wLabel, g.Label+"\n")
   300  		return decorate("mismatched labels:\n%q", diff.ToUnified("want", "got", wLabel, d))
   301  	}
   302  	var paramParts []string
   303  	for _, p := range g.Parameters {
   304  		paramParts = append(paramParts, p.Label)
   305  	}
   306  	paramsStr := strings.Join(paramParts, ", ")
   307  	if !strings.Contains(g.Label, paramsStr) {
   308  		return decorate("expected signature %q to contain params %q", g.Label, paramsStr)
   309  	}
   310  	return ""
   311  }
   312  
   313  // DiffCallHierarchyItems returns the diff between expected and actual call locations for incoming/outgoing call hierarchies
   314  func DiffCallHierarchyItems(gotCalls []protocol.CallHierarchyItem, expectedCalls []protocol.CallHierarchyItem) string {
   315  	expected := make(map[protocol.Location]bool)
   316  	for _, call := range expectedCalls {
   317  		expected[protocol.Location{URI: call.URI, Range: call.Range}] = true
   318  	}
   319  
   320  	got := make(map[protocol.Location]bool)
   321  	for _, call := range gotCalls {
   322  		got[protocol.Location{URI: call.URI, Range: call.Range}] = true
   323  	}
   324  	if len(got) != len(expected) {
   325  		return fmt.Sprintf("expected %d calls but got %d", len(expected), len(got))
   326  	}
   327  	for spn := range got {
   328  		if !expected[spn] {
   329  			return fmt.Sprintf("incorrect calls, expected locations %v but got locations %v", expected, got)
   330  		}
   331  	}
   332  	return ""
   333  }
   334  
   335  func ToProtocolCompletionItems(items []source.CompletionItem) []protocol.CompletionItem {
   336  	var result []protocol.CompletionItem
   337  	for _, item := range items {
   338  		result = append(result, ToProtocolCompletionItem(item))
   339  	}
   340  	return result
   341  }
   342  
   343  func ToProtocolCompletionItem(item source.CompletionItem) protocol.CompletionItem {
   344  	pItem := protocol.CompletionItem{
   345  		Label:         item.Label,
   346  		Kind:          item.Kind,
   347  		Detail:        item.Detail,
   348  		Documentation: item.Documentation,
   349  		InsertText:    item.InsertText,
   350  		TextEdit: &protocol.TextEdit{
   351  			NewText: item.Snippet(),
   352  		},
   353  		// Negate score so best score has lowest sort text like real API.
   354  		SortText: fmt.Sprint(-item.Score),
   355  	}
   356  	if pItem.InsertText == "" {
   357  		pItem.InsertText = pItem.Label
   358  	}
   359  	return pItem
   360  }
   361  
   362  func FilterBuiltins(src span.Span, items []protocol.CompletionItem) []protocol.CompletionItem {
   363  	var (
   364  		got          []protocol.CompletionItem
   365  		wantBuiltins = strings.Contains(string(src.URI()), "builtins")
   366  		wantKeywords = strings.Contains(string(src.URI()), "keywords")
   367  	)
   368  	for _, item := range items {
   369  		if !wantBuiltins && isBuiltin(item.Label, item.Detail, item.Kind) {
   370  			continue
   371  		}
   372  
   373  		if !wantKeywords && token.Lookup(item.Label).IsKeyword() {
   374  			continue
   375  		}
   376  
   377  		got = append(got, item)
   378  	}
   379  	return got
   380  }
   381  
   382  func isBuiltin(label, detail string, kind protocol.CompletionItemKind) bool {
   383  	if detail == "" && kind == protocol.ClassCompletion {
   384  		return true
   385  	}
   386  	// Remaining builtin constants, variables, interfaces, and functions.
   387  	trimmed := label
   388  	if i := strings.Index(trimmed, "("); i >= 0 {
   389  		trimmed = trimmed[:i]
   390  	}
   391  	switch trimmed {
   392  	case "append", "cap", "close", "complex", "copy", "delete",
   393  		"error", "false", "imag", "iota", "len", "make", "new",
   394  		"nil", "panic", "print", "println", "real", "recover", "true":
   395  		return true
   396  	}
   397  	return false
   398  }
   399  
   400  func CheckCompletionOrder(want, got []protocol.CompletionItem, strictScores bool) string {
   401  	var (
   402  		matchedIdxs []int
   403  		lastGotIdx  int
   404  		lastGotSort float64
   405  		inOrder     = true
   406  		errorMsg    = "completions out of order"
   407  	)
   408  	for _, w := range want {
   409  		var found bool
   410  		for i, g := range got {
   411  			if w.Label == g.Label && w.Detail == g.Detail && w.Kind == g.Kind {
   412  				matchedIdxs = append(matchedIdxs, i)
   413  				found = true
   414  
   415  				if i < lastGotIdx {
   416  					inOrder = false
   417  				}
   418  				lastGotIdx = i
   419  
   420  				sort, _ := strconv.ParseFloat(g.SortText, 64)
   421  				if strictScores && len(matchedIdxs) > 1 && sort <= lastGotSort {
   422  					inOrder = false
   423  					errorMsg = "candidate scores not strictly decreasing"
   424  				}
   425  				lastGotSort = sort
   426  
   427  				break
   428  			}
   429  		}
   430  		if !found {
   431  			return summarizeCompletionItems(-1, []protocol.CompletionItem{w}, got, "didn't find expected completion")
   432  		}
   433  	}
   434  
   435  	sort.Ints(matchedIdxs)
   436  	matched := make([]protocol.CompletionItem, 0, len(matchedIdxs))
   437  	for _, idx := range matchedIdxs {
   438  		matched = append(matched, got[idx])
   439  	}
   440  
   441  	if !inOrder {
   442  		return summarizeCompletionItems(-1, want, matched, errorMsg)
   443  	}
   444  
   445  	return ""
   446  }
   447  
   448  func DiffSnippets(want string, got *protocol.CompletionItem) string {
   449  	if want == "" {
   450  		if got != nil {
   451  			x := got.TextEdit
   452  			return fmt.Sprintf("expected no snippet but got %s", x.NewText)
   453  		}
   454  	} else {
   455  		if got == nil {
   456  			return fmt.Sprintf("couldn't find completion matching %q", want)
   457  		}
   458  		x := got.TextEdit
   459  		if want != x.NewText {
   460  			return fmt.Sprintf("expected snippet %q, got %q", want, x.NewText)
   461  		}
   462  	}
   463  	return ""
   464  }
   465  
   466  func FindItem(list []protocol.CompletionItem, want source.CompletionItem) *protocol.CompletionItem {
   467  	for _, item := range list {
   468  		if item.Label == want.Label {
   469  			return &item
   470  		}
   471  	}
   472  	return nil
   473  }
   474  
   475  // DiffCompletionItems prints the diff between expected and actual completion
   476  // test results.
   477  func DiffCompletionItems(want, got []protocol.CompletionItem) string {
   478  	if len(got) != len(want) {
   479  		return summarizeCompletionItems(-1, want, got, "different lengths got %v want %v", len(got), len(want))
   480  	}
   481  	for i, w := range want {
   482  		g := got[i]
   483  		if w.Label != g.Label {
   484  			return summarizeCompletionItems(i, want, got, "incorrect Label got %v want %v", g.Label, w.Label)
   485  		}
   486  		if w.Detail != g.Detail {
   487  			return summarizeCompletionItems(i, want, got, "incorrect Detail got %v want %v", g.Detail, w.Detail)
   488  		}
   489  		if w.Documentation != "" && !strings.HasPrefix(w.Documentation, "@") {
   490  			if w.Documentation != g.Documentation {
   491  				return summarizeCompletionItems(i, want, got, "incorrect Documentation got %v want %v", g.Documentation, w.Documentation)
   492  			}
   493  		}
   494  		if w.Kind != g.Kind {
   495  			return summarizeCompletionItems(i, want, got, "incorrect Kind got %v want %v", g.Kind, w.Kind)
   496  		}
   497  	}
   498  	return ""
   499  }
   500  
   501  func summarizeCompletionItems(i int, want, got []protocol.CompletionItem, reason string, args ...interface{}) string {
   502  	msg := &bytes.Buffer{}
   503  	fmt.Fprint(msg, "completion failed")
   504  	if i >= 0 {
   505  		fmt.Fprintf(msg, " at %d", i)
   506  	}
   507  	fmt.Fprint(msg, " because of ")
   508  	fmt.Fprintf(msg, reason, args...)
   509  	fmt.Fprint(msg, ":\nexpected:\n")
   510  	for _, d := range want {
   511  		fmt.Fprintf(msg, "  %v\n", d)
   512  	}
   513  	fmt.Fprintf(msg, "got:\n")
   514  	for _, d := range got {
   515  		fmt.Fprintf(msg, "  %v\n", d)
   516  	}
   517  	return msg.String()
   518  }
   519  
   520  func FormatFolderName(folder string) string {
   521  	if index := strings.Index(folder, "testdata"); index != -1 {
   522  		return folder[index:]
   523  	}
   524  	return folder
   525  }
   526  
   527  func EnableAllAnalyzers(view source.View, opts *source.Options) {
   528  	if opts.UserEnabledAnalyses == nil {
   529  		opts.UserEnabledAnalyses = make(map[string]bool)
   530  	}
   531  	for _, a := range opts.DefaultAnalyzers {
   532  		if !a.Enabled(view) {
   533  			opts.UserEnabledAnalyses[a.Analyzer.Name] = true
   534  		}
   535  	}
   536  	for _, a := range opts.TypeErrorAnalyzers {
   537  		if !a.Enabled(view) {
   538  			opts.UserEnabledAnalyses[a.Analyzer.Name] = true
   539  		}
   540  	}
   541  	for _, a := range opts.ConvenienceAnalyzers {
   542  		if !a.Enabled(view) {
   543  			opts.UserEnabledAnalyses[a.Analyzer.Name] = true
   544  		}
   545  	}
   546  }
   547  
   548  func Diff(want, got string) string {
   549  	if want == got {
   550  		return ""
   551  	}
   552  	// Add newlines to avoid newline messages in diff.
   553  	want += "\n"
   554  	got += "\n"
   555  	d := myers.ComputeEdits("", want, got)
   556  	return fmt.Sprintf("%q", diff.ToUnified("want", "got", want, d))
   557  }