feat(golang-rewrite): create installs and installtest packages to avoid circular dependency

* Correct `go test` command in GitHub test workflow
* Update execute tests to work on Github Actions
* Check in `shims/testdata` directory
* Create `installtest` helper package
* Create `installs` package
This commit is contained in:
Trevor Brown 2024-09-05 09:55:13 -04:00
parent 05e0a4a57f
commit be52d8f39c
11 changed files with 230 additions and 100 deletions

View File

@ -52,7 +52,7 @@ jobs:
- name: Install dependencies
run: go get .
- name: Run Go tests
run: go test
run: go test -coverprofile=/tmp/coverage.out -bench= -race ./...
# Because I changed the test helper code Bash tests now fail. I removed them
# from here to get passing checks. They can be added back at a later time if

View File

@ -10,6 +10,7 @@ import (
"asdf/internal/config"
"asdf/internal/info"
"asdf/internal/installs"
"asdf/internal/plugins"
"asdf/internal/shims"
"asdf/internal/versions"
@ -425,7 +426,7 @@ func latestForPlugin(conf config.Config, toolName, pattern string, showStatus bo
}
if showStatus {
installed := versions.IsInstalled(conf, plugin, latest)
installed := installs.IsInstalled(conf, plugin, latest)
fmt.Printf("%s\t%s\t%s\n", plugin.Name, latest, installedStatus(installed))
} else {
fmt.Printf("%s\n", latest)

View File

@ -33,7 +33,7 @@ func TestRun_Command(t *testing.T) {
err := cmd.Run()
assert.Nil(t, err)
assert.Equal(t, "sh is /bin/sh\n", stdout.String())
assert.Contains(t, stdout.String(), "sh is /")
})
t.Run("positional arg is passed to command", func(t *testing.T) {
@ -108,7 +108,7 @@ func TestRun_Expression(t *testing.T) {
err := cmd.Run()
assert.Nil(t, err)
assert.Equal(t, "sh is /bin/sh\n", stdout.String())
assert.Contains(t, stdout.String(), "sh is /")
})
t.Run("positional arg is passed to expression", func(t *testing.T) {

View File

@ -0,0 +1,64 @@
// Package installs contains tool installation logic. It is "dumb" when it comes
// to versions and treats versions as opaque strings. It cannot depend on the
// versions package because the versions package relies on this page.
package installs
import (
"io/fs"
"os"
"path/filepath"
"asdf/internal/config"
"asdf/internal/plugins"
)
const (
dataDirInstalls = "installs"
dataDirDownloads = "downloads"
)
// Installed returns a slice of all installed versions for a given plugin
func Installed(conf config.Config, plugin plugins.Plugin) (versions []string, err error) {
installDirectory := pluginInstallPath(conf, plugin)
files, err := os.ReadDir(installDirectory)
if err != nil {
if _, ok := err.(*fs.PathError); ok {
return versions, nil
}
return versions, err
}
for _, file := range files {
if !file.IsDir() {
continue
}
versions = append(versions, file.Name())
}
return versions, err
}
// InstallPath returns the path to a tool installation
func InstallPath(conf config.Config, plugin plugins.Plugin, version string) string {
return filepath.Join(pluginInstallPath(conf, plugin), version)
}
// DownloadPath returns the download path for a particular plugin and version
func DownloadPath(conf config.Config, plugin plugins.Plugin, version string) string {
return filepath.Join(conf.DataDir, dataDirDownloads, plugin.Name, version)
}
// IsInstalled checks if a specific version of a tool is installed
func IsInstalled(conf config.Config, plugin plugins.Plugin, version string) bool {
installDir := InstallPath(conf, plugin, version)
// Check if version already installed
_, err := os.Stat(installDir)
return !os.IsNotExist(err)
}
func pluginInstallPath(conf config.Config, plugin plugins.Plugin) string {
return filepath.Join(conf.DataDir, dataDirInstalls, plugin.Name)
}

View File

@ -0,0 +1,72 @@
package installs
import (
"os"
"testing"
"asdf/internal/config"
"asdf/internal/installtest"
"asdf/internal/plugins"
"asdf/repotest"
"github.com/stretchr/testify/assert"
)
const testPluginName = "lua"
func TestInstalled(t *testing.T) {
conf, plugin := generateConfig(t)
t.Run("returns empty slice for newly installed plugin", func(t *testing.T) {
installedVersions, err := Installed(conf, plugin)
assert.Nil(t, err)
assert.Empty(t, installedVersions)
})
t.Run("returns slice of all installed versions for a tool", func(t *testing.T) {
mockInstall(t, conf, plugin, "1.0.0")
installedVersions, err := Installed(conf, plugin)
assert.Nil(t, err)
assert.Equal(t, installedVersions, []string{"1.0.0"})
})
}
func TestIsInstalled(t *testing.T) {
conf, plugin := generateConfig(t)
installVersion(t, conf, plugin, "1.0.0")
t.Run("returns false when not installed", func(t *testing.T) {
assert.False(t, IsInstalled(conf, plugin, "4.0.0"))
})
t.Run("returns true when installed", func(t *testing.T) {
assert.True(t, IsInstalled(conf, plugin, "1.0.0"))
})
}
// helper functions
func generateConfig(t *testing.T) (config.Config, plugins.Plugin) {
t.Helper()
testDataDir := t.TempDir()
conf, err := config.LoadConfig()
assert.Nil(t, err)
conf.DataDir = testDataDir
_, err = repotest.InstallPlugin("dummy_plugin", testDataDir, testPluginName)
assert.Nil(t, err)
return conf, plugins.New(conf, testPluginName)
}
func mockInstall(t *testing.T, conf config.Config, plugin plugins.Plugin, version string) {
t.Helper()
path := InstallPath(conf, plugin, version)
err := os.MkdirAll(path, os.ModePerm)
assert.Nil(t, err)
}
func installVersion(t *testing.T, conf config.Config, plugin plugins.Plugin, version string) {
t.Helper()
err := installtest.InstallOneVersion(conf, plugin, "version", version)
assert.Nil(t, err)
}

View File

@ -0,0 +1,78 @@
// Package installtest provides functions used by various asdf tests for
// installing versions of tools. It provides a simplified version of the
// versions.InstallOneVersion function.
package installtest
import (
"fmt"
"os"
"path/filepath"
"strings"
"asdf/internal/config"
"asdf/internal/plugins"
)
const (
dataDirInstalls = "installs"
dataDirDownloads = "downloads"
)
// InstallOneVersion is a simplified version of versions.InstallOneVersion
// function for use in Go tests.
func InstallOneVersion(conf config.Config, plugin plugins.Plugin, versionType, version string) error {
var stdOut strings.Builder
var stdErr strings.Builder
err := plugin.Exists()
if err != nil {
return err
}
downloadDir := DownloadPath(conf, plugin, version)
installDir := InstallPath(conf, plugin, version)
env := map[string]string{
"ASDF_INSTALL_TYPE": versionType,
"ASDF_INSTALL_VERSION": version,
"ASDF_INSTALL_PATH": installDir,
"ASDF_DOWNLOAD_PATH": downloadDir,
"ASDF_CONCURRENCY": "1",
}
err = os.MkdirAll(downloadDir, 0o777)
if err != nil {
return fmt.Errorf("unable to create download dir: %w", err)
}
err = plugin.RunCallback("download", []string{}, env, &stdOut, &stdErr)
if _, ok := err.(plugins.NoCallbackError); err != nil && !ok {
return fmt.Errorf("failed to run download callback: %w", err)
}
err = os.MkdirAll(installDir, 0o777)
if err != nil {
return fmt.Errorf("unable to create install dir: %w", err)
}
err = plugin.RunCallback("install", []string{}, env, &stdOut, &stdErr)
if err != nil {
return fmt.Errorf("failed to run install callback: %w", err)
}
return nil
}
// InstallPath returns the path to a tool installation
func InstallPath(conf config.Config, plugin plugins.Plugin, version string) string {
return filepath.Join(pluginInstallPath(conf, plugin), version)
}
// DownloadPath returns the download path for a particular plugin and version
func DownloadPath(conf config.Config, plugin plugins.Plugin, version string) string {
return filepath.Join(conf.DataDir, dataDirDownloads, plugin.Name, version)
}
func pluginInstallPath(conf config.Config, plugin plugins.Plugin) string {
return filepath.Join(conf.DataDir, dataDirInstalls, plugin.Name)
}

View File

@ -11,9 +11,9 @@ import (
"asdf/internal/config"
"asdf/internal/hook"
"asdf/internal/installs"
"asdf/internal/plugins"
"asdf/internal/toolversions"
"asdf/internal/versions"
"golang.org/x/sys/unix"
)
@ -56,7 +56,7 @@ func GenerateAll(conf config.Config, stdOut io.Writer, stdErr io.Writer) error {
// GenerateForPluginVersions generates all shims for all installed versions of
// a tool.
func GenerateForPluginVersions(conf config.Config, plugin plugins.Plugin, stdOut io.Writer, stdErr io.Writer) error {
installedVersions, err := versions.Installed(conf, plugin)
installedVersions, err := installs.Installed(conf, plugin)
if err != nil {
return err
}
@ -135,7 +135,7 @@ func ToolExecutables(conf config.Config, plugin plugins.Plugin, version string)
return executables, err
}
installPath := versions.InstallPath(conf, plugin, version)
installPath := installs.InstallPath(conf, plugin, version)
paths := dirsToPaths(dirs, installPath)
for _, path := range paths {

View File

@ -9,8 +9,8 @@ import (
"testing"
"asdf/internal/config"
"asdf/internal/installtest"
"asdf/internal/plugins"
"asdf/internal/versions"
"asdf/repotest"
"github.com/stretchr/testify/assert"
@ -266,7 +266,6 @@ func installPlugin(t *testing.T, conf config.Config, fixture, pluginName string)
func installVersion(t *testing.T, conf config.Config, plugin plugins.Plugin, version string) {
t.Helper()
stdout, stderr := buildOutputs()
err := versions.InstallOneVersion(conf, plugin, version, &stdout, &stderr)
err := installtest.InstallOneVersion(conf, plugin, "version", version)
assert.Nil(t, err)
}

2
internal/shims/testdata/asdfrc vendored Normal file
View File

@ -0,0 +1,2 @@
pre_asdf_reshim_lua = echo pre_reshim $@
post_asdf_reshim_lua = echo post_reshim $@

View File

@ -6,14 +6,13 @@ import (
"errors"
"fmt"
"io"
"io/fs"
"os"
"path/filepath"
"regexp"
"strings"
"asdf/internal/config"
"asdf/internal/hook"
"asdf/internal/installs"
"asdf/internal/plugins"
"asdf/internal/resolve"
)
@ -22,8 +21,6 @@ const (
systemVersion = "system"
latestVersion = "latest"
uninstallableVersionMsg = "uninstallable version: system"
dataDirDownloads = "downloads"
dataDirInstalls = "installs"
latestFilterRegex = "(?i)(^Available versions:|-src|-dev|-latest|-stm|[-\\.]rc|-milestone|-alpha|-beta|[-\\.]pre|-next|(a|b|c)[0-9]+|snapshot|master)"
noLatestVersionErrMsg = "no latest version found"
)
@ -49,29 +46,6 @@ func (e NoVersionSetError) Error() string {
return "no version set"
}
// Installed returns a slice of all installed versions for a given plugin
func Installed(conf config.Config, plugin plugins.Plugin) (versions []string, err error) {
installDirectory := pluginInstallPath(conf, plugin)
files, err := os.ReadDir(installDirectory)
if err != nil {
if _, ok := err.(*fs.PathError); ok {
return versions, nil
}
return versions, err
}
for _, file := range files {
if !file.IsDir() {
continue
}
versions = append(versions, file.Name())
}
return versions, err
}
// InstallAll installs all specified versions of every tool for the current
// directory. Typically this will just be a single version, if not already
// installed, but it may be multiple versions if multiple versions for the tool
@ -154,11 +128,11 @@ func InstallOneVersion(conf config.Config, plugin plugins.Plugin, version string
return UninstallableVersionError{}
}
downloadDir := downloadPath(conf, plugin, version)
installDir := InstallPath(conf, plugin, version)
downloadDir := installs.DownloadPath(conf, plugin, version)
installDir := installs.InstallPath(conf, plugin, version)
versionType, version := ParseString(version)
if IsInstalled(conf, plugin, version) {
if installs.IsInstalled(conf, plugin, version) {
return fmt.Errorf("version %s of %s is already installed", version, plugin.Name)
}
@ -222,15 +196,6 @@ func asdfConcurrency(conf config.Config) string {
return val
}
// IsInstalled checks if a specific version of a tool is installed
func IsInstalled(conf config.Config, plugin plugins.Plugin, version string) bool {
installDir := InstallPath(conf, plugin, version)
// Check if version already installed
_, err := os.Stat(installDir)
return !os.IsNotExist(err)
}
// Latest invokes the plugin's latest-stable callback if it exists and returns
// the version it returns. If the callback is missing it invokes the list-all
// callback and returns the last version matching the query, if a query is
@ -338,16 +303,3 @@ func ParseString(version string) (string, string) {
return "version", version
}
func downloadPath(conf config.Config, plugin plugins.Plugin, version string) string {
return filepath.Join(conf.DataDir, dataDirDownloads, plugin.Name, version)
}
// InstallPath returns the path to a tool installation
func InstallPath(conf config.Config, plugin plugins.Plugin, version string) string {
return filepath.Join(pluginInstallPath(conf, plugin), version)
}
func pluginInstallPath(conf config.Config, plugin plugins.Plugin) string {
return filepath.Join(conf.DataDir, dataDirInstalls, plugin.Name)
}

View File

@ -16,30 +16,6 @@ import (
const testPluginName = "lua"
func TestInstalled(t *testing.T) {
conf, plugin := generateConfig(t)
//stdout, stderr := buildOutputs()
//currentDir := t.TempDir()
//secondPlugin := installPlugin(t, conf, "dummy_plugin", "another")
//version := "1.0.0"
t.Run("returns empty slice for newly installed plugin", func(t *testing.T) {
installedVersions, err := Installed(conf, plugin)
assert.Nil(t, err)
assert.Empty(t, installedVersions)
})
t.Run("returns slice of all installed versions for a tool", func(t *testing.T) {
stdout, stderr := buildOutputs()
err := InstallOneVersion(conf, plugin, "1.0.0", &stdout, &stderr)
assert.Nil(t, err)
installedVersions, err := Installed(conf, plugin)
assert.Nil(t, err)
assert.Equal(t, installedVersions, []string{"1.0.0"})
})
}
func TestInstallAll(t *testing.T) {
t.Run("installs multiple tools when multiple tool versions are specified", func(t *testing.T) {
conf, plugin := generateConfig(t)
@ -287,20 +263,6 @@ func TestInstallOneVersion(t *testing.T) {
})
}
func TestIsInstalled(t *testing.T) {
conf, plugin := generateConfig(t)
stdout, stderr := buildOutputs()
err := InstallOneVersion(conf, plugin, "1.0.0", &stdout, &stderr)
assert.Nil(t, err)
t.Run("returns false when not installed", func(t *testing.T) {
assert.False(t, IsInstalled(conf, plugin, "4.0.0"))
})
t.Run("returns true when installed", func(t *testing.T) {
assert.True(t, IsInstalled(conf, plugin, "1.0.0"))
})
}
func TestLatest(t *testing.T) {
pluginName := "latest_test"
conf, _ := generateConfig(t)