Skip to content

Commit ea51688

Browse files
committed
go/packages: fix misbehavior when an overlay changes the package name
The new code checks for inconsistencies between the package name on disk and the package name in overlays (as might happen when editing). This Cl is meant to fix golang/go#38328 Change-Id: If56b542775c216b4e0ed8ce91d6721c672bff792 Reviewed-on: https://go-review.googlesource.com/c/tools/+/228236 Run-TryBot: Peter Weinberger <[email protected]> TryBot-Result: Gobot Gobot <[email protected]> Reviewed-by: Michael Matloob <[email protected]>
1 parent 5744cfd commit ea51688

File tree

2 files changed

+186
-0
lines changed

2 files changed

+186
-0
lines changed

go/packages/golist_overlay.go

+52
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"fmt"
66
"go/parser"
77
"go/token"
8+
"log"
89
"os"
910
"path/filepath"
1011
"sort"
@@ -22,10 +23,15 @@ func (state *golistState) processGolistOverlay(response *responseDeduper) (modif
2223
needPkgsSet := make(map[string]bool)
2324
modifiedPkgsSet := make(map[string]bool)
2425

26+
pkgOfDir := make(map[string][]*Package)
2527
for _, pkg := range response.dr.Packages {
2628
// This is an approximation of import path to id. This can be
2729
// wrong for tests, vendored packages, and a number of other cases.
2830
havePkgs[pkg.PkgPath] = pkg.ID
31+
x := commonDir(pkg.GoFiles)
32+
if x != "" {
33+
pkgOfDir[x] = append(pkgOfDir[x], pkg)
34+
}
2935
}
3036

3137
// If no new imports are added, it is safe to avoid loading any needPkgs.
@@ -64,6 +70,9 @@ func (state *golistState) processGolistOverlay(response *responseDeduper) (modif
6470
// to the overlay.
6571
continue
6672
}
73+
// if all the overlay files belong to a different package, change the package
74+
// name to that package. Otherwise leave it alone; there will be an error message.
75+
maybeFixPackageName(pkgName, pkgOfDir, dir)
6776
nextPackage:
6877
for _, p := range response.dr.Packages {
6978
if pkgName != p.Name && p.ID != "command-line-arguments" {
@@ -384,3 +393,46 @@ func extractPackageName(filename string, contents []byte) (string, bool) {
384393
}
385394
return f.Name.Name, true
386395
}
396+
397+
func commonDir(a []string) string {
398+
seen := make(map[string]bool)
399+
x := append([]string{}, a...)
400+
for _, f := range x {
401+
seen[filepath.Dir(f)] = true
402+
}
403+
if len(seen) > 1 {
404+
log.Fatalf("commonDir saw %v for %v", seen, x)
405+
}
406+
for k := range seen {
407+
// len(seen) == 1
408+
return k
409+
}
410+
return "" // no files
411+
}
412+
413+
// It is possible that the files in the disk directory dir have a different package
414+
// name from newName, which is deduced from the overlays. If they all have a different
415+
// package name, and they all have the same package name, then that name becomes
416+
// the package name.
417+
// It returns true if it changes the package name, false otherwise.
418+
func maybeFixPackageName(newName string, pkgOfDir map[string][]*Package, dir string) bool {
419+
names := make(map[string]int)
420+
for _, p := range pkgOfDir[dir] {
421+
names[p.Name]++
422+
}
423+
if len(names) != 1 {
424+
// some files are in different packages
425+
return false
426+
}
427+
oldName := ""
428+
for k := range names {
429+
oldName = k
430+
}
431+
if newName == oldName {
432+
return false
433+
}
434+
for _, p := range pkgOfDir[dir] {
435+
p.Name = newName
436+
}
437+
return true
438+
}

go/packages/overlay_test.go

+134
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
package packages_test
2+
3+
import (
4+
"log"
5+
"path/filepath"
6+
"testing"
7+
8+
"golang.org/x/tools/go/packages"
9+
"golang.org/x/tools/go/packages/packagestest"
10+
)
11+
12+
const commonMode = packages.NeedName | packages.NeedFiles |
13+
packages.NeedCompiledGoFiles | packages.NeedImports | packages.NeedSyntax
14+
15+
func TestOverlayChangesPackage(t *testing.T) {
16+
log.SetFlags(log.Lshortfile)
17+
exported := packagestest.Export(t, packagestest.GOPATH, []packagestest.Module{{
18+
Name: "fake",
19+
Files: map[string]interface{}{
20+
"a.go": "package foo\nfunc f(){}\n",
21+
},
22+
Overlay: map[string][]byte{
23+
"a.go": []byte("package foox\nfunc f(){}\n"),
24+
},
25+
}})
26+
defer exported.Cleanup()
27+
exported.Config.Mode = packages.NeedName
28+
29+
initial, err := packages.Load(exported.Config,
30+
filepath.Dir(exported.File("fake", "a.go")))
31+
if err != nil {
32+
t.Fatalf("failed to load: %v", err)
33+
}
34+
if len(initial) != 1 || initial[0].ID != "fake" || initial[0].Name != "foox" {
35+
t.Fatalf("got %v, expected [fake]", initial)
36+
}
37+
if len(initial[0].Errors) != 0 {
38+
t.Fatalf("got %v, expected no errors", initial[0].Errors)
39+
}
40+
log.SetFlags(0)
41+
}
42+
func TestOverlayChangesBothPackages(t *testing.T) {
43+
log.SetFlags(log.Lshortfile)
44+
exported := packagestest.Export(t, packagestest.GOPATH, []packagestest.Module{{
45+
Name: "fake",
46+
Files: map[string]interface{}{
47+
"a.go": "package foo\nfunc g(){}\n",
48+
"a_test.go": "package foo\nfunc f(){}\n",
49+
},
50+
Overlay: map[string][]byte{
51+
"a.go": []byte("package foox\nfunc g(){}\n"),
52+
"a_test.go": []byte("package foox\nfunc f(){}\n"),
53+
},
54+
}})
55+
defer exported.Cleanup()
56+
exported.Config.Mode = commonMode
57+
58+
initial, err := packages.Load(exported.Config,
59+
filepath.Dir(exported.File("fake", "a.go")))
60+
if err != nil {
61+
t.Fatalf("failed to load: %v", err)
62+
}
63+
if len(initial) != 3 {
64+
t.Errorf("got %d packges, expected 3", len(initial))
65+
}
66+
want := []struct {
67+
id, name string
68+
count int
69+
}{
70+
{"fake", "foox", 1},
71+
{"fake [fake.test]", "foox", 2},
72+
{"fake.test", "main", 1},
73+
}
74+
for i := 0; i < 3; i++ {
75+
if ok := checkPkg(t, initial[i], want[i].id, want[i].name, want[i].count); !ok {
76+
t.Errorf("%d: got {%s %s %d}, expected %v", i, initial[i].ID,
77+
initial[i].Name, len(initial[i].Syntax), want[i])
78+
}
79+
if len(initial[i].Errors) != 0 {
80+
t.Errorf("%d: got %v, expected no errors", i, initial[i].Errors)
81+
}
82+
}
83+
log.SetFlags(0)
84+
}
85+
86+
func TestOverlayChangesTestPackage(t *testing.T) {
87+
log.SetFlags(log.Lshortfile)
88+
exported := packagestest.Export(t, packagestest.GOPATH, []packagestest.Module{{
89+
Name: "fake",
90+
Files: map[string]interface{}{
91+
"a_test.go": "package foo\nfunc f(){}\n",
92+
},
93+
Overlay: map[string][]byte{
94+
"a_test.go": []byte("package foox\nfunc f(){}\n"),
95+
},
96+
}})
97+
defer exported.Cleanup()
98+
exported.Config.Mode = commonMode
99+
100+
initial, err := packages.Load(exported.Config,
101+
filepath.Dir(exported.File("fake", "a_test.go")))
102+
if err != nil {
103+
t.Fatalf("failed to load: %v", err)
104+
}
105+
if len(initial) != 3 {
106+
t.Errorf("got %d packges, expected 3", len(initial))
107+
}
108+
want := []struct {
109+
id, name string
110+
count int
111+
}{
112+
{"fake", "foo", 0},
113+
{"fake [fake.test]", "foox", 1},
114+
{"fake.test", "main", 1},
115+
}
116+
for i := 0; i < 3; i++ {
117+
if ok := checkPkg(t, initial[i], want[i].id, want[i].name, want[i].count); !ok {
118+
t.Errorf("got {%s %s %d}, expected %v", initial[i].ID,
119+
initial[i].Name, len(initial[i].Syntax), want[i])
120+
}
121+
}
122+
if len(initial[0].Errors) != 0 {
123+
t.Fatalf("got %v, expected no errors", initial[0].Errors)
124+
}
125+
log.SetFlags(0)
126+
}
127+
128+
func checkPkg(t *testing.T, p *packages.Package, id, name string, syntax int) bool {
129+
t.Helper()
130+
if p.ID == id && p.Name == name && len(p.Syntax) == syntax {
131+
return true
132+
}
133+
return false
134+
}

0 commit comments

Comments
 (0)