Skip to content

Commit c20fd7c

Browse files
author
Bryan C. Mills
committed
internal/lsp/regtest: eliminate arbitrary timeouts
We care that gopls operations complete within a reasonable time. However, what is “reasonable” depends strongly on the specifics of the user and the hardware they are running on: a timeout that would be perfectly reasonable on a high-powered user workstation with little other load may be far too short on an overloaded and/or underpowered CI builder. This change adjusts the regtest runner to use the test deadline instead of an arbitrary, flag-defined timeout; we expect the user or system running the test to scale the test timeout appropriately to the specific platform and system load. When the testing package gains support for per-test timeouts (golang/go#48157), this approach will automatically apply those timeouts too. If we decide that we also want to test specific performance and/or latency targets, we can set up specific configurations for that (as either aggressive per-test timeouts or benchmarks) in a followup change. For golang/go#50582 Change-Id: I1ab11b2049effb097aa620046fe11609269f91c4 Reviewed-on: https://go-review.googlesource.com/c/tools/+/380497 Trust: Bryan Mills <bcmills@google.com> Run-TryBot: Bryan Mills <bcmills@google.com> gopls-CI: kokoro <noreply+kokoro@google.com> TryBot-Result: Gopher Robot <gobot@golang.org> Reviewed-by: Robert Findley <rfindley@google.com>
1 parent 97de9ec commit c20fd7c

File tree

4 files changed

+43
-28
lines changed

4 files changed

+43
-28
lines changed

gopls/internal/regtest/bench/bench_test.go

+3-4
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import (
1010
"os"
1111
"runtime/pprof"
1212
"testing"
13-
"time"
1413

1514
"golang.org/x/tools/gopls/internal/hooks"
1615
"golang.org/x/tools/internal/lsp/fake"
@@ -32,9 +31,9 @@ func benchmarkOptions(dir string) []RunOption {
3231
SkipLogs(),
3332
// The Debug server only makes sense if running in singleton mode.
3433
Modes(Singleton),
35-
// Set a generous timeout. Individual tests should control their own
36-
// graceful termination.
37-
Timeout(20 * time.Minute),
34+
// Remove the default timeout. Individual tests should control their
35+
// own graceful termination.
36+
NoDefaultTimeout(),
3837

3938
// Use the actual proxy, since we want our builds to succeed.
4039
GOPROXY("https://proxy.golang.org"),

gopls/internal/regtest/codelens/codelens_test.go

-9
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import (
99
"runtime"
1010
"strings"
1111
"testing"
12-
"time"
1312

1413
"golang.org/x/tools/gopls/internal/hooks"
1514
. "golang.org/x/tools/internal/lsp/regtest"
@@ -292,13 +291,6 @@ func TestGCDetails(t *testing.T) {
292291
t.Skipf("the gc details code lens doesn't work on Android")
293292
}
294293

295-
// TestGCDetails seems to suffer from poor performance on certain builders.
296-
// Give it as long as it needs to complete.
297-
timeout := 60 * time.Second
298-
if d, ok := testenv.Deadline(t); ok {
299-
timeout = time.Until(d) * 19 / 20 // Leave 5% headroom for cleanup.
300-
}
301-
302294
const mod = `
303295
-- go.mod --
304296
module mod.com
@@ -318,7 +310,6 @@ func main() {
318310
CodeLenses: map[string]bool{
319311
"gc_details": true,
320312
}},
321-
Timeout(timeout),
322313
).Run(t, mod, func(t *testing.T, env *Env) {
323314
env.OpenFile("main.go")
324315
env.ExecuteCodeLensCommand("main.go", command.GCDetails)

internal/lsp/regtest/regtest.go

+14-1
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,24 @@ import (
2323
var (
2424
runSubprocessTests = flag.Bool("enable_gopls_subprocess_tests", false, "run regtests against a gopls subprocess")
2525
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")
26-
regtestTimeout = flag.Duration("regtest_timeout", 20*time.Second, "default timeout for each regtest")
26+
regtestTimeout = flag.Duration("regtest_timeout", defaultRegtestTimeout(), "if nonzero, default timeout for each regtest; defaults to GOPLS_REGTEST_TIMEOUT")
2727
skipCleanup = flag.Bool("regtest_skip_cleanup", false, "whether to skip cleaning up temp directories")
2828
printGoroutinesOnFailure = flag.Bool("regtest_print_goroutines", false, "whether to print goroutines info on failure")
2929
)
3030

31+
func defaultRegtestTimeout() time.Duration {
32+
s := os.Getenv("GOPLS_REGTEST_TIMEOUT")
33+
if s == "" {
34+
return 0
35+
}
36+
d, err := time.ParseDuration(s)
37+
if err != nil {
38+
fmt.Fprintf(os.Stderr, "invalid GOPLS_REGTEST_TIMEOUT %q: %v\n", s, err)
39+
os.Exit(2)
40+
}
41+
return d
42+
}
43+
3144
var runner *Runner
3245

3346
type regtestRunner interface {

internal/lsp/regtest/runner.go

+26-14
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import (
2929
"golang.org/x/tools/internal/lsp/lsprpc"
3030
"golang.org/x/tools/internal/lsp/protocol"
3131
"golang.org/x/tools/internal/lsp/source"
32+
"golang.org/x/tools/internal/testenv"
3233
"golang.org/x/tools/internal/xcontext"
3334
)
3435

@@ -71,20 +72,19 @@ type Runner struct {
7172
}
7273

7374
type runConfig struct {
74-
editor fake.EditorConfig
75-
sandbox fake.SandboxConfig
76-
modes Mode
77-
timeout time.Duration
78-
debugAddr string
79-
skipLogs bool
80-
skipHooks bool
81-
optionsHook func(*source.Options)
75+
editor fake.EditorConfig
76+
sandbox fake.SandboxConfig
77+
modes Mode
78+
noDefaultTimeout bool
79+
debugAddr string
80+
skipLogs bool
81+
skipHooks bool
82+
optionsHook func(*source.Options)
8283
}
8384

8485
func (r *Runner) defaultConfig() *runConfig {
8586
return &runConfig{
8687
modes: r.DefaultModes,
87-
timeout: r.Timeout,
8888
optionsHook: r.OptionsHook,
8989
}
9090
}
@@ -100,10 +100,12 @@ func (f optionSetter) set(opts *runConfig) {
100100
f(opts)
101101
}
102102

103-
// Timeout configures a custom timeout for this test run.
104-
func Timeout(d time.Duration) RunOption {
103+
// NoDefaultTimeout removes the timeout set by the -regtest_timeout flag, for
104+
// individual tests that are expected to run longer than is reasonable for
105+
// ordinary regression tests.
106+
func NoDefaultTimeout() RunOption {
105107
return optionSetter(func(opts *runConfig) {
106-
opts.timeout = d
108+
opts.noDefaultTimeout = true
107109
})
108110
}
109111

@@ -257,8 +259,18 @@ func (r *Runner) Run(t *testing.T, files string, test TestFunc, opts ...RunOptio
257259
}
258260

259261
t.Run(tc.name, func(t *testing.T) {
260-
ctx, cancel := context.WithTimeout(context.Background(), config.timeout)
261-
defer cancel()
262+
ctx := context.Background()
263+
if r.Timeout != 0 && !config.noDefaultTimeout {
264+
var cancel context.CancelFunc
265+
ctx, cancel = context.WithTimeout(ctx, r.Timeout)
266+
defer cancel()
267+
} else if d, ok := testenv.Deadline(t); ok {
268+
timeout := time.Until(d) * 19 / 20 // Leave an arbitrary 5% for cleanup.
269+
var cancel context.CancelFunc
270+
ctx, cancel = context.WithTimeout(ctx, timeout)
271+
defer cancel()
272+
}
273+
262274
ctx = debug.WithInstance(ctx, "", "off")
263275
if config.debugAddr != "" {
264276
di := debug.GetInstance(ctx)

0 commit comments

Comments
 (0)