feat(golang-rewrite): offline Go tests

Some of the Go tests actually cloned remote repos, which meant slow tests, and tests failures if I was working offline. These changes allow all Go tests to run offline. The test code now clones local Git repos instead of remotes ones.

* Add otiai10/copy as a dependency
* Create repotest package for test code that needs to work with Git repos
* Add back missing test script for execute package
* Update git, pluginindex, and plugins package tests so they work offline
This commit is contained in:
Trevor Brown 2024-07-27 17:20:59 -04:00
parent 2e185a0e5b
commit f74efbf1bf
8 changed files with 298 additions and 185 deletions

3
execute/testdata/script vendored Executable file
View File

@ -0,0 +1,3 @@
#!/usr/bin/env bash
echo $@

View File

@ -5,35 +5,27 @@ import (
"path/filepath"
"testing"
"asdf/repotest"
"github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/plumbing"
"github.com/stretchr/testify/assert"
)
// TODO: Switch to local repo so tests don't go over the network
const (
testRepo = "https://github.com/Stratus3D/asdf-lua"
testPluginName = "lua"
)
func TestPluginClone(t *testing.T) {
t.Run("when plugin name is valid but URL is invalid prints an error", func(t *testing.T) {
tempDir := t.TempDir()
directory := filepath.Join(tempDir, testPluginName)
plugin := NewRepo(directory)
plugin := NewRepo(t.TempDir())
err := plugin.Clone("foobar")
assert.ErrorContains(t, err, "unable to clone plugin: repository not found")
})
t.Run("clones provided Git URL to plugin directory when URL is valid", func(t *testing.T) {
tempDir := t.TempDir()
directory := filepath.Join(tempDir, testPluginName)
repoDir := generateRepo(t)
directory := t.TempDir()
plugin := NewRepo(directory)
err := plugin.Clone(testRepo)
err := plugin.Clone(repoDir)
assert.Nil(t, err)
// Assert plugin directory contains Git repo with bin directory
@ -42,17 +34,17 @@ func TestPluginClone(t *testing.T) {
entries, err := os.ReadDir(directory + "/bin")
assert.Nil(t, err)
assert.Equal(t, 5, len(entries))
assert.Equal(t, 12, len(entries))
})
}
func TestPluginHead(t *testing.T) {
tempDir := t.TempDir()
directory := filepath.Join(tempDir, testPluginName)
repoDir := generateRepo(t)
directory := t.TempDir()
plugin := NewRepo(directory)
err := plugin.Clone(testRepo)
err := plugin.Clone(repoDir)
assert.Nil(t, err)
head, err := plugin.Head()
@ -61,12 +53,12 @@ func TestPluginHead(t *testing.T) {
}
func TestPluginRemoteURL(t *testing.T) {
tempDir := t.TempDir()
directory := filepath.Join(tempDir, testPluginName)
repoDir := generateRepo(t)
directory := t.TempDir()
plugin := NewRepo(directory)
err := plugin.Clone(testRepo)
err := plugin.Clone(repoDir)
assert.Nil(t, err)
url, err := plugin.RemoteURL()
@ -75,16 +67,16 @@ func TestPluginRemoteURL(t *testing.T) {
}
func TestPluginUpdate(t *testing.T) {
tempDir := t.TempDir()
directory := filepath.Join(tempDir, testPluginName)
repoDir := generateRepo(t)
directory := t.TempDir()
plugin := NewRepo(directory)
err := plugin.Clone(testRepo)
err := plugin.Clone(repoDir)
assert.Nil(t, err)
t.Run("returns error when plugin with name does not exist", func(t *testing.T) {
nonexistantPath := filepath.Join(tempDir, "nonexistant")
nonexistantPath := filepath.Join(directory, "nonexistant")
nonexistantPlugin := NewRepo(nonexistantPath)
updatedToRef, err := nonexistantPlugin.Update("")
@ -96,7 +88,7 @@ func TestPluginUpdate(t *testing.T) {
t.Run("returns error when plugin repo does not exist", func(t *testing.T) {
badPluginName := "badplugin"
badPluginDir := filepath.Join(tempDir, badPluginName)
badPluginDir := filepath.Join(directory, badPluginName)
err := os.MkdirAll(badPluginDir, 0o777)
assert.Nil(t, err)
@ -198,3 +190,12 @@ func checkoutPreviousCommit(path string) (string, error) {
return previousHash.String(), nil
}
func generateRepo(t *testing.T) string {
t.Helper()
tempDir := t.TempDir()
path, err := repotest.GeneratePlugin("dummy_plugin", tempDir, "lua")
assert.Nil(t, err)
return path
}

2
go.mod
View File

@ -5,6 +5,7 @@ go 1.21.5
require (
github.com/go-git/go-git/v5 v5.11.0
github.com/mitchellh/go-homedir v1.1.0
github.com/otiai10/copy v1.14.0
github.com/sethvargo/go-envconfig v1.0.0
github.com/stretchr/testify v1.8.4
github.com/urfave/cli/v2 v2.27.1
@ -35,6 +36,7 @@ require (
golang.org/x/crypto v0.16.0 // indirect
golang.org/x/mod v0.12.0 // indirect
golang.org/x/net v0.19.0 // indirect
golang.org/x/sync v0.3.0 // indirect
golang.org/x/sys v0.15.0 // indirect
golang.org/x/tools v0.13.0 // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect

4
go.sum
View File

@ -52,6 +52,10 @@ github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI=
github.com/onsi/gomega v1.27.10/go.mod h1:RsS8tutOdbdgzbPtzzATp12yT7kM5I5aElG3evPbQ0M=
github.com/otiai10/copy v1.14.0 h1:dCI/t1iTdYGtkvCuBG2BgR6KZa83PTclw4U5n2wAllU=
github.com/otiai10/copy v1.14.0/go.mod h1:ECfuL02W+/FkTWZWgQqXPWZgW9oeKCSQ5qVfSc4qc4w=
github.com/otiai10/mint v1.5.1 h1:XaPLeE+9vGbuyEHem1JNk3bYc7KKqyI/na0/mLd/Kks=
github.com/otiai10/mint v1.5.1/go.mod h1:MJm72SBthJjz8qhefc4z1PYEieWmy8Bku7CjcAqyUSM=
github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4=
github.com/pjbgf/sha1cd v0.3.0/go.mod h1:nZ1rrWOcGJ5uZgEEVL1VUM9iRQiZvWdbZjkKyFzPPsI=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=

View File

@ -63,7 +63,7 @@ func (p PluginIndex) Refresh() (bool, error) {
// directory empty, clone down repo
err := p.repo.Clone(p.url)
if err != nil {
return false, err
return false, fmt.Errorf("unable to initialize index: %w", err)
}
return touchFS(p.directory)
@ -90,7 +90,7 @@ func (p PluginIndex) doUpdate() (bool, error) {
// commit is
_, err := p.repo.Update("")
if err != nil {
return false, err
return false, fmt.Errorf("unable to update plugin index: %w", err)
}
// Touch update file

View File

@ -9,13 +9,15 @@ import (
"time"
"asdf/git"
"asdf/repotest"
"github.com/stretchr/testify/assert"
)
const (
realIndexURL = "https://github.com/asdf-vm/asdf-plugins.git"
mockIndexURL = "https://github.com/asdf-vm/asdf-plugins.git"
badIndexURL = "http://asdf-vm.com/non-existent"
fooPluginURL = "http://example.com/foo"
elixirPluginURL = "https://github.com/asdf-vm/asdf-elixir.git"
erlangPluginURL = "https://github.com/asdf-vm/asdf-erlang.git"
)
@ -84,15 +86,22 @@ func writeMockPluginFile(dir, pluginName, pluginURL string) error {
func TestGetPluginSourceURL(t *testing.T) {
t.Run("with Git returns a plugin url when provided name of existing plugin", func(t *testing.T) {
dir := t.TempDir()
pluginIndex := New(dir, realIndexURL, true, 0, &git.Repo{Directory: dir})
url, err := pluginIndex.GetPluginSourceURL("elixir")
indexDir := filepath.Join(dir, "index")
err := os.Mkdir(indexDir, 0o777)
assert.Nil(t, err)
assert.Equal(t, url, elixirPluginURL)
repoPath, err := repotest.GeneratePluginIndex(dir)
assert.Nil(t, err)
pluginIndex := New(indexDir, repoPath, true, 0, &git.Repo{Directory: indexDir})
url, err := pluginIndex.GetPluginSourceURL("foo")
assert.Nil(t, err)
assert.Equal(t, url, fooPluginURL)
})
t.Run("returns a plugin url when provided name of existing plugin", func(t *testing.T) {
dir := t.TempDir()
pluginIndex := New(dir, realIndexURL, true, 0, &MockIndex{Directory: dir})
pluginIndex := New(dir, mockIndexURL, true, 0, &MockIndex{Directory: dir})
url, err := pluginIndex.GetPluginSourceURL("elixir")
assert.Nil(t, err)
assert.Equal(t, url, elixirPluginURL)
@ -100,7 +109,7 @@ func TestGetPluginSourceURL(t *testing.T) {
t.Run("returns a plugin url when provided name of existing plugin when loading from cache", func(t *testing.T) {
dir := t.TempDir()
pluginIndex := New(dir, realIndexURL, false, 10, &MockIndex{Directory: dir})
pluginIndex := New(dir, mockIndexURL, false, 10, &MockIndex{Directory: dir})
url, err := pluginIndex.GetPluginSourceURL("elixir")
assert.Nil(t, err)
assert.Equal(t, url, elixirPluginURL)
@ -112,7 +121,7 @@ func TestGetPluginSourceURL(t *testing.T) {
t.Run("returns an error when given a name that isn't in the index", func(t *testing.T) {
dir := t.TempDir()
pluginIndex := New(dir, realIndexURL, false, 10, &MockIndex{Directory: dir})
pluginIndex := New(dir, mockIndexURL, false, 10, &MockIndex{Directory: dir})
url, err := pluginIndex.GetPluginSourceURL("foobar")
assert.EqualError(t, err, "plugin foobar not found in repository")
assert.Equal(t, url, "")
@ -130,7 +139,7 @@ func TestGetPluginSourceURL(t *testing.T) {
pluginIndex := New(dir, badIndexURL, false, 10, &repo)
url, err := pluginIndex.GetPluginSourceURL("lua")
assert.EqualError(t, err, "unable to clone: repository not found")
assert.EqualError(t, err, "unable to update plugin index: unable to clone: repository not found")
assert.Equal(t, url, "")
})
@ -138,7 +147,7 @@ func TestGetPluginSourceURL(t *testing.T) {
dir := t.TempDir()
pluginIndex := New(dir, badIndexURL, false, 10, &MockIndex{Directory: dir})
url, err := pluginIndex.GetPluginSourceURL("lua")
assert.EqualError(t, err, "unable to clone: repository not found")
assert.EqualError(t, err, "unable to initialize index: unable to clone: repository not found")
assert.Equal(t, url, "")
})
}
@ -146,10 +155,17 @@ func TestGetPluginSourceURL(t *testing.T) {
func TestRefresh(t *testing.T) {
t.Run("with Git updates repo when called once", func(t *testing.T) {
dir := t.TempDir()
pluginIndex := New(dir, realIndexURL, false, 0, &git.Repo{Directory: dir})
url, err := pluginIndex.GetPluginSourceURL("elixir")
indexDir := filepath.Join(dir, "index")
err := os.Mkdir(indexDir, 0o777)
assert.Nil(t, err)
assert.Equal(t, url, elixirPluginURL)
repoPath, err := repotest.GeneratePluginIndex(dir)
assert.Nil(t, err)
pluginIndex := New(indexDir, repoPath, false, 0, &git.Repo{Directory: indexDir})
url, err := pluginIndex.GetPluginSourceURL("foo")
assert.Nil(t, err)
assert.Equal(t, url, fooPluginURL)
updated, err := pluginIndex.Refresh()
assert.Nil(t, err)
@ -158,7 +174,7 @@ func TestRefresh(t *testing.T) {
t.Run("updates repo when called once", func(t *testing.T) {
dir := t.TempDir()
pluginIndex := New(dir, realIndexURL, false, 0, &MockIndex{Directory: dir})
pluginIndex := New(dir, mockIndexURL, false, 0, &MockIndex{Directory: dir})
updated, err := pluginIndex.Refresh()
assert.Nil(t, err)
@ -171,7 +187,7 @@ func TestRefresh(t *testing.T) {
t.Run("does not update index when time has not elaspsed", func(t *testing.T) {
dir := t.TempDir()
pluginIndex := New(dir, realIndexURL, false, 10, &MockIndex{Directory: dir})
pluginIndex := New(dir, mockIndexURL, false, 10, &MockIndex{Directory: dir})
// Call Refresh twice, the second call should not perform an update
updated, err := pluginIndex.Refresh()
@ -185,7 +201,7 @@ func TestRefresh(t *testing.T) {
t.Run("updates plugin index when time has elaspsed", func(t *testing.T) {
dir := t.TempDir()
pluginIndex := New(dir, realIndexURL, false, 0, &MockIndex{Directory: dir})
pluginIndex := New(dir, mockIndexURL, false, 0, &MockIndex{Directory: dir})
// Call Refresh twice, the second call should perform an update
updated, err := pluginIndex.Refresh()
@ -200,10 +216,10 @@ func TestRefresh(t *testing.T) {
t.Run("returns error when plugin index repo doesn't exist", func(t *testing.T) {
dir := t.TempDir()
pluginIndex := New(dir, badIndexURL, false, 0, &MockIndex{Directory: dir})
pluginIndex := New(dir, badIndexURL, false, 0, &MockIndex{Directory: dir})
updated, err := pluginIndex.Refresh()
assert.EqualError(t, err, "unable to clone: repository not found")
assert.EqualError(t, err, "unable to initialize index: unable to clone: repository not found")
assert.False(t, updated)
})
}

View File

@ -1,28 +1,23 @@
package plugins
import (
"fmt"
"os"
"os/exec"
"path/filepath"
"strings"
"testing"
"asdf/config"
"asdf/repotest"
"github.com/stretchr/testify/assert"
)
// TODO: Switch to local repo so tests don't go over the network
const (
testRepo = "https://github.com/Stratus3D/asdf-lua"
testPluginName = "lua"
)
const testPluginName = "lua"
func TestList(t *testing.T) {
testDataDir := t.TempDir()
conf := config.Config{DataDir: testDataDir}
testRepo, err := installMockPluginRepo(testDataDir, testPluginName)
testRepo, err := repotest.GeneratePlugin("dummy_plugin", testDataDir, testPluginName)
assert.Nil(t, err)
err = Add(conf, testPluginName, testRepo)
@ -93,7 +88,7 @@ func TestAdd(t *testing.T) {
for _, invalid := range invalids {
t.Run(invalid, func(t *testing.T) {
err := Add(config.Config{}, invalid, testRepo)
err := Add(config.Config{}, invalid, "never-cloned")
expectedErrMsg := "is invalid. Name may only contain lowercase letters, numbers, '_', and '-'"
if !strings.Contains(err.Error(), expectedErrMsg) {
@ -107,13 +102,16 @@ func TestAdd(t *testing.T) {
conf := config.Config{DataDir: testDataDir}
// Add plugin
err := Add(conf, testPluginName, testRepo)
repoPath, err := repotest.GeneratePlugin("dummy_plugin", testDataDir, testPluginName)
assert.Nil(t, err)
err = Add(conf, testPluginName, repoPath)
if err != nil {
t.Fatal("Expected to be able to add plugin")
}
// Add it again to trigger error
err = Add(conf, testPluginName, testRepo)
err = Add(conf, testPluginName, repoPath)
if err == nil {
t.Fatal("expected error got nil")
@ -136,8 +134,10 @@ func TestAdd(t *testing.T) {
t.Run("when plugin name and URL are valid installs plugin", func(t *testing.T) {
testDataDir := t.TempDir()
conf := config.Config{DataDir: testDataDir}
pluginPath, err := repotest.GeneratePlugin("dummy_plugin", testDataDir, testPluginName)
assert.Nil(t, err)
err := Add(conf, testPluginName, testRepo)
err = Add(conf, testPluginName, pluginPath)
assert.Nil(t, err, "Expected to be able to add plugin")
@ -149,14 +149,17 @@ func TestAdd(t *testing.T) {
entries, err := os.ReadDir(pluginDir + "/bin")
assert.Nil(t, err)
assert.Equal(t, 5, len(entries))
assert.Equal(t, 12, len(entries))
})
t.Run("when parameters are valid creates plugin download dir", func(t *testing.T) {
testDataDir := t.TempDir()
conf := config.Config{DataDir: testDataDir}
err := Add(conf, testPluginName, testRepo)
repoPath, err := repotest.GeneratePlugin("dummy_plugin", testDataDir, testPluginName)
assert.Nil(t, err)
err = Add(conf, testPluginName, repoPath)
assert.Nil(t, err)
// Assert download dir exists
@ -170,7 +173,10 @@ func TestRemove(t *testing.T) {
testDataDir := t.TempDir()
conf := config.Config{DataDir: testDataDir}
err := Add(conf, testPluginName, testRepo)
repoPath, err := repotest.GeneratePlugin("dummy_plugin", testDataDir, testPluginName)
assert.Nil(t, err)
err = Add(conf, testPluginName, repoPath)
assert.Nil(t, err)
t.Run("returns error when plugin with name does not exist", func(t *testing.T) {
@ -197,7 +203,7 @@ func TestRemove(t *testing.T) {
})
t.Run("removes plugin download dir when passed name of installed plugin", func(t *testing.T) {
err := Add(conf, testPluginName, testRepo)
err := Add(conf, testPluginName, repoPath)
assert.Nil(t, err)
err = Remove(conf, testPluginName)
@ -214,7 +220,10 @@ func TestUpdate(t *testing.T) {
testDataDir := t.TempDir()
conf := config.Config{DataDir: testDataDir}
err := Add(conf, testPluginName, testRepo)
repoPath, err := repotest.GeneratePlugin("dummy_plugin", testDataDir, testPluginName)
assert.Nil(t, err)
err = Add(conf, testPluginName, repoPath)
assert.Nil(t, err)
badPluginName := "badplugin"
@ -359,7 +368,7 @@ func TestRunCallback(t *testing.T) {
testDataDir := t.TempDir()
conf := config.Config{DataDir: testDataDir}
testRepo, err := installMockPluginRepo(testDataDir, testPluginName)
testRepo, err := repotest.GeneratePlugin("dummy_plugin", testDataDir, testPluginName)
assert.Nil(t, err)
err = Add(conf, testPluginName, testRepo)
@ -412,125 +421,3 @@ func touchFile(name string) error {
}
return file.Close()
}
func installMockPluginRepo(dataDir, name string) (string, error) {
// Because the legacy dummy plugin directory is relative to the root of this
// project I cannot use the usual testing functions to locate it. To
// determine the location of it we compute the module root, which also
// happens to be the root of the repo.
modRootDir, err := moduleRoot()
if err != nil {
return "", err
}
location := dataDir + "/repo-" + name
// Then we specify the path to the dummy plugin relative to the module root
err = runCmd("cp", "-r", filepath.Join(modRootDir, "test/fixtures/dummy_plugin"), location)
if err != nil {
return location, err
}
// Definitely some opportunities to refactor here. This code might be
// simplified by switching to the Go git library
err = runCmd("git", "-C", location, "init", "-q")
if err != nil {
return location, err
}
err = runCmd("git", "-C", location, "config", "user.name", "\"Test\"")
if err != nil {
return location, err
}
err = runCmd("git", "-C", location, "config", "user.email", "\"test@example.com\"")
if err != nil {
return location, err
}
err = runCmd("git", "-C", location, "add", "-A")
if err != nil {
return location, err
}
err = runCmd("git", "-C", location, "commit", "-q", "-m", fmt.Sprintf("\"asdf %s plugin init\"", name))
if err != nil {
return location, err
}
err = runCmd("touch", filepath.Join(location, "README.md"))
if err != nil {
return location, err
}
err = runCmd("git", "-C", location, "add", "-A")
if err != nil {
return location, err
}
err = runCmd("git", "-C", location, "commit", "-q", "-m", fmt.Sprintf("\"asdf %s plugin readme \"", name))
if err != nil {
return location, err
}
// kind of ugly but I want a remote with a valid path so I use the same
// location as the remote. Probably should refactor
err = runCmd("git", "-C", location, "remote", "add", "origin", location)
if err != nil {
return location, err
}
return location, err
}
func moduleRoot() (string, error) {
currentDir, err := os.Getwd()
if err != nil {
return "", err
}
return findModuleRoot(currentDir), nil
}
// Taken from https://github.com/golang/go/blob/9e3b1d53a012e98cfd02de2de8b1bd53522464d4/src/cmd/go/internal/modload/init.go#L1504C1-L1522C2 because that function is in an internal module
// and I can't rely on it.
func findModuleRoot(dir string) (roots string) {
if dir == "" {
panic("dir not set")
}
dir = filepath.Clean(dir)
// Look for enclosing go.mod.
for {
if fi, err := os.Stat(filepath.Join(dir, "go.mod")); err == nil && !fi.IsDir() {
return dir
}
d := filepath.Dir(dir)
if d == dir {
break
}
dir = d
}
return ""
}
// helper function to make running commands easier
func runCmd(cmdName string, args ...string) error {
cmd := exec.Command(cmdName, args...)
// Capture stdout and stderr
var stdout strings.Builder
var stderr strings.Builder
cmd.Stdout = &stdout
cmd.Stderr = &stderr
err := cmd.Run()
if err != nil {
// If command fails print both stderr and stdout
fmt.Println("stdout:", stdout.String())
fmt.Println("stderr:", stderr.String())
return err
}
return nil
}

200
repotest/repotest.go Normal file
View File

@ -0,0 +1,200 @@
// Package repotest contains various test helpers for tests that work with code
// relying on plugin Git repos and the asdf plugin index
//
// Three main actions:
//
// * Install plugin index repo into asdf (index contains records that point to
// local plugins defined by this package)
// * Install plugin into asdf data dir
// * Create local plugin repo that can be cloned into asdf
package repotest
import (
"fmt"
"os"
"os/exec"
"path/filepath"
"strings"
cp "github.com/otiai10/copy"
)
const fixturesDir = "fixtures"
// Setup copies all files into place and initializes all repos for any Go test
// that needs either plugin repos or the plugin index repo.
func Setup(asdfDataDir string) error {
if err := InstallPluginIndex(asdfDataDir); err != nil {
return err
}
return nil
}
// GeneratePlugin copies in the specified plugin fixture into a test directory
// and initializes a Git repo for it so it can be installed by asdf.
func GeneratePlugin(fixtureName, asdfDataDir, pluginName string) (string, error) {
root, err := getModuleRoot()
if err != nil {
return "", err
}
fixturesDir := filepath.Join(asdfDataDir, fixturesDir)
return generatePluginInDir(root, fixtureName, fixturesDir, pluginName)
}
// InstallPluginIndex generates and installs a plugin index Git repo inside of
// the provided asdf data directory.
func InstallPluginIndex(asdfDataDir string) error {
root, err := getModuleRoot()
if err != nil {
return err
}
// Copy in plugin index
source := filepath.Join(root, "test/fixtures/dummy_plugins_repo")
return cp.Copy(source, filepath.Join(asdfDataDir, "plugin-index"))
}
// GeneratePluginIndex generates a mock plugin index Git repo inside the given
// directory.
func GeneratePluginIndex(asdfDataDir string) (string, error) {
root, err := getModuleRoot()
if err != nil {
return "", err
}
// Copy in plugin index
source := filepath.Join(root, "test/fixtures/dummy_plugins_repo")
destination := filepath.Join(asdfDataDir, fixturesDir, "plugin-index")
err = cp.Copy(source, destination)
if err != nil {
return destination, fmt.Errorf("unable to copy in plugin index: %w", err)
}
// Generate git repo for plugin
return createGitRepo(destination)
}
func generatePluginInDir(root, fixtureName, outputDir, pluginName string) (string, error) {
// Copy in plugin files into output dir
pluginPath, err := copyInPlugin(root, fixtureName, outputDir, pluginName)
if err != nil {
return pluginPath, fmt.Errorf("unable to copy in plugin files: %w", err)
}
// Generate git repo for plugin
return createGitRepo(pluginPath)
}
func getModuleRoot() (string, error) {
cwd, err := os.Getwd()
if err != nil {
return "", fmt.Errorf("unable to get current working directory: %w", err)
}
root := findModuleRoot(cwd)
return root, nil
}
func createGitRepo(location string) (string, error) {
// Definitely some opportunities to refactor here. This code might be
// simplified by switching to the Go git library
err := runCmd("git", "-C", location, "init", "-q")
if err != nil {
return location, err
}
err = runCmd("git", "-C", location, "config", "user.name", "\"Test\"")
if err != nil {
return location, err
}
err = runCmd("git", "-C", location, "config", "user.email", "\"test@example.com\"")
if err != nil {
return location, err
}
err = runCmd("git", "-C", location, "add", "-A")
if err != nil {
return location, err
}
err = runCmd("git", "-C", location, "commit", "-q", "-m", "init repo")
if err != nil {
return location, err
}
err = runCmd("touch", filepath.Join(location, "README.md"))
if err != nil {
return location, err
}
err = runCmd("git", "-C", location, "add", "-A")
if err != nil {
return location, err
}
err = runCmd("git", "-C", location, "commit", "-q", "-m", "add readme")
if err != nil {
return location, err
}
// kind of ugly but I want a remote with a valid path so I use the same
// location as the remote. Probably should refactor
err = runCmd("git", "-C", location, "remote", "add", "origin", location)
if err != nil {
return location, err
}
return location, err
}
func copyInPlugin(root, name, destination, newName string) (string, error) {
source := filepath.Join(root, "test/fixtures/", name)
dest := filepath.Join(destination, newName)
return dest, cp.Copy(source, dest)
}
// Taken from https://github.com/golang/go/blob/9e3b1d53a012e98cfd02de2de8b1bd53522464d4/src/cmd/go/internal/modload/init.go#L1504C1-L1522C2 because that function is in an internal module
// and I can't rely on it.
func findModuleRoot(dir string) (roots string) {
if dir == "" {
panic("dir not set")
}
dir = filepath.Clean(dir)
// Look for enclosing go.mod.
for {
if fi, err := os.Stat(filepath.Join(dir, "go.mod")); err == nil && !fi.IsDir() {
return dir
}
d := filepath.Dir(dir)
if d == dir {
break
}
dir = d
}
return ""
}
// helper function to make running commands easier
func runCmd(cmdName string, args ...string) error {
cmd := exec.Command(cmdName, args...)
// Capture stdout and stderr
var stdout strings.Builder
var stderr strings.Builder
cmd.Stdout = &stdout
cmd.Stderr = &stderr
err := cmd.Run()
if err != nil {
// If command fails print both stderr and stdout
fmt.Println("stdout:", stdout.String())
fmt.Println("stderr:", stderr.String())
return err
}
return nil
}