Skip to content

Commit 92fa1ff

Browse files
committed
internal/lsp/regtest: add support for custom test proxy data
Certain regtests require referencing external data. To support this, add the ability to use a file-based proxy populated with testdata. To expose this configuration, augment the regtest runner with variadic options. Also use this to replace the Runner.RunInMode function. Add a simple regtest that uses this functionality. Updates golang/go#36879 Change-Id: I7e6314430abcd127dbb7bca12574ef9935bf1f83 Reviewed-on: https://go-review.googlesource.com/c/tools/+/228235 Run-TryBot: Robert Findley <[email protected]> TryBot-Result: Gobot Gobot <[email protected]> Reviewed-by: Rebecca Stambler <[email protected]>
1 parent 405595e commit 92fa1ff

File tree

7 files changed

+199
-23
lines changed

7 files changed

+199
-23
lines changed

Diff for: internal/lsp/fake/editor_test.go

+1-1
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 := NewWorkspace("TestClientEditing", []byte(exampleProgram))
51+
ws, err := NewWorkspace("TestClientEditing", exampleProgram, "")
5252
if err != nil {
5353
t.Fatal(err)
5454
}

Diff for: internal/lsp/fake/workspace.go

+74-8
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515

1616
"golang.org/x/tools/internal/gocommand"
1717
"golang.org/x/tools/internal/lsp/protocol"
18+
"golang.org/x/tools/internal/proxydir"
1819
"golang.org/x/tools/internal/span"
1920
"golang.org/x/tools/txtar"
2021
)
@@ -29,9 +30,10 @@ type FileEvent struct {
2930
// The Workspace type represents a temporary workspace to use for editing Go
3031
// files in tests.
3132
type Workspace struct {
32-
name string
33-
gopath string
34-
workdir string
33+
name string
34+
gopath string
35+
workdir string
36+
proxydir string
3537

3638
watcherMu sync.Mutex
3739
watchers []func(context.Context, []FileEvent)
@@ -40,7 +42,7 @@ type Workspace struct {
4042
// NewWorkspace creates a named workspace populated by the txtar-encoded
4143
// content given by txt. It creates temporary directories for the workspace
4244
// content and for GOPATH.
43-
func NewWorkspace(name string, txt []byte) (_ *Workspace, err error) {
45+
func NewWorkspace(name string, srctxt string, proxytxt string) (_ *Workspace, err error) {
4446
w := &Workspace{name: name}
4547
defer func() {
4648
// Clean up if we fail at any point in this constructor.
@@ -58,15 +60,73 @@ func NewWorkspace(name string, txt []byte) (_ *Workspace, err error) {
5860
return nil, fmt.Errorf("creating temporary gopath: %v", err)
5961
}
6062
w.gopath = gopath
61-
archive := txtar.Parse(txt)
62-
for _, f := range archive.Files {
63-
if err := w.writeFileData(f.Name, string(f.Data)); err != nil {
64-
return nil, err
63+
files := unpackTxt(srctxt)
64+
for name, data := range files {
65+
if err := w.writeFileData(name, string(data)); err != nil {
66+
return nil, fmt.Errorf("writing to workdir: %v", err)
6567
}
6668
}
69+
pd, err := ioutil.TempDir("", fmt.Sprintf("goplstest-proxy-%s-", name))
70+
if err != nil {
71+
return nil, fmt.Errorf("creating temporary proxy dir: %v", err)
72+
}
73+
w.proxydir = pd
74+
if err := writeProxyDir(unpackTxt(proxytxt), w.proxydir); err != nil {
75+
return nil, fmt.Errorf("writing proxy dir: %v", err)
76+
}
6777
return w, nil
6878
}
6979

80+
func unpackTxt(txt string) map[string][]byte {
81+
dataMap := make(map[string][]byte)
82+
archive := txtar.Parse([]byte(txt))
83+
for _, f := range archive.Files {
84+
dataMap[f.Name] = f.Data
85+
}
86+
return dataMap
87+
}
88+
89+
func writeProxyDir(files map[string][]byte, dir string) error {
90+
type moduleVersion struct {
91+
modulePath, version string
92+
}
93+
// Transform into the format expected by the proxydir package.
94+
filesByModule := make(map[moduleVersion]map[string][]byte)
95+
for name, data := range files {
96+
modulePath, version, suffix := splitModuleVersionPath(name)
97+
mv := moduleVersion{modulePath, version}
98+
if _, ok := filesByModule[mv]; !ok {
99+
filesByModule[mv] = make(map[string][]byte)
100+
}
101+
filesByModule[mv][suffix] = data
102+
}
103+
for mv, files := range filesByModule {
104+
if err := proxydir.WriteModuleVersion(dir, mv.modulePath, mv.version, files); err != nil {
105+
return fmt.Errorf("error writing %s@%s: %v", mv.modulePath, mv.version, err)
106+
}
107+
}
108+
return nil
109+
}
110+
111+
// splitModuleVersionPath extracts module information from files stored in the
112+
// directory structure modulePath@version/suffix.
113+
// For example:
114+
// splitModuleVersionPath("[email protected]/package") = ("mod.com", "v1.2.3", "package")
115+
func splitModuleVersionPath(path string) (modulePath, version, suffix string) {
116+
parts := strings.Split(path, "/")
117+
var modulePathParts []string
118+
for i, p := range parts {
119+
if strings.Contains(p, "@") {
120+
mv := strings.SplitN(p, "@", 2)
121+
modulePathParts = append(modulePathParts, mv[0])
122+
return strings.Join(modulePathParts, "/"), mv[1], strings.Join(parts[i+1:], "/")
123+
}
124+
modulePathParts = append(modulePathParts, p)
125+
}
126+
// Default behavior: this is just a module path.
127+
return path, "", ""
128+
}
129+
70130
// RootURI returns the root URI for this workspace.
71131
func (w *Workspace) RootURI() protocol.DocumentURI {
72132
return toURI(w.workdir)
@@ -77,6 +137,11 @@ func (w *Workspace) GOPATH() string {
77137
return w.gopath
78138
}
79139

140+
// GOPROXY returns the value that GOPROXY should be set to for this workspace.
141+
func (w *Workspace) GOPROXY() string {
142+
return proxydir.ToURL(w.proxydir)
143+
}
144+
80145
// AddWatcher registers the given func to be called on any file change.
81146
func (w *Workspace) AddWatcher(watcher func(context.Context, []FileEvent)) {
82147
w.watcherMu.Lock()
@@ -156,6 +221,7 @@ func (w *Workspace) RemoveFile(ctx context.Context, path string) error {
156221
func (w *Workspace) GoEnv() []string {
157222
return []string{
158223
"GOPATH=" + w.GOPATH(),
224+
"GOPROXY=" + w.GOPROXY(),
159225
"GO111MODULE=",
160226
"GOSUMDB=off",
161227
}

Diff for: internal/lsp/fake/workspace_test.go

+23-2
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,13 @@ Hello World!
2121
func newWorkspace(t *testing.T) (*Workspace, <-chan []FileEvent, func()) {
2222
t.Helper()
2323

24-
ws, err := NewWorkspace("default", []byte(data))
24+
ws, err := NewWorkspace("default", data, "")
2525
if err != nil {
2626
t.Fatal(err)
2727
}
2828
cleanup := func() {
2929
if err := ws.Close(); err != nil {
30-
t.Fatal(err)
30+
t.Errorf("closing workspace: %v", err)
3131
}
3232
}
3333

@@ -90,3 +90,24 @@ func TestWorkspace_WriteFile(t *testing.T) {
9090
}
9191
}
9292
}
93+
94+
func TestSplitModuleVersionPath(t *testing.T) {
95+
tests := []struct {
96+
path string
97+
wantModule, wantVersion, wantSuffix string
98+
}{
99+
{"[email protected]/bar", "foo.com", "v1.2.3", "bar"},
100+
{"foo.com/[email protected]/bar", "foo.com/module", "v1.2.3", "bar"},
101+
{"[email protected]", "foo.com", "v1.2.3", ""},
102+
{"[email protected]", "std", "v1.14.0", ""},
103+
{"another/module/path", "another/module/path", "", ""},
104+
}
105+
106+
for _, test := range tests {
107+
module, version, suffix := splitModuleVersionPath(test.path)
108+
if module != test.wantModule || version != test.wantVersion || suffix != test.wantSuffix {
109+
t.Errorf("splitModuleVersionPath(%q) =\n\t(%q, %q, %q)\nwant\n\t(%q, %q, %q)",
110+
test.path, module, version, suffix, test.wantModule, test.wantVersion, test.wantSuffix)
111+
}
112+
}
113+
}

Diff for: internal/lsp/lsprpc/lsprpc_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,7 @@ func TestDebugInfoLifecycle(t *testing.T) {
190190
resetExitFuncs := OverrideExitFuncsForTest()
191191
defer resetExitFuncs()
192192

193-
ws, err := fake.NewWorkspace("gopls-lsprpc-test", []byte(exampleProgram))
193+
ws, err := fake.NewWorkspace("gopls-lsprpc-test", exampleProgram, "")
194194
if err != nil {
195195
t.Fatal(err)
196196
}

Diff for: internal/lsp/regtest/diagnostics_test.go

+45
Original file line numberDiff line numberDiff line change
@@ -259,3 +259,48 @@ func TestPackageChange(t *testing.T) {
259259
env.Await(EmptyDiagnostics("a.go"))
260260
})
261261
}
262+
263+
const testPackageWithRequire = `
264+
-- go.mod --
265+
module mod.com
266+
267+
go 1.12
268+
269+
require (
270+
foo.test v1.2.3
271+
)
272+
-- print.go --
273+
package lib
274+
275+
import (
276+
"fmt"
277+
278+
"foo.test/bar"
279+
)
280+
281+
func PrintAnswer() {
282+
fmt.Printf("answer: %s", bar.Answer)
283+
}
284+
`
285+
286+
const testPackageWithRequireProxy = `
287+
-- [email protected]/go.mod --
288+
module foo.test
289+
290+
go 1.12
291+
-- [email protected]/bar/const.go --
292+
package bar
293+
294+
const Answer = 42
295+
`
296+
297+
func TestResolveDiagnosticWithDownload(t *testing.T) {
298+
runner.Run(t, testPackageWithRequire, func(t *testing.T, env *Env) {
299+
env.OpenFile("print.go")
300+
env.W.RunGoCommand(env.Ctx, "mod", "download")
301+
// Check that gopackages correctly loaded this dependency. We should get a
302+
// diagnostic for the wrong formatting type.
303+
// TODO: we should be able to easily also match the diagnostic message.
304+
env.Await(env.DiagnosticAtRegexp("print.go", "fmt.Printf"))
305+
}, WithProxy(testPackageWithRequireProxy))
306+
}

Diff for: internal/lsp/regtest/env.go

+53-9
Original file line numberDiff line numberDiff line change
@@ -163,17 +163,61 @@ func (r *Runner) Close() error {
163163
return nil
164164
}
165165

166+
type runConfig struct {
167+
modes EnvMode
168+
proxyTxt string
169+
timeout time.Duration
170+
}
171+
172+
func (r *Runner) defaultConfig() *runConfig {
173+
return &runConfig{
174+
modes: r.defaultModes,
175+
timeout: r.timeout,
176+
}
177+
}
178+
179+
// A RunOption augments the behavior of the test runner.
180+
type RunOption interface {
181+
set(*runConfig)
182+
}
183+
184+
type optionSetter func(*runConfig)
185+
186+
func (f optionSetter) set(opts *runConfig) {
187+
f(opts)
188+
}
189+
190+
// WithTimeout configures a custom timeout for this test run.
191+
func WithTimeout(d time.Duration) RunOption {
192+
return optionSetter(func(opts *runConfig) {
193+
opts.timeout = d
194+
})
195+
}
196+
197+
// WithProxy configures a file proxy using the given txtar-encoded string.
198+
func WithProxy(txt string) RunOption {
199+
return optionSetter(func(opts *runConfig) {
200+
opts.proxyTxt = txt
201+
})
202+
}
203+
204+
// WithModes configures the execution modes that the test should run in.
205+
func WithModes(modes EnvMode) RunOption {
206+
return optionSetter(func(opts *runConfig) {
207+
opts.modes = modes
208+
})
209+
}
210+
166211
// Run executes the test function in the default configured gopls execution
167212
// modes. For each a test run, a new workspace is created containing the
168213
// un-txtared files specified by filedata.
169-
func (r *Runner) Run(t *testing.T, filedata string, test func(t *testing.T, e *Env)) {
214+
func (r *Runner) Run(t *testing.T, filedata string, test func(t *testing.T, e *Env), opts ...RunOption) {
170215
t.Helper()
171-
r.RunInMode(r.defaultModes, t, filedata, test)
172-
}
216+
config := r.defaultConfig()
217+
for _, opt := range opts {
218+
opt.set(config)
219+
}
173220

174-
// RunInMode runs the test in the execution modes specified by the modes bitmask.
175-
func (r *Runner) RunInMode(modes EnvMode, t *testing.T, filedata string, test func(t *testing.T, e *Env)) {
176-
t.Helper()
177221
tests := []struct {
178222
name string
179223
mode EnvMode
@@ -186,16 +230,16 @@ func (r *Runner) RunInMode(modes EnvMode, t *testing.T, filedata string, test fu
186230

187231
for _, tc := range tests {
188232
tc := tc
189-
if modes&tc.mode == 0 {
233+
if config.modes&tc.mode == 0 {
190234
continue
191235
}
192236
t.Run(tc.name, func(t *testing.T) {
193237
t.Helper()
194-
ctx, cancel := context.WithTimeout(context.Background(), r.timeout)
238+
ctx, cancel := context.WithTimeout(context.Background(), config.timeout)
195239
defer cancel()
196240
ctx = debug.WithInstance(ctx, "", "")
197241

198-
ws, err := fake.NewWorkspace("regtest", []byte(filedata))
242+
ws, err := fake.NewWorkspace("regtest", filedata, config.proxyTxt)
199243
if err != nil {
200244
t.Fatal(err)
201245
}

Diff for: internal/lsp/regtest/shared_test.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,12 @@ func main() {
2525
func runShared(t *testing.T, program string, testFunc func(env1 *Env, env2 *Env)) {
2626
// Only run these tests in forwarded modes.
2727
modes := runner.Modes() & (Forwarded | SeparateProcess)
28-
runner.RunInMode(modes, t, sharedProgram, func(t *testing.T, env1 *Env) {
28+
runner.Run(t, sharedProgram, func(t *testing.T, env1 *Env) {
2929
// Create a second test session connected to the same workspace and server
3030
// as the first.
3131
env2 := NewEnv(env1.Ctx, t, env1.W, env1.Server)
3232
testFunc(env1, env2)
33-
})
33+
}, WithModes(modes))
3434
}
3535

3636
func TestSimultaneousEdits(t *testing.T) {

0 commit comments

Comments
 (0)