From 6642fb5f1a407673872eeced96df6fc0af417431 Mon Sep 17 00:00:00 2001 From: Aaron Friel Date: Fri, 9 Dec 2022 11:46:22 -0800 Subject: [PATCH] Improve ProgramTest errors and path handling for Go programs --- pkg/go.mod | 1 + pkg/testing/integration/program.go | 69 ++++++++++++++-- pkg/testing/integration/program_test.go | 84 ++++++++++++++++++++ tests/go.mod | 1 + tests/go.sum | 1 + tests/integration/aliases/aliases_go_test.go | 2 +- 6 files changed, 149 insertions(+), 9 deletions(-) diff --git a/pkg/go.mod b/pkg/go.mod index 20403125a8d7..fea371feeef7 100644 --- a/pkg/go.mod +++ b/pkg/go.mod @@ -77,6 +77,7 @@ require ( github.com/rivo/uniseg v0.2.0 github.com/segmentio/encoding v0.3.5 github.com/shirou/gopsutil/v3 v3.22.3 + golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 ) diff --git a/pkg/testing/integration/program.go b/pkg/testing/integration/program.go index d7b3925d27f1..1c6adaf07034 100644 --- a/pkg/testing/integration/program.go +++ b/pkg/testing/integration/program.go @@ -36,6 +36,7 @@ import ( "time" multierror "github.com/hashicorp/go-multierror" + "golang.org/x/mod/modfile" "gopkg.in/yaml.v3" "github.com/pulumi/pulumi/pkg/v3/backend/filestate" @@ -2242,14 +2243,10 @@ func (pt *ProgramTester) prepareGoProject(projinfo *engine.Projinfo) error { } // link local dependencies - for _, pkg := range pt.opts.Dependencies { - var editStr string - if strings.ContainsRune(pkg, '=') { - // Use a literal replacement path. - editStr = pkg - } else { - dep := getRewritePath(pkg, gopath, depRoot) - editStr = fmt.Sprintf("%s=%s", pkg, dep) + for _, dep := range pt.opts.Dependencies { + editStr, err := getEditStr(dep, gopath, depRoot) + if err != nil { + return fmt.Errorf("error generating go mod replacement for dep %q: %w", dep, err) } err = pt.runCommand("go-mod-edit", []string{goBin, "mod", "edit", "-replace", editStr}, cwd) if err != nil { @@ -2282,6 +2279,62 @@ func (pt *ProgramTester) prepareGoProject(projinfo *engine.Projinfo) error { return nil } +func getEditStr(dep string, gopath string, depRoot string) (string, error) { + checkModName := true + var err error + var modName string + var modDir string + if strings.ContainsRune(dep, '=') { + parts := strings.Split(dep, "=") + modName = parts[0] + modDir = parts[1] + } else if !modfile.IsDirectoryPath(dep) { + modName = dep + modDir = getRewritePath(dep, gopath, depRoot) + } else { + modDir = dep + modName, err = getModName(modDir) + if err != nil { + return "", err + } + // We've read the package name from the go.mod file, skip redundant check below. + checkModName = false + } + + modDir, err = filepath.Abs(modDir) + if err != nil { + return "", err + } + + if checkModName { + actualModName, err := getModName(modDir) + if err != nil { + return "", fmt.Errorf("no go.mod at directory, set the path to the module explicitly or place "+ + "the dependency in the path specified by PULUMI_GO_DEP_ROOT or the default GOPATH: %w", err) + } + if actualModName != modName { + return "", fmt.Errorf("found module %s, expected %s", actualModName, modName) + } + } + + editStr := fmt.Sprintf("%s=%s", modName, modDir) + return editStr, nil +} + +func getModName(dir string) (string, error) { + pkgModPath := filepath.Join(dir, "go.mod") + pkgModData, err := os.ReadFile(pkgModPath) + if err != nil { + return "", fmt.Errorf("error reading go.mod at %s: %w", dir, err) + } + pkgMod, err := modfile.Parse(pkgModPath, pkgModData, nil) + if err != nil { + return "", fmt.Errorf("error parsing go.mod at %s: %w", dir, err) + } + + return pkgMod.Module.Mod.Path, nil +} + // prepareDotNetProject runs setup necessary to get a .NET project ready for `pulumi` commands. func (pt *ProgramTester) prepareDotNetProject(projinfo *engine.Projinfo) error { dotNetBin, err := pt.getDotNetBin() diff --git a/pkg/testing/integration/program_test.go b/pkg/testing/integration/program_test.go index 868b7067748b..4cc96a0a0616 100644 --- a/pkg/testing/integration/program_test.go +++ b/pkg/testing/integration/program_test.go @@ -22,6 +22,7 @@ import ( "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) // Test that RunCommand writes the command's output to a log file. @@ -91,3 +92,86 @@ func TestDepRootCalc(t *testing.T) { dep = getRewritePath("github.com/pulumi/pulumi-auth0/sdk", "gopath", "/my-go-src") assert.Equal(t, "/my-go-src/pulumi-auth0/sdk", filepath.ToSlash(dep)) } + +func TestGoModEdits(t *testing.T) { + t.Parallel() + + depRoot := os.Getenv("PULUMI_GO_DEP_ROOT") + gopath, err := GoPath() + require.NoError(t, err) + + cwd, err := os.Getwd() + require.NoError(t, err) + + // Were we to commit this go.mod file, `make tidy` would fail, and we should keep the complexity + // of tests constrained to the test itself. + + // The dir must be a relative path as well, so we make it relative to cwd (which is absolute). + badModDir := t.TempDir() + badModDir, err = filepath.Rel(cwd, badModDir) + require.NoError(t, err) + badModFile := filepath.Join(badModDir, "go.mod") + err = os.WriteFile(badModFile, []byte(` +# invalid go.mod +`), 0600) + require.NoError(t, err) + + tests := []struct { + name string + dep string + expectedValue string + expectedError string + }{ + { + name: "valid-path", + dep: "../../../sdk", + expectedValue: "github.com/pulumi/pulumi/sdk/v3=" + filepath.Join(cwd, "../../../sdk"), + }, + { + name: "invalid-path-non-existent", + dep: "../../../.tmp.non-existent-dir", + expectedError: "open ../../../.tmp.non-existent-dir/go.mod: no such file or directory", + }, + { + name: "invalid-path-bad-go-mod", + dep: badModDir, + expectedError: "error parsing go.mod", + }, + { + name: "valid-module-name", + dep: "github.com/pulumi/pulumi/sdk/v3", + expectedValue: "github.com/pulumi/pulumi/sdk/v3=" + filepath.Join(cwd, "../../../sdk"), + }, + { + name: "invalid-module-name", + dep: "github.com/pulumi/pulumi/sdk/v2", // v2 not v3 + expectedError: "found module github.com/pulumi/pulumi/sdk/v3, expected github.com/pulumi/pulumi/sdk/v2", + }, + { + name: "valid-rel-path", + dep: "github.com/pulumi/pulumi/sdk/v3=../../../sdk", + expectedValue: "github.com/pulumi/pulumi/sdk/v3=" + filepath.Join(cwd, "../../../sdk"), + }, + { + name: "invalid-rel-path", + dep: "github.com/pulumi/pulumi/sdk/v2=../../../sdk", + expectedError: "found module github.com/pulumi/pulumi/sdk/v3, expected github.com/pulumi/pulumi/sdk/v2", + }, + } + + for _, test := range tests { + test := test + t.Run(test.name, func(t *testing.T) { + t.Parallel() + + editStr, err := getEditStr(test.dep, gopath, depRoot) + + if test.expectedError != "" { + assert.ErrorContains(t, err, test.expectedError) + } else { + assert.NoError(t, err) + assert.Equal(t, test.expectedValue, editStr) + } + }) + } +} diff --git a/tests/go.mod b/tests/go.mod index 03ab0e248af4..78f6b6c118ee 100644 --- a/tests/go.mod +++ b/tests/go.mod @@ -159,6 +159,7 @@ require ( gocloud.dev v0.27.0 // indirect gocloud.dev/secrets/hashivault v0.24.0 // indirect golang.org/x/crypto v0.0.0-20220824171710-5757bc0c5503 // indirect + golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect golang.org/x/net v0.0.0-20220802222814-0bcc04d9c69b // indirect golang.org/x/oauth2 v0.0.0-20220722155238-128564f6959c // indirect golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 // indirect diff --git a/tests/go.sum b/tests/go.sum index 39be0468ca36..1ea3ef6e4c15 100644 --- a/tests/go.sum +++ b/tests/go.sum @@ -1818,6 +1818,7 @@ golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.5.0/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= diff --git a/tests/integration/aliases/aliases_go_test.go b/tests/integration/aliases/aliases_go_test.go index bed68f3ded01..8bffa5b761af 100644 --- a/tests/integration/aliases/aliases_go_test.go +++ b/tests/integration/aliases/aliases_go_test.go @@ -28,7 +28,7 @@ func TestGoAliases(t *testing.T) { integration.ProgramTest(t, &integration.ProgramTestOptions{ Dir: filepath.Join(d, "step1"), Dependencies: []string{ - "github.com/pulumi/pulumi/sdk/v3", + "github.com/pulumi/pulumi/sdk/v3=../../../sdk", }, Quick: true, EditDirs: []integration.EditDir{