x/exp/apidiff: copy changes from x/tools/internal/apidiff
We copied golang.org/x/exp/apidiff to x/tools a few months ago in
anticipation of developing gorelease in x/tools, which would depend on
apidiff.
We've decided to develop gorelease here in x/exp instead, which means
the copy in x/tools is no longer needed. This CL copies changes made
to the copy in x/tools since it was made. Another CL will delete the
copy in x/tools.
Change-Id: Ied79138616c2f3b2f49a0ee5ca95ff3179351354
Reviewed-on: https://go-review.googlesource.com/c/exp/+/197298
Run-TryBot: Jay Conrod <jayconrod@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Jonathan Amsterdam <jba@google.com>
diff --git a/apidiff/README.md b/apidiff/README.md
index 04a1f4e..3d9576c 100644
--- a/apidiff/README.md
+++ b/apidiff/README.md
@@ -265,7 +265,7 @@
var y int64 = x // fails with new: different types in assignment
```
-A change to the value of a constant can break compatiblity if the value is used
+A change to the value of a constant can break compatibility if the value is used
in an array type:
```
diff --git a/apidiff/apidiff.go b/apidiff/apidiff.go
index dc0f0e7..76669d8 100644
--- a/apidiff/apidiff.go
+++ b/apidiff/apidiff.go
@@ -24,10 +24,14 @@
func Changes(old, new *types.Package) Report {
d := newDiffer(old, new)
d.checkPackage()
- return Report{
- Incompatible: d.incompatibles.collect(),
- Compatible: d.compatibles.collect(),
+ r := Report{}
+ for _, m := range d.incompatibles.collect() {
+ r.Changes = append(r.Changes, Change{Message: m, Compatible: false})
}
+ for _, m := range d.compatibles.collect() {
+ r.Changes = append(r.Changes, Change{Message: m, Compatible: true})
+ }
+ return r
}
type differ struct {
diff --git a/apidiff/apidiff_test.go b/apidiff/apidiff_test.go
index 4dcb4d8..5f23542 100644
--- a/apidiff/apidiff_test.go
+++ b/apidiff/apidiff_test.go
@@ -6,8 +6,10 @@
"go/types"
"io/ioutil"
"os"
+ "os/exec"
"path/filepath"
"reflect"
+ "runtime"
"sort"
"strings"
"testing"
@@ -26,22 +28,24 @@
sort.Strings(wanti)
sort.Strings(wantc)
- oldpkg, err := load("apidiff/old", dir)
+ oldpkg, err := load(t, "apidiff/old", dir)
if err != nil {
t.Fatal(err)
}
- newpkg, err := load("apidiff/new", dir)
+ newpkg, err := load(t, "apidiff/new", dir)
if err != nil {
t.Fatal(err)
}
report := Changes(oldpkg.Types, newpkg.Types)
- if !reflect.DeepEqual(report.Incompatible, wanti) {
- t.Errorf("incompatibles: got %v\nwant %v\n", report.Incompatible, wanti)
+ got := report.messages(false)
+ if !reflect.DeepEqual(got, wanti) {
+ t.Errorf("incompatibles: got %v\nwant %v\n", got, wanti)
}
- if !reflect.DeepEqual(report.Compatible, wantc) {
- t.Errorf("compatibles: got %v\nwant %v\n", report.Compatible, wantc)
+ got = report.messages(true)
+ if !reflect.DeepEqual(got, wantc) {
+ t.Errorf("compatibles: got %v\nwant %v\n", got, wantc)
}
}
@@ -113,7 +117,9 @@
return
}
-func load(importPath, goPath string) (*packages.Package, error) {
+func load(t *testing.T, importPath, goPath string) (*packages.Package, error) {
+ needsGoPackages(t)
+
cfg := &packages.Config{
Mode: packages.LoadTypes,
}
@@ -132,7 +138,7 @@
}
func TestExportedFields(t *testing.T) {
- pkg, err := load("golang.org/x/exp/apidiff/testdata/exported_fields", "")
+ pkg, err := load(t, "golang.org/x/exp/apidiff/testdata/exported_fields", "")
if err != nil {
t.Fatal(err)
}
@@ -164,3 +170,69 @@
}
}
}
+
+// needsGoPackages skips t if the go/packages driver (or 'go' tool) implied by
+// the current process environment is not present in the path.
+//
+// Copied and adapted from golang.org/x/tools/internal/testenv.
+func needsGoPackages(t *testing.T) {
+ t.Helper()
+
+ tool := os.Getenv("GOPACKAGESDRIVER")
+ switch tool {
+ case "off":
+ // "off" forces go/packages to use the go command.
+ tool = "go"
+ case "":
+ if _, err := exec.LookPath("gopackagesdriver"); err == nil {
+ tool = "gopackagesdriver"
+ } else {
+ tool = "go"
+ }
+ }
+
+ needsTool(t, tool)
+}
+
+// needsTool skips t if the named tool is not present in the path.
+//
+// Copied and adapted from golang.org/x/tools/internal/testenv.
+func needsTool(t *testing.T, tool string) {
+ _, err := exec.LookPath(tool)
+ if err == nil {
+ return
+ }
+
+ t.Helper()
+ if allowMissingTool(tool) {
+ t.Skipf("skipping because %s tool not available: %v", tool, err)
+ } else {
+ t.Fatalf("%s tool not available: %v", tool, err)
+ }
+}
+
+func allowMissingTool(tool string) bool {
+ if runtime.GOOS == "android" {
+ // Android builds generally run tests on a separate machine from the build,
+ // so don't expect any external tools to be available.
+ return true
+ }
+
+ if tool == "go" && os.Getenv("GO_BUILDER_NAME") == "illumos-amd64-joyent" {
+ // Work around a misconfigured builder (see https://golang.org/issue/33950).
+ return true
+ }
+
+ // If a developer is actively working on this test, we expect them to have all
+ // of its dependencies installed. However, if it's just a dependency of some
+ // other module (for example, being run via 'go test all'), we should be more
+ // tolerant of unusual environments.
+ return !packageMainIsDevel()
+}
+
+// packageMainIsDevel reports whether the module containing package main
+// is a development version (if module information is available).
+//
+// Builds in GOPATH mode and builds that lack module information are assumed to
+// be development versions.
+var packageMainIsDevel = func() bool { return true }
diff --git a/apidiff/report.go b/apidiff/report.go
index fd346b1..ce79e27 100644
--- a/apidiff/report.go
+++ b/apidiff/report.go
@@ -8,7 +8,23 @@
// Report describes the changes detected by Changes.
type Report struct {
- Incompatible, Compatible []string
+ Changes []Change
+}
+
+// A Change describes a single API change.
+type Change struct {
+ Message string
+ Compatible bool
+}
+
+func (r Report) messages(compatible bool) []string {
+ var msgs []string
+ for _, c := range r.Changes {
+ if c.Compatible == compatible {
+ msgs = append(msgs, c.Message)
+ }
+ }
+ return msgs
}
func (r Report) String() string {
@@ -28,13 +44,13 @@
func (r Report) TextIncompatible(w io.Writer, withHeader bool) error {
if withHeader {
- return r.writeMessages(w, "Incompatible changes:", r.Incompatible)
+ return r.writeMessages(w, "Incompatible changes:", r.messages(false))
}
- return r.writeMessages(w, "", r.Incompatible)
+ return r.writeMessages(w, "", r.messages(false))
}
func (r Report) TextCompatible(w io.Writer) error {
- return r.writeMessages(w, "Compatible changes:", r.Compatible)
+ return r.writeMessages(w, "Compatible changes:", r.messages(true))
}
func (r Report) writeMessages(w io.Writer, header string, msgs []string) error {
diff --git a/go.mod b/go.mod
index 06739c5..9edc3b5 100644
--- a/go.mod
+++ b/go.mod
@@ -10,5 +10,5 @@
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028
golang.org/x/mod v0.1.0
golang.org/x/sys v0.0.0-20190412213103-97732733099d
- golang.org/x/tools v0.0.0-20190816200558-6889da9d5479
+ golang.org/x/tools v0.0.0-20190925164712-ae58c0ff6b32
)
diff --git a/go.sum b/go.sum
index 2a7c68a..6ce4043 100644
--- a/go.sum
+++ b/go.sum
@@ -23,6 +23,6 @@
golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
-golang.org/x/tools v0.0.0-20190816200558-6889da9d5479 h1:lfN2PY/jymfnxkNHlbBF5DwPsUvhqUnrdgfK01iH2s0=
-golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20190925164712-ae58c0ff6b32 h1:xE6VFETO5vvJp3W3iihukTHFkQiu9yrTMJNKu20CwE4=
+golang.org/x/tools v0.0.0-20190925164712-ae58c0ff6b32/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=