golang.org/x/tools/gopls@v0.15.3/internal/test/integration/regtest.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 6 7 import ( 8 "context" 9 "flag" 10 "fmt" 11 "os" 12 "runtime" 13 "testing" 14 "time" 15 16 "golang.org/x/tools/gopls/internal/cache" 17 "golang.org/x/tools/gopls/internal/cmd" 18 "golang.org/x/tools/gopls/internal/settings" 19 "golang.org/x/tools/internal/gocommand" 20 "golang.org/x/tools/internal/memoize" 21 "golang.org/x/tools/internal/testenv" 22 "golang.org/x/tools/internal/tool" 23 ) 24 25 var ( 26 runSubprocessTests = flag.Bool("enable_gopls_subprocess_tests", false, "run integration tests against a gopls subprocess (default: in-process)") 27 goplsBinaryPath = flag.String("gopls_test_binary", "", "path to the gopls binary for use as a remote, for use with the -enable_gopls_subprocess_tests flag") 28 timeout = flag.Duration("timeout", defaultTimeout(), "if nonzero, default timeout for each integration test; defaults to GOPLS_INTEGRATION_TEST_TIMEOUT") 29 skipCleanup = flag.Bool("skip_cleanup", false, "whether to skip cleaning up temp directories") 30 printGoroutinesOnFailure = flag.Bool("print_goroutines", false, "whether to print goroutines info on failure") 31 printLogs = flag.Bool("print_logs", false, "whether to print LSP logs") 32 ) 33 34 func defaultTimeout() time.Duration { 35 s := os.Getenv("GOPLS_INTEGRATION_TEST_TIMEOUT") 36 if s == "" { 37 return 0 38 } 39 d, err := time.ParseDuration(s) 40 if err != nil { 41 fmt.Fprintf(os.Stderr, "invalid GOPLS_INTEGRATION_TEST_TIMEOUT %q: %v\n", s, err) 42 os.Exit(2) 43 } 44 return d 45 } 46 47 var runner *Runner 48 49 // The integrationTestRunner interface abstracts the Run operation, 50 // enables decorators for various optional features. 51 type integrationTestRunner interface { 52 Run(t *testing.T, files string, f TestFunc) 53 } 54 55 func Run(t *testing.T, files string, f TestFunc) { 56 runner.Run(t, files, f) 57 } 58 59 func WithOptions(opts ...RunOption) configuredRunner { 60 return configuredRunner{opts: opts} 61 } 62 63 type configuredRunner struct { 64 opts []RunOption 65 } 66 67 func (r configuredRunner) Run(t *testing.T, files string, f TestFunc) { 68 // Print a warning if the test's temporary directory is not 69 // suitable as a workspace folder, as this may lead to 70 // otherwise-cryptic failures. This situation typically occurs 71 // when an arbitrary string (e.g. "foo.") is used as a subtest 72 // name, on a platform with filename restrictions (e.g. no 73 // trailing period on Windows). 74 tmp := t.TempDir() 75 if err := cache.CheckPathValid(tmp); err != nil { 76 t.Logf("Warning: testing.T.TempDir(%s) is not valid as a workspace folder: %s", 77 tmp, err) 78 } 79 80 runner.Run(t, files, f, r.opts...) 81 } 82 83 type RunMultiple []struct { 84 Name string 85 Runner integrationTestRunner 86 } 87 88 func (r RunMultiple) Run(t *testing.T, files string, f TestFunc) { 89 for _, runner := range r { 90 t.Run(runner.Name, func(t *testing.T) { 91 runner.Runner.Run(t, files, f) 92 }) 93 } 94 } 95 96 // DefaultModes returns the default modes to run for each regression test (they 97 // may be reconfigured by the tests themselves). 98 func DefaultModes() Mode { 99 modes := Default 100 if !testing.Short() { 101 modes |= Experimental | Forwarded 102 } 103 if *runSubprocessTests { 104 modes |= SeparateProcess 105 } 106 return modes 107 } 108 109 var runFromMain = false // true if Main has been called 110 111 // Main sets up and tears down the shared integration test state. 112 func Main(m *testing.M, hook func(*settings.Options)) { 113 runFromMain = true 114 115 // golang/go#54461: enable additional debugging around hanging Go commands. 116 gocommand.DebugHangingGoCommands = true 117 118 // If this magic environment variable is set, run gopls instead of the test 119 // suite. See the documentation for runTestAsGoplsEnvvar for more details. 120 if os.Getenv(runTestAsGoplsEnvvar) == "true" { 121 tool.Main(context.Background(), cmd.New(hook), os.Args[1:]) 122 os.Exit(0) 123 } 124 125 if !testenv.HasExec() { 126 fmt.Printf("skipping all tests: exec not supported on %s/%s\n", runtime.GOOS, runtime.GOARCH) 127 os.Exit(0) 128 } 129 testenv.ExitIfSmallMachine() 130 131 // Disable GOPACKAGESDRIVER, as it can cause spurious test failures. 132 os.Setenv("GOPACKAGESDRIVER", "off") 133 134 if skipReason := checkBuilder(); skipReason != "" { 135 fmt.Printf("Skipping all tests: %s\n", skipReason) 136 os.Exit(0) 137 } 138 139 if err := testenv.HasTool("go"); err != nil { 140 fmt.Println("Missing go command") 141 os.Exit(1) 142 } 143 144 flag.Parse() 145 146 runner = &Runner{ 147 DefaultModes: DefaultModes(), 148 Timeout: *timeout, 149 PrintGoroutinesOnFailure: *printGoroutinesOnFailure, 150 SkipCleanup: *skipCleanup, 151 OptionsHook: hook, 152 store: memoize.NewStore(memoize.NeverEvict), 153 } 154 155 runner.goplsPath = *goplsBinaryPath 156 if runner.goplsPath == "" { 157 var err error 158 runner.goplsPath, err = os.Executable() 159 if err != nil { 160 panic(fmt.Sprintf("finding test binary path: %v", err)) 161 } 162 } 163 164 dir, err := os.MkdirTemp("", "gopls-test-") 165 if err != nil { 166 panic(fmt.Errorf("creating temp directory: %v", err)) 167 } 168 runner.tempDir = dir 169 170 var code int 171 defer func() { 172 if err := runner.Close(); err != nil { 173 fmt.Fprintf(os.Stderr, "closing test runner: %v\n", err) 174 // Cleanup is broken in go1.12 and earlier, and sometimes flakes on 175 // Windows due to file locking, but this is OK for our CI. 176 // 177 // Fail on go1.13+, except for windows and android which have shutdown problems. 178 if testenv.Go1Point() >= 13 && runtime.GOOS != "windows" && runtime.GOOS != "android" { 179 os.Exit(1) 180 } 181 } 182 os.Exit(code) 183 }() 184 code = m.Run() 185 }