Skip to content

Commit cd1be5d

Browse files
committed
gopls/internal/regtest: add a failing regtest for vscode-go#1489
Unimported completion computes invalid text edits with windows line endings. To enable this test, add support for windows line endings in the regtest framework. Doing this required decoupling the txtar encoding from the sandbox, which was a good change anyway. For golang/vscode-go#1489 Change-Id: I6c1075fd38d24090271a7a7f33b11ddd8f9decf5 Reviewed-on: https://go-review.googlesource.com/c/tools/+/319089 Trust: Robert Findley <[email protected]> Run-TryBot: Robert Findley <[email protected]> gopls-CI: kokoro <[email protected]> TryBot-Result: Go Bot <[email protected]> Reviewed-by: Rebecca Stambler <[email protected]>
1 parent 57c3a74 commit cd1be5d

File tree

11 files changed

+99
-32
lines changed

11 files changed

+99
-32
lines changed

gopls/internal/regtest/completion/completion_test.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -503,3 +503,41 @@ func doit() {
503503
}
504504
})
505505
}
506+
507+
func TestUnimportedCompletion_VSCodeIssue1489(t *testing.T) {
508+
t.Skip("broken due to golang/vscode-go#1489")
509+
testenv.NeedsGo1Point(t, 14)
510+
511+
const src = `
512+
-- go.mod --
513+
module mod.com
514+
515+
go 1.14
516+
517+
-- main.go --
518+
package main
519+
520+
import "fmt"
521+
522+
func main() {
523+
fmt.Println("a")
524+
math.Sqr
525+
}
526+
`
527+
WithOptions(
528+
WindowsLineEndings,
529+
ProxyFiles(proxy),
530+
).Run(t, src, func(t *testing.T, env *Env) {
531+
// Trigger unimported completions for the example.com/blah package.
532+
env.OpenFile("main.go")
533+
env.Await(env.DoneWithOpen())
534+
pos := env.RegexpSearch("main.go", "Sqr()")
535+
completions := env.Completion("main.go", pos)
536+
if len(completions.Items) == 0 {
537+
t.Fatalf("no completion items")
538+
}
539+
env.AcceptCompletion("main.go", pos, completions.Items[0])
540+
env.Await(env.DoneWithChange())
541+
t.Log(env.Editor.BufferText("main.go"))
542+
})
543+
}

internal/lsp/cache/view_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ module de
6666
-- f/g/go.mod --
6767
module fg
6868
`
69-
dir, err := fake.Tempdir(workspace)
69+
dir, err := fake.Tempdir(fake.UnpackTxt(workspace))
7070
if err != nil {
7171
t.Fatal(err)
7272
}

internal/lsp/cache/workspace_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -269,7 +269,7 @@ replace gopls.test => ../../gopls.test2`, false},
269269
for _, test := range tests {
270270
t.Run(test.desc, func(t *testing.T) {
271271
ctx := context.Background()
272-
dir, err := fake.Tempdir(test.initial)
272+
dir, err := fake.Tempdir(fake.UnpackTxt(test.initial))
273273
if err != nil {
274274
t.Fatal(err)
275275
}

internal/lsp/fake/editor.go

Lines changed: 35 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -55,14 +55,19 @@ type CallCounts struct {
5555
}
5656

5757
type buffer struct {
58-
version int
59-
path string
60-
lines []string
61-
dirty bool
58+
windowsLineEndings bool
59+
version int
60+
path string
61+
lines []string
62+
dirty bool
6263
}
6364

6465
func (b buffer) text() string {
65-
return strings.Join(b.lines, "\n")
66+
eol := "\n"
67+
if b.windowsLineEndings {
68+
eol = "\r\n"
69+
}
70+
return strings.Join(b.lines, eol)
6671
}
6772

6873
// EditorConfig configures the editor's LSP session. This is similar to
@@ -106,6 +111,9 @@ type EditorConfig struct {
106111
// the PID. This can only be set by one test.
107112
SendPID bool
108113

114+
// Whether to edit files with windows line endings.
115+
WindowsLineEndings bool
116+
109117
DirectoryFilters []string
110118

111119
VerboseOutput bool
@@ -338,11 +346,11 @@ func (e *Editor) onFileChanges(ctx context.Context, evts []FileEvent) {
338346
continue // A race with some other operation.
339347
}
340348
// No need to update if the buffer content hasn't changed.
341-
if content == strings.Join(buf.lines, "\n") {
349+
if content == buf.text() {
342350
continue
343351
}
344352
// During shutdown, this call will fail. Ignore the error.
345-
_ = e.setBufferContentLocked(ctx, evt.Path, false, strings.Split(content, "\n"), nil)
353+
_ = e.setBufferContentLocked(ctx, evt.Path, false, lines(content), nil)
346354
}
347355
}
348356
e.Server.DidChangeWatchedFiles(ctx, &protocol.DidChangeWatchedFilesParams{
@@ -383,10 +391,11 @@ func (e *Editor) CreateBuffer(ctx context.Context, path, content string) error {
383391

384392
func (e *Editor) createBuffer(ctx context.Context, path string, dirty bool, content string) error {
385393
buf := buffer{
386-
version: 1,
387-
path: path,
388-
lines: strings.Split(content, "\n"),
389-
dirty: dirty,
394+
windowsLineEndings: e.Config.WindowsLineEndings,
395+
version: 1,
396+
path: path,
397+
lines: lines(content),
398+
dirty: dirty,
390399
}
391400
e.mu.Lock()
392401
defer e.mu.Unlock()
@@ -406,6 +415,15 @@ func (e *Editor) createBuffer(ctx context.Context, path string, dirty bool, cont
406415
return nil
407416
}
408417

418+
// lines returns line-ending agnostic line representation of content.
419+
func lines(content string) []string {
420+
lines := strings.Split(content, "\n")
421+
for i, l := range lines {
422+
lines[i] = strings.TrimSuffix(l, "\r")
423+
}
424+
return lines
425+
}
426+
409427
// CloseBuffer removes the current buffer (regardless of whether it is saved).
410428
func (e *Editor) CloseBuffer(ctx context.Context, path string) error {
411429
e.mu.Lock()
@@ -528,6 +546,7 @@ var (
528546
// regexpRange returns the start and end of the first occurrence of either re
529547
// or its singular subgroup. It returns ErrNoMatch if the regexp doesn't match.
530548
func regexpRange(content, re string) (Pos, Pos, error) {
549+
content = normalizeEOL(content)
531550
var start, end int
532551
rec, err := regexp.Compile(re)
533552
if err != nil {
@@ -558,6 +577,10 @@ func regexpRange(content, re string) (Pos, Pos, error) {
558577
return startPos, endPos, nil
559578
}
560579

580+
func normalizeEOL(content string) string {
581+
return strings.Join(lines(content), "\n")
582+
}
583+
561584
// RegexpRange returns the first range in the buffer bufName matching re. See
562585
// RegexpSearch for more information on matching.
563586
func (e *Editor) RegexpRange(bufName, re string) (Pos, Pos, error) {
@@ -615,7 +638,7 @@ func (e *Editor) EditBuffer(ctx context.Context, path string, edits []Edit) erro
615638
func (e *Editor) SetBufferContent(ctx context.Context, path, content string) error {
616639
e.mu.Lock()
617640
defer e.mu.Unlock()
618-
lines := strings.Split(content, "\n")
641+
lines := lines(content)
619642
return e.setBufferContentLocked(ctx, path, true, lines, nil)
620643
}
621644

internal/lsp/fake/editor_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ func main() {
4848
`
4949

5050
func TestClientEditing(t *testing.T) {
51-
ws, err := NewSandbox(&SandboxConfig{Files: exampleProgram})
51+
ws, err := NewSandbox(&SandboxConfig{Files: UnpackTxt(exampleProgram)})
5252
if err != nil {
5353
t.Fatal(err)
5454
}

internal/lsp/fake/proxy.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,7 @@ import (
1212

1313
// WriteProxy creates a new proxy file tree using the txtar-encoded content,
1414
// and returns its URL.
15-
func WriteProxy(tmpdir, txt string) (string, error) {
16-
files := unpackTxt(txt)
15+
func WriteProxy(tmpdir string, files map[string][]byte) (string, error) {
1716
type moduleVersion struct {
1817
modulePath, version string
1918
}

internal/lsp/fake/sandbox.go

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ type SandboxConfig struct {
3838
//
3939
// For convenience, the special substring "$SANDBOX_WORKDIR" is replaced with
4040
// the sandbox's resolved working directory before writing files.
41-
Files string
41+
Files map[string][]byte
4242
// InGoPath specifies that the working directory should be within the
4343
// temporary GOPATH.
4444
InGoPath bool
@@ -51,10 +51,9 @@ type SandboxConfig struct {
5151
//
5252
// This option is incompatible with InGoPath or Files.
5353
Workdir string
54-
5554
// ProxyFiles holds a txtar-encoded archive of files to populate a file-based
5655
// Go proxy.
57-
ProxyFiles string
56+
ProxyFiles map[string][]byte
5857
// GOPROXY is the explicit GOPROXY value that should be used for the sandbox.
5958
//
6059
// This option is incompatible with ProxyFiles.
@@ -141,12 +140,11 @@ func NewSandbox(config *SandboxConfig) (_ *Sandbox, err error) {
141140
// Tempdir creates a new temp directory with the given txtar-encoded files. It
142141
// is the responsibility of the caller to call os.RemoveAll on the returned
143142
// file path when it is no longer needed.
144-
func Tempdir(txt string) (string, error) {
143+
func Tempdir(files map[string][]byte) (string, error) {
145144
dir, err := ioutil.TempDir("", "gopls-tempdir-")
146145
if err != nil {
147146
return "", err
148147
}
149-
files := unpackTxt(txt)
150148
for name, data := range files {
151149
if err := WriteFileData(name, data, RelativeTo(dir)); err != nil {
152150
return "", errors.Errorf("writing to tempdir: %w", err)
@@ -155,7 +153,7 @@ func Tempdir(txt string) (string, error) {
155153
return dir, nil
156154
}
157155

158-
func unpackTxt(txt string) map[string][]byte {
156+
func UnpackTxt(txt string) map[string][]byte {
159157
dataMap := make(map[string][]byte)
160158
archive := txtar.Parse([]byte(txt))
161159
for _, f := range archive.Files {
@@ -165,13 +163,13 @@ func unpackTxt(txt string) map[string][]byte {
165163
}
166164

167165
func validateConfig(config SandboxConfig) error {
168-
if filepath.IsAbs(config.Workdir) && (config.Files != "" || config.InGoPath) {
166+
if filepath.IsAbs(config.Workdir) && (config.Files != nil || config.InGoPath) {
169167
return errors.New("absolute Workdir cannot be set in conjunction with Files or InGoPath")
170168
}
171169
if config.Workdir != "" && config.InGoPath {
172170
return errors.New("Workdir cannot be set in conjunction with InGoPath")
173171
}
174-
if config.GOPROXY != "" && config.ProxyFiles != "" {
172+
if config.GOPROXY != "" && config.ProxyFiles != nil {
175173
return errors.New("GOPROXY cannot be set in conjunction with ProxyFiles")
176174
}
177175
return nil

internal/lsp/fake/workdir.go

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ func (r RelativeTo) RelPath(fp string) string {
5050
}
5151

5252
func writeTxtar(txt string, rel RelativeTo) error {
53-
files := unpackTxt(txt)
53+
files := UnpackTxt(txt)
5454
for name, data := range files {
5555
if err := WriteFileData(name, data, rel); err != nil {
5656
return errors.Errorf("writing to workdir: %w", err)
@@ -96,8 +96,7 @@ func hashFile(data []byte) string {
9696
return fmt.Sprintf("%x", sha256.Sum256(data))
9797
}
9898

99-
func (w *Workdir) writeInitialFiles(txt string) error {
100-
files := unpackTxt(txt)
99+
func (w *Workdir) writeInitialFiles(files map[string][]byte) error {
101100
w.files = map[string]string{}
102101
for name, data := range files {
103102
w.files[name] = hashFile(data)

internal/lsp/fake/workdir_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ func newWorkdir(t *testing.T) (*Workdir, <-chan []FileEvent, func()) {
3030
t.Fatal(err)
3131
}
3232
wd := NewWorkdir(tmpdir)
33-
if err := wd.writeInitialFiles(data); err != nil {
33+
if err := wd.writeInitialFiles(UnpackTxt(data)); err != nil {
3434
t.Fatal(err)
3535
}
3636
cleanup := func() {

internal/lsp/lsprpc/lsprpc_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,7 @@ func main() {
197197
}`
198198

199199
func TestDebugInfoLifecycle(t *testing.T) {
200-
sb, err := fake.NewSandbox(&fake.SandboxConfig{Files: exampleProgram})
200+
sb, err := fake.NewSandbox(&fake.SandboxConfig{Files: fake.UnpackTxt(exampleProgram)})
201201
if err != nil {
202202
t.Fatal(err)
203203
}

internal/lsp/regtest/runner.go

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ func Timeout(d time.Duration) RunOption {
109109
// ProxyFiles configures a file proxy using the given txtar-encoded string.
110110
func ProxyFiles(txt string) RunOption {
111111
return optionSetter(func(opts *runConfig) {
112-
opts.sandbox.ProxyFiles = txt
112+
opts.sandbox.ProxyFiles = fake.UnpackTxt(txt)
113113
})
114114
}
115115

@@ -177,6 +177,10 @@ func DebugAddress(addr string) RunOption {
177177
})
178178
}
179179

180+
var WindowsLineEndings = optionSetter(func(opts *runConfig) {
181+
opts.editor.WindowsLineEndings = true
182+
})
183+
180184
// SkipLogs skips the buffering of logs during test execution. It is intended
181185
// for long-running stress tests.
182186
func SkipLogs() RunOption {
@@ -269,6 +273,12 @@ func (r *Runner) Run(t *testing.T, files string, test TestFunc, opts ...RunOptio
269273
if err := os.MkdirAll(rootDir, 0755); err != nil {
270274
t.Fatal(err)
271275
}
276+
files := fake.UnpackTxt(files)
277+
if config.editor.WindowsLineEndings {
278+
for name, data := range files {
279+
files[name] = bytes.ReplaceAll(data, []byte("\n"), []byte("\r\n"))
280+
}
281+
}
272282
config.sandbox.Files = files
273283
config.sandbox.RootDir = rootDir
274284
sandbox, err := fake.NewSandbox(&config.sandbox)
@@ -385,7 +395,7 @@ func singletonServer(ctx context.Context, t *testing.T, optsHook func(*source.Op
385395
return lsprpc.NewStreamServer(cache.New(optsHook), false)
386396
}
387397

388-
func experimentalWorkspaceModule(_ context.Context, t *testing.T, optsHook func(*source.Options)) jsonrpc2.StreamServer {
398+
func experimentalWorkspaceModule(_ context.Context, _ *testing.T, optsHook func(*source.Options)) jsonrpc2.StreamServer {
389399
options := func(o *source.Options) {
390400
optsHook(o)
391401
o.ExperimentalWorkspaceModule = true

0 commit comments

Comments
 (0)