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

     1  // Copyright 2023 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 workspace
     6  
     7  import (
     8  	"strings"
     9  	"testing"
    10  
    11  	"github.com/google/go-cmp/cmp"
    12  	"github.com/google/go-cmp/cmp/cmpopts"
    13  	"golang.org/x/tools/gopls/internal/cache"
    14  	"golang.org/x/tools/gopls/internal/protocol/command"
    15  
    16  	. "golang.org/x/tools/gopls/internal/test/integration"
    17  )
    18  
    19  func TestAddAndRemoveGoWork(t *testing.T) {
    20  	// Use a workspace with a module in the root directory to exercise the case
    21  	// where a go.work is added to the existing root directory. This verifies
    22  	// that we're detecting changes to the module source, not just the root
    23  	// directory.
    24  	const nomod = `
    25  -- go.mod --
    26  module a.com
    27  
    28  go 1.16
    29  -- main.go --
    30  package main
    31  
    32  func main() {}
    33  -- b/go.mod --
    34  module b.com
    35  
    36  go 1.16
    37  -- b/main.go --
    38  package main
    39  
    40  func main() {}
    41  `
    42  	WithOptions(
    43  		Modes(Default),
    44  	).Run(t, nomod, func(t *testing.T, env *Env) {
    45  		env.OpenFile("main.go")
    46  		env.OpenFile("b/main.go")
    47  
    48  		summary := func(typ cache.ViewType, root, folder string) command.View {
    49  			return command.View{
    50  				Type:   typ.String(),
    51  				Root:   env.Sandbox.Workdir.URI(root),
    52  				Folder: env.Sandbox.Workdir.URI(folder),
    53  			}
    54  		}
    55  		checkViews := func(want ...command.View) {
    56  			got := env.Views()
    57  			if diff := cmp.Diff(want, got, cmpopts.IgnoreFields(command.View{}, "ID")); diff != "" {
    58  				t.Errorf("SummarizeViews() mismatch (-want +got):\n%s", diff)
    59  			}
    60  		}
    61  
    62  		// Zero-config gopls makes this work.
    63  		env.AfterChange(
    64  			NoDiagnostics(ForFile("main.go")),
    65  			NoDiagnostics(env.AtRegexp("b/main.go", "package (main)")),
    66  		)
    67  		checkViews(summary(cache.GoModView, ".", "."), summary(cache.GoModView, "b", "."))
    68  
    69  		env.WriteWorkspaceFile("go.work", `go 1.16
    70  
    71  use (
    72  	.
    73  	b
    74  )
    75  `)
    76  		env.AfterChange(NoDiagnostics())
    77  		checkViews(summary(cache.GoWorkView, ".", "."))
    78  
    79  		// Removing the go.work file should put us back where we started.
    80  		env.RemoveWorkspaceFile("go.work")
    81  
    82  		// Again, zero-config gopls makes this work.
    83  		env.AfterChange(
    84  			NoDiagnostics(ForFile("main.go")),
    85  			NoDiagnostics(env.AtRegexp("b/main.go", "package (main)")),
    86  		)
    87  		checkViews(summary(cache.GoModView, ".", "."), summary(cache.GoModView, "b", "."))
    88  
    89  		// Close and reopen b, to ensure the views are adjusted accordingly.
    90  		env.CloseBuffer("b/main.go")
    91  		env.AfterChange()
    92  		checkViews(summary(cache.GoModView, ".", "."))
    93  
    94  		env.OpenFile("b/main.go")
    95  		env.AfterChange()
    96  		checkViews(summary(cache.GoModView, ".", "."), summary(cache.GoModView, "b", "."))
    97  	})
    98  }
    99  
   100  func TestOpenAndClosePorts(t *testing.T) {
   101  	// This test checks that as we open and close files requiring a different
   102  	// port, the set of Views is adjusted accordingly.
   103  	const files = `
   104  -- go.mod --
   105  module a.com/a
   106  
   107  go 1.20
   108  
   109  -- a_linux.go --
   110  package a
   111  
   112  -- a_darwin.go --
   113  package a
   114  
   115  -- a_windows.go --
   116  package a
   117  `
   118  
   119  	WithOptions(
   120  		EnvVars{
   121  			"GOOS": "linux", // assume that linux is the default GOOS
   122  		},
   123  	).Run(t, files, func(t *testing.T, env *Env) {
   124  		summary := func(envOverlay ...string) command.View {
   125  			return command.View{
   126  				Type:       cache.GoModView.String(),
   127  				Root:       env.Sandbox.Workdir.URI("."),
   128  				Folder:     env.Sandbox.Workdir.URI("."),
   129  				EnvOverlay: envOverlay,
   130  			}
   131  		}
   132  		checkViews := func(want ...command.View) {
   133  			got := env.Views()
   134  			if diff := cmp.Diff(want, got, cmpopts.IgnoreFields(command.View{}, "ID")); diff != "" {
   135  				t.Errorf("SummarizeViews() mismatch (-want +got):\n%s", diff)
   136  			}
   137  		}
   138  		checkViews(summary())
   139  		env.OpenFile("a_linux.go")
   140  		checkViews(summary())
   141  		env.OpenFile("a_darwin.go")
   142  		checkViews(
   143  			summary(),
   144  			summary("GOARCH=amd64", "GOOS=darwin"),
   145  		)
   146  		env.OpenFile("a_windows.go")
   147  		checkViews(
   148  			summary(),
   149  			summary("GOARCH=amd64", "GOOS=darwin"),
   150  			summary("GOARCH=amd64", "GOOS=windows"),
   151  		)
   152  		env.CloseBuffer("a_darwin.go")
   153  		checkViews(
   154  			summary(),
   155  			summary("GOARCH=amd64", "GOOS=windows"),
   156  		)
   157  		env.CloseBuffer("a_linux.go")
   158  		checkViews(
   159  			summary(),
   160  			summary("GOARCH=amd64", "GOOS=windows"),
   161  		)
   162  		env.CloseBuffer("a_windows.go")
   163  		checkViews(summary())
   164  	})
   165  }
   166  
   167  func TestCriticalErrorsInOrphanedFiles(t *testing.T) {
   168  	// This test checks that as we open and close files requiring a different
   169  	// port, the set of Views is adjusted accordingly.
   170  	const files = `
   171  -- go.mod --
   172  modul golang.org/lsptests/broken
   173  
   174  go 1.20
   175  
   176  -- a.go --
   177  package broken
   178  
   179  const C = 0
   180  `
   181  
   182  	Run(t, files, func(t *testing.T, env *Env) {
   183  		env.OpenFile("a.go")
   184  		env.AfterChange(
   185  			Diagnostics(env.AtRegexp("go.mod", "modul")),
   186  			Diagnostics(env.AtRegexp("a.go", "broken"), WithMessage("initialization failed")),
   187  		)
   188  	})
   189  }
   190  
   191  func TestGoModReplace(t *testing.T) {
   192  	// This test checks that we treat locally replaced modules as workspace
   193  	// modules, according to the "includeReplaceInWorkspace" setting.
   194  	const files = `
   195  -- moda/go.mod --
   196  module golang.org/a
   197  
   198  require golang.org/b v1.2.3
   199  
   200  replace golang.org/b => ../modb
   201  
   202  go 1.20
   203  
   204  -- moda/a.go --
   205  package a
   206  
   207  import "golang.org/b"
   208  
   209  const A = b.B
   210  
   211  -- modb/go.mod --
   212  module golang.org/b
   213  
   214  go 1.20
   215  
   216  -- modb/b.go --
   217  package b
   218  
   219  const B = 1
   220  `
   221  
   222  	for useReplace, expectation := range map[bool]Expectation{
   223  		true:  FileWatchMatching("modb"),
   224  		false: NoFileWatchMatching("modb"),
   225  	} {
   226  		WithOptions(
   227  			WorkspaceFolders("moda"),
   228  			Settings{
   229  				"includeReplaceInWorkspace": useReplace,
   230  			},
   231  		).Run(t, files, func(t *testing.T, env *Env) {
   232  			env.OnceMet(
   233  				InitialWorkspaceLoad,
   234  				expectation,
   235  			)
   236  		})
   237  	}
   238  }
   239  
   240  func TestDisableZeroConfig(t *testing.T) {
   241  	// This test checks that we treat locally replaced modules as workspace
   242  	// modules, according to the "includeReplaceInWorkspace" setting.
   243  	const files = `
   244  -- moda/go.mod --
   245  module golang.org/a
   246  
   247  go 1.20
   248  
   249  -- moda/a.go --
   250  package a
   251  
   252  -- modb/go.mod --
   253  module golang.org/b
   254  
   255  go 1.20
   256  
   257  -- modb/b.go --
   258  package b
   259  
   260  `
   261  
   262  	WithOptions(
   263  		Settings{"zeroConfig": false},
   264  	).Run(t, files, func(t *testing.T, env *Env) {
   265  		env.OpenFile("moda/a.go")
   266  		env.OpenFile("modb/b.go")
   267  		env.AfterChange()
   268  		if got := env.Views(); len(got) != 1 || got[0].Type != cache.AdHocView.String() {
   269  			t.Errorf("Views: got %v, want one adhoc view", got)
   270  		}
   271  	})
   272  }
   273  
   274  func TestVendorExcluded(t *testing.T) {
   275  	// Test that we don't create Views for vendored modules.
   276  	//
   277  	// We construct the vendor directory manually here, as `go mod vendor` will
   278  	// omit the go.mod file. This synthesizes the setup of Kubernetes, where the
   279  	// entire module is vendored through a symlinked directory.
   280  	const src = `
   281  -- go.mod --
   282  module example.com/a
   283  
   284  go 1.18
   285  
   286  require other.com/b v1.0.0
   287  
   288  -- a.go --
   289  package a
   290  import "other.com/b"
   291  var _ b.B
   292  
   293  -- vendor/modules.txt --
   294  # other.com/b v1.0.0
   295  ## explicit; go 1.14
   296  other.com/b
   297  
   298  -- vendor/other.com/b/go.mod --
   299  module other.com/b
   300  go 1.14
   301  
   302  -- vendor/other.com/b/b.go --
   303  package b
   304  type B int
   305  
   306  func _() {
   307  	var V int // unused
   308  }
   309  `
   310  	WithOptions(
   311  		Modes(Default),
   312  	).Run(t, src, func(t *testing.T, env *Env) {
   313  		env.OpenFile("a.go")
   314  		env.AfterChange(NoDiagnostics())
   315  		loc := env.GoToDefinition(env.RegexpSearch("a.go", `b\.(B)`))
   316  		if !strings.Contains(string(loc.URI), "/vendor/") {
   317  			t.Fatalf("Definition(b.B) = %v, want vendored location", loc.URI)
   318  		}
   319  		env.AfterChange(
   320  			Diagnostics(env.AtRegexp("vendor/other.com/b/b.go", "V"), WithMessage("not used")),
   321  		)
   322  
   323  		if views := env.Views(); len(views) != 1 {
   324  			t.Errorf("After opening /vendor/, got %d views, want 1. Views:\n%v", len(views), views)
   325  		}
   326  	})
   327  }