mirror of
https://github.com/asdf-vm/asdf.git
synced 2024-12-24 20:35:03 -07:00
5d5d04fbb7
* Replace direct `fmt.Println()` usage in a test with `t.Log()` * Rename `cmd` to `cli` * Move asdf command from module root * Fix some linter warnings, thus enabling some tests that were being skipped * Fix `Makefile` * Rename module to `github.com/asdf-vm/asdf` * Fix `TestGetAllToolsAndVersionsInContent/returns_empty_list_with_found_true_and_no_error_when_empty_content` * Rewrite `Unique()` to be a bit more straightforwards * Get workflow checks passing again toolversions.Unique is ever so slightly faster, technically. ``` goos: linux goarch: amd64 pkg: github.com/asdf-vm/asdf/internal/toolversions cpu: AMD Ryzen 9 3900X 12-Core Processor │ /tmp/old.txt │ /tmp/new.txt │ │ sec/op │ sec/op vs base │ Unique-24 346.5n ± 1% 342.4n ± 1% -1.17% (p=0.027 n=10) │ /tmp/old.txt │ /tmp/new.txt │ │ B/op │ B/op vs base │ Unique-24 160.0 ± 0% 160.0 ± 0% ~ (p=1.000 n=10) ¹ ¹ all samples are equal │ /tmp/old.txt │ /tmp/new.txt │ │ allocs/op │ allocs/op vs base │ Unique-24 3.000 ± 0% 3.000 ± 0% ~ (p=1.000 n=10) ¹ ¹ all samples are equal ```
461 lines
15 KiB
Go
461 lines
15 KiB
Go
package versions
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/asdf-vm/asdf/internal/config"
|
|
"github.com/asdf-vm/asdf/internal/plugins"
|
|
"github.com/asdf-vm/asdf/internal/toolversions"
|
|
"github.com/asdf-vm/asdf/repotest"
|
|
"github.com/stretchr/testify/assert"
|
|
)
|
|
|
|
const testPluginName = "lua"
|
|
|
|
func TestInstallAll(t *testing.T) {
|
|
t.Run("installs multiple tools when multiple tool versions are specified", func(t *testing.T) {
|
|
conf, plugin := generateConfig(t)
|
|
stdout, stderr := buildOutputs()
|
|
currentDir := t.TempDir()
|
|
secondPlugin := installPlugin(t, conf, "dummy_plugin", "another")
|
|
version := "1.0.0"
|
|
|
|
// write a version file
|
|
content := fmt.Sprintf("%s %s\n%s %s", plugin.Name, version, secondPlugin.Name, version)
|
|
writeVersionFile(t, currentDir, content)
|
|
|
|
err := InstallAll(conf, currentDir, &stdout, &stderr)
|
|
assert.Nil(t, err)
|
|
|
|
assertVersionInstalled(t, conf.DataDir, plugin.Name, version)
|
|
assertVersionInstalled(t, conf.DataDir, secondPlugin.Name, version)
|
|
})
|
|
|
|
t.Run("only installs tools with versions specified for current directory", func(t *testing.T) {
|
|
conf, plugin := generateConfig(t)
|
|
stdout, stderr := buildOutputs()
|
|
currentDir := t.TempDir()
|
|
secondPlugin := installPlugin(t, conf, "dummy_plugin", "another")
|
|
version := "1.0.0"
|
|
|
|
// write a version file
|
|
content := fmt.Sprintf("%s %s\n", plugin.Name, version)
|
|
writeVersionFile(t, currentDir, content)
|
|
|
|
err := InstallAll(conf, currentDir, &stdout, &stderr)
|
|
assert.ErrorContains(t, err[0], "no version set")
|
|
|
|
assertVersionInstalled(t, conf.DataDir, plugin.Name, version)
|
|
assertNotInstalled(t, conf.DataDir, secondPlugin.Name, version)
|
|
})
|
|
|
|
t.Run("installs all tools even after one fails to install", func(t *testing.T) {
|
|
conf, plugin := generateConfig(t)
|
|
stdout, stderr := buildOutputs()
|
|
currentDir := t.TempDir()
|
|
secondPlugin := installPlugin(t, conf, "dummy_plugin", "another")
|
|
version := "1.0.0"
|
|
|
|
// write a version file
|
|
content := fmt.Sprintf("%s %s\n%s %s", secondPlugin.Name, "non-existent-version", plugin.Name, version)
|
|
writeVersionFile(t, currentDir, content)
|
|
|
|
err := InstallAll(conf, currentDir, &stdout, &stderr)
|
|
assert.Empty(t, err)
|
|
|
|
assertNotInstalled(t, conf.DataDir, secondPlugin.Name, version)
|
|
assertVersionInstalled(t, conf.DataDir, plugin.Name, version)
|
|
})
|
|
}
|
|
|
|
func TestInstall(t *testing.T) {
|
|
conf, plugin := generateConfig(t)
|
|
stdout, stderr := buildOutputs()
|
|
currentDir := t.TempDir()
|
|
|
|
t.Run("installs version of tool specified for current directory", func(t *testing.T) {
|
|
version := "1.0.0"
|
|
// write a version file
|
|
data := []byte(fmt.Sprintf("%s %s", plugin.Name, version))
|
|
err := os.WriteFile(filepath.Join(currentDir, ".tool-versions"), data, 0o666)
|
|
assert.Nil(t, err)
|
|
|
|
err = Install(conf, plugin, currentDir, &stdout, &stderr)
|
|
assert.Nil(t, err)
|
|
|
|
assertVersionInstalled(t, conf.DataDir, plugin.Name, version)
|
|
})
|
|
|
|
t.Run("returns error when plugin doesn't exist", func(t *testing.T) {
|
|
conf, _ := generateConfig(t)
|
|
stdout, stderr := buildOutputs()
|
|
err := Install(conf, plugins.New(conf, "non-existent"), currentDir, &stdout, &stderr)
|
|
assert.IsType(t, plugins.PluginMissing{}, err)
|
|
})
|
|
|
|
t.Run("returns error when no version set", func(t *testing.T) {
|
|
conf, _ := generateConfig(t)
|
|
stdout, stderr := buildOutputs()
|
|
currentDir := t.TempDir()
|
|
err := Install(conf, plugin, currentDir, &stdout, &stderr)
|
|
assert.EqualError(t, err, "no version set")
|
|
})
|
|
|
|
t.Run("if multiple versions are defined installs all of them", func(t *testing.T) {
|
|
conf, plugin := generateConfig(t)
|
|
stdout, stderr := buildOutputs()
|
|
currentDir := t.TempDir()
|
|
|
|
versions := "1.0.0 2.0.0"
|
|
// write a version file
|
|
data := []byte(fmt.Sprintf("%s %s", plugin.Name, versions))
|
|
err := os.WriteFile(filepath.Join(currentDir, ".tool-versions"), data, 0o666)
|
|
assert.Nil(t, err)
|
|
|
|
err = Install(conf, plugin, currentDir, &stdout, &stderr)
|
|
assert.Nil(t, err)
|
|
|
|
assertVersionInstalled(t, conf.DataDir, plugin.Name, "1.0.0")
|
|
assertVersionInstalled(t, conf.DataDir, plugin.Name, "2.0.0")
|
|
})
|
|
}
|
|
|
|
func TestInstallVersion(t *testing.T) {
|
|
t.Setenv("ASDF_CONFIG_FILE", "testdata/asdfrc")
|
|
|
|
t.Run("returns error when plugin doesn't exist", func(t *testing.T) {
|
|
conf, _ := generateConfig(t)
|
|
stdout, stderr := buildOutputs()
|
|
version := toolversions.Version{Type: "version", Value: "1.2.3"}
|
|
err := InstallVersion(conf, plugins.New(conf, "non-existent"), version, &stdout, &stderr)
|
|
assert.IsType(t, plugins.PluginMissing{}, err)
|
|
})
|
|
|
|
t.Run("installs latest version of tool when version is 'latest'", func(t *testing.T) {
|
|
conf, plugin := generateConfig(t)
|
|
stdout, stderr := buildOutputs()
|
|
version := toolversions.Version{Type: "latest", Value: ""}
|
|
err := InstallVersion(conf, plugin, version, &stdout, &stderr)
|
|
assert.Nil(t, err)
|
|
|
|
assertVersionInstalled(t, conf.DataDir, plugin.Name, "2.0.0")
|
|
})
|
|
|
|
t.Run("installs specific version of tool", func(t *testing.T) {
|
|
conf, plugin := generateConfig(t)
|
|
stdout, stderr := buildOutputs()
|
|
|
|
version := toolversions.Version{Type: "latest", Value: "^1."}
|
|
err := InstallVersion(conf, plugin, version, &stdout, &stderr)
|
|
assert.Nil(t, err)
|
|
|
|
assertVersionInstalled(t, conf.DataDir, plugin.Name, "1.1.0")
|
|
})
|
|
}
|
|
|
|
func TestInstallOneVersion(t *testing.T) {
|
|
t.Setenv("ASDF_CONFIG_FILE", "testdata/asdfrc")
|
|
|
|
t.Run("returns error when plugin doesn't exist", func(t *testing.T) {
|
|
conf, _ := generateConfig(t)
|
|
stdout, stderr := buildOutputs()
|
|
err := InstallOneVersion(conf, plugins.New(conf, "non-existent"), "1.2.3", false, &stdout, &stderr)
|
|
assert.IsType(t, plugins.PluginMissing{}, err)
|
|
})
|
|
|
|
t.Run("returns error when passed a path version", func(t *testing.T) {
|
|
conf, plugin := generateConfig(t)
|
|
stdout, stderr := buildOutputs()
|
|
err := InstallOneVersion(conf, plugin, "path:/foo/bar", false, &stdout, &stderr)
|
|
|
|
assert.ErrorContains(t, err, "uninstallable version: path")
|
|
})
|
|
|
|
t.Run("returns error when plugin version is 'system'", func(t *testing.T) {
|
|
conf, plugin := generateConfig(t)
|
|
stdout, stderr := buildOutputs()
|
|
err := InstallOneVersion(conf, plugin, "system", false, &stdout, &stderr)
|
|
assert.IsType(t, UninstallableVersionError{}, err)
|
|
})
|
|
|
|
t.Run("returns error when version doesn't exist", func(t *testing.T) {
|
|
version := "other-dummy"
|
|
conf, plugin := generateConfig(t)
|
|
stdout, stderr := buildOutputs()
|
|
err := InstallOneVersion(conf, plugin, version, false, &stdout, &stderr)
|
|
assert.Errorf(t, err, "failed to run install callback: exit status 1")
|
|
|
|
want := "pre_asdf_download_lua other-dummy\npre_asdf_install_lua other-dummy\nDummy couldn't install version: other-dummy (on purpose)\n"
|
|
assert.Equal(t, want, stdout.String())
|
|
|
|
assertNotInstalled(t, conf.DataDir, plugin.Name, version)
|
|
})
|
|
|
|
t.Run("returns error when version already installed", func(t *testing.T) {
|
|
conf, plugin := generateConfig(t)
|
|
stdout, stderr := buildOutputs()
|
|
err := InstallOneVersion(conf, plugin, "1.0.0", false, &stdout, &stderr)
|
|
assert.Nil(t, err)
|
|
assertVersionInstalled(t, conf.DataDir, plugin.Name, "1.0.0")
|
|
|
|
// Install a second time
|
|
err = InstallOneVersion(conf, plugin, "1.0.0", false, &stdout, &stderr)
|
|
assert.NotNil(t, err)
|
|
})
|
|
|
|
t.Run("creates download directory", func(t *testing.T) {
|
|
conf, plugin := generateConfig(t)
|
|
stdout, stderr := buildOutputs()
|
|
err := InstallOneVersion(conf, plugin, "1.0.0", false, &stdout, &stderr)
|
|
assert.Nil(t, err)
|
|
|
|
downloadPath := filepath.Join(conf.DataDir, "downloads", plugin.Name, "1.0.0")
|
|
pathInfo, err := os.Stat(downloadPath)
|
|
assert.Nil(t, err)
|
|
assert.True(t, pathInfo.IsDir())
|
|
})
|
|
|
|
t.Run("creates install directory", func(t *testing.T) {
|
|
conf, plugin := generateConfig(t)
|
|
stdout, stderr := buildOutputs()
|
|
err := InstallOneVersion(conf, plugin, "1.0.0", false, &stdout, &stderr)
|
|
assert.Nil(t, err)
|
|
|
|
installPath := filepath.Join(conf.DataDir, "installs", plugin.Name, "1.0.0")
|
|
pathInfo, err := os.Stat(installPath)
|
|
assert.Nil(t, err)
|
|
assert.True(t, pathInfo.IsDir())
|
|
})
|
|
|
|
t.Run("runs pre-download, pre-install and post-install hooks when installation successful", func(t *testing.T) {
|
|
conf, plugin := generateConfig(t)
|
|
stdout, stderr := buildOutputs()
|
|
err := InstallOneVersion(conf, plugin, "1.0.0", false, &stdout, &stderr)
|
|
assert.Nil(t, err)
|
|
assert.Equal(t, "", stderr.String())
|
|
want := "pre_asdf_download_lua 1.0.0\npre_asdf_install_lua 1.0.0\npost_asdf_install_lua 1.0.0\n"
|
|
assert.Equal(t, want, stdout.String())
|
|
})
|
|
|
|
t.Run("installs successfully when plugin exists but version does not", func(t *testing.T) {
|
|
conf, plugin := generateConfig(t)
|
|
stdout, stderr := buildOutputs()
|
|
err := InstallOneVersion(conf, plugin, "1.0.0", false, &stdout, &stderr)
|
|
assert.Nil(t, err)
|
|
|
|
// Check download directory
|
|
downloadPath := filepath.Join(conf.DataDir, "downloads", plugin.Name, "1.0.0")
|
|
entries, err := os.ReadDir(downloadPath)
|
|
assert.Nil(t, err)
|
|
// mock plugin doesn't write anything
|
|
assert.Empty(t, entries)
|
|
|
|
// Check install directory
|
|
assertVersionInstalled(t, conf.DataDir, plugin.Name, "1.0.0")
|
|
})
|
|
|
|
t.Run("install successfully when plugin lacks download callback", func(t *testing.T) {
|
|
conf, _ := generateConfig(t)
|
|
stdout, stderr := buildOutputs()
|
|
testPluginName := "no-download"
|
|
_, err := repotest.InstallPlugin("dummy_plugin_no_download", conf.DataDir, testPluginName)
|
|
assert.Nil(t, err)
|
|
plugin := plugins.New(conf, testPluginName)
|
|
|
|
err = InstallOneVersion(conf, plugin, "1.0.0", false, &stdout, &stderr)
|
|
assert.Nil(t, err)
|
|
|
|
// no-download install script prints 'install'
|
|
assert.Equal(t, "install", stdout.String())
|
|
})
|
|
}
|
|
|
|
func TestLatest(t *testing.T) {
|
|
pluginName := "latest_test"
|
|
conf, _ := generateConfig(t)
|
|
_, err := repotest.InstallPlugin("dummy_legacy_plugin", conf.DataDir, pluginName)
|
|
assert.Nil(t, err)
|
|
plugin := plugins.New(conf, pluginName)
|
|
|
|
t.Run("when plugin has a latest-stable callback invokes it and returns version it printed", func(t *testing.T) {
|
|
pluginName := "latest-with-callback"
|
|
_, err := repotest.InstallPlugin("dummy_plugin", conf.DataDir, pluginName)
|
|
assert.Nil(t, err)
|
|
plugin := plugins.New(conf, pluginName)
|
|
|
|
version, err := Latest(plugin, "")
|
|
assert.Nil(t, err)
|
|
assert.Equal(t, "2.0.0", version)
|
|
})
|
|
|
|
t.Run("when given query matching no versions return empty slice of versions", func(t *testing.T) {
|
|
version, err := Latest(plugin, "impossible-to-satisfy-query")
|
|
assert.Error(t, err, "no latest version found")
|
|
assert.Equal(t, version, "")
|
|
})
|
|
|
|
t.Run("when given no query returns latest version of plugin", func(t *testing.T) {
|
|
version, err := Latest(plugin, "")
|
|
assert.Nil(t, err)
|
|
assert.Equal(t, "5.1.0", version)
|
|
})
|
|
|
|
t.Run("when given no query returns latest version of plugin", func(t *testing.T) {
|
|
version, err := Latest(plugin, "4")
|
|
assert.Nil(t, err)
|
|
assert.Equal(t, "4.0.0", version)
|
|
})
|
|
}
|
|
|
|
func TestAllVersions(t *testing.T) {
|
|
pluginName := "list-all-test"
|
|
conf, _ := generateConfig(t)
|
|
_, err := repotest.InstallPlugin("dummy_plugin", conf.DataDir, pluginName)
|
|
assert.Nil(t, err)
|
|
plugin := plugins.New(conf, pluginName)
|
|
|
|
t.Run("returns slice of available versions from plugin", func(t *testing.T) {
|
|
versions, err := AllVersions(plugin)
|
|
assert.Nil(t, err)
|
|
assert.Equal(t, versions, []string{"1.0.0", "1.1.0", "2.0.0"})
|
|
})
|
|
|
|
t.Run("returns error when callback missing", func(t *testing.T) {
|
|
pluginName = "list-all-fail"
|
|
_, err := repotest.InstallPlugin("dummy_plugin_no_download", conf.DataDir, pluginName)
|
|
assert.Nil(t, err)
|
|
plugin := plugins.New(conf, pluginName)
|
|
|
|
versions, err := AllVersions(plugin)
|
|
assert.Equal(t, err.(plugins.NoCallbackError).Error(), "Plugin named list-all-fail does not have a callback named list-all")
|
|
assert.Empty(t, versions)
|
|
})
|
|
}
|
|
|
|
func TestUninstall(t *testing.T) {
|
|
t.Setenv("ASDF_CONFIG_FILE", "testdata/uninstall-asdfrc")
|
|
pluginName := "uninstall-test"
|
|
conf, _ := generateConfig(t)
|
|
_, err := repotest.InstallPlugin("dummy_plugin", conf.DataDir, pluginName)
|
|
assert.Nil(t, err)
|
|
plugin := plugins.New(conf, pluginName)
|
|
stdout, stderr := buildOutputs()
|
|
|
|
t.Run("returns error when version is 'latest'", func(t *testing.T) {
|
|
stdout, stderr := buildOutputs()
|
|
err := Uninstall(conf, plugin, "latest", &stdout, &stderr)
|
|
assert.Error(t, err, "'latest' is a special version value that cannot be used for uninstall command")
|
|
})
|
|
|
|
t.Run("returns an error when version not installed", func(t *testing.T) {
|
|
err := Uninstall(conf, plugin, "4.0.0", &stdout, &stderr)
|
|
assert.Error(t, err, "No such version")
|
|
})
|
|
|
|
t.Run("uninstalls successfully when plugin and version are installed", func(t *testing.T) {
|
|
err = InstallOneVersion(conf, plugin, "1.0.0", false, &stdout, &stderr)
|
|
assert.Nil(t, err)
|
|
|
|
err := Uninstall(conf, plugin, "1.0.0", &stdout, &stderr)
|
|
assert.Nil(t, err)
|
|
assertNotInstalled(t, conf.DataDir, plugin.Name, "1.0.0")
|
|
})
|
|
|
|
t.Run("runs pre and post-uninstall hooks", func(t *testing.T) {
|
|
stdout, stderr := buildOutputs()
|
|
err = InstallOneVersion(conf, plugin, "1.0.0", false, &stdout, &stderr)
|
|
assert.Nil(t, err)
|
|
|
|
err := Uninstall(conf, plugin, "1.0.0", &stdout, &stderr)
|
|
assert.Nil(t, err)
|
|
want := "pre_asdf_uninstall_test 1.0.0\npost_asdf_uninstall_test 1.0.0\n"
|
|
assert.Equal(t, want, stdout.String())
|
|
})
|
|
|
|
t.Run("invokes uninstall callback when present", func(t *testing.T) {
|
|
stdout, stderr := buildOutputs()
|
|
err = InstallOneVersion(conf, plugin, "1.0.0", false, &stdout, &stderr)
|
|
assert.Nil(t, err)
|
|
|
|
data := []byte("echo custom uninstall")
|
|
err := os.WriteFile(filepath.Join(plugin.Dir, "bin", "uninstall"), data, 0o755)
|
|
assert.Nil(t, err)
|
|
|
|
err = Uninstall(conf, plugin, "1.0.0", &stdout, &stderr)
|
|
assert.Nil(t, err)
|
|
want := "pre_asdf_uninstall_test 1.0.0\ncustom uninstall\npost_asdf_uninstall_test 1.0.0\n"
|
|
assert.Equal(t, want, stdout.String())
|
|
})
|
|
}
|
|
|
|
// Helper functions
|
|
func buildOutputs() (strings.Builder, strings.Builder) {
|
|
var stdout strings.Builder
|
|
var stderr strings.Builder
|
|
|
|
return stdout, stderr
|
|
}
|
|
|
|
func assertVersionInstalled(t *testing.T, dataDir, pluginName, version string) {
|
|
t.Helper()
|
|
|
|
installDir := filepath.Join(dataDir, "installs", pluginName, version)
|
|
installedVersionFile := filepath.Join(installDir, "version")
|
|
|
|
bytes, err := os.ReadFile(installedVersionFile)
|
|
assert.Nil(t, err, "expected file from install to exist")
|
|
|
|
want := fmt.Sprintf("%s\n", version)
|
|
assert.Equal(t, want, string(bytes), "got wrong version")
|
|
|
|
entries, err := os.ReadDir(installDir)
|
|
assert.Nil(t, err)
|
|
|
|
var fileNames []string
|
|
for _, e := range entries {
|
|
fileNames = append(fileNames, e.Name())
|
|
}
|
|
|
|
assert.Equal(t, fileNames, []string{"bin", "env", "version"})
|
|
}
|
|
|
|
func assertNotInstalled(t *testing.T, dataDir, pluginName, version string) {
|
|
t.Helper()
|
|
|
|
installPath := filepath.Join(dataDir, "installs", pluginName, version)
|
|
entries, err := os.ReadDir(installPath)
|
|
if err != nil && !os.IsNotExist(err) {
|
|
t.Errorf("failed to check directory %s due to error %s", installPath, err)
|
|
}
|
|
assert.Empty(t, entries)
|
|
}
|
|
|
|
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 installPlugin(t *testing.T, conf config.Config, fixture, name string) plugins.Plugin {
|
|
_, err := repotest.InstallPlugin(fixture, conf.DataDir, name)
|
|
assert.Nil(t, err)
|
|
return plugins.New(conf, name)
|
|
}
|
|
|
|
func writeVersionFile(t *testing.T, dir, contents string) {
|
|
t.Helper()
|
|
err := os.WriteFile(filepath.Join(dir, ".tool-versions"), []byte(contents), 0o666)
|
|
assert.Nil(t, err)
|
|
}
|