golang.org/x/tools/gopls@v0.15.3/internal/test/integration/bench/didchange_test.go (about)

     1  // Copyright 2022 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 bench
     6  
     7  import (
     8  	"fmt"
     9  	"sync/atomic"
    10  	"testing"
    11  	"time"
    12  
    13  	"golang.org/x/tools/gopls/internal/protocol"
    14  	"golang.org/x/tools/gopls/internal/test/integration/fake"
    15  )
    16  
    17  // Use a global edit counter as bench function may execute multiple times, and
    18  // we want to avoid cache hits. Use time.Now to also avoid cache hits from the
    19  // shared file cache.
    20  var editID int64 = time.Now().UnixNano()
    21  
    22  type changeTest struct {
    23  	repo    string
    24  	file    string
    25  	canSave bool
    26  }
    27  
    28  var didChangeTests = []changeTest{
    29  	{"google-cloud-go", "internal/annotate.go", true},
    30  	{"istio", "pkg/fuzz/util.go", true},
    31  	{"kubernetes", "pkg/controller/lookup_cache.go", true},
    32  	{"kuma", "api/generic/insights.go", true},
    33  	{"oracle", "dataintegration/data_type.go", false}, // diagnoseSave fails because this package is generated
    34  	{"pkgsite", "internal/frontend/server.go", true},
    35  	{"starlark", "starlark/eval.go", true},
    36  	{"tools", "internal/lsp/cache/snapshot.go", true},
    37  }
    38  
    39  // BenchmarkDidChange benchmarks modifications of a single file by making
    40  // synthetic modifications in a comment. It controls pacing by waiting for the
    41  // server to actually start processing the didChange notification before
    42  // proceeding. Notably it does not wait for diagnostics to complete.
    43  func BenchmarkDidChange(b *testing.B) {
    44  	for _, test := range didChangeTests {
    45  		b.Run(test.repo, func(b *testing.B) {
    46  			env := getRepo(b, test.repo).sharedEnv(b)
    47  			env.OpenFile(test.file)
    48  			defer closeBuffer(b, env, test.file)
    49  
    50  			// Insert the text we'll be modifying at the top of the file.
    51  			env.EditBuffer(test.file, protocol.TextEdit{NewText: "// __TEST_PLACEHOLDER_0__\n"})
    52  			env.AfterChange()
    53  			b.ResetTimer()
    54  
    55  			if stopAndRecord := startProfileIfSupported(b, env, qualifiedName(test.repo, "didchange")); stopAndRecord != nil {
    56  				defer stopAndRecord()
    57  			}
    58  
    59  			for i := 0; i < b.N; i++ {
    60  				edits := atomic.AddInt64(&editID, 1)
    61  				env.EditBuffer(test.file, protocol.TextEdit{
    62  					Range: protocol.Range{
    63  						Start: protocol.Position{Line: 0, Character: 0},
    64  						End:   protocol.Position{Line: 1, Character: 0},
    65  					},
    66  					// Increment the placeholder text, to ensure cache misses.
    67  					NewText: fmt.Sprintf("// __TEST_PLACEHOLDER_%d__\n", edits),
    68  				})
    69  				env.Await(env.StartedChange())
    70  			}
    71  		})
    72  	}
    73  }
    74  
    75  func BenchmarkDiagnoseChange(b *testing.B) {
    76  	for _, test := range didChangeTests {
    77  		runChangeDiagnosticsBenchmark(b, test, false, "diagnoseChange")
    78  	}
    79  }
    80  
    81  // TODO(rfindley): add a benchmark for with a metadata-affecting change, when
    82  // this matters.
    83  func BenchmarkDiagnoseSave(b *testing.B) {
    84  	for _, test := range didChangeTests {
    85  		runChangeDiagnosticsBenchmark(b, test, true, "diagnoseSave")
    86  	}
    87  }
    88  
    89  // runChangeDiagnosticsBenchmark runs a benchmark to edit the test file and
    90  // await the resulting diagnostics pass. If save is set, the file is also saved.
    91  func runChangeDiagnosticsBenchmark(b *testing.B, test changeTest, save bool, operation string) {
    92  	b.Run(test.repo, func(b *testing.B) {
    93  		if !test.canSave {
    94  			b.Skipf("skipping as %s cannot be saved", test.file)
    95  		}
    96  		sharedEnv := getRepo(b, test.repo).sharedEnv(b)
    97  		config := fake.EditorConfig{
    98  			Env: map[string]string{
    99  				"GOPATH": sharedEnv.Sandbox.GOPATH(),
   100  			},
   101  			Settings: map[string]interface{}{
   102  				"diagnosticsDelay": "0s",
   103  			},
   104  		}
   105  		// Use a new env to avoid the diagnostic delay: we want to measure how
   106  		// long it takes to produce the diagnostics.
   107  		env := getRepo(b, test.repo).newEnv(b, config, operation, false)
   108  		defer env.Close()
   109  		env.OpenFile(test.file)
   110  		// Insert the text we'll be modifying at the top of the file.
   111  		env.EditBuffer(test.file, protocol.TextEdit{NewText: "// __TEST_PLACEHOLDER_0__\n"})
   112  		if save {
   113  			env.SaveBuffer(test.file)
   114  		}
   115  		env.AfterChange()
   116  		b.ResetTimer()
   117  
   118  		// We must use an extra subtest layer here, so that we only set up the
   119  		// shared env once (otherwise we pay additional overhead and the profiling
   120  		// flags don't work).
   121  		b.Run("diagnose", func(b *testing.B) {
   122  			if stopAndRecord := startProfileIfSupported(b, env, qualifiedName(test.repo, operation)); stopAndRecord != nil {
   123  				defer stopAndRecord()
   124  			}
   125  			for i := 0; i < b.N; i++ {
   126  				edits := atomic.AddInt64(&editID, 1)
   127  				env.EditBuffer(test.file, protocol.TextEdit{
   128  					Range: protocol.Range{
   129  						Start: protocol.Position{Line: 0, Character: 0},
   130  						End:   protocol.Position{Line: 1, Character: 0},
   131  					},
   132  					// Increment the placeholder text, to ensure cache misses.
   133  					NewText: fmt.Sprintf("// __TEST_PLACEHOLDER_%d__\n", edits),
   134  				})
   135  				if save {
   136  					env.SaveBuffer(test.file)
   137  				}
   138  				env.AfterChange()
   139  			}
   140  		})
   141  	})
   142  }