blob: 106734a1864552a46fb91122f25427650bfcb79d [file] [log] [blame]
// Copyright 2024 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package workspace
import (
"sort"
"strings"
"testing"
"github.com/google/go-cmp/cmp"
"golang.org/x/tools/gopls/internal/protocol"
"golang.org/x/tools/gopls/internal/protocol/command"
. "golang.org/x/tools/gopls/internal/test/integration"
)
func TestPackages(t *testing.T) {
const files = `
-- go.mod --
module foo
-- foo.go --
package foo
func Foo()
-- bar/bar.go --
package bar
func Bar()
-- baz/go.mod --
module baz
-- baz/baz.go --
package baz
func Baz()
`
t.Run("file", func(t *testing.T) {
Run(t, files, func(t *testing.T, env *Env) {
checkPackages(t, env, []protocol.DocumentURI{env.Editor.DocumentURI("foo.go")}, false, 0, []command.Package{
{
Path: "foo",
ModulePath: "foo",
},
}, map[string]command.Module{
"foo": {
Path: "foo",
GoMod: env.Editor.DocumentURI("go.mod"),
},
}, []string{})
})
})
t.Run("package", func(t *testing.T) {
Run(t, files, func(t *testing.T, env *Env) {
checkPackages(t, env, []protocol.DocumentURI{env.Editor.DocumentURI("bar")}, false, 0, []command.Package{
{
Path: "foo/bar",
ModulePath: "foo",
},
}, map[string]command.Module{
"foo": {
Path: "foo",
GoMod: env.Editor.DocumentURI("go.mod"),
},
}, []string{})
})
})
t.Run("workspace", func(t *testing.T) {
Run(t, files, func(t *testing.T, env *Env) {
checkPackages(t, env, []protocol.DocumentURI{env.Editor.DocumentURI("")}, true, 0, []command.Package{
{
Path: "foo",
ModulePath: "foo",
},
{
Path: "foo/bar",
ModulePath: "foo",
},
}, map[string]command.Module{
"foo": {
Path: "foo",
GoMod: env.Editor.DocumentURI("go.mod"),
},
}, []string{})
})
})
t.Run("nested module", func(t *testing.T) {
Run(t, files, func(t *testing.T, env *Env) {
// Load the nested module
env.OpenFile("baz/baz.go")
// Request packages using the URI of the nested module _directory_
checkPackages(t, env, []protocol.DocumentURI{env.Editor.DocumentURI("baz")}, true, 0, []command.Package{
{
Path: "baz",
ModulePath: "baz",
},
}, map[string]command.Module{
"baz": {
Path: "baz",
GoMod: env.Editor.DocumentURI("baz/go.mod"),
},
}, []string{})
})
})
}
func TestPackagesWithTests(t *testing.T) {
const files = `
-- go.mod --
module foo
-- foo.go --
package foo
import "testing"
func Foo()
func TestFoo2(t *testing.T)
-- foo_test.go --
package foo
import "testing"
func TestFoo(t *testing.T)
-- foo2_test.go --
package foo_test
import "testing"
func TestBar(t *testing.T) {}
-- baz/baz_test.go --
package baz
import "testing"
func TestBaz(*testing.T)
func BenchmarkBaz(*testing.B)
func FuzzBaz(*testing.F)
func ExampleBaz()
-- bat/go.mod --
module bat
-- bat/bat_test.go --
package bat
import "testing"
func Test(*testing.T)
`
t.Run("file", func(t *testing.T) {
Run(t, files, func(t *testing.T, env *Env) {
checkPackages(t, env, []protocol.DocumentURI{env.Editor.DocumentURI("foo_test.go")}, false, command.NeedTests, []command.Package{
{
Path: "foo",
ModulePath: "foo",
},
{
Path: "foo",
ForTest: "foo",
ModulePath: "foo",
TestFiles: []command.TestFile{
{
URI: env.Editor.DocumentURI("foo_test.go"),
Tests: []command.TestCase{
{Name: "TestFoo"},
},
},
},
},
{
Path: "foo_test",
ForTest: "foo",
ModulePath: "foo",
TestFiles: []command.TestFile{
{
URI: env.Editor.DocumentURI("foo2_test.go"),
Tests: []command.TestCase{
{Name: "TestBar"},
},
},
},
},
}, map[string]command.Module{
"foo": {
Path: "foo",
GoMod: env.Editor.DocumentURI("go.mod"),
},
}, []string{
"func TestFoo(t *testing.T)",
"func TestBar(t *testing.T) {}",
})
})
})
t.Run("package", func(t *testing.T) {
Run(t, files, func(t *testing.T, env *Env) {
checkPackages(t, env, []protocol.DocumentURI{env.Editor.DocumentURI("baz")}, false, command.NeedTests, []command.Package{
{
Path: "foo/baz",
ForTest: "foo/baz",
ModulePath: "foo",
TestFiles: []command.TestFile{
{
URI: env.Editor.DocumentURI("baz/baz_test.go"),
Tests: []command.TestCase{
{Name: "TestBaz"},
{Name: "BenchmarkBaz"},
{Name: "FuzzBaz"},
{Name: "ExampleBaz"},
},
},
},
},
}, map[string]command.Module{
"foo": {
Path: "foo",
GoMod: env.Editor.DocumentURI("go.mod"),
},
}, []string{
"func TestBaz(*testing.T)",
"func BenchmarkBaz(*testing.B)",
"func FuzzBaz(*testing.F)",
"func ExampleBaz()",
})
})
})
t.Run("workspace", func(t *testing.T) {
Run(t, files, func(t *testing.T, env *Env) {
checkPackages(t, env, []protocol.DocumentURI{env.Editor.DocumentURI(".")}, true, command.NeedTests, []command.Package{
{
Path: "foo",
ModulePath: "foo",
},
{
Path: "foo",
ForTest: "foo",
ModulePath: "foo",
TestFiles: []command.TestFile{
{
URI: env.Editor.DocumentURI("foo_test.go"),
Tests: []command.TestCase{
{Name: "TestFoo"},
},
},
},
},
{
Path: "foo/baz",
ForTest: "foo/baz",
ModulePath: "foo",
TestFiles: []command.TestFile{
{
URI: env.Editor.DocumentURI("baz/baz_test.go"),
Tests: []command.TestCase{
{Name: "TestBaz"},
{Name: "BenchmarkBaz"},
{Name: "FuzzBaz"},
{Name: "ExampleBaz"},
},
},
},
},
{
Path: "foo_test",
ForTest: "foo",
ModulePath: "foo",
TestFiles: []command.TestFile{
{
URI: env.Editor.DocumentURI("foo2_test.go"),
Tests: []command.TestCase{
{Name: "TestBar"},
},
},
},
},
}, map[string]command.Module{
"foo": {
Path: "foo",
GoMod: env.Editor.DocumentURI("go.mod"),
},
}, []string{
"func TestFoo(t *testing.T)",
"func TestBaz(*testing.T)",
"func BenchmarkBaz(*testing.B)",
"func FuzzBaz(*testing.F)",
"func ExampleBaz()",
"func TestBar(t *testing.T) {}",
})
})
})
t.Run("nested module", func(t *testing.T) {
Run(t, files, func(t *testing.T, env *Env) {
// Load the nested module
env.OpenFile("bat/bat_test.go")
// Request packages using the URI of the nested module _directory_
checkPackages(t, env, []protocol.DocumentURI{env.Editor.DocumentURI("bat")}, true, command.NeedTests, []command.Package{
{
Path: "bat",
ForTest: "bat",
ModulePath: "bat",
TestFiles: []command.TestFile{
{
URI: env.Editor.DocumentURI("bat/bat_test.go"),
Tests: []command.TestCase{
{Name: "Test"},
},
},
},
},
}, map[string]command.Module{
"bat": {
Path: "bat",
GoMod: env.Editor.DocumentURI("bat/go.mod"),
},
}, []string{
"func Test(*testing.T)",
})
})
})
}
func TestPackagesWithSubtests(t *testing.T) {
const files = `
-- go.mod --
module foo
-- foo_test.go --
package foo
import "testing"
// Verify that examples don't break subtest detection
func ExampleFoo() {}
func TestFoo(t *testing.T) {
t.Run("Bar", func(t *testing.T) {
t.Run("Baz", func(t *testing.T) {})
})
t.Run("Bar", func(t *testing.T) {})
t.Run("Bar", func(t *testing.T) {})
t.Run("with space", func(t *testing.T) {})
var x X
y := func(t *testing.T) {
t.Run("VarSub", func(t *testing.T) {})
}
t.Run("SubtestFunc", SubtestFunc)
t.Run("SubtestMethod", x.SubtestMethod)
t.Run("SubtestVar", y)
}
func SubtestFunc(t *testing.T) {
t.Run("FuncSub", func(t *testing.T) {})
}
type X int
func (X) SubtestMethod(t *testing.T) {
t.Run("MethodSub", func(t *testing.T) {})
}
`
Run(t, files, func(t *testing.T, env *Env) {
checkPackages(t, env, []protocol.DocumentURI{env.Editor.DocumentURI("foo_test.go")}, false, command.NeedTests, []command.Package{
{
Path: "foo",
ForTest: "foo",
ModulePath: "foo",
TestFiles: []command.TestFile{
{
URI: env.Editor.DocumentURI("foo_test.go"),
Tests: []command.TestCase{
{Name: "ExampleFoo"},
{Name: "TestFoo"},
{Name: "TestFoo/Bar"},
{Name: "TestFoo/Bar/Baz"},
{Name: "TestFoo/Bar#01"},
{Name: "TestFoo/Bar#02"},
{Name: "TestFoo/with_space"},
{Name: "TestFoo/SubtestFunc"},
{Name: "TestFoo/SubtestFunc/FuncSub"},
{Name: "TestFoo/SubtestMethod"},
{Name: "TestFoo/SubtestMethod/MethodSub"},
{Name: "TestFoo/SubtestVar"},
// {Name: "TestFoo/SubtestVar/VarSub"}, // TODO
},
},
},
},
}, map[string]command.Module{
"foo": {
Path: "foo",
GoMod: env.Editor.DocumentURI("go.mod"),
},
}, []string{
"func ExampleFoo() {}",
`func TestFoo(t *testing.T) {
t.Run("Bar", func(t *testing.T) {
t.Run("Baz", func(t *testing.T) {})
})
t.Run("Bar", func(t *testing.T) {})
t.Run("Bar", func(t *testing.T) {})
t.Run("with space", func(t *testing.T) {})
var x X
y := func(t *testing.T) {
t.Run("VarSub", func(t *testing.T) {})
}
t.Run("SubtestFunc", SubtestFunc)
t.Run("SubtestMethod", x.SubtestMethod)
t.Run("SubtestVar", y)
}`,
"t.Run(\"Bar\", func(t *testing.T) {\n\t\tt.Run(\"Baz\", func(t *testing.T) {})\n\t})",
`t.Run("Baz", func(t *testing.T) {})`,
`t.Run("Bar", func(t *testing.T) {})`,
`t.Run("Bar", func(t *testing.T) {})`,
`t.Run("with space", func(t *testing.T) {})`,
`t.Run("SubtestFunc", SubtestFunc)`,
`t.Run("FuncSub", func(t *testing.T) {})`,
`t.Run("SubtestMethod", x.SubtestMethod)`,
`t.Run("MethodSub", func(t *testing.T) {})`,
`t.Run("SubtestVar", y)`,
})
})
}
func checkPackages(t testing.TB, env *Env, files []protocol.DocumentURI, recursive bool, mode command.PackagesMode, wantPkg []command.Package, wantModule map[string]command.Module, wantSource []string) {
t.Helper()
cmd := command.NewPackagesCommand("Packages", command.PackagesArgs{Files: files, Recursive: recursive, Mode: mode})
var result command.PackagesResult
env.ExecuteCommand(&protocol.ExecuteCommandParams{
Command: command.Packages.String(),
Arguments: cmd.Arguments,
}, &result)
// The ordering of packages is undefined so sort the results to ensure
// consistency
sort.Slice(result.Packages, func(i, j int) bool {
a, b := result.Packages[i], result.Packages[j]
c := strings.Compare(a.Path, b.Path)
if c != 0 {
return c < 0
}
return strings.Compare(a.ForTest, b.ForTest) < 0
})
// Instead of testing the exact values of the test locations (which would
// make these tests significantly more trouble to maintain), verify the
// source range they refer to.
gotSource := []string{} // avoid issues with comparing null to []
for i := range result.Packages {
pkg := &result.Packages[i]
for i := range pkg.TestFiles {
file := &pkg.TestFiles[i]
env.OpenFile(file.URI.Path())
for i := range file.Tests {
test := &file.Tests[i]
gotSource = append(gotSource, env.FileContentAt(test.Loc))
test.Loc = protocol.Location{}
}
}
}
if diff := cmp.Diff(wantPkg, result.Packages); diff != "" {
t.Errorf("Packages(%v) returned unexpected packages (-want +got):\n%s", files, diff)
}
if diff := cmp.Diff(wantModule, result.Module); diff != "" {
t.Errorf("Packages(%v) returned unexpected modules (-want +got):\n%s", files, diff)
}
// Don't check the source if the response is incorrect
if !t.Failed() {
if diff := cmp.Diff(wantSource, gotSource); diff != "" {
t.Errorf("Packages(%v) returned unexpected test case ranges (-want +got):\n%s", files, diff)
}
}
}