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