-
Notifications
You must be signed in to change notification settings - Fork 2.3k
/
Copy pathdoc.go
156 lines (155 loc) · 6.78 KB
/
doc.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
// Copyright 2020 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package integration provides a framework for writing integration tests of gopls.
//
// The behaviors that matter to users, and the scenarios they
// typically describe in bug report, are usually expressed in terms of
// editor interactions. For example: "When I open my editor in this
// directory, navigate to this file, and change this line, I get a
// diagnostic that doesn't make sense". The integration package
// provides an API for gopls maintainers to express these types of
// user interactions in ordinary Go tests, validate them, and run them
// in a variety of execution modes.
//
// # Test package setup
//
// The integration test package uses a couple of uncommon patterns to reduce
// boilerplate in test bodies. First, it is intended to be imported as "." so
// that helpers do not need to be qualified. Second, it requires some setup
// that is currently implemented in the integration.Main function, which must be
// invoked by TestMain. Therefore, a minimal integration testing package looks
// like this:
//
// package feature
//
// import (
// "fmt"
// "testing"
//
// "golang.org/x/tools/gopls/internal/hooks"
// . "golang.org/x/tools/gopls/internal/test/integration"
// )
//
// func TestMain(m *testing.M) {
// os.Exit(Main(m, hooks.Options))
// }
//
// # Writing a simple integration test
//
// To run an integration test use the integration.Run function, which accepts a
// txtar-encoded archive defining the initial workspace state. This function
// sets up the workspace in a temporary directory, creates a fake text editor,
// starts gopls, and initializes an LSP session. It then invokes the provided
// test function with an *Env encapsulating the newly created
// environment. Because gopls may be run in various modes (as a sidecar or
// daemon process, with different settings), the test runner may perform this
// process multiple times, re-running the test function each time with a new
// environment.
//
// func TestOpenFile(t *testing.T) {
// const files = `
// -- go.mod --
// module mod.com
//
// go 1.12
// -- foo.go --
// package foo
// `
// Run(t, files, func(t *testing.T, env *Env) {
// env.OpenFile("foo.go")
// })
// }
//
// # Configuring integration test execution
//
// The integration package exposes several options that affect the setup process
// described above. To use these options, use the WithOptions function:
//
// WithOptions(opts...).Run(...)
//
// See options.go for a full list of available options.
//
// # Operating on editor state
//
// To operate on editor state within the test body, the Env type provides
// access to the workspace directory (Env.SandBox), text editor (Env.Editor),
// LSP server (Env.Server), and 'awaiter' (Env.Awaiter).
//
// In most cases, operations on these primitive building blocks of the
// integration test environment expect a Context (which should be a child of
// env.Ctx), and return an error. To avoid boilerplate, the Env exposes a set
// of wrappers in wrappers.go for use in scripting:
//
// env.CreateBuffer("c/c.go", "")
// env.EditBuffer("c/c.go", editor.Edit{
// Text: `package c`,
// })
//
// These wrappers thread through Env.Ctx, and call t.Fatal on any errors.
//
// # Expressing expectations
//
// The general pattern for an integration test is to script interactions with the
// fake editor and sandbox, and assert that gopls behaves correctly after each
// state change. Unfortunately, this is complicated by the fact that state
// changes are communicated to gopls via unidirectional client->server
// notifications (didOpen, didChange, etc.), and resulting gopls behavior such
// as diagnostics, logs, or messages is communicated back via server->client
// notifications. Therefore, within integration tests we must be able to say "do
// this, and then eventually gopls should do that". To achieve this, the
// integration package provides a framework for expressing conditions that must
// eventually be met, in terms of the Expectation type.
//
// To express the assertion that "eventually gopls must meet these
// expectations", use env.Await(...):
//
// env.RegexpReplace("x/x.go", `package x`, `package main`)
// env.Await(env.DiagnosticAtRegexp("x/main.go", `fmt`))
//
// Await evaluates the provided expectations atomically, whenever the client
// receives a state-changing notification from gopls. See expectation.go for a
// full list of available expectations.
//
// A problem with this model is that if gopls never meets the provided
// expectations, the test runner will hang until the test timeout
// (which defaults to 10m). There are two ways to work around this
// poor behavior:
//
// 1. Use a precondition to define precisely when we expect conditions to be
// met. Gopls provides the OnceMet(precondition, expectations...) pattern
// to express ("once this precondition is met, the following expectations
// must all hold"). To instrument preconditions, gopls uses verbose
// progress notifications to inform the client about ongoing work (see
// CompletedWork). The most common precondition is to wait for gopls to be
// done processing all change notifications, for which the integration package
// provides the AfterChange helper. For example:
//
// // We expect diagnostics to be cleared after gopls is done processing the
// // didSave notification.
// env.SaveBuffer("a/go.mod")
// env.AfterChange(EmptyDiagnostics("a/go.mod"))
//
// 2. Set a shorter timeout during development, if you expect to be breaking
// tests. By setting the environment variable GOPLS_INTEGRATION_TEST_TIMEOUT=5s,
// integration tests will time out after 5 seconds.
//
// # Tips & Tricks
//
// Here are some tips and tricks for working with integration tests:
//
// 1. Set the environment variable GOPLS_INTEGRRATION_TEST_TIMEOUT=5s during development.
// 2. Run tests with -short. This will only run integration tests in the
// default gopls execution mode.
// 3. Use capture groups to narrow regexp positions. All regular-expression
// based positions (such as DiagnosticAtRegexp) will match the position of
// the first capture group, if any are provided. This can be used to
// identify a specific position in the code for a pattern that may occur in
// multiple places. For example `var (mu) sync.Mutex` matches the position
// of "mu" within the variable declaration.
// 4. Read diagnostics into a variable to implement more complicated
// assertions about diagnostic state in the editor. To do this, use the
// pattern OnceMet(precondition, ReadDiagnostics("file.go", &d)) to capture
// the current diagnostics as soon as the precondition is met. This is
// preferable to accessing the diagnostics directly, as it avoids races.
package integration