whatcanGOwrong
This commit is contained in:
@@ -0,0 +1,163 @@
|
||||
// Command apidiff determines whether two versions of a package are compatible
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"flag"
|
||||
"fmt"
|
||||
"go/token"
|
||||
"go/types"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/exp/apidiff"
|
||||
"golang.org/x/tools/go/gcexportdata"
|
||||
"golang.org/x/tools/go/packages"
|
||||
)
|
||||
|
||||
var (
|
||||
exportDataOutfile = flag.String("w", "", "file for export data")
|
||||
incompatibleOnly = flag.Bool("incompatible", false, "display only incompatible changes")
|
||||
allowInternal = flag.Bool("allow-internal", false, "allow apidiff to compare internal packages")
|
||||
)
|
||||
|
||||
func main() {
|
||||
flag.Usage = func() {
|
||||
w := flag.CommandLine.Output()
|
||||
fmt.Fprintf(w, "usage:\n")
|
||||
fmt.Fprintf(w, "apidiff OLD NEW\n")
|
||||
fmt.Fprintf(w, " compares OLD and NEW package APIs\n")
|
||||
fmt.Fprintf(w, " where OLD and NEW are either import paths or files of export data\n")
|
||||
fmt.Fprintf(w, "apidiff -w FILE IMPORT_PATH\n")
|
||||
fmt.Fprintf(w, " writes export data of the package at IMPORT_PATH to FILE\n")
|
||||
fmt.Fprintf(w, " NOTE: In a GOPATH-less environment, this option consults the\n")
|
||||
fmt.Fprintf(w, " module cache by default, unless used in the directory that\n")
|
||||
fmt.Fprintf(w, " contains the go.mod module definition that IMPORT_PATH belongs\n")
|
||||
fmt.Fprintf(w, " to. In most cases users want the latter behavior, so be sure\n")
|
||||
fmt.Fprintf(w, " to cd to the exact directory which contains the module\n")
|
||||
fmt.Fprintf(w, " definition of IMPORT_PATH.\n")
|
||||
flag.PrintDefaults()
|
||||
}
|
||||
|
||||
flag.Parse()
|
||||
if *exportDataOutfile != "" {
|
||||
if len(flag.Args()) != 1 {
|
||||
flag.Usage()
|
||||
os.Exit(2)
|
||||
}
|
||||
pkg := mustLoadPackage(flag.Arg(0))
|
||||
if err := writeExportData(pkg, *exportDataOutfile); err != nil {
|
||||
die("writing export data: %v", err)
|
||||
}
|
||||
} else {
|
||||
if len(flag.Args()) != 2 {
|
||||
flag.Usage()
|
||||
os.Exit(2)
|
||||
}
|
||||
oldpkg := mustLoadOrRead(flag.Arg(0))
|
||||
newpkg := mustLoadOrRead(flag.Arg(1))
|
||||
if !*allowInternal {
|
||||
if isInternalPackage(oldpkg.Path()) && isInternalPackage(newpkg.Path()) {
|
||||
fmt.Fprintf(os.Stderr, "Ignoring internal package %s\n", oldpkg.Path())
|
||||
os.Exit(0)
|
||||
}
|
||||
}
|
||||
report := apidiff.Changes(oldpkg, newpkg)
|
||||
var err error
|
||||
if *incompatibleOnly {
|
||||
err = report.TextIncompatible(os.Stdout, false)
|
||||
} else {
|
||||
err = report.Text(os.Stdout)
|
||||
}
|
||||
if err != nil {
|
||||
die("writing report: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func mustLoadOrRead(importPathOrFile string) *types.Package {
|
||||
fileInfo, err := os.Stat(importPathOrFile)
|
||||
if err == nil && fileInfo.Mode().IsRegular() {
|
||||
pkg, err := readExportData(importPathOrFile)
|
||||
if err != nil {
|
||||
die("reading export data from %s: %v", importPathOrFile, err)
|
||||
}
|
||||
return pkg
|
||||
} else {
|
||||
return mustLoadPackage(importPathOrFile).Types
|
||||
}
|
||||
}
|
||||
|
||||
func mustLoadPackage(importPath string) *packages.Package {
|
||||
pkg, err := loadPackage(importPath)
|
||||
if err != nil {
|
||||
die("loading %s: %v", importPath, err)
|
||||
}
|
||||
return pkg
|
||||
}
|
||||
|
||||
func loadPackage(importPath string) (*packages.Package, error) {
|
||||
cfg := &packages.Config{Mode: packages.LoadTypes |
|
||||
packages.NeedName | packages.NeedTypes | packages.NeedImports | packages.NeedDeps,
|
||||
}
|
||||
pkgs, err := packages.Load(cfg, importPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(pkgs) == 0 {
|
||||
return nil, fmt.Errorf("found no packages for import %s", importPath)
|
||||
}
|
||||
if len(pkgs[0].Errors) > 0 {
|
||||
return nil, pkgs[0].Errors[0]
|
||||
}
|
||||
return pkgs[0], nil
|
||||
}
|
||||
|
||||
func readExportData(filename string) (*types.Package, error) {
|
||||
f, err := os.Open(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
r := bufio.NewReader(f)
|
||||
m := map[string]*types.Package{}
|
||||
pkgPath, err := r.ReadString('\n')
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pkgPath = pkgPath[:len(pkgPath)-1] // remove delimiter
|
||||
return gcexportdata.Read(r, token.NewFileSet(), m, pkgPath)
|
||||
}
|
||||
|
||||
func writeExportData(pkg *packages.Package, filename string) error {
|
||||
f, err := os.Create(filename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Include the package path in the file. The exportdata format does
|
||||
// not record the path of the package being written.
|
||||
fmt.Fprintln(f, pkg.PkgPath)
|
||||
err1 := gcexportdata.Write(f, pkg.Fset, pkg.Types)
|
||||
err2 := f.Close()
|
||||
if err1 != nil {
|
||||
return err1
|
||||
}
|
||||
return err2
|
||||
}
|
||||
|
||||
func die(format string, args ...interface{}) {
|
||||
fmt.Fprintf(os.Stderr, format+"\n", args...)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
func isInternalPackage(pkgPath string) bool {
|
||||
switch {
|
||||
case strings.HasSuffix(pkgPath, "/internal"):
|
||||
return true
|
||||
case strings.Contains(pkgPath, "/internal/"):
|
||||
return true
|
||||
case pkgPath == "internal", strings.HasPrefix(pkgPath, "internal/"):
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
+81
@@ -0,0 +1,81 @@
|
||||
// Copyright 2019 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 main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/mod/module"
|
||||
)
|
||||
|
||||
type usageError struct {
|
||||
err error
|
||||
}
|
||||
|
||||
func usageErrorf(format string, args ...interface{}) error {
|
||||
return &usageError{err: fmt.Errorf(format, args...)}
|
||||
}
|
||||
|
||||
const usageText = `usage: gorelease [-base=version] [-version=version]`
|
||||
|
||||
func (e *usageError) Error() string {
|
||||
msg := ""
|
||||
if !errors.Is(e.err, flag.ErrHelp) {
|
||||
msg = e.err.Error()
|
||||
}
|
||||
return usageText + "\n" + msg + "\nFor more information, run go doc golang.org/x/exp/cmd/gorelease"
|
||||
}
|
||||
|
||||
type baseVersionError struct {
|
||||
err error
|
||||
modPath string
|
||||
}
|
||||
|
||||
func (e *baseVersionError) Error() string {
|
||||
firstVersion := "v0.1.0"
|
||||
_, major, _ := module.SplitPathVersion(e.modPath)
|
||||
if major != "" {
|
||||
firstVersion = major[1:] + ".0.0"
|
||||
}
|
||||
|
||||
return fmt.Sprintf("could not find base version. Consider setting -version=%s if this is a first release, or explicitly set -base=none: %v", firstVersion, e.err)
|
||||
}
|
||||
|
||||
func (e *baseVersionError) Unwrap() error {
|
||||
return e.err
|
||||
}
|
||||
|
||||
type downloadError struct {
|
||||
m module.Version
|
||||
err error
|
||||
}
|
||||
|
||||
func (e *downloadError) Error() string {
|
||||
msg := e.err.Error()
|
||||
sep := " "
|
||||
if strings.Contains(msg, "\n") {
|
||||
sep = "\n"
|
||||
}
|
||||
return fmt.Sprintf("error downloading module %s@%s:%s%s", e.m.Path, e.m.Version, sep, msg)
|
||||
}
|
||||
|
||||
// cleanCmdError simplifies error messages from os/exec.Cmd.Run.
|
||||
// For ExitErrors, it trims and returns stderr. This is useful for go commands
|
||||
// that print well-formatted errors. By default, ExitError prints the exit
|
||||
// status but not stderr.
|
||||
//
|
||||
// cleanCmdError returns other errors unmodified.
|
||||
func cleanCmdError(err error) error {
|
||||
if xerr, ok := err.(*exec.ExitError); ok {
|
||||
if stderr := strings.TrimSpace(string(xerr.Stderr)); stderr != "" {
|
||||
return errors.New(stderr)
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
+1516
File diff suppressed because it is too large
Load Diff
+503
@@ -0,0 +1,503 @@
|
||||
// Copyright 2019 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 main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"golang.org/x/mod/module"
|
||||
"golang.org/x/tools/txtar"
|
||||
)
|
||||
|
||||
var (
|
||||
testwork = flag.Bool("testwork", false, "preserve work directory")
|
||||
updateGolden = flag.Bool("u", false, "update expected text in test files instead of failing")
|
||||
)
|
||||
|
||||
var hasGitCache struct {
|
||||
once sync.Once
|
||||
found bool
|
||||
}
|
||||
|
||||
// hasGit reports whether the git executable exists on the PATH.
|
||||
func hasGit() bool {
|
||||
hasGitCache.once.Do(func() {
|
||||
if _, err := exec.LookPath("git"); err != nil {
|
||||
return
|
||||
}
|
||||
hasGitCache.found = true
|
||||
})
|
||||
return hasGitCache.found
|
||||
}
|
||||
|
||||
// prepareProxy creates a proxy dir and returns an associated ctx.
|
||||
//
|
||||
// proxyVersions must be a map of module version to true. If proxyVersions is
|
||||
// empty, all modules in mod/ will be included in the proxy list. If proxy
|
||||
// versions is non-empty, only those modules in mod/ that match an entry in
|
||||
// proxyVersions will be included.
|
||||
//
|
||||
// ctx must be used in runRelease.
|
||||
// cleanup must be called when the relevant tests are finished.
|
||||
func prepareProxy(proxyVersions map[module.Version]bool, tests []*test) (ctx context.Context, cleanup func(), _ error) {
|
||||
env := append(os.Environ(), "GO111MODULE=on", "GOSUMDB=off")
|
||||
|
||||
proxyDir, proxyURL, err := buildProxyDir(proxyVersions, tests)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("error building proxy dir: %v", err)
|
||||
}
|
||||
env = append(env, fmt.Sprintf("GOPROXY=%s", proxyURL))
|
||||
|
||||
cacheDir, err := os.MkdirTemp("", "gorelease_test-gocache")
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
env = append(env, fmt.Sprintf("GOPATH=%s", cacheDir))
|
||||
|
||||
return context.WithValue(context.Background(), "env", env), func() {
|
||||
if *testwork {
|
||||
fmt.Fprintf(os.Stderr, "test cache dir: %s\n", cacheDir)
|
||||
fmt.Fprintf(os.Stderr, "test proxy dir: %s\ntest proxy URL: %s\n", proxyDir, proxyURL)
|
||||
} else {
|
||||
cmd := exec.Command("go", "clean", "-modcache")
|
||||
cmd.Env = env
|
||||
if err := cmd.Run(); err != nil {
|
||||
fmt.Fprintln(os.Stderr, fmt.Errorf("error running go clean: %v", err))
|
||||
}
|
||||
|
||||
if err := os.RemoveAll(cacheDir); err != nil {
|
||||
fmt.Fprintln(os.Stderr, fmt.Errorf("error removing cache dir %s: %v", cacheDir, err))
|
||||
}
|
||||
if err := os.RemoveAll(proxyDir); err != nil {
|
||||
fmt.Fprintln(os.Stderr, fmt.Errorf("error removing proxy dir %s: %v", proxyDir, err))
|
||||
}
|
||||
}
|
||||
}, nil
|
||||
}
|
||||
|
||||
// test describes an individual test case, written as a .test file in the
|
||||
// testdata directory.
|
||||
//
|
||||
// Each test is a txtar archive (see golang.org/x/tools/txtar). The comment
|
||||
// section (before the first file) contains a sequence of key=value pairs
|
||||
// (one per line) that configure the test.
|
||||
//
|
||||
// Most tests include a file named "want". The output of gorelease is compared
|
||||
// against this file. If the -u flag is set, this file is replaced with the
|
||||
// actual output of gorelease, and the test is written back to disk. This is
|
||||
// useful for updating tests after cosmetic changes.
|
||||
type test struct {
|
||||
txtar.Archive
|
||||
|
||||
// testPath is the name of the .test file describing the test.
|
||||
testPath string
|
||||
|
||||
// modPath (set with mod=...) is the path of the module being tested. Used
|
||||
// to retrieve files from the test proxy.
|
||||
modPath string
|
||||
|
||||
// version (set with version=...) is the name of a version to check out
|
||||
// from the test proxy into the working directory. Some tests use this
|
||||
// instead of specifying files they need in the txtar archive.
|
||||
version string
|
||||
|
||||
// baseVersion (set with base=...) is the value of the -base flag to pass
|
||||
// to gorelease.
|
||||
baseVersion string
|
||||
|
||||
// releaseVersion (set with release=...) is the value of the -version flag
|
||||
// to pass to gorelease.
|
||||
releaseVersion string
|
||||
|
||||
// dir (set with dir=...) is the directory where gorelease should be invoked.
|
||||
// If unset, gorelease is invoked in the directory where the txtar archive
|
||||
// is unpacked. This is useful for invoking gorelease in a subdirectory.
|
||||
dir string
|
||||
|
||||
// wantError (set with error=...) is true if the test expects a hard error
|
||||
// (returned by runRelease).
|
||||
wantError bool
|
||||
|
||||
// wantSuccess (set with success=...) is true if the test expects a report
|
||||
// to be returned without errors or diagnostics. True by default.
|
||||
wantSuccess bool
|
||||
|
||||
// skip (set with skip=...) is non-empty if the test should be skipped.
|
||||
skip string
|
||||
|
||||
// want is set to the contents of the file named "want" in the txtar archive.
|
||||
want []byte
|
||||
|
||||
// proxyVersions is used to set the exact contents of the GOPROXY.
|
||||
//
|
||||
// If empty, all of testadata/mod/ will be included in the proxy.
|
||||
// If it is not empty, each entry must be of the form <modpath>@v<version>
|
||||
// and exist in testdata/mod/.
|
||||
proxyVersions map[module.Version]bool
|
||||
|
||||
// vcs is used to set the VCS that the root of the test should
|
||||
// emulate. Allowed values are git, and hg.
|
||||
vcs string
|
||||
}
|
||||
|
||||
// readTest reads and parses a .test file with the given name.
|
||||
func readTest(testPath string) (*test, error) {
|
||||
arc, err := txtar.ParseFile(testPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
t := &test{
|
||||
Archive: *arc,
|
||||
testPath: testPath,
|
||||
wantSuccess: true,
|
||||
}
|
||||
|
||||
for n, line := range bytes.Split(t.Comment, []byte("\n")) {
|
||||
lineNum := n + 1
|
||||
if i := bytes.IndexByte(line, '#'); i >= 0 {
|
||||
line = line[:i]
|
||||
}
|
||||
line = bytes.TrimSpace(line)
|
||||
if len(line) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
var key, value string
|
||||
if i := bytes.IndexByte(line, '='); i < 0 {
|
||||
return nil, fmt.Errorf("%s:%d: no '=' found", testPath, lineNum)
|
||||
} else {
|
||||
key = strings.TrimSpace(string(line[:i]))
|
||||
value = strings.TrimSpace(string(line[i+1:]))
|
||||
}
|
||||
switch key {
|
||||
case "mod":
|
||||
t.modPath = value
|
||||
case "version":
|
||||
t.version = value
|
||||
case "base":
|
||||
t.baseVersion = value
|
||||
case "release":
|
||||
t.releaseVersion = value
|
||||
case "dir":
|
||||
t.dir = value
|
||||
case "skip":
|
||||
t.skip = value
|
||||
case "success":
|
||||
t.wantSuccess, err = strconv.ParseBool(value)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s:%d: %v", testPath, lineNum, err)
|
||||
}
|
||||
case "error":
|
||||
t.wantError, err = strconv.ParseBool(value)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s:%d: %v", testPath, lineNum, err)
|
||||
}
|
||||
case "proxyVersions":
|
||||
if len(value) == 0 {
|
||||
break
|
||||
}
|
||||
proxyVersions := make(map[module.Version]bool)
|
||||
parts := strings.Split(value, ",")
|
||||
for _, modpathWithVersion := range parts {
|
||||
vParts := strings.Split(modpathWithVersion, "@")
|
||||
if len(vParts) != 2 {
|
||||
return nil, fmt.Errorf("proxyVersions entry %s is invalid: it should be of the format <modpath>@v<semver> (ex: github.com/foo/bar@v1.2.3)", modpathWithVersion)
|
||||
}
|
||||
modPath, version := vParts[0], vParts[1]
|
||||
mv := module.Version{
|
||||
Path: modPath,
|
||||
Version: version,
|
||||
}
|
||||
proxyVersions[mv] = true
|
||||
}
|
||||
t.proxyVersions = proxyVersions
|
||||
case "vcs":
|
||||
t.vcs = value
|
||||
default:
|
||||
return nil, fmt.Errorf("%s:%d: unknown key: %q", testPath, lineNum, key)
|
||||
}
|
||||
}
|
||||
if t.modPath == "" && (t.version != "" || (t.baseVersion != "" && t.baseVersion != "none")) {
|
||||
return nil, fmt.Errorf("%s: version or base was set but mod was not set", testPath)
|
||||
}
|
||||
|
||||
haveFiles := false
|
||||
for _, f := range t.Files {
|
||||
if f.Name == "want" {
|
||||
t.want = bytes.TrimSpace(f.Data)
|
||||
continue
|
||||
}
|
||||
haveFiles = true
|
||||
}
|
||||
|
||||
if haveFiles && t.version != "" {
|
||||
return nil, fmt.Errorf("%s: version is set but files are present", testPath)
|
||||
}
|
||||
|
||||
return t, nil
|
||||
}
|
||||
|
||||
// updateTest replaces the contents of the file named "want" within a test's
|
||||
// txtar archive, then formats and writes the test file.
|
||||
func updateTest(t *test, want []byte) error {
|
||||
var wantFile *txtar.File
|
||||
for i := range t.Files {
|
||||
if t.Files[i].Name == "want" {
|
||||
wantFile = &t.Files[i]
|
||||
break
|
||||
}
|
||||
}
|
||||
if wantFile == nil {
|
||||
t.Files = append(t.Files, txtar.File{Name: "want"})
|
||||
wantFile = &t.Files[len(t.Files)-1]
|
||||
}
|
||||
|
||||
wantFile.Data = want
|
||||
data := txtar.Format(&t.Archive)
|
||||
return os.WriteFile(t.testPath, data, 0666)
|
||||
}
|
||||
|
||||
func TestRelease(t *testing.T) {
|
||||
testPaths, err := filepath.Glob(filepath.FromSlash("testdata/*/*.test"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(testPaths) == 0 {
|
||||
t.Fatal("no .test files found in testdata directory")
|
||||
}
|
||||
|
||||
var tests []*test
|
||||
for _, testPath := range testPaths {
|
||||
test, err := readTest(testPath)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
tests = append(tests, test)
|
||||
}
|
||||
|
||||
defaultContext, cleanup, err := prepareProxy(nil, tests)
|
||||
if err != nil {
|
||||
t.Fatalf("preparing test proxy: %v", err)
|
||||
}
|
||||
t.Cleanup(cleanup)
|
||||
|
||||
for _, test := range tests {
|
||||
testName := strings.TrimSuffix(strings.TrimPrefix(filepath.ToSlash(test.testPath), "testdata/"), ".test")
|
||||
t.Run(testName, testRelease(defaultContext, tests, test))
|
||||
}
|
||||
}
|
||||
|
||||
func TestRelease_gitRepo_uncommittedChanges(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
buf := &bytes.Buffer{}
|
||||
releaseDir, err := os.MkdirTemp("", "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
goModInit(t, releaseDir)
|
||||
gitInit(t, releaseDir)
|
||||
|
||||
// Create an uncommitted change.
|
||||
bContents := `package b
|
||||
const B = "b"`
|
||||
if err := os.WriteFile(filepath.Join(releaseDir, "b.go"), []byte(bContents), 0644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
success, err := runRelease(ctx, buf, releaseDir, nil)
|
||||
if got, want := err.Error(), fmt.Sprintf("repo %s has uncommitted changes", releaseDir); got != want {
|
||||
t.Errorf("runRelease:\ngot error:\n%q\nwant error\n%q", got, want)
|
||||
}
|
||||
if success {
|
||||
t.Errorf("runRelease: expected failure, got success")
|
||||
}
|
||||
}
|
||||
|
||||
func testRelease(ctx context.Context, tests []*test, test *test) func(t *testing.T) {
|
||||
return func(t *testing.T) {
|
||||
if test.skip != "" {
|
||||
t.Skip(test.skip)
|
||||
}
|
||||
|
||||
t.Parallel()
|
||||
|
||||
if len(test.proxyVersions) > 0 {
|
||||
var cleanup func()
|
||||
var err error
|
||||
ctx, cleanup, err = prepareProxy(test.proxyVersions, tests)
|
||||
if err != nil {
|
||||
t.Fatalf("preparing test proxy: %v", err)
|
||||
}
|
||||
t.Cleanup(cleanup)
|
||||
}
|
||||
|
||||
// Extract the files in the release version. They may be part of the
|
||||
// test archive or in testdata/mod.
|
||||
testDir, err := os.MkdirTemp("", "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if *testwork {
|
||||
fmt.Fprintf(os.Stderr, "test dir: %s\n", testDir)
|
||||
} else {
|
||||
t.Cleanup(func() {
|
||||
os.RemoveAll(testDir)
|
||||
})
|
||||
}
|
||||
|
||||
var arc *txtar.Archive
|
||||
if test.version != "" {
|
||||
arcBase := fmt.Sprintf("%s_%s.txt", strings.ReplaceAll(test.modPath, "/", "_"), test.version)
|
||||
arcPath := filepath.Join("testdata/mod", arcBase)
|
||||
var err error
|
||||
arc, err = txtar.ParseFile(arcPath)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
} else {
|
||||
arc = &test.Archive
|
||||
}
|
||||
if err := extractTxtar(testDir, arc); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
switch test.vcs {
|
||||
case "git":
|
||||
// Convert testDir to a git repository with a single commit, to
|
||||
// simulate a real user's module-in-a-git-repo.
|
||||
gitInit(t, testDir)
|
||||
case "hg":
|
||||
// Convert testDir to a mercurial repository to simulate a real
|
||||
// user's module-in-a-hg-repo.
|
||||
hgInit(t, testDir)
|
||||
case "":
|
||||
// No VCS.
|
||||
default:
|
||||
t.Fatalf("unknown vcs %q", test.vcs)
|
||||
}
|
||||
|
||||
// Generate the report and compare it against the expected text.
|
||||
var args []string
|
||||
if test.baseVersion != "" {
|
||||
args = append(args, "-base="+test.baseVersion)
|
||||
}
|
||||
if test.releaseVersion != "" {
|
||||
args = append(args, "-version="+test.releaseVersion)
|
||||
}
|
||||
buf := &bytes.Buffer{}
|
||||
releaseDir := filepath.Join(testDir, test.dir)
|
||||
success, err := runRelease(ctx, buf, releaseDir, args)
|
||||
if err != nil {
|
||||
if !test.wantError {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if errMsg := []byte(err.Error()); !bytes.Equal(errMsg, bytes.TrimSpace(test.want)) {
|
||||
if *updateGolden {
|
||||
if err := updateTest(test, errMsg); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
} else {
|
||||
t.Fatalf("got error: %s; want error: %s", errMsg, test.want)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
if test.wantError {
|
||||
t.Fatalf("got success; want error %s", test.want)
|
||||
}
|
||||
|
||||
got := bytes.TrimSpace(buf.Bytes())
|
||||
if filepath.Separator != '/' {
|
||||
got = bytes.ReplaceAll(got, []byte{filepath.Separator}, []byte{'/'})
|
||||
}
|
||||
if !bytes.Equal(got, test.want) {
|
||||
if *updateGolden {
|
||||
if err := updateTest(test, got); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
} else {
|
||||
t.Fatalf("got:\n%s\n\nwant:\n%s", got, test.want)
|
||||
}
|
||||
}
|
||||
if success != test.wantSuccess {
|
||||
t.Fatalf("got success: %v; want success %v", success, test.wantSuccess)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// hgInit initialises a directory as a mercurial repo.
|
||||
func hgInit(t *testing.T, dir string) {
|
||||
t.Helper()
|
||||
|
||||
if err := os.Mkdir(filepath.Join(dir, ".hg"), 0777); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := os.WriteFile(filepath.Join(dir, ".hg", "branch"), []byte("default"), 0777); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// gitInit initialises a directory as a git repo, and adds a simple commit.
|
||||
func gitInit(t *testing.T, dir string) {
|
||||
t.Helper()
|
||||
|
||||
if !hasGit() {
|
||||
t.Skip("PATH does not contain git")
|
||||
}
|
||||
|
||||
stdout := &bytes.Buffer{}
|
||||
stderr := &bytes.Buffer{}
|
||||
|
||||
for _, args := range [][]string{
|
||||
{"git", "init"},
|
||||
{"git", "config", "user.name", "Gopher"},
|
||||
{"git", "config", "user.email", "gopher@golang.org"},
|
||||
{"git", "checkout", "-b", "test"},
|
||||
{"git", "add", "-A"},
|
||||
{"git", "commit", "-m", "test"},
|
||||
} {
|
||||
cmd := exec.Command(args[0], args[1:]...)
|
||||
cmd.Dir = dir
|
||||
cmd.Stdout = stdout
|
||||
cmd.Stderr = stderr
|
||||
if err := cmd.Run(); err != nil {
|
||||
cmdArgs := strings.Join(args, " ")
|
||||
t.Fatalf("%s\n%s\nerror running %q on dir %s: %v", stdout.String(), stderr.String(), cmdArgs, dir, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// goModInit runs `go mod init` in the given directory.
|
||||
func goModInit(t *testing.T, dir string) {
|
||||
t.Helper()
|
||||
|
||||
aContents := `package a
|
||||
const A = "a"`
|
||||
if err := os.WriteFile(filepath.Join(dir, "a.go"), []byte(aContents), 0644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
stdout := &bytes.Buffer{}
|
||||
stderr := &bytes.Buffer{}
|
||||
cmd := exec.Command("go", "mod", "init", "example.com/uncommitted")
|
||||
cmd.Stdout = stdout
|
||||
cmd.Stderr = stderr
|
||||
cmd.Dir = dir
|
||||
if err := cmd.Run(); err != nil {
|
||||
t.Fatalf("error running `go mod init`: %s, %v", stderr.String(), err)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
// Copyright 2020 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 main
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// hasPathPrefix reports whether the slash-separated path s
|
||||
// begins with the elements in prefix.
|
||||
// Copied from cmd/go/internal/str.HasPathPrefix.
|
||||
func hasPathPrefix(s, prefix string) bool {
|
||||
if len(s) == len(prefix) {
|
||||
return s == prefix
|
||||
}
|
||||
if prefix == "" {
|
||||
return true
|
||||
}
|
||||
if len(s) > len(prefix) {
|
||||
if prefix[len(prefix)-1] == '/' || s[len(prefix)] == '/' {
|
||||
return s[:len(prefix)] == prefix
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// hasFilePathPrefix reports whether the filesystem path s
|
||||
// begins with the elements in prefix.
|
||||
// Copied from cmd/go/internal/str.HasFilePathPrefix.
|
||||
func hasFilePathPrefix(s, prefix string) bool {
|
||||
sv := strings.ToUpper(filepath.VolumeName(s))
|
||||
pv := strings.ToUpper(filepath.VolumeName(prefix))
|
||||
s = s[len(sv):]
|
||||
prefix = prefix[len(pv):]
|
||||
switch {
|
||||
default:
|
||||
return false
|
||||
case pv != "" && sv != pv:
|
||||
return false
|
||||
case len(s) == len(prefix):
|
||||
return s == prefix
|
||||
case prefix == "":
|
||||
return true
|
||||
case len(s) > len(prefix):
|
||||
if prefix[len(prefix)-1] == filepath.Separator {
|
||||
return strings.HasPrefix(s, prefix)
|
||||
}
|
||||
return s[len(prefix)] == filepath.Separator && s[:len(prefix)] == prefix
|
||||
}
|
||||
}
|
||||
|
||||
// trimFilePathPrefix returns the given filesystem path s without the leading
|
||||
// prefix.
|
||||
func trimFilePathPrefix(s, prefix string) string {
|
||||
sv := strings.ToUpper(filepath.VolumeName(s))
|
||||
pv := strings.ToUpper(filepath.VolumeName(prefix))
|
||||
s = s[len(sv):]
|
||||
prefix = prefix[len(pv):]
|
||||
|
||||
if !hasFilePathPrefix(s, prefix) || len(prefix) == 0 {
|
||||
return s
|
||||
}
|
||||
if len(s) == len(prefix) {
|
||||
return ""
|
||||
}
|
||||
if prefix[len(prefix)-1] == filepath.Separator {
|
||||
return strings.TrimPrefix(s, prefix)
|
||||
}
|
||||
return s[len(prefix)+1:]
|
||||
}
|
||||
|
||||
// trimPathPrefix returns p without the leading prefix. Unlike
|
||||
// strings.TrimPrefix, the prefix will only match on slash-separted component
|
||||
// boundaries, so trimPathPrefix("aa/b", "aa") returns "b", but
|
||||
// trimPathPrefix("aa/b", "a") returns "aa/b".
|
||||
func trimPathPrefix(p, prefix string) string {
|
||||
if prefix == "" {
|
||||
return p
|
||||
}
|
||||
if prefix == p {
|
||||
return ""
|
||||
}
|
||||
return strings.TrimPrefix(p, prefix+"/")
|
||||
}
|
||||
+227
@@ -0,0 +1,227 @@
|
||||
// Copyright 2020 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 main
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestHasPathPrefix(t *testing.T) {
|
||||
for _, test := range []struct {
|
||||
desc, path, prefix string
|
||||
want bool
|
||||
}{
|
||||
{
|
||||
desc: "empty_prefix",
|
||||
path: "a/b",
|
||||
prefix: "",
|
||||
want: true,
|
||||
}, {
|
||||
desc: "partial_prefix",
|
||||
path: "a/b",
|
||||
prefix: "a",
|
||||
want: true,
|
||||
}, {
|
||||
desc: "full_prefix",
|
||||
path: "a/b",
|
||||
prefix: "a/b",
|
||||
want: true,
|
||||
}, {
|
||||
desc: "partial_component",
|
||||
path: "aa/b",
|
||||
prefix: "a",
|
||||
want: false,
|
||||
},
|
||||
} {
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
if got := hasPathPrefix(test.path, test.prefix); got != test.want {
|
||||
t.Errorf("hasPathPrefix(%q, %q): got %v, want %v", test.path, test.prefix, got, test.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestHasFilePathPrefix(t *testing.T) {
|
||||
type test struct {
|
||||
desc, path, prefix string
|
||||
want bool
|
||||
}
|
||||
var tests []test
|
||||
if runtime.GOOS == "windows" {
|
||||
tests = []test{
|
||||
{
|
||||
desc: "empty_prefix",
|
||||
path: `c:\a\b`,
|
||||
prefix: "",
|
||||
want: true,
|
||||
}, {
|
||||
desc: "drive_prefix",
|
||||
path: `c:\a\b`,
|
||||
prefix: `c:\`,
|
||||
want: true,
|
||||
}, {
|
||||
desc: "partial_prefix",
|
||||
path: `c:\a\b`,
|
||||
prefix: `c:\a`,
|
||||
want: true,
|
||||
}, {
|
||||
desc: "full_prefix",
|
||||
path: `c:\a\b`,
|
||||
prefix: `c:\a\b`,
|
||||
want: true,
|
||||
}, {
|
||||
desc: "partial_component",
|
||||
path: `c:\aa\b`,
|
||||
prefix: `c:\a`,
|
||||
want: false,
|
||||
},
|
||||
}
|
||||
} else {
|
||||
tests = []test{
|
||||
{
|
||||
desc: "empty_prefix",
|
||||
path: "/a/b",
|
||||
prefix: "",
|
||||
want: true,
|
||||
}, {
|
||||
desc: "partial_prefix",
|
||||
path: "/a/b",
|
||||
prefix: "/a",
|
||||
want: true,
|
||||
}, {
|
||||
desc: "full_prefix",
|
||||
path: "/a/b",
|
||||
prefix: "/a/b",
|
||||
want: true,
|
||||
}, {
|
||||
desc: "partial_component",
|
||||
path: "/aa/b",
|
||||
prefix: "/a",
|
||||
want: false,
|
||||
},
|
||||
}
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
if got := hasFilePathPrefix(test.path, test.prefix); got != test.want {
|
||||
t.Errorf("hasFilePathPrefix(%q, %q): got %v, want %v", test.path, test.prefix, got, test.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestTrimFilePathPrefix(t *testing.T) {
|
||||
type test struct {
|
||||
desc, path, prefix, want string
|
||||
}
|
||||
var tests []test
|
||||
if runtime.GOOS == "windows" {
|
||||
tests = []test{
|
||||
// Note: these two cases in which the result preserves the leading \
|
||||
// don't come up in reality in gorelease. That's because prefix is
|
||||
// always far to the right of the path parts (ex github.com/foo/bar
|
||||
// in C:\Users\foo\AppData\Local\Temp\...\github.com\foo\bar).
|
||||
{
|
||||
desc: "empty_prefix",
|
||||
path: `c:\a\b`,
|
||||
prefix: "",
|
||||
want: `\a\b`,
|
||||
}, {
|
||||
desc: "partial_component",
|
||||
path: `c:\aa\b`,
|
||||
prefix: `c:\a`,
|
||||
want: `\aa\b`,
|
||||
},
|
||||
|
||||
{
|
||||
desc: "drive_prefix",
|
||||
path: `c:\a\b`,
|
||||
prefix: `c:\`,
|
||||
want: `a\b`,
|
||||
}, {
|
||||
desc: "partial_prefix",
|
||||
path: `c:\a\b`,
|
||||
prefix: `c:\a`,
|
||||
want: `b`,
|
||||
}, {
|
||||
desc: "full_prefix",
|
||||
path: `c:\a\b`,
|
||||
prefix: `c:\a\b`,
|
||||
want: "",
|
||||
},
|
||||
}
|
||||
} else {
|
||||
tests = []test{
|
||||
{
|
||||
desc: "empty_prefix",
|
||||
path: "/a/b",
|
||||
prefix: "",
|
||||
want: "/a/b",
|
||||
}, {
|
||||
desc: "partial_prefix",
|
||||
path: "/a/b",
|
||||
prefix: "/a",
|
||||
want: "b",
|
||||
}, {
|
||||
desc: "full_prefix",
|
||||
path: "/a/b",
|
||||
prefix: "/a/b",
|
||||
want: "",
|
||||
}, {
|
||||
desc: "partial_component",
|
||||
path: "/aa/b",
|
||||
prefix: "/a",
|
||||
want: "/aa/b",
|
||||
},
|
||||
}
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
if got := trimFilePathPrefix(test.path, test.prefix); got != test.want {
|
||||
t.Errorf("hasFilePathPrefix(%q, %q): got %v, want %v", test.path, test.prefix, got, test.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestTrimPathPrefix(t *testing.T) {
|
||||
for _, test := range []struct {
|
||||
desc, path, prefix, want string
|
||||
}{
|
||||
{
|
||||
desc: "empty_prefix",
|
||||
path: "a/b",
|
||||
prefix: "",
|
||||
want: "a/b",
|
||||
}, {
|
||||
desc: "abs_empty_prefix",
|
||||
path: "/a/b",
|
||||
prefix: "",
|
||||
want: "/a/b",
|
||||
}, {
|
||||
desc: "partial_prefix",
|
||||
path: "a/b",
|
||||
prefix: "a",
|
||||
want: "b",
|
||||
}, {
|
||||
desc: "full_prefix",
|
||||
path: "a/b",
|
||||
prefix: "a/b",
|
||||
want: "",
|
||||
}, {
|
||||
desc: "partial_component",
|
||||
path: "aa/b",
|
||||
prefix: "a",
|
||||
want: "aa/b",
|
||||
},
|
||||
} {
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
if got := trimPathPrefix(test.path, test.prefix); got != test.want {
|
||||
t.Errorf("trimPathPrefix(%q, %q): got %q, want %q", test.path, test.prefix, got, test.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
+206
@@ -0,0 +1,206 @@
|
||||
// Copyright 2019 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 main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"golang.org/x/mod/module"
|
||||
"golang.org/x/mod/semver"
|
||||
"golang.org/x/mod/zip"
|
||||
"golang.org/x/tools/txtar"
|
||||
)
|
||||
|
||||
// buildProxyDir constructs a temporary directory suitable for use as a
|
||||
// module proxy with a file:// URL. The caller is responsible for deleting
|
||||
// the directory when it's no longer needed.
|
||||
//
|
||||
// proxyVersions must be a map of module version true. If proxyVersions is
|
||||
// empty, all modules in mod/ will be included in the proxy list. If proxy
|
||||
// versions is non-empty, only those modules in mod/ that match an entry in
|
||||
// proxyVersions will be included.
|
||||
func buildProxyDir(proxyVersions map[module.Version]bool, tests []*test) (proxyDir, proxyURL string, err error) {
|
||||
proxyDir, err = os.MkdirTemp("", "gorelease-proxy")
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
txtarPaths, err := filepath.Glob(filepath.FromSlash("testdata/mod/*.txt"))
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
// Map of modPath to versions for that modPath.
|
||||
versionLists := make(map[string][]string)
|
||||
|
||||
for _, t := range tests {
|
||||
versionLists[t.modPath] = []string{}
|
||||
modDir := filepath.Join(proxyDir, t.modPath, "@v")
|
||||
if err := os.MkdirAll(modDir, 0777); err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
}
|
||||
|
||||
for _, txtarPath := range txtarPaths {
|
||||
base := filepath.Base(txtarPath)
|
||||
stem := base[:len(base)-len(".txt")]
|
||||
i := strings.LastIndexByte(base, '_')
|
||||
if i < 0 {
|
||||
return "", "", fmt.Errorf("invalid module archive: %s", base)
|
||||
}
|
||||
modPath := strings.ReplaceAll(stem[:i], "_", "/")
|
||||
version := stem[i+1:]
|
||||
mv := module.Version{
|
||||
Path: modPath,
|
||||
Version: version,
|
||||
}
|
||||
|
||||
// User has supplied proxyVersions. Honor proxy versions by only
|
||||
// accepting those versions supplied in proxyVersions.
|
||||
if len(proxyVersions) > 0 {
|
||||
if !proxyVersions[mv] {
|
||||
// modPath@version is not in proxyVersions: skip.
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
versionLists[modPath] = append(versionLists[modPath], version)
|
||||
|
||||
modDir := filepath.Join(proxyDir, modPath, "@v")
|
||||
if err := os.MkdirAll(modDir, 0777); err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
arc, err := txtar.ParseFile(txtarPath)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
isCanonical := version == module.CanonicalVersion(version)
|
||||
var zipContents []zip.File
|
||||
var haveInfo, haveMod bool
|
||||
var goMod txtar.File
|
||||
for _, af := range arc.Files {
|
||||
if !isCanonical && af.Name != ".info" {
|
||||
return "", "", fmt.Errorf("%s: version is non-canonical but contains files other than .info", txtarPath)
|
||||
}
|
||||
if af.Name == ".info" || af.Name == ".mod" {
|
||||
if af.Name == ".info" {
|
||||
haveInfo = true
|
||||
} else {
|
||||
haveMod = true
|
||||
}
|
||||
outPath := filepath.Join(modDir, version+af.Name)
|
||||
if err := os.WriteFile(outPath, af.Data, 0666); err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
continue
|
||||
}
|
||||
if af.Name == "go.mod" {
|
||||
goMod = af
|
||||
}
|
||||
|
||||
zipContents = append(zipContents, txtarFile{af})
|
||||
}
|
||||
if !isCanonical && !haveInfo {
|
||||
return "", "", fmt.Errorf("%s: version is non-canonical but does not have .info", txtarPath)
|
||||
}
|
||||
|
||||
if !haveInfo {
|
||||
outPath := filepath.Join(modDir, version+".info")
|
||||
outContent := fmt.Sprintf(`{"Version":"%s"}`, version)
|
||||
if err := os.WriteFile(outPath, []byte(outContent), 0666); err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
}
|
||||
if !haveMod && goMod.Name != "" {
|
||||
outPath := filepath.Join(modDir, version+".mod")
|
||||
if err := os.WriteFile(outPath, goMod.Data, 0666); err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
}
|
||||
|
||||
if len(zipContents) > 0 {
|
||||
zipPath := filepath.Join(modDir, version+".zip")
|
||||
zipFile, err := os.Create(zipPath)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
defer zipFile.Close()
|
||||
if err := zip.Create(zipFile, module.Version{Path: modPath, Version: version}, zipContents); err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
if err := zipFile.Close(); err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
buf := &bytes.Buffer{}
|
||||
for modPath, versions := range versionLists {
|
||||
outPath := filepath.Join(proxyDir, modPath, "@v", "list")
|
||||
sort.Slice(versions, func(i, j int) bool {
|
||||
return semver.Compare(versions[i], versions[j]) < 0
|
||||
})
|
||||
for _, v := range versions {
|
||||
fmt.Fprintln(buf, v)
|
||||
}
|
||||
if err := os.WriteFile(outPath, buf.Bytes(), 0666); err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
buf.Reset()
|
||||
}
|
||||
|
||||
// Make sure the URL path starts with a slash on Windows. Absolute paths
|
||||
// normally start with a drive letter.
|
||||
// TODO(golang.org/issue/32456): use url.FromFilePath when implemented.
|
||||
if strings.HasPrefix(proxyDir, "/") {
|
||||
proxyURL = "file://" + proxyDir
|
||||
} else {
|
||||
proxyURL = "file:///" + filepath.FromSlash(proxyDir)
|
||||
}
|
||||
return proxyDir, proxyURL, nil
|
||||
}
|
||||
|
||||
type txtarFile struct {
|
||||
f txtar.File
|
||||
}
|
||||
|
||||
func (f txtarFile) Path() string { return f.f.Name }
|
||||
func (f txtarFile) Lstat() (os.FileInfo, error) { return txtarFileInfo{f.f}, nil }
|
||||
func (f txtarFile) Open() (io.ReadCloser, error) {
|
||||
return io.NopCloser(bytes.NewReader(f.f.Data)), nil
|
||||
}
|
||||
|
||||
type txtarFileInfo struct {
|
||||
f txtar.File
|
||||
}
|
||||
|
||||
func (f txtarFileInfo) Name() string { return f.f.Name }
|
||||
func (f txtarFileInfo) Size() int64 { return int64(len(f.f.Data)) }
|
||||
func (f txtarFileInfo) Mode() os.FileMode { return 0444 }
|
||||
func (f txtarFileInfo) ModTime() time.Time { return time.Time{} }
|
||||
func (f txtarFileInfo) IsDir() bool { return false }
|
||||
func (f txtarFileInfo) Sys() interface{} { return nil }
|
||||
|
||||
func extractTxtar(destDir string, arc *txtar.Archive) error {
|
||||
for _, f := range arc.Files {
|
||||
outPath := filepath.Join(destDir, f.Name)
|
||||
if err := os.MkdirAll(filepath.Dir(outPath), 0777); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := os.WriteFile(outPath, f.Data, 0666); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
+463
@@ -0,0 +1,463 @@
|
||||
// Copyright 2019 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 main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/exp/apidiff"
|
||||
"golang.org/x/mod/module"
|
||||
"golang.org/x/mod/semver"
|
||||
"golang.org/x/tools/go/packages"
|
||||
)
|
||||
|
||||
// report describes the differences in the public API between two versions
|
||||
// of a module.
|
||||
type report struct {
|
||||
// base contains information about the "old" module version being compared
|
||||
// against. base.version may be "none", indicating there is no base version
|
||||
// (for example, if this is the first release). base.version may not be "".
|
||||
base moduleInfo
|
||||
|
||||
// release contains information about the version of the module to release.
|
||||
// The version may be set explicitly with -version or suggested using
|
||||
// suggestVersion, in which case release.versionInferred is true.
|
||||
release moduleInfo
|
||||
|
||||
// packages is a list of package reports, describing the differences
|
||||
// for individual packages, sorted by package path.
|
||||
packages []packageReport
|
||||
|
||||
// versionInvalid explains why the proposed or suggested version is not valid.
|
||||
versionInvalid *versionMessage
|
||||
|
||||
// haveCompatibleChanges is true if there are any backward-compatible
|
||||
// changes in non-internal packages.
|
||||
haveCompatibleChanges bool
|
||||
|
||||
// haveIncompatibleChanges is true if there are any backward-incompatible
|
||||
// changes in non-internal packages.
|
||||
haveIncompatibleChanges bool
|
||||
|
||||
// haveBaseErrors is true if there were errors loading packages
|
||||
// in the base version.
|
||||
haveBaseErrors bool
|
||||
|
||||
// haveReleaseErrors is true if there were errors loading packages
|
||||
// in the release version.
|
||||
haveReleaseErrors bool
|
||||
}
|
||||
|
||||
// String returns a human-readable report that lists errors, compatible changes,
|
||||
// and incompatible changes in each package. If releaseVersion is set, the
|
||||
// report states whether releaseVersion is valid (and why). If releaseVersion is
|
||||
// not set, it suggests a new version.
|
||||
func (r *report) String() string {
|
||||
buf := &strings.Builder{}
|
||||
for _, p := range r.packages {
|
||||
buf.WriteString(p.String())
|
||||
}
|
||||
|
||||
if !r.canVerifyReleaseVersion() {
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
if len(r.release.diagnostics) > 0 {
|
||||
buf.WriteString("# diagnostics\n")
|
||||
for _, d := range r.release.diagnostics {
|
||||
fmt.Fprintln(buf, d)
|
||||
}
|
||||
buf.WriteByte('\n')
|
||||
}
|
||||
|
||||
buf.WriteString("# summary\n")
|
||||
baseVersion := r.base.version
|
||||
if r.base.modPath != r.release.modPath {
|
||||
baseVersion = r.base.modPath + "@" + baseVersion
|
||||
}
|
||||
if r.base.versionInferred {
|
||||
fmt.Fprintf(buf, "Inferred base version: %s\n", baseVersion)
|
||||
} else if r.base.versionQuery != "" {
|
||||
fmt.Fprintf(buf, "Base version: %s (%s)\n", baseVersion, r.base.versionQuery)
|
||||
}
|
||||
|
||||
if r.versionInvalid != nil {
|
||||
fmt.Fprintln(buf, r.versionInvalid)
|
||||
} else if r.release.versionInferred {
|
||||
if r.release.tagPrefix == "" {
|
||||
fmt.Fprintf(buf, "Suggested version: %s\n", r.release.version)
|
||||
} else {
|
||||
fmt.Fprintf(buf, "Suggested version: %[1]s (with tag %[2]s%[1]s)\n", r.release.version, r.release.tagPrefix)
|
||||
}
|
||||
} else if r.release.version != "" {
|
||||
if r.release.tagPrefix == "" {
|
||||
fmt.Fprintf(buf, "%s is a valid semantic version for this release.\n", r.release.version)
|
||||
|
||||
if semver.Compare(r.release.version, "v0.0.0-99999999999999-zzzzzzzzzzzz") < 0 {
|
||||
fmt.Fprintf(buf, `Note: %s sorts lower in MVS than pseudo-versions, which may be
|
||||
unexpected for users. So, it may be better to choose a different suffix.`, r.release.version)
|
||||
}
|
||||
} else {
|
||||
fmt.Fprintf(buf, "%[1]s (with tag %[2]s%[1]s) is a valid semantic version for this release\n", r.release.version, r.release.tagPrefix)
|
||||
}
|
||||
}
|
||||
|
||||
if r.versionInvalid == nil && r.haveBaseErrors {
|
||||
fmt.Fprintln(buf, "Errors were found in the base version. Some API changes may be omitted.")
|
||||
}
|
||||
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
func (r *report) addPackage(p packageReport) {
|
||||
r.packages = append(r.packages, p)
|
||||
if len(p.baseErrors) == 0 && len(p.releaseErrors) == 0 {
|
||||
// Only count compatible and incompatible changes if there were no errors.
|
||||
// When there are errors, definitions may be missing, and fixes may appear
|
||||
// incompatible when they are not. Changes will still be reported, but
|
||||
// they won't affect version validation or suggestions.
|
||||
for _, c := range p.Changes {
|
||||
if !c.Compatible && len(p.releaseErrors) == 0 {
|
||||
r.haveIncompatibleChanges = true
|
||||
} else if c.Compatible && len(p.baseErrors) == 0 && len(p.releaseErrors) == 0 {
|
||||
r.haveCompatibleChanges = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(p.baseErrors) > 0 {
|
||||
r.haveBaseErrors = true
|
||||
}
|
||||
if len(p.releaseErrors) > 0 {
|
||||
r.haveReleaseErrors = true
|
||||
}
|
||||
}
|
||||
|
||||
// validateReleaseVersion checks whether r.release.version is valid.
|
||||
// If r.release.version is not valid, an error is returned explaining why.
|
||||
// r.release.version must be set.
|
||||
func (r *report) validateReleaseVersion() {
|
||||
if r.release.version == "" {
|
||||
panic("validateVersion called without version")
|
||||
}
|
||||
setNotValid := func(format string, args ...interface{}) {
|
||||
r.versionInvalid = &versionMessage{
|
||||
message: fmt.Sprintf("%s is not a valid semantic version for this release.", r.release.version),
|
||||
reason: fmt.Sprintf(format, args...),
|
||||
}
|
||||
}
|
||||
|
||||
if r.haveReleaseErrors {
|
||||
if r.haveReleaseErrors {
|
||||
setNotValid("Errors were found in one or more packages.")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(jayconrod): link to documentation for all of these errors.
|
||||
|
||||
// Check that the major version matches the module path.
|
||||
_, suffix, ok := module.SplitPathVersion(r.release.modPath)
|
||||
if !ok {
|
||||
setNotValid("%s: could not find version suffix in module path", r.release.modPath)
|
||||
return
|
||||
}
|
||||
if suffix != "" {
|
||||
if suffix[0] != '/' && suffix[0] != '.' {
|
||||
setNotValid("%s: unknown module path version suffix: %q", r.release.modPath, suffix)
|
||||
return
|
||||
}
|
||||
pathMajor := suffix[1:]
|
||||
major := semver.Major(r.release.version)
|
||||
if pathMajor != major {
|
||||
setNotValid(`The major version %s does not match the major version suffix
|
||||
in the module path: %s`, major, r.release.modPath)
|
||||
return
|
||||
}
|
||||
} else if major := semver.Major(r.release.version); major != "v0" && major != "v1" {
|
||||
setNotValid(`The module path does not end with the major version suffix /%s,
|
||||
which is required for major versions v2 or greater.`, major)
|
||||
return
|
||||
}
|
||||
|
||||
for _, v := range r.base.existingVersions {
|
||||
if semver.Compare(v, r.release.version) == 0 {
|
||||
setNotValid("version %s already exists", v)
|
||||
}
|
||||
}
|
||||
|
||||
// Check that compatible / incompatible changes are consistent.
|
||||
if semver.Major(r.base.version) == "v0" || r.base.modPath != r.release.modPath {
|
||||
return
|
||||
}
|
||||
if r.haveIncompatibleChanges {
|
||||
setNotValid("There are incompatible changes.")
|
||||
return
|
||||
}
|
||||
if r.haveCompatibleChanges && semver.MajorMinor(r.base.version) == semver.MajorMinor(r.release.version) {
|
||||
setNotValid(`There are compatible changes, but the minor version is not incremented
|
||||
over the base version (%s).`, r.base.version)
|
||||
return
|
||||
}
|
||||
|
||||
if r.release.highestTransitiveVersion != "" && semver.Compare(r.release.highestTransitiveVersion, r.release.version) > 0 {
|
||||
setNotValid(`Module indirectly depends on a higher version of itself (%s).
|
||||
`, r.release.highestTransitiveVersion)
|
||||
}
|
||||
}
|
||||
|
||||
// suggestReleaseVersion suggests a new version consistent with observed
|
||||
// changes.
|
||||
func (r *report) suggestReleaseVersion() {
|
||||
setNotValid := func(format string, args ...interface{}) {
|
||||
r.versionInvalid = &versionMessage{
|
||||
message: "Cannot suggest a release version.",
|
||||
reason: fmt.Sprintf(format, args...),
|
||||
}
|
||||
}
|
||||
setVersion := func(v string) {
|
||||
r.release.version = v
|
||||
r.release.versionInferred = true
|
||||
}
|
||||
|
||||
if r.base.modPath != r.release.modPath {
|
||||
setNotValid("Base module path is different from release.")
|
||||
return
|
||||
}
|
||||
|
||||
if r.haveReleaseErrors || r.haveBaseErrors {
|
||||
setNotValid("Errors were found.")
|
||||
return
|
||||
}
|
||||
|
||||
var major, minor, patch, pre string
|
||||
if r.base.version != "none" {
|
||||
minVersion := r.base.version
|
||||
if r.release.highestTransitiveVersion != "" && semver.Compare(r.release.highestTransitiveVersion, minVersion) > 0 {
|
||||
setNotValid("Module indirectly depends on a higher version of itself (%s) than the base version (%s).", r.release.highestTransitiveVersion, r.base.version)
|
||||
return
|
||||
}
|
||||
|
||||
var err error
|
||||
major, minor, patch, pre, _, err = parseVersion(minVersion)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("could not parse base version: %v", err))
|
||||
}
|
||||
}
|
||||
|
||||
if r.haveIncompatibleChanges && r.base.version != "none" && pre == "" && major != "0" {
|
||||
setNotValid("Incompatible changes were detected.")
|
||||
return
|
||||
// TODO(jayconrod): briefly explain how to prepare major version releases
|
||||
// and link to documentation.
|
||||
}
|
||||
|
||||
// Check whether we're comparing to the latest version of base.
|
||||
//
|
||||
// This could happen further up, but we want the more pressing errors above
|
||||
// to take precedence.
|
||||
var latestForBaseMajor string
|
||||
for _, v := range r.base.existingVersions {
|
||||
if semver.Major(v) != semver.Major(r.base.version) {
|
||||
continue
|
||||
}
|
||||
if latestForBaseMajor == "" || semver.Compare(latestForBaseMajor, v) < 0 {
|
||||
latestForBaseMajor = v
|
||||
}
|
||||
}
|
||||
if latestForBaseMajor != "" && latestForBaseMajor != r.base.version {
|
||||
setNotValid(fmt.Sprintf("Can only suggest a release version when compared against the most recent version of this major: %s.", latestForBaseMajor))
|
||||
return
|
||||
}
|
||||
|
||||
if r.base.version == "none" {
|
||||
if _, pathMajor, ok := module.SplitPathVersion(r.release.modPath); !ok {
|
||||
panic(fmt.Sprintf("could not parse module path %q", r.release.modPath))
|
||||
} else if pathMajor == "" {
|
||||
setVersion("v0.1.0")
|
||||
} else {
|
||||
setVersion(pathMajor[1:] + ".0.0")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if pre != "" {
|
||||
// suggest non-prerelease version
|
||||
} else if r.haveCompatibleChanges || (r.haveIncompatibleChanges && major == "0") || r.requirementsChanged() {
|
||||
minor = incDecimal(minor)
|
||||
patch = "0"
|
||||
} else {
|
||||
patch = incDecimal(patch)
|
||||
}
|
||||
setVersion(fmt.Sprintf("v%s.%s.%s", major, minor, patch))
|
||||
return
|
||||
}
|
||||
|
||||
// canVerifyReleaseVersion returns true if we can safely suggest a new version
|
||||
// or if we can verify the version passed in with -version is safe to tag.
|
||||
func (r *report) canVerifyReleaseVersion() bool {
|
||||
// For now, return true if the base and release module paths are the same,
|
||||
// ignoring the major version suffix.
|
||||
// TODO(#37562, #39192, #39666, #40267): there are many more situations when
|
||||
// we can't verify a new version.
|
||||
basePath := strings.TrimSuffix(r.base.modPath, r.base.modPathMajor)
|
||||
releasePath := strings.TrimSuffix(r.release.modPath, r.release.modPathMajor)
|
||||
return basePath == releasePath
|
||||
}
|
||||
|
||||
// requirementsChanged reports whether requirements have changed from base to
|
||||
// version.
|
||||
//
|
||||
// requirementsChanged reports true for,
|
||||
// - A requirement was upgraded to a higher minor version.
|
||||
// - A requirement was added.
|
||||
// - The version of Go was incremented.
|
||||
//
|
||||
// It does not report true when, for example, a requirement was downgraded or
|
||||
// remove. We care more about the former since that might force dependent
|
||||
// modules that have the same dependency to upgrade.
|
||||
func (r *report) requirementsChanged() bool {
|
||||
if r.base.goModFile == nil {
|
||||
// There wasn't a modfile before, and now there is.
|
||||
return true
|
||||
}
|
||||
|
||||
// baseReqs is a map of module path to MajorMinor of the base module
|
||||
// requirements.
|
||||
baseReqs := make(map[string]string)
|
||||
for _, r := range r.base.goModFile.Require {
|
||||
baseReqs[r.Mod.Path] = r.Mod.Version
|
||||
}
|
||||
|
||||
for _, r := range r.release.goModFile.Require {
|
||||
if _, ok := baseReqs[r.Mod.Path]; !ok {
|
||||
// A module@version was added to the "require" block between base
|
||||
// and release.
|
||||
return true
|
||||
}
|
||||
if semver.Compare(semver.MajorMinor(r.Mod.Version), semver.MajorMinor(baseReqs[r.Mod.Path])) > 0 {
|
||||
// The version of r.Mod.Path increased from base to release.
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
if r.release.goModFile.Go != nil && r.base.goModFile.Go != nil {
|
||||
if r.release.goModFile.Go.Version > r.base.goModFile.Go.Version {
|
||||
// The Go version increased from base to release.
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// isSuccessful returns true the module appears to be safe to release at the
|
||||
// proposed or suggested version.
|
||||
func (r *report) isSuccessful() bool {
|
||||
return len(r.release.diagnostics) == 0 && r.versionInvalid == nil
|
||||
}
|
||||
|
||||
type versionMessage struct {
|
||||
message, reason string
|
||||
}
|
||||
|
||||
func (m versionMessage) String() string {
|
||||
return m.message + "\n" + m.reason + "\n"
|
||||
}
|
||||
|
||||
// incDecimal returns the decimal string incremented by 1.
|
||||
func incDecimal(decimal string) string {
|
||||
// Scan right to left turning 9s to 0s until you find a digit to increment.
|
||||
digits := []byte(decimal)
|
||||
i := len(digits) - 1
|
||||
for ; i >= 0 && digits[i] == '9'; i-- {
|
||||
digits[i] = '0'
|
||||
}
|
||||
if i >= 0 {
|
||||
digits[i]++
|
||||
} else {
|
||||
// digits is all zeros
|
||||
digits[0] = '1'
|
||||
digits = append(digits, '0')
|
||||
}
|
||||
return string(digits)
|
||||
}
|
||||
|
||||
type packageReport struct {
|
||||
apidiff.Report
|
||||
path string
|
||||
baseErrors, releaseErrors []packages.Error
|
||||
}
|
||||
|
||||
func (p *packageReport) String() string {
|
||||
if len(p.Changes) == 0 && len(p.baseErrors) == 0 && len(p.releaseErrors) == 0 {
|
||||
return ""
|
||||
}
|
||||
buf := &strings.Builder{}
|
||||
fmt.Fprintf(buf, "# %s\n", p.path)
|
||||
if len(p.baseErrors) > 0 {
|
||||
fmt.Fprintf(buf, "## errors in base version:\n")
|
||||
for _, e := range p.baseErrors {
|
||||
fmt.Fprintln(buf, e)
|
||||
}
|
||||
buf.WriteByte('\n')
|
||||
}
|
||||
if len(p.releaseErrors) > 0 {
|
||||
fmt.Fprintf(buf, "## errors in release version:\n")
|
||||
for _, e := range p.releaseErrors {
|
||||
fmt.Fprintln(buf, e)
|
||||
}
|
||||
buf.WriteByte('\n')
|
||||
}
|
||||
if len(p.Changes) > 0 {
|
||||
var compatible, incompatible []apidiff.Change
|
||||
for _, c := range p.Changes {
|
||||
if c.Compatible {
|
||||
compatible = append(compatible, c)
|
||||
} else {
|
||||
incompatible = append(incompatible, c)
|
||||
}
|
||||
}
|
||||
if len(incompatible) > 0 {
|
||||
fmt.Fprintf(buf, "## incompatible changes\n")
|
||||
for _, c := range incompatible {
|
||||
fmt.Fprintln(buf, c.Message)
|
||||
}
|
||||
}
|
||||
if len(compatible) > 0 {
|
||||
fmt.Fprintf(buf, "## compatible changes\n")
|
||||
for _, c := range compatible {
|
||||
fmt.Fprintln(buf, c.Message)
|
||||
}
|
||||
}
|
||||
buf.WriteByte('\n')
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// parseVersion returns the major, minor, and patch numbers, prerelease text,
|
||||
// and metadata for a given version.
|
||||
//
|
||||
// TODO(jayconrod): extend semver to do this and delete this function.
|
||||
func parseVersion(vers string) (major, minor, patch, pre, meta string, err error) {
|
||||
if !strings.HasPrefix(vers, "v") {
|
||||
return "", "", "", "", "", fmt.Errorf("version %q does not start with 'v'", vers)
|
||||
}
|
||||
base := vers[1:]
|
||||
if i := strings.IndexByte(base, '+'); i >= 0 {
|
||||
meta = base[i+1:]
|
||||
base = base[:i]
|
||||
}
|
||||
if i := strings.IndexByte(base, '-'); i >= 0 {
|
||||
pre = base[i+1:]
|
||||
base = base[:i]
|
||||
}
|
||||
parts := strings.Split(base, ".")
|
||||
if len(parts) != 3 {
|
||||
return "", "", "", "", "", fmt.Errorf("version %q should have three numbers", vers)
|
||||
}
|
||||
major, minor, patch = parts[0], parts[1], parts[2]
|
||||
return major, minor, patch, pre, meta, nil
|
||||
}
|
||||
Vendored
+95
@@ -0,0 +1,95 @@
|
||||
This directory contains most tests for gorelease. Each test runs gorelease (the
|
||||
`runRelease` function) with a given set of flags in a temporary directory
|
||||
populated with files specified in the test itself or files from the module test
|
||||
proxy. The output is compared against a golden `want` file specified in the
|
||||
test.
|
||||
|
||||
## Test flags
|
||||
|
||||
A specific test may be run with a command like:
|
||||
|
||||
go test -run=TestRelease/basic/v0_patch_suggest
|
||||
|
||||
where `basic/v0_patch_suggest` matches the file
|
||||
`testdata/basic/v0_patch_suggest.test`.
|
||||
|
||||
The `-u` flag adds or updates the `want` file in each test to match the output.
|
||||
This is useful for fixing tests after an intended change in behavior.
|
||||
|
||||
go test -run=TestRelease/basic/v0_patch_suggest -u
|
||||
|
||||
The `-testwork` flag instructs the test framework to leave the test's temporary
|
||||
directory and module proxy in place after running the test. This is useful
|
||||
for debugging.
|
||||
|
||||
## Test format
|
||||
|
||||
Tests are written in `.test` files in `testdata` subdirectories. Each `.test`
|
||||
file is a valid txtar file (see `golang.org/x/tools/txtar`). The comment section
|
||||
contains the test parameters, which are a series of `key=value` pairs. Blank
|
||||
lines and comments starting with `#` are allowed in this section. Valid keys
|
||||
are:
|
||||
|
||||
* `mod`: sets the module path. Must be specified together with `version`. Copies
|
||||
the content of a module out of the test proxy into a temporary directory
|
||||
where `gorelease` is run.
|
||||
* `version`: specified together with `mod`, it sets the version to retrieve from
|
||||
the test proxy. See more information below.
|
||||
* `base`: the value of the `-base` flag passed to `gorelease`.
|
||||
* `release`: the value of the `-version` flag passed to `gorelease`.
|
||||
* `dir`: the directory where `gorelease` should be invoked. Useful when the test
|
||||
describes a whole repository, and `gorelease` should be invoked in a
|
||||
subdirectory.
|
||||
* `error`: true if the test expects a hard error. False by default.
|
||||
* `success`: true if the test expects a report to be printed with no errors
|
||||
or diagnostics. True by default.
|
||||
* `skip`: non-empty if the test should be skipped. The value is a string passed
|
||||
to `t.Skip`.
|
||||
* `proxyVersions`: empty if the test should include all `mod/` entries in the
|
||||
proxy, or else a comma-separated list of the modpath@version's it should
|
||||
include.
|
||||
|
||||
Test archives have a file named `want`, containing the expected output of the
|
||||
test. A test will fail if the actual output differs from `want`.
|
||||
|
||||
If the `mod` and `version` parameters are not set, other files will be extracted
|
||||
to the temporary directory where `gorelease` runs.
|
||||
|
||||
## Populating module contents
|
||||
|
||||
When building a test in `testdata/`, there are two ways to populate the module
|
||||
directory being tested:
|
||||
|
||||
### Option 1: inline
|
||||
|
||||
You can inline files in the `.test` folder as described in
|
||||
https://pkg.go.dev/golang.org/x/tools/txtar#hdr-Txtar_format. For example,
|
||||
|
||||
```
|
||||
-- some.file --
|
||||
the contents
|
||||
of the file
|
||||
```
|
||||
|
||||
### Option 2: specify an existing file
|
||||
|
||||
Often, multiple tests want to share the same setup - the same files. So, users
|
||||
can write these common files in `testdata/mod/`, and use one of these files as
|
||||
the module directory contents.
|
||||
|
||||
To specify a file in `testdata/mod/` to use as the module contents.
|
||||
|
||||
## Module format
|
||||
|
||||
Tests run with `GOPROXY` set to a local URL that points to a test proxy. The
|
||||
test proxy serves modules described by `.txt` files in the `testdata/mod/`
|
||||
subdirectory.
|
||||
|
||||
Each module is a txtar archive named `$modpath_$version.txt` where `$modpath`
|
||||
is the module path (with slashes replaced with underscores) and `$version` is
|
||||
the version. If the archive contains a file named `.mod`, that will be used to
|
||||
respond to `.mod` requests; otherwise, `go.mod` will be used (`.mod` is only
|
||||
necessary for modules that lack `go.mod` files). If the archive contains a
|
||||
file named `.info`, that will be used to respond to `.info` requests; otherwise,
|
||||
`.info` is synthesized from the version. All other files in the archive are
|
||||
packed into a `.zip` file to satisfy `.zip` requests.
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
mod=example.com/basic
|
||||
base=v1.0.1
|
||||
success=false
|
||||
# A() was removed, which is a breaking change: it shouldn't try to suggest a
|
||||
# higher version.
|
||||
-- want --
|
||||
# example.com/basic/a
|
||||
## incompatible changes
|
||||
A: removed
|
||||
## compatible changes
|
||||
B: added
|
||||
|
||||
# summary
|
||||
Cannot suggest a release version.
|
||||
Incompatible changes were detected.
|
||||
-- go.mod --
|
||||
module example.com/basic
|
||||
|
||||
go 1.12
|
||||
-- a/a.go --
|
||||
package a
|
||||
|
||||
func B() int { return 0 }
|
||||
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
mod=example.com/basic
|
||||
base=v0.0.1
|
||||
success=false
|
||||
# B() was added, so now it should suggest a new minor version. But, there's a
|
||||
# later version that already exists: so it should not try to suggest anything at
|
||||
# all.
|
||||
-- want --
|
||||
# example.com/basic/a
|
||||
## compatible changes
|
||||
B: added
|
||||
|
||||
# summary
|
||||
Cannot suggest a release version.
|
||||
Can only suggest a release version when compared against the most recent version of this major: v0.1.2.
|
||||
-- go.mod --
|
||||
module example.com/basic
|
||||
|
||||
go 1.12
|
||||
-- a/a.go --
|
||||
package a
|
||||
|
||||
func A() int { return 0 }
|
||||
func B() int { return 0 }
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
mod=example.com/basic
|
||||
base=v0.1.0
|
||||
success=false
|
||||
# A() was changed in a small way, so now it should suggest a new patch version.
|
||||
# But, there's a later version that already exists: so it should not try to
|
||||
# suggest anything at all.
|
||||
-- want --
|
||||
# summary
|
||||
Cannot suggest a release version.
|
||||
Can only suggest a release version when compared against the most recent version of this major: v0.1.2.
|
||||
-- go.mod --
|
||||
module example.com/basic
|
||||
|
||||
go 1.12
|
||||
-- a/a.go --
|
||||
package a
|
||||
|
||||
func A() int { return 1 }
|
||||
func A2() int { return 2 }
|
||||
-- b/b.go --
|
||||
package b
|
||||
|
||||
func B() int { return 3 }
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
mod=example.com/basic
|
||||
base=v0.0.1
|
||||
release=v0.1.0
|
||||
success=false
|
||||
# The contents below are a copy of the v0.0.1 contents - nothing has changed.
|
||||
# But v0.1.0 already exists, so it should present a diagnostic.
|
||||
-- want --
|
||||
# summary
|
||||
v0.1.0 is not a valid semantic version for this release.
|
||||
version v0.1.0 already exists
|
||||
-- go.mod --
|
||||
module example.com/basic
|
||||
|
||||
go 1.12
|
||||
-- a/a.go --
|
||||
package a
|
||||
|
||||
func A() int { return 0 }
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
Module example.com/basic tests basic functionality of gorelease.
|
||||
It verifies that versions are correctly suggested or verified after
|
||||
various changes.
|
||||
|
||||
All revisions are stored in the mod directory. The same series of
|
||||
changes is made across three major versions, v0, v1, and v2:
|
||||
|
||||
vX.0.1 - simple package
|
||||
vX.1.0 - compatible change: add a function and a package
|
||||
vX.1.1 - internal change: function returns different value
|
||||
vX.1.2 - incompatible change: delete a function and a package
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
mod=example.com/basic
|
||||
version=v0.1.0
|
||||
base=v0.0.1
|
||||
proxyVersions=example.com/basic@v0.0.1
|
||||
-- want --
|
||||
# example.com/basic/a
|
||||
## compatible changes
|
||||
A2: added
|
||||
|
||||
# example.com/basic/b
|
||||
## compatible changes
|
||||
package added
|
||||
|
||||
# summary
|
||||
Suggested version: v0.1.0
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
mod=example.com/basic
|
||||
version=v0.1.0
|
||||
base=v0.0.1
|
||||
proxyVersions=example.com/basic@v0.0.1
|
||||
vcs=git
|
||||
-- want --
|
||||
# example.com/basic/a
|
||||
## compatible changes
|
||||
A2: added
|
||||
|
||||
# example.com/basic/b
|
||||
## compatible changes
|
||||
package added
|
||||
|
||||
# summary
|
||||
Suggested version: v0.1.0
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
mod=example.com/basic
|
||||
version=v0.1.0
|
||||
base=v0.0.1
|
||||
proxyVersions=example.com/basic@v0.0.1
|
||||
vcs=hg
|
||||
-- want --
|
||||
# example.com/basic/a
|
||||
## compatible changes
|
||||
A2: added
|
||||
|
||||
# example.com/basic/b
|
||||
## compatible changes
|
||||
package added
|
||||
|
||||
# summary
|
||||
Suggested version: v0.1.0
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
mod=example.com/basic
|
||||
version=v0.1.0
|
||||
base=v0.0.1
|
||||
release=v0.1.0
|
||||
proxyVersions=example.com/basic@v0.0.1
|
||||
-- want --
|
||||
# example.com/basic/a
|
||||
## compatible changes
|
||||
A2: added
|
||||
|
||||
# example.com/basic/b
|
||||
## compatible changes
|
||||
package added
|
||||
|
||||
# summary
|
||||
v0.1.0 is a valid semantic version for this release.
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
mod=example.com/basic
|
||||
version=v0.1.2
|
||||
base=v0.1.1
|
||||
proxyVersions=example.com/basic@v0.1.1
|
||||
-- want --
|
||||
# example.com/basic/a
|
||||
## incompatible changes
|
||||
A2: removed
|
||||
|
||||
# example.com/basic/b
|
||||
## incompatible changes
|
||||
package removed
|
||||
|
||||
# summary
|
||||
Suggested version: v0.2.0
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
mod=example.com/basic
|
||||
version=v0.1.2
|
||||
base=v0.1.1
|
||||
release=v0.1.2
|
||||
proxyVersions=example.com/basic@v0.1.1
|
||||
-- want --
|
||||
# example.com/basic/a
|
||||
## incompatible changes
|
||||
A2: removed
|
||||
|
||||
# example.com/basic/b
|
||||
## incompatible changes
|
||||
package removed
|
||||
|
||||
# summary
|
||||
v0.1.2 is a valid semantic version for this release.
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
mod=example.com/basic
|
||||
version=v0.1.1
|
||||
base=none
|
||||
-- want --
|
||||
# summary
|
||||
Suggested version: v0.1.0
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
mod=example.com/basic
|
||||
version=v0.1.1
|
||||
base=v0.1.0
|
||||
proxyVersions=example.com/basic@v0.1.0
|
||||
-- want --
|
||||
# summary
|
||||
Suggested version: v0.1.1
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
mod=example.com/basic
|
||||
version=v0.1.1
|
||||
base=v0.1.0
|
||||
release=v0.1.1
|
||||
proxyVersions=example.com/basic@v0.1.0
|
||||
-- want --
|
||||
# summary
|
||||
v0.1.1 is a valid semantic version for this release.
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
mod=example.com/basic
|
||||
version=v0.1.2
|
||||
base=v0.1.1-pre
|
||||
proxyVersions=example.com/basic@v0.1.1-pre
|
||||
-- want --
|
||||
# example.com/basic/a
|
||||
## incompatible changes
|
||||
A2: removed
|
||||
|
||||
# example.com/basic/b
|
||||
## incompatible changes
|
||||
package removed
|
||||
|
||||
# summary
|
||||
Suggested version: v0.1.1
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
mod=example.com/basic
|
||||
version=v0.1.2
|
||||
base=v0.1.1
|
||||
release=v1.0.0
|
||||
-- want --
|
||||
# example.com/basic/a
|
||||
## incompatible changes
|
||||
A2: removed
|
||||
|
||||
# example.com/basic/b
|
||||
## incompatible changes
|
||||
package removed
|
||||
|
||||
# summary
|
||||
v1.0.0 is a valid semantic version for this release.
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
mod=example.com/basic
|
||||
version=v0.1.2
|
||||
-- want --
|
||||
# summary
|
||||
Inferred base version: v1.1.2
|
||||
Suggested version: v1.1.3
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
mod=example.com/basic
|
||||
version=v1.0.1
|
||||
release=v1.0.2
|
||||
-- want --
|
||||
# summary
|
||||
Inferred base version: v1.0.1
|
||||
v1.0.2 is a valid semantic version for this release.
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
mod=example.com/basic
|
||||
version=v1.1.0
|
||||
base=v1.0.1
|
||||
proxyVersions=example.com/basic@v1.0.1
|
||||
-- want --
|
||||
# example.com/basic/a
|
||||
## compatible changes
|
||||
A2: added
|
||||
|
||||
# example.com/basic/b
|
||||
## compatible changes
|
||||
package added
|
||||
|
||||
# summary
|
||||
Suggested version: v1.1.0
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
mod=example.com/basic
|
||||
version=v1.1.0
|
||||
base=v1.0.1
|
||||
release=v1.1.0
|
||||
proxyVersions=example.com/basic@v1.0.1
|
||||
-- want --
|
||||
# example.com/basic/a
|
||||
## compatible changes
|
||||
A2: added
|
||||
|
||||
# example.com/basic/b
|
||||
## compatible changes
|
||||
package added
|
||||
|
||||
# summary
|
||||
v1.1.0 is a valid semantic version for this release.
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
mod=example.com/basicfork
|
||||
base=example.com/basic@v1.1.1
|
||||
version=v1.1.2
|
||||
release=v1.1.2
|
||||
proxyVersions=example.com/basic@v1.1.1
|
||||
-- want --
|
||||
# example.com/basic/a
|
||||
## incompatible changes
|
||||
A2: removed
|
||||
## compatible changes
|
||||
A3: added
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
# Compare a fork (with module path example.com/basic, downloaded from
|
||||
# example.com/basicfork) with a local module (with module path
|
||||
# example.com/basic).
|
||||
mod=example.com/basic
|
||||
version=v1.1.2
|
||||
base=example.com/basicfork@v1.1.2
|
||||
release=v1.1.3
|
||||
-- want --
|
||||
# example.com/basicfork/a
|
||||
## incompatible changes
|
||||
A3: removed
|
||||
|
||||
# example.com/basicfork/b
|
||||
## incompatible changes
|
||||
package removed
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
mod=example.com/basic
|
||||
version=v1.1.2
|
||||
base=v1.1.1
|
||||
success=false
|
||||
-- want --
|
||||
# example.com/basic/a
|
||||
## incompatible changes
|
||||
A2: removed
|
||||
|
||||
# example.com/basic/b
|
||||
## incompatible changes
|
||||
package removed
|
||||
|
||||
# summary
|
||||
Cannot suggest a release version.
|
||||
Incompatible changes were detected.
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
mod=example.com/basic
|
||||
version=v1.1.2
|
||||
base=v1.1.1
|
||||
success=false
|
||||
release=v1.1.2
|
||||
proxyVersions=example.com/basic@v1.1.1
|
||||
-- want --
|
||||
# example.com/basic/a
|
||||
## incompatible changes
|
||||
A2: removed
|
||||
|
||||
# example.com/basic/b
|
||||
## incompatible changes
|
||||
package removed
|
||||
|
||||
# summary
|
||||
v1.1.2 is not a valid semantic version for this release.
|
||||
There are incompatible changes.
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
mod=example.com/basic
|
||||
version=v1.1.1
|
||||
base=v1.1.0
|
||||
proxyVersions=example.com/basic@v1.1.0
|
||||
-- want --
|
||||
# summary
|
||||
Suggested version: v1.1.1
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
mod=example.com/basic
|
||||
version=v1.1.1
|
||||
base=v1.1.0
|
||||
release=v1.1.1
|
||||
proxyVersions=example.com/basic@v1.1.0
|
||||
-- want --
|
||||
# summary
|
||||
v1.1.1 is a valid semantic version for this release.
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
mod=example.com/basic
|
||||
version=v1.1.2
|
||||
base=v1.1.1-pre
|
||||
proxyVersions=example.com/basic@v1.1.1-pre
|
||||
-- want --
|
||||
# example.com/basic/a
|
||||
## incompatible changes
|
||||
A2: removed
|
||||
|
||||
# example.com/basic/b
|
||||
## incompatible changes
|
||||
package removed
|
||||
|
||||
# summary
|
||||
Suggested version: v1.1.1
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
mod=example.com/basic
|
||||
version=v1.0.1
|
||||
release=v1.0.1
|
||||
base=>v1.0.1
|
||||
error=true
|
||||
-- want --
|
||||
base version v1.1.0 (>v1.0.1) must be lower than release version v1.0.1
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
mod=example.com/basic
|
||||
version=v1.0.1
|
||||
base=version-1.0.1
|
||||
proxyVersions=example.com/basic@version-1.0.1,example.com/basic@v1.0.1
|
||||
-- want --
|
||||
# summary
|
||||
Base version: v1.0.1 (version-1.0.1)
|
||||
Suggested version: v1.0.2
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
mod=example.com/basic
|
||||
version=v1.0.1
|
||||
base=version-1.0.1
|
||||
release=v1.0.2
|
||||
-- want --
|
||||
# summary
|
||||
Base version: v1.0.1 (version-1.0.1)
|
||||
v1.0.2 is a valid semantic version for this release.
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
mod=example.com/basic/v2
|
||||
base=example.com/basic@>=v1.1.0
|
||||
version=v2.0.1
|
||||
release=v2.0.1
|
||||
proxyVersions=example.com/basic@v1.1.0
|
||||
-- want --
|
||||
# example.com/basic/a
|
||||
## incompatible changes
|
||||
A2: removed
|
||||
|
||||
# example.com/basic/b
|
||||
## incompatible changes
|
||||
package removed
|
||||
|
||||
# summary
|
||||
Base version: example.com/basic@v1.1.0 (>=v1.1.0)
|
||||
v2.0.1 is a valid semantic version for this release.
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
mod=example.com/basic/v2
|
||||
base=example.com/basic
|
||||
version=v2.1.0
|
||||
success=false
|
||||
-- want --
|
||||
# example.com/basic/a
|
||||
## compatible changes
|
||||
A2: added
|
||||
|
||||
# example.com/basic/v2/b
|
||||
## compatible changes
|
||||
package added
|
||||
|
||||
# summary
|
||||
Inferred base version: example.com/basic@v1.1.2
|
||||
Cannot suggest a release version.
|
||||
Base module path is different from release.
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
mod=example.com/basic/v2
|
||||
base=example.com/basic
|
||||
version=v2.1.0
|
||||
release=v2.1.0
|
||||
proxyVersions=example.com/basic@v1.1.2
|
||||
-- want --
|
||||
# example.com/basic/a
|
||||
## compatible changes
|
||||
A2: added
|
||||
|
||||
# example.com/basic/v2/b
|
||||
## compatible changes
|
||||
package added
|
||||
|
||||
# summary
|
||||
Inferred base version: example.com/basic@v1.1.2
|
||||
v2.1.0 is a valid semantic version for this release.
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
mod=example.com/basic/v2
|
||||
base=example.com/basic@v1.1.0
|
||||
version=v2.0.1
|
||||
release=v2.0.1
|
||||
proxyVersions=example.com/basic@v1.1.0
|
||||
-- want --
|
||||
# example.com/basic/a
|
||||
## incompatible changes
|
||||
A2: removed
|
||||
|
||||
# example.com/basic/b
|
||||
## incompatible changes
|
||||
package removed
|
||||
|
||||
# summary
|
||||
v2.0.1 is a valid semantic version for this release.
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
mod=example.com/basic/v2
|
||||
base=v1.1.0
|
||||
version=v2.1.0
|
||||
success=false
|
||||
-- want --
|
||||
# summary
|
||||
Cannot suggest a release version.
|
||||
Base module path is different from release.
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
mod=example.com/basic/v2
|
||||
base=v1.1.0
|
||||
version=v2.1.0
|
||||
release=v2.1.0
|
||||
-- want --
|
||||
# summary
|
||||
v2.1.0 is a valid semantic version for this release.
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
mod=example.com/basic
|
||||
version=v1.1.2
|
||||
base=v1.1.1
|
||||
release=v2.0.0
|
||||
success=false
|
||||
-- want --
|
||||
# example.com/basic/a
|
||||
## incompatible changes
|
||||
A2: removed
|
||||
|
||||
# example.com/basic/b
|
||||
## incompatible changes
|
||||
package removed
|
||||
|
||||
# summary
|
||||
v2.0.0 is not a valid semantic version for this release.
|
||||
The module path does not end with the major version suffix /v2,
|
||||
which is required for major versions v2 or greater.
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
mod=example.com/basic/v2
|
||||
version=v2.1.0
|
||||
base=v2.0.1
|
||||
proxyVersions=example.com/basic/v2@v2.0.1
|
||||
-- want --
|
||||
# example.com/basic/v2/a
|
||||
## compatible changes
|
||||
A2: added
|
||||
|
||||
# example.com/basic/v2/b
|
||||
## compatible changes
|
||||
package added
|
||||
|
||||
# summary
|
||||
Suggested version: v2.1.0
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
mod=example.com/basic/v2
|
||||
version=v2.1.0
|
||||
base=v2.0.1
|
||||
release=v2.1.0
|
||||
proxyVersions=example.com/basic/v2@v2.0.1
|
||||
-- want --
|
||||
# example.com/basic/v2/a
|
||||
## compatible changes
|
||||
A2: added
|
||||
|
||||
# example.com/basic/v2/b
|
||||
## compatible changes
|
||||
package added
|
||||
|
||||
# summary
|
||||
v2.1.0 is a valid semantic version for this release.
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
mod=example.com/basic/v2
|
||||
version=v2.1.2
|
||||
base=v2.1.1
|
||||
success=false
|
||||
-- want --
|
||||
# example.com/basic/v2/a
|
||||
## incompatible changes
|
||||
A2: removed
|
||||
|
||||
# example.com/basic/v2/b
|
||||
## incompatible changes
|
||||
package removed
|
||||
|
||||
# summary
|
||||
Cannot suggest a release version.
|
||||
Incompatible changes were detected.
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
mod=example.com/basic/v2
|
||||
version=v2.1.2
|
||||
base=v2.1.1
|
||||
success=false
|
||||
release=v2.1.2
|
||||
proxyVersions=example.com/basic/v2@v2.1.1
|
||||
-- want --
|
||||
# example.com/basic/v2/a
|
||||
## incompatible changes
|
||||
A2: removed
|
||||
|
||||
# example.com/basic/v2/b
|
||||
## incompatible changes
|
||||
package removed
|
||||
|
||||
# summary
|
||||
v2.1.2 is not a valid semantic version for this release.
|
||||
There are incompatible changes.
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
mod=example.com/basic/v2
|
||||
version=v2.1.1
|
||||
base=none
|
||||
-- want --
|
||||
# summary
|
||||
Suggested version: v2.0.0
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
mod=example.com/basic/v2
|
||||
version=v2.1.1
|
||||
base=v2.1.0
|
||||
proxyVersions=example.com/basic/v2@v2.1.0
|
||||
-- want --
|
||||
# summary
|
||||
Suggested version: v2.1.1
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
mod=example.com/basic/v2
|
||||
version=v2.1.1
|
||||
base=v2.1.0
|
||||
release=v2.1.1
|
||||
proxyVersions=example.com/basic/v2@v2.1.0
|
||||
-- want --
|
||||
# summary
|
||||
v2.1.1 is a valid semantic version for this release.
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
mod=example.com/basic/v2
|
||||
version=v2.1.2
|
||||
base=v2.1.1-pre
|
||||
proxyVersions=example.com/basic/v2@v2.1.1-pre
|
||||
-- want --
|
||||
# example.com/basic/v2/a
|
||||
## incompatible changes
|
||||
A2: removed
|
||||
|
||||
# example.com/basic/v2/b
|
||||
## incompatible changes
|
||||
package removed
|
||||
|
||||
# summary
|
||||
Suggested version: v2.1.1
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
mod=example.com/basic/v3
|
||||
-- go.mod --
|
||||
module example.com/basic/v3
|
||||
|
||||
go 1.13
|
||||
-- want --
|
||||
# summary
|
||||
Inferred base version: none
|
||||
Suggested version: v3.0.0
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
mod=example.com/basic/v3
|
||||
version=v3.0.0-ignore
|
||||
release=v3.1.0
|
||||
error=true
|
||||
-- want --
|
||||
could not find base version. Consider setting -version=v3.0.0 if this is a first release, or explicitly set -base=none: no versions found lower than v3.1.0
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
mod=example.com/basic/v3
|
||||
version=v3.0.0-ignore
|
||||
release=v3.0.0
|
||||
-- want --
|
||||
# summary
|
||||
Inferred base version: none
|
||||
v3.0.0 is a valid semantic version for this release.
|
||||
go/pkg/mod/golang.org/x/exp@v0.0.0-20230224173230-c95f2b4c22f2/cmd/gorelease/testdata/cgo/README.txt
Vendored
+7
@@ -0,0 +1,7 @@
|
||||
Module example.com/cgo is used to test that packages with cgo code
|
||||
can be loaded without errors when cgo is enabled.
|
||||
|
||||
TODO(jayconrod): test modules with cgo-only and cgo / pure Go implementations
|
||||
with CGO_ENABLED=0 and 1. But first, decide how multiple platforms and
|
||||
build constraints should be handled. Currently, gorelease only considers
|
||||
the same configuration as 'go list'.
|
||||
Vendored
+16
@@ -0,0 +1,16 @@
|
||||
base=none
|
||||
release=v1.0.0
|
||||
-- go.mod --
|
||||
module example.com/cgo
|
||||
|
||||
go 1.13
|
||||
-- c.go --
|
||||
package cgo
|
||||
|
||||
// const int x = 12;
|
||||
import "C"
|
||||
|
||||
func X() int { return int(C.x) }
|
||||
-- want --
|
||||
# summary
|
||||
v1.0.0 is a valid semantic version for this release.
|
||||
+1
@@ -0,0 +1 @@
|
||||
This directory is for tests related to module cycles.
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
mod=example.com/cycle/v2
|
||||
version=v2.0.0
|
||||
-- want --
|
||||
# summary
|
||||
Inferred base version: v2.0.0
|
||||
Suggested version: v2.0.1
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
mod=example.com/cycle
|
||||
base=v1.0.0
|
||||
version=v1.0.0
|
||||
success=false
|
||||
-- want --
|
||||
# summary
|
||||
Cannot suggest a release version.
|
||||
Module indirectly depends on a higher version of itself (v1.5.0) than the base version (v1.0.0).
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
mod=example.com/cycle
|
||||
base=v1.0.0
|
||||
version=v1.0.0
|
||||
release=v1.0.1
|
||||
success=false
|
||||
-- want --
|
||||
# summary
|
||||
v1.0.1 is not a valid semantic version for this release.
|
||||
Module indirectly depends on a higher version of itself (v1.5.0).
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
Module example.com/empty is used to test that gorelease works
|
||||
in a module with no packages.
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
mod=example.com/empty
|
||||
base=v0.0.1
|
||||
version=v0.0.2
|
||||
release=v0.0.2
|
||||
proxyVersions=example.com/empty@v0.0.1
|
||||
-- want --
|
||||
# summary
|
||||
v0.0.2 is a valid semantic version for this release.
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
Tests in this directory check that user errors invoking gorelease
|
||||
are correctly reported.
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
mod=example.com/ziperrors
|
||||
dir=x
|
||||
base=none
|
||||
error=true
|
||||
vcs=git
|
||||
-- want --
|
||||
testdata/this_file_also_has_a_bad_filename'.txt: malformed file path "testdata/this_file_also_has_a_bad_filename'.txt": invalid char '\''
|
||||
testdata/this_file_has_a_bad_filename'.txt: malformed file path "testdata/this_file_has_a_bad_filename'.txt": invalid char '\''
|
||||
-- x/go.mod --
|
||||
module example.com/x
|
||||
|
||||
go 1.12
|
||||
-- x/testdata/this_file_has_a_bad_filename'.txt --
|
||||
-- x/testdata/this_file_also_has_a_bad_filename'.txt --
|
||||
# Verify that errors in submodules are not reported.
|
||||
-- x/y/go.mod --
|
||||
-- x/y/submodule_bad_filename'.txt --
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
mod=example.com/errors
|
||||
base=v0.1.0
|
||||
release=master
|
||||
error=true
|
||||
-- want --
|
||||
usage: gorelease [-base=version] [-version=version]
|
||||
release version "master" is not a canonical semantic version
|
||||
For more information, run go doc golang.org/x/exp/cmd/gorelease
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
mod=example.com/errors
|
||||
base=v0.2.0
|
||||
release=v0.1.0
|
||||
error=true
|
||||
-- want --
|
||||
usage: gorelease [-base=version] [-version=version]
|
||||
base version ("v0.2.0") must be lower than release version ("v0.1.0")
|
||||
For more information, run go doc golang.org/x/exp/cmd/gorelease
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
mod=example.com/basic/v2
|
||||
base=example.com/basic@none
|
||||
error=true
|
||||
-- want --
|
||||
usage: gorelease [-base=version] [-version=version]
|
||||
base version ("example.com/basic@none") cannot have version "none" with explicit module path
|
||||
For more information, run go doc golang.org/x/exp/cmd/gorelease
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
mod=example.com/errors
|
||||
version=v0.2.0
|
||||
base=v0.1.0
|
||||
release=v0.2.0
|
||||
success=false
|
||||
proxyVersions=example.com/errors@v0.1.0
|
||||
-- want --
|
||||
# example.com/errors/added
|
||||
## errors in release version:
|
||||
added/added.go:3:15: undefined: Missing
|
||||
|
||||
## compatible changes
|
||||
package added
|
||||
|
||||
# example.com/errors/broken
|
||||
## errors in release version:
|
||||
broken/broken.go:3:15: undefined: Missing
|
||||
|
||||
## incompatible changes
|
||||
X: value changed from 12 to unknown
|
||||
|
||||
# example.com/errors/deleted
|
||||
## errors in base version:
|
||||
deleted/deleted.go:3:15: undefined: Missing
|
||||
|
||||
## incompatible changes
|
||||
package removed
|
||||
|
||||
# example.com/errors/fixed
|
||||
## errors in base version:
|
||||
fixed/fixed.go:3:15: undefined: Missing
|
||||
|
||||
## incompatible changes
|
||||
X: value changed from unknown to 12
|
||||
|
||||
# summary
|
||||
v0.2.0 is not a valid semantic version for this release.
|
||||
Errors were found in one or more packages.
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
mod=example.com/errors
|
||||
base=v0.1.0
|
||||
release=v0.1.0
|
||||
error=true
|
||||
-- want --
|
||||
usage: gorelease [-base=version] [-version=version]
|
||||
-base and -version must be different
|
||||
For more information, run go doc golang.org/x/exp/cmd/gorelease
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
mod=example.com/errors
|
||||
version=v0.1.0
|
||||
base=upgrade
|
||||
error=true
|
||||
-- want --
|
||||
could not resolve version example.com/errors@upgrade: query is based on requirements in main go.mod file
|
||||
+1
@@ -0,0 +1 @@
|
||||
Module example.com/first is used to test the first tag for a major version.
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
mod=example.com/first
|
||||
base=none
|
||||
release=v0.0.0
|
||||
-- want --
|
||||
# summary
|
||||
v0.0.0 is a valid semantic version for this release.
|
||||
-- go.mod --
|
||||
module example.com/first
|
||||
|
||||
go 1.12
|
||||
-- p.go --
|
||||
package p
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
mod=example.com/first
|
||||
base=none
|
||||
release=v0.0.1
|
||||
-- want --
|
||||
# summary
|
||||
v0.0.1 is a valid semantic version for this release.
|
||||
-- go.mod --
|
||||
module example.com/first
|
||||
|
||||
go 1.12
|
||||
-- p.go --
|
||||
package p
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
mod=example.com/first
|
||||
base=none
|
||||
release=v0.1.0-alpha.1
|
||||
-- want --
|
||||
# summary
|
||||
v0.1.0-alpha.1 is a valid semantic version for this release.
|
||||
-- go.mod --
|
||||
module example.com/first
|
||||
|
||||
go 1.12
|
||||
-- p.go --
|
||||
package p
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
mod=example.com/first
|
||||
base=none
|
||||
release=v0.1.0
|
||||
-- want --
|
||||
# summary
|
||||
v0.1.0 is a valid semantic version for this release.
|
||||
-- go.mod --
|
||||
module example.com/first
|
||||
|
||||
go 1.12
|
||||
-- p.go --
|
||||
package p
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
mod=example.com/first
|
||||
base=none
|
||||
release=v0.0.0
|
||||
success=false
|
||||
|
||||
# TODO(golang.org/issue/36087): go list doesn't report positions in correct
|
||||
# place for scanner errors.
|
||||
skip=packages.Load gives error with extra "-: " prefix
|
||||
-- want --
|
||||
example.com/first
|
||||
-----------------
|
||||
errors in new version:
|
||||
p.go:1:9: illegal character U+003F '?'
|
||||
-- go.mod --
|
||||
module example.com/first
|
||||
|
||||
go 1.12
|
||||
-- p.go --
|
||||
package ?
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
mod=example.com/first
|
||||
base=none
|
||||
release=v1.0.0
|
||||
-- want --
|
||||
# summary
|
||||
v1.0.0 is a valid semantic version for this release.
|
||||
-- go.mod --
|
||||
module example.com/first
|
||||
|
||||
go 1.12
|
||||
-- p.go --
|
||||
package p
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
mod=example.com/first
|
||||
base=none
|
||||
release=v1.2.3
|
||||
proxyVersions=
|
||||
-- want --
|
||||
# summary
|
||||
v1.2.3 is a valid semantic version for this release.
|
||||
-- go.mod --
|
||||
module example.com/first
|
||||
|
||||
go 1.12
|
||||
-- p.go --
|
||||
package p
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
mod=example.com/first/v2
|
||||
base=none
|
||||
release=v2.0.0
|
||||
success=false
|
||||
|
||||
# TODO(golang.org/issue/36087): go list doesn't report positions in correct
|
||||
# place for scanner errors.
|
||||
skip=packages.Load gives error with extra "-: " prefix
|
||||
-- want --
|
||||
example.com/first
|
||||
-----------------
|
||||
errors in new version:
|
||||
p.go:1:9: illegal character U+003F '?'
|
||||
-- go.mod --
|
||||
module example.com/first
|
||||
|
||||
go 1.12
|
||||
-- p.go --
|
||||
package ?
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
mod=example.com/first
|
||||
base=none
|
||||
release=v2.0.0
|
||||
success=false
|
||||
-- want --
|
||||
# summary
|
||||
v2.0.0 is not a valid semantic version for this release.
|
||||
The module path does not end with the major version suffix /v2,
|
||||
which is required for major versions v2 or greater.
|
||||
-- go.mod --
|
||||
module example.com/first
|
||||
|
||||
go 1.12
|
||||
-- p.go --
|
||||
package p
|
||||
go/pkg/mod/golang.org/x/exp@v0.0.0-20230224173230-c95f2b4c22f2/cmd/gorelease/testdata/fix/README.txt
Vendored
+7
@@ -0,0 +1,7 @@
|
||||
Tests in this directory cover scenarios where errors in a package are fixed.
|
||||
|
||||
v1.0.0 is used as the base version for all tests.
|
||||
It has an error: the return type of bad.Broken is undefined.
|
||||
|
||||
Each test fixes the error and may make other changes (compatible or not).
|
||||
Note that fixing a type error in the API appears to be an incompatible change.
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
mod=example.com/fix
|
||||
base=v1.0.0
|
||||
version=v1.1.0-compatible-other
|
||||
success=false
|
||||
-- want --
|
||||
# example.com/fix/bad
|
||||
## errors in base version:
|
||||
bad/bad.go:3:15: undefined: NOTYPE
|
||||
|
||||
## incompatible changes
|
||||
Broken: changed from func() invalid type to func() int
|
||||
|
||||
# example.com/fix/good
|
||||
## compatible changes
|
||||
Better: added
|
||||
|
||||
# summary
|
||||
Cannot suggest a release version.
|
||||
Errors were found.
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
mod=example.com/fix
|
||||
base=v1.0.0
|
||||
version=v1.1.0-compatible-other
|
||||
release=v1.1.0
|
||||
-- want --
|
||||
# example.com/fix/bad
|
||||
## errors in base version:
|
||||
bad/bad.go:3:15: undefined: NOTYPE
|
||||
|
||||
## incompatible changes
|
||||
Broken: changed from func() invalid type to func() int
|
||||
|
||||
# example.com/fix/good
|
||||
## compatible changes
|
||||
Better: added
|
||||
|
||||
# summary
|
||||
v1.1.0 is a valid semantic version for this release.
|
||||
Errors were found in the base version. Some API changes may be omitted.
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
mod=example.com/fix
|
||||
base=v1.0.0
|
||||
version=v1.1.0-compatible-other
|
||||
release=v1.0.1
|
||||
success=false
|
||||
-- want --
|
||||
# example.com/fix/bad
|
||||
## errors in base version:
|
||||
bad/bad.go:3:15: undefined: NOTYPE
|
||||
|
||||
## incompatible changes
|
||||
Broken: changed from func() invalid type to func() int
|
||||
|
||||
# example.com/fix/good
|
||||
## compatible changes
|
||||
Better: added
|
||||
|
||||
# summary
|
||||
v1.0.1 is not a valid semantic version for this release.
|
||||
There are compatible changes, but the minor version is not incremented
|
||||
over the base version (v1.0.0).
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
mod=example.com/fix
|
||||
base=v1.0.0
|
||||
version=v1.1.0-compatible-same
|
||||
success=false
|
||||
-- want --
|
||||
# example.com/fix/bad
|
||||
## errors in base version:
|
||||
bad/bad.go:3:15: undefined: NOTYPE
|
||||
|
||||
## incompatible changes
|
||||
Broken: changed from func() invalid type to func() int
|
||||
## compatible changes
|
||||
Worse: added
|
||||
|
||||
# summary
|
||||
Cannot suggest a release version.
|
||||
Errors were found.
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
mod=example.com/fix
|
||||
base=v1.0.0
|
||||
version=v1.1.0-compatible-same
|
||||
release=v1.0.1 # not actually valid, but gorelease can't tell
|
||||
-- want --
|
||||
# example.com/fix/bad
|
||||
## errors in base version:
|
||||
bad/bad.go:3:15: undefined: NOTYPE
|
||||
|
||||
## incompatible changes
|
||||
Broken: changed from func() invalid type to func() int
|
||||
## compatible changes
|
||||
Worse: added
|
||||
|
||||
# summary
|
||||
v1.0.1 is a valid semantic version for this release.
|
||||
Errors were found in the base version. Some API changes may be omitted.
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
mod=example.com/fix
|
||||
base=v1.0.0
|
||||
version=v1.1.0-incompatible-other
|
||||
success=false
|
||||
-- want --
|
||||
# example.com/fix/bad
|
||||
## errors in base version:
|
||||
bad/bad.go:3:15: undefined: NOTYPE
|
||||
|
||||
## incompatible changes
|
||||
Broken: changed from func() invalid type to func() int
|
||||
|
||||
# example.com/fix/good
|
||||
## incompatible changes
|
||||
Good: changed from func() int to func() string
|
||||
|
||||
# summary
|
||||
Cannot suggest a release version.
|
||||
Errors were found.
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
mod=example.com/fix
|
||||
base=v1.0.0
|
||||
version=v1.1.0-incompatible-other
|
||||
release=v1.1.0
|
||||
success=false
|
||||
-- want --
|
||||
# example.com/fix/bad
|
||||
## errors in base version:
|
||||
bad/bad.go:3:15: undefined: NOTYPE
|
||||
|
||||
## incompatible changes
|
||||
Broken: changed from func() invalid type to func() int
|
||||
|
||||
# example.com/fix/good
|
||||
## incompatible changes
|
||||
Good: changed from func() int to func() string
|
||||
|
||||
# summary
|
||||
v1.1.0 is not a valid semantic version for this release.
|
||||
There are incompatible changes.
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
mod=example.com/fix
|
||||
base=v1.0.0
|
||||
version=v1.1.0-incompatible-same
|
||||
success=false
|
||||
-- want --
|
||||
# example.com/fix/bad
|
||||
## errors in base version:
|
||||
bad/bad.go:3:15: undefined: NOTYPE
|
||||
|
||||
## incompatible changes
|
||||
Bad: changed from func() int to func() string
|
||||
Broken: changed from func() invalid type to func() int
|
||||
|
||||
# summary
|
||||
Cannot suggest a release version.
|
||||
Errors were found.
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
mod=example.com/fix
|
||||
base=v1.0.0
|
||||
version=v1.1.0-incompatible-same
|
||||
release=v1.0.1 # not actually valid, but gorelease can't tell
|
||||
-- want --
|
||||
# example.com/fix/bad
|
||||
## errors in base version:
|
||||
bad/bad.go:3:15: undefined: NOTYPE
|
||||
|
||||
## incompatible changes
|
||||
Bad: changed from func() int to func() string
|
||||
Broken: changed from func() invalid type to func() int
|
||||
|
||||
# summary
|
||||
v1.0.1 is a valid semantic version for this release.
|
||||
Errors were found in the base version. Some API changes may be omitted.
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
mod=example.com/fix
|
||||
base=v1.0.0
|
||||
version=v1.0.1-patch
|
||||
success=false
|
||||
-- want --
|
||||
# example.com/fix/bad
|
||||
## errors in base version:
|
||||
bad/bad.go:3:15: undefined: NOTYPE
|
||||
|
||||
## incompatible changes
|
||||
Broken: changed from func() invalid type to func() int
|
||||
|
||||
# summary
|
||||
Cannot suggest a release version.
|
||||
Errors were found.
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
mod=example.com/fix
|
||||
base=v1.0.0
|
||||
version=v1.0.1-patch
|
||||
release=v1.0.1
|
||||
-- want --
|
||||
# example.com/fix/bad
|
||||
## errors in base version:
|
||||
bad/bad.go:3:15: undefined: NOTYPE
|
||||
|
||||
## incompatible changes
|
||||
Broken: changed from func() invalid type to func() int
|
||||
|
||||
# summary
|
||||
v1.0.1 is a valid semantic version for this release.
|
||||
Errors were found in the base version. Some API changes may be omitted.
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
mod=example.com/generics
|
||||
base=v0.0.1
|
||||
-- want --
|
||||
# example.com/generics/a
|
||||
## incompatible changes
|
||||
Foo: changed from Foo[V any] to Foo[V Number]
|
||||
## compatible changes
|
||||
Number: added
|
||||
|
||||
# summary
|
||||
Suggested version: v0.1.0
|
||||
-- go.mod --
|
||||
module example.com/generics
|
||||
|
||||
go 1.18
|
||||
-- a/a.go --
|
||||
package a
|
||||
|
||||
type Number interface {
|
||||
int64 | float64
|
||||
}
|
||||
|
||||
type Foo[V Number] interface {
|
||||
Value() any
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
mod=example.com/generics
|
||||
base=v0.0.1
|
||||
-- want --
|
||||
# example.com/generics/a
|
||||
## incompatible changes
|
||||
Foo[V any].Value: changed from func() V to func() int
|
||||
|
||||
# summary
|
||||
Suggested version: v0.1.0
|
||||
-- go.mod --
|
||||
module example.com/generics
|
||||
|
||||
go 1.18
|
||||
-- a/a.go --
|
||||
package a
|
||||
|
||||
type Foo[V any] interface {
|
||||
Value() int
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
mod=example.com/generics
|
||||
base=v0.0.1
|
||||
-- want --
|
||||
# summary
|
||||
Suggested version: v0.0.2
|
||||
-- go.mod --
|
||||
module example.com/generics
|
||||
|
||||
go 1.18
|
||||
-- a/a.go --
|
||||
package a
|
||||
|
||||
type Foo[V any] interface {
|
||||
Value() V
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
Modules example.com/internalcompat/{a,b} are copies. One could be a fork
|
||||
of the other. An external package p exposes a type from a package q
|
||||
within the same module.
|
||||
|
||||
gorelease should not report differences between these packages. The types
|
||||
are distinct, but they correspond (in apidiff terminology), which is the
|
||||
important property when considering differences between modules.
|
||||
|
||||
There are three use cases to consider:
|
||||
|
||||
1. One module substitutes for the other via a `replace` directive.
|
||||
Only the replacement module is used, and the package paths are effectively
|
||||
identical, so the types are not distinct.
|
||||
2. One module subsititutes for the other by rewriting `import` statements
|
||||
globally. All references to the original type become references to the
|
||||
new type, so there is no conflict.
|
||||
3. One module substitutes for the other by rewriting some `import` statements
|
||||
but not others (for example, those within a specific consumer package).
|
||||
In this case, the types are distinct, and even if there are no changes,
|
||||
the types are not compatible.
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
mod=example.com/internalcompat/b
|
||||
version=v1.0.0
|
||||
release=v1.0.1
|
||||
base=example.com/internalcompat/a@v1.0.0
|
||||
-- want --
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user