whatcanGOwrong
This commit is contained in:
@@ -0,0 +1,92 @@
|
||||
// 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.
|
||||
|
||||
// The drivertest package provides a fake implementation of the go/packages
|
||||
// driver protocol that delegates to the go list driver. It may be used to test
|
||||
// programs such as gopls that specialize behavior when a go/packages driver is
|
||||
// in use.
|
||||
//
|
||||
// The driver is run as a child of the current process, by calling [RunIfChild]
|
||||
// at process start, and running go/packages with the environment variables set
|
||||
// by [Env].
|
||||
package drivertest
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"golang.org/x/tools/go/packages"
|
||||
)
|
||||
|
||||
const runAsDriverEnv = "DRIVERTEST_RUN_AS_DRIVER"
|
||||
|
||||
// RunIfChild runs the current process as a go/packages driver, if configured
|
||||
// to do so by the current environment (see [Env]).
|
||||
//
|
||||
// Otherwise, RunIfChild is a no op.
|
||||
func RunIfChild() {
|
||||
if os.Getenv(runAsDriverEnv) != "" {
|
||||
main()
|
||||
os.Exit(0)
|
||||
}
|
||||
}
|
||||
|
||||
// Env returns additional environment variables for use in [packages.Config]
|
||||
// to enable the use of drivertest as the driver.
|
||||
//
|
||||
// t abstracts a *testing.T or log.Default().
|
||||
func Env(t interface{ Fatal(...any) }) []string {
|
||||
exe, err := os.Executable()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return []string{"GOPACKAGESDRIVER=" + exe, runAsDriverEnv + "=1"}
|
||||
}
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
|
||||
dec := json.NewDecoder(os.Stdin)
|
||||
var request packages.DriverRequest
|
||||
if err := dec.Decode(&request); err != nil {
|
||||
log.Fatalf("decoding request: %v", err)
|
||||
}
|
||||
|
||||
config := packages.Config{
|
||||
Mode: request.Mode,
|
||||
Env: append(request.Env, "GOPACKAGESDRIVER=off"), // avoid recursive invocation
|
||||
BuildFlags: request.BuildFlags,
|
||||
Tests: request.Tests,
|
||||
Overlay: request.Overlay,
|
||||
}
|
||||
pkgs, err := packages.Load(&config, flag.Args()...)
|
||||
if err != nil {
|
||||
log.Fatalf("load failed: %v", err)
|
||||
}
|
||||
|
||||
var roots []string
|
||||
for _, pkg := range pkgs {
|
||||
roots = append(roots, pkg.ID)
|
||||
}
|
||||
var allPackages []*packages.Package
|
||||
packages.Visit(pkgs, nil, func(pkg *packages.Package) {
|
||||
newImports := make(map[string]*packages.Package)
|
||||
for path, imp := range pkg.Imports {
|
||||
newImports[path] = &packages.Package{ID: imp.ID}
|
||||
}
|
||||
pkg.Imports = newImports
|
||||
allPackages = append(allPackages, pkg)
|
||||
})
|
||||
|
||||
enc := json.NewEncoder(os.Stdout)
|
||||
response := packages.DriverResponse{
|
||||
Roots: roots,
|
||||
Packages: allPackages,
|
||||
}
|
||||
if err := enc.Encode(response); err != nil {
|
||||
log.Fatalf("encoding response: %v", err)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
// 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 drivertest_test
|
||||
|
||||
// This file is both a test of drivertest and an example of how to use it in your own tests.
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"golang.org/x/tools/go/packages"
|
||||
"golang.org/x/tools/internal/diff"
|
||||
"golang.org/x/tools/internal/diff/myers"
|
||||
"golang.org/x/tools/internal/drivertest"
|
||||
"golang.org/x/tools/internal/packagesinternal"
|
||||
"golang.org/x/tools/internal/testenv"
|
||||
"golang.org/x/tools/internal/testfiles"
|
||||
"golang.org/x/tools/txtar"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
drivertest.RunIfChild()
|
||||
|
||||
os.Exit(m.Run())
|
||||
}
|
||||
|
||||
func TestDriverConformance(t *testing.T) {
|
||||
testenv.NeedsExec(t)
|
||||
|
||||
const workspace = `
|
||||
-- go.mod --
|
||||
module example.com/m
|
||||
|
||||
go 1.20
|
||||
|
||||
-- m.go --
|
||||
package m
|
||||
|
||||
-- lib/lib.go --
|
||||
package lib
|
||||
`
|
||||
|
||||
fs, err := txtar.FS(txtar.Parse([]byte(workspace)))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
dir := testfiles.CopyToTmp(t, fs)
|
||||
|
||||
// TODO(rfindley): on mac, this is required to fix symlink path mismatches.
|
||||
// But why? Where is the symlink being evaluated in go/packages?
|
||||
dir, err = filepath.EvalSymlinks(dir)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
baseConfig := packages.Config{
|
||||
Dir: dir,
|
||||
Mode: packages.NeedName |
|
||||
packages.NeedFiles |
|
||||
packages.NeedCompiledGoFiles |
|
||||
packages.NeedImports |
|
||||
packages.NeedDeps |
|
||||
packages.NeedTypesSizes |
|
||||
packages.NeedModule |
|
||||
packages.NeedEmbedFiles |
|
||||
packages.LoadMode(packagesinternal.DepsErrors) |
|
||||
packages.LoadMode(packagesinternal.ForTest),
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
query string
|
||||
overlay string
|
||||
}{
|
||||
{
|
||||
name: "load all",
|
||||
query: "./...",
|
||||
},
|
||||
{
|
||||
name: "overlays",
|
||||
query: "./...",
|
||||
overlay: `
|
||||
-- m.go --
|
||||
package m
|
||||
|
||||
import . "lib"
|
||||
-- a/a.go --
|
||||
package a
|
||||
`,
|
||||
},
|
||||
{
|
||||
name: "std",
|
||||
query: "std",
|
||||
},
|
||||
{
|
||||
name: "builtin",
|
||||
query: "builtin",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
cfg := baseConfig
|
||||
if test.overlay != "" {
|
||||
cfg.Overlay = make(map[string][]byte)
|
||||
for _, file := range txtar.Parse([]byte(test.overlay)).Files {
|
||||
name := filepath.Join(dir, filepath.FromSlash(file.Name))
|
||||
cfg.Overlay[name] = file.Data
|
||||
}
|
||||
}
|
||||
|
||||
// Compare JSON-encoded packages with and without GOPACKAGESDRIVER.
|
||||
//
|
||||
// Note that this does not guarantee that the go/packages results
|
||||
// themselves are equivalent, only that their encoded JSON is equivalent.
|
||||
// Certain fields such as Module are intentionally omitted from external
|
||||
// drivers, because they don't make sense for an arbitrary build system.
|
||||
var jsons []string
|
||||
for _, env := range [][]string{
|
||||
{"GOPACKAGESDRIVER=off"},
|
||||
drivertest.Env(t),
|
||||
} {
|
||||
cfg.Env = append(os.Environ(), env...)
|
||||
pkgs, err := packages.Load(&cfg, test.query)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to load (env: %v): %v", env, err)
|
||||
}
|
||||
data, err := json.MarshalIndent(pkgs, "", "\t")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to marshal (env: %v): %v", env, err)
|
||||
}
|
||||
jsons = append(jsons, string(data))
|
||||
}
|
||||
|
||||
listJSON := jsons[0]
|
||||
driverJSON := jsons[1]
|
||||
|
||||
// Use the myers package for better line diffs.
|
||||
edits := myers.ComputeEdits(listJSON, driverJSON)
|
||||
d, err := diff.ToUnified("go list", "driver", listJSON, edits, 0)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if d != "" {
|
||||
t.Errorf("mismatching JSON:\n%s", d)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user