blob: bf459a5829c7d14d594d689f59f3527809ab926d [file] [log] [blame]
// Copyright 2023 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 versions_test
import (
"fmt"
"go/ast"
"go/importer"
"go/parser"
"go/token"
"go/types"
"strings"
"testing"
"golang.org/x/tools/internal/testenv"
"golang.org/x/tools/internal/versions"
)
var contents = map[string]string{
"gobuild122.go": `
//go:build go1.22
package p
`,
"gobuild121.go": `
//go:build go1.21
package p
`,
"gobuild120.go": `
//go:build go1.20
package p
`,
"gobuild119.go": `
//go:build go1.19
package p
`,
"noversion.go": `
package p
`,
}
func Test(t *testing.T) {
testenv.NeedsGo1Point(t, 23) // TODO(#69749): Allow on 1.22 if a fix for #69749 is submitted.
for _, item := range []struct {
goversion string
pversion string
tests []fileTest
}{
{
"", "", []fileTest{
{"noversion.go", ""},
{"gobuild119.go", "go1.21"},
{"gobuild120.go", "go1.21"},
{"gobuild121.go", "go1.21"},
{"gobuild122.go", "go1.22"}},
},
{
"go1.20", "go1.20", []fileTest{
{"noversion.go", "go1.20"},
{"gobuild119.go", "go1.21"},
{"gobuild120.go", "go1.21"},
{"gobuild121.go", "go1.21"},
{"gobuild122.go", "go1.22"}},
},
{
"go1.21", "go1.21", []fileTest{
{"noversion.go", "go1.21"},
{"gobuild119.go", "go1.21"},
{"gobuild120.go", "go1.21"},
{"gobuild121.go", "go1.21"},
{"gobuild122.go", "go1.22"}},
},
{
"go1.22", "go1.22", []fileTest{
{"noversion.go", "go1.22"},
{"gobuild119.go", "go1.21"},
{"gobuild120.go", "go1.21"},
{"gobuild121.go", "go1.21"},
{"gobuild122.go", "go1.22"}},
},
} {
name := fmt.Sprintf("types.Config{GoVersion:%q}", item.goversion)
t.Run(name, func(t *testing.T) {
testFiles(t, item.goversion, item.pversion, item.tests)
})
}
}
func TestToolchain122(t *testing.T) {
// TestToolchain122 tests the 1.22 toolchain for the FileVersion it returns.
// These results are at the moment unique to 1.22. So test it with distinct
// expectations.
// TODO(#69749): Remove requirement if a fix for #69749 is submitted.
if testenv.Go1Point() != 22 {
t.Skip("Expectations are only for 1.22 toolchain")
}
for _, item := range []struct {
goversion string
pversion string
tests []fileTest
}{
{
"", "", []fileTest{
{"noversion.go", ""},
{"gobuild119.go", ""}, // differs
{"gobuild120.go", ""}, // differs
{"gobuild121.go", ""}, // differs
{"gobuild122.go", ""}}, // differs
},
{
"go1.20", "go1.20", []fileTest{
{"noversion.go", "go1.20"},
{"gobuild119.go", "go1.20"}, // differs
{"gobuild120.go", "go1.20"}, // differs
{"gobuild121.go", "go1.21"},
{"gobuild122.go", "go1.22"}},
},
{
"go1.21", "go1.21", []fileTest{
{"noversion.go", "go1.21"},
{"gobuild119.go", "go1.19"}, // differs
{"gobuild120.go", "go1.20"}, // differs
{"gobuild121.go", "go1.21"},
{"gobuild122.go", "go1.22"}},
},
{
"go1.22", "go1.22", []fileTest{
{"noversion.go", "go1.22"},
{"gobuild119.go", "go1.19"}, // differs
{"gobuild120.go", "go1.20"}, // differs
{"gobuild121.go", "go1.21"},
{"gobuild122.go", "go1.22"}},
},
} {
name := fmt.Sprintf("types.Config{GoVersion:%q}", item.goversion)
t.Run(name, func(t *testing.T) {
testFiles(t, item.goversion, item.pversion, item.tests)
})
}
}
type fileTest struct {
fname string
want string
}
func testFiles(t *testing.T, goversion string, pversion string, tests []fileTest) {
fset := token.NewFileSet()
files := make([]*ast.File, len(tests))
for i, test := range tests {
files[i] = parse(t, fset, test.fname, contents[test.fname])
}
pkg, info, err := typeCheck(fset, files, goversion)
if err != nil {
t.Fatal(err)
}
if got, want := pkg.GoVersion(), pversion; versions.Compare(got, want) != 0 {
t.Errorf("GoVersion()=%q. expected %q", got, want)
}
if got := versions.FileVersion(info, nil); got != "" {
t.Errorf(`FileVersions(nil)=%q. expected ""`, got)
}
for i, test := range tests {
if got, want := versions.FileVersion(info, files[i]), test.want; got != want {
t.Errorf("FileVersions(%s)=%q. expected %q", test.fname, got, want)
}
}
}
func TestTooNew(t *testing.T) {
testenv.NeedsGo1Point(t, 23) // TODO(#69749): Allow on 1.22 if a fix for #69749 is submitted.
const contents = `
//go:build go1.99
package p
`
type fileTest struct {
fname string
want string
}
for _, goversion := range []string{
"",
"go1.22",
} {
name := fmt.Sprintf("types.Config{GoVersion:%q}", goversion)
t.Run(name, func(t *testing.T) {
fset := token.NewFileSet()
files := []*ast.File{parse(t, fset, "p.go", contents)}
_, _, err := typeCheck(fset, files, goversion)
if err == nil {
t.Fatal("Expected an error from a using a TooNew file version")
}
got := err.Error()
want := "file requires newer Go version go1.99"
if !strings.Contains(got, want) {
t.Errorf("Error message %q did not include %q", got, want)
}
})
}
}
func parse(t *testing.T, fset *token.FileSet, name, src string) *ast.File {
file, err := parser.ParseFile(fset, name, src, 0)
if err != nil {
t.Fatal(err)
}
return file
}
func typeCheck(fset *token.FileSet, files []*ast.File, goversion string) (*types.Package, *types.Info, error) {
conf := types.Config{
Importer: importer.Default(),
GoVersion: goversion,
}
info := types.Info{
FileVersions: make(map[*ast.File]string),
}
pkg, err := conf.Check("", fset, files, &info)
return pkg, &info, err
}