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