asdf/internal/shims/shims_test.go

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

471 lines
17 KiB
Go
Raw Normal View History

package shims
import (
"errors"
"fmt"
"os"
"path/filepath"
"strings"
"testing"
"asdf/internal/config"
"asdf/internal/installs"
"asdf/internal/installtest"
"asdf/internal/plugins"
"asdf/internal/toolversions"
"asdf/repotest"
"github.com/stretchr/testify/assert"
"golang.org/x/sys/unix"
)
const testPluginName = "lua"
func TestFindExecutable(t *testing.T) {
version := "1.1.0"
conf, plugin := generateConfig(t)
installVersion(t, conf, plugin, version)
stdout, stderr := buildOutputs()
assert.Nil(t, GenerateAll(conf, &stdout, &stderr))
currentDir := t.TempDir()
t.Run("returns error when shim with name does not exist", func(t *testing.T) {
executable, _, version, found, err := FindExecutable(conf, "foo", currentDir)
assert.Empty(t, executable)
assert.False(t, found)
assert.Empty(t, version)
assert.Equal(t, err.(UnknownCommandError).Error(), "unknown command: foo")
})
t.Run("returns error when shim is present but no version is set", func(t *testing.T) {
executable, _, version, found, err := FindExecutable(conf, "dummy", currentDir)
assert.Empty(t, executable)
assert.False(t, found)
assert.Empty(t, version)
assert.Equal(t, err.(NoVersionSetError).Error(), "no versions set for dummy")
})
t.Run("returns string containing path to executable when found", func(t *testing.T) {
// write a version file
data := []byte("lua 1.1.0")
assert.Nil(t, os.WriteFile(filepath.Join(currentDir, ".tool-versions"), data, 0o666))
executable, gotPlugin, version, found, err := FindExecutable(conf, "dummy", currentDir)
assert.Equal(t, filepath.Base(filepath.Dir(filepath.Dir(executable))), "1.1.0")
assert.Equal(t, filepath.Base(executable), "dummy")
assert.Equal(t, plugin, gotPlugin)
assert.Equal(t, version, "1.1.0")
assert.True(t, found)
assert.Nil(t, err)
})
t.Run("returns string containing path to system executable when system version set", func(t *testing.T) {
// Create dummy `ls` executable
versionStruct := toolversions.Version{Type: "version", Value: version}
path := filepath.Join(installs.InstallPath(conf, plugin, versionStruct), "bin", "ls")
assert.Nil(t, os.WriteFile(path, []byte("echo 'I'm ls'"), 0o777))
// write system version to version file
toolpath := filepath.Join(currentDir, ".tool-versions")
assert.Nil(t, os.WriteFile(toolpath, []byte("lua system\n"), 0o666))
assert.Nil(t, GenerateAll(conf, &stdout, &stderr))
executable, gotPlugin, version, found, err := FindExecutable(conf, "ls", currentDir)
assert.Equal(t, plugin, gotPlugin)
assert.Equal(t, version, "system")
assert.True(t, found)
assert.Nil(t, err)
// see that it actually returns path to system ls
assert.Equal(t, filepath.Base(executable), "ls")
assert.NotEqual(t, executable, path)
})
t.Run("returns path to executable on path when path version set", func(t *testing.T) {
// write system version to version file
toolpath := filepath.Join(currentDir, ".tool-versions")
dir := installs.InstallPath(conf, plugin, toolversions.Version{Type: "version", Value: "1.1.0"})
pathVersion := fmt.Sprintf("path:%s/./", dir)
assert.Nil(t, os.WriteFile(toolpath, []byte(fmt.Sprintf("lua %s\n", pathVersion)), 0o666))
assert.Nil(t, GenerateAll(conf, &stdout, &stderr))
executable, gotPlugin, version, found, err := FindExecutable(conf, "dummy", currentDir)
assert.Equal(t, plugin, gotPlugin)
assert.Equal(t, version, pathVersion)
assert.True(t, found)
assert.Nil(t, err)
// see that it actually returns path to system ls
assert.Equal(t, filepath.Base(executable), "dummy")
assert.True(t, strings.HasPrefix(executable, dir))
})
}
func TestGetExecutablePath(t *testing.T) {
version := toolversions.Version{Type: "version", Value: "1.1.0"}
conf, plugin := generateConfig(t)
installVersion(t, conf, plugin, version.Value)
t.Run("returns path to executable", func(t *testing.T) {
path, err := GetExecutablePath(conf, plugin, "dummy", version)
assert.Nil(t, err)
assert.Equal(t, filepath.Base(path), "dummy")
assert.Equal(t, filepath.Base(filepath.Dir(filepath.Dir(path))), version.Value)
})
t.Run("returns error when executable with name not found", func(t *testing.T) {
path, err := GetExecutablePath(conf, plugin, "foo", version)
assert.ErrorContains(t, err, "executable not found")
assert.Equal(t, path, "")
})
t.Run("returns custom path when plugin has exec-path callback", func(t *testing.T) {
// Create exec-path callback
installDummyExecPathScript(t, conf, plugin, version, "dummy", "echo 'bin/custom/dummy'")
path, err := GetExecutablePath(conf, plugin, "dummy", version)
assert.Nil(t, err)
assert.Equal(t, filepath.Base(filepath.Dir(path)), "custom")
// Doesn't contain any trailing whitespace (newlines as the last char are common)
assert.Equal(t, path, strings.TrimSpace(path))
})
t.Run("returns default path when plugin has exec-path callback that prints third argument", func(t *testing.T) {
// Create exec-path callback
installDummyExecPathScript(t, conf, plugin, version, "dummy", "echo \"$3\"")
path, err := GetExecutablePath(conf, plugin, "dummy", version)
assert.Nil(t, err)
assert.Equal(t, filepath.Base(path), "dummy")
assert.Equal(t, filepath.Base(filepath.Dir(path)), "bin")
// Doesn't contain any trailing whitespace (newlines as the last char are common)
assert.Equal(t, path, strings.TrimSpace(path))
})
}
func TestRemoveAll(t *testing.T) {
version := "1.1.0"
conf, plugin := generateConfig(t)
installVersion(t, conf, plugin, version)
executables, err := ToolExecutables(conf, plugin, toolversions.Version{Type: "version", Value: version})
assert.Nil(t, err)
stdout, stderr := buildOutputs()
t.Run("removes all files in shim directory", func(t *testing.T) {
assert.Nil(t, GenerateAll(conf, &stdout, &stderr))
assert.Nil(t, RemoveAll(conf))
// check for generated shims
for _, executable := range executables {
_, err := os.Stat(Path(conf, filepath.Base(executable)))
assert.True(t, errors.Is(err, os.ErrNotExist))
}
})
}
func TestGenerateAll(t *testing.T) {
version := "1.1.0"
version2 := "2.0.0"
conf, plugin := generateConfig(t)
installVersion(t, conf, plugin, version)
installPlugin(t, conf, "dummy_plugin", "ruby")
installVersion(t, conf, plugin, version2)
executables, err := ToolExecutables(conf, plugin, toolversions.Version{Type: "version", Value: version})
assert.Nil(t, err)
stdout, stderr := buildOutputs()
t.Run("generates shim script for every executable in every version of every tool", func(t *testing.T) {
assert.Nil(t, GenerateAll(conf, &stdout, &stderr))
// check for generated shims
for _, executable := range executables {
shimName := filepath.Base(executable)
shimPath := Path(conf, shimName)
assert.Nil(t, unix.Access(shimPath, unix.X_OK))
// shim exists and has expected contents
content, err := os.ReadFile(shimPath)
assert.Nil(t, err)
want := fmt.Sprintf("#!/usr/bin/env bash\n# asdf-plugin: lua 2.0.0\n# asdf-plugin: lua 1.1.0\nexec asdf exec \"%s\" \"$@\"", shimName)
assert.Equal(t, want, string(content))
}
})
}
func TestGenerateForPluginVersions(t *testing.T) {
t.Setenv("ASDF_CONFIG_FILE", "testdata/asdfrc")
version := "1.1.0"
version2 := "2.0.0"
conf, plugin := generateConfig(t)
installVersion(t, conf, plugin, version)
installVersion(t, conf, plugin, version2)
executables, err := ToolExecutables(conf, plugin, toolversions.Version{Type: "version", Value: version})
assert.Nil(t, err)
stdout, stderr := buildOutputs()
t.Run("generates shim script for every executable in every version the tool", func(t *testing.T) {
assert.Nil(t, GenerateForPluginVersions(conf, plugin, &stdout, &stderr))
// check for generated shims
for _, executable := range executables {
shimName := filepath.Base(executable)
shimPath := Path(conf, shimName)
assert.Nil(t, unix.Access(shimPath, unix.X_OK))
// shim exists and has expected contents
content, err := os.ReadFile(shimPath)
assert.Nil(t, err)
want := fmt.Sprintf("#!/usr/bin/env bash\n# asdf-plugin: lua 2.0.0\n# asdf-plugin: lua 1.1.0\nexec asdf exec \"%s\" \"$@\"", shimName)
assert.Equal(t, want, string(content))
}
})
t.Run("runs pre and post reshim hooks", func(t *testing.T) {
stdout, stderr := buildOutputs()
assert.Nil(t, GenerateForPluginVersions(conf, plugin, &stdout, &stderr))
want := "pre_reshim 1.1.0\npost_reshim 1.1.0\npre_reshim 2.0.0\npost_reshim 2.0.0\n"
assert.Equal(t, want, stdout.String())
})
}
func TestGenerateForVersion(t *testing.T) {
version := toolversions.Version{Type: "version", Value: "1.1.0"}
version2 := toolversions.Version{Type: "version", Value: "2.0.0"}
conf, plugin := generateConfig(t)
installVersion(t, conf, plugin, version.Value)
installVersion(t, conf, plugin, version2.Value)
executables, err := ToolExecutables(conf, plugin, version)
assert.Nil(t, err)
t.Run("generates shim script for every executable in version", func(t *testing.T) {
stdout, stderr := buildOutputs()
assert.Nil(t, GenerateForVersion(conf, plugin, version, &stdout, &stderr))
// check for generated shims
for _, executable := range executables {
shimName := filepath.Base(executable)
shimPath := Path(conf, shimName)
assert.Nil(t, unix.Access(shimPath, unix.X_OK))
}
})
t.Run("updates existing shims for every executable in version", func(t *testing.T) {
stdout, stderr := buildOutputs()
assert.Nil(t, GenerateForVersion(conf, plugin, version, &stdout, &stderr))
assert.Nil(t, GenerateForVersion(conf, plugin, version2, &stdout, &stderr))
// check for generated shims
for _, executable := range executables {
shimName := filepath.Base(executable)
shimPath := Path(conf, shimName)
assert.Nil(t, unix.Access(shimPath, unix.X_OK))
}
})
}
func TestWrite(t *testing.T) {
version := toolversions.Version{Type: "version", Value: "1.1.0"}
version2 := toolversions.Version{Type: "version", Value: "2.0.0"}
conf, plugin := generateConfig(t)
installVersion(t, conf, plugin, version.Value)
installVersion(t, conf, plugin, version2.Value)
executables, err := ToolExecutables(conf, plugin, version)
executable := executables[0]
assert.Nil(t, err)
t.Run("writes a new shim file when doesn't exist", func(t *testing.T) {
executable := executables[0]
err = Write(conf, plugin, version, executable)
assert.Nil(t, err)
// shim is executable
shimName := filepath.Base(executable)
shimPath := Path(conf, shimName)
assert.Nil(t, unix.Access(shimPath, unix.X_OK))
// shim exists and has expected contents
content, err := os.ReadFile(shimPath)
assert.Nil(t, err)
want := "#!/usr/bin/env bash\n# asdf-plugin: lua 1.1.0\nexec asdf exec \"dummy\" \"$@\""
assert.Equal(t, want, string(content))
os.Remove(shimPath)
})
t.Run("updates an existing shim file when already present", func(t *testing.T) {
// Write same shim for two versions
assert.Nil(t, Write(conf, plugin, version, executable))
assert.Nil(t, Write(conf, plugin, version2, executable))
// shim is still executable
shimName := filepath.Base(executable)
shimPath := Path(conf, shimName)
assert.Nil(t, unix.Access(shimPath, unix.X_OK))
// has expected contents
content, err := os.ReadFile(shimPath)
assert.Nil(t, err)
want := "#!/usr/bin/env bash\n# asdf-plugin: lua 2.0.0\n# asdf-plugin: lua 1.1.0\nexec asdf exec \"dummy\" \"$@\""
assert.Equal(t, want, string(content))
os.Remove(shimPath)
})
t.Run("doesn't add the same version to a shim file twice", func(t *testing.T) {
assert.Nil(t, Write(conf, plugin, version, executable))
assert.Nil(t, Write(conf, plugin, version, executable))
// Shim doesn't contain any duplicate lines
shimPath := Path(conf, filepath.Base(executable))
content, err := os.ReadFile(shimPath)
assert.Nil(t, err)
want := "#!/usr/bin/env bash\n# asdf-plugin: lua 1.1.0\nexec asdf exec \"dummy\" \"$@\""
assert.Equal(t, want, string(content))
os.Remove(shimPath)
})
}
func TestToolExecutables(t *testing.T) {
version := toolversions.Version{Type: "version", Value: "1.1.0"}
conf, plugin := generateConfig(t)
installVersion(t, conf, plugin, version.Value)
t.Run("returns list of executables for plugin", func(t *testing.T) {
executables, err := ToolExecutables(conf, plugin, version)
assert.Nil(t, err)
var filenames []string
for _, executablePath := range executables {
assert.True(t, strings.HasPrefix(executablePath, conf.DataDir))
filenames = append(filenames, filepath.Base(executablePath))
}
assert.Equal(t, filenames, []string{"dummy"})
})
t.Run("returns list of executables for version installed in arbitrary directory", func(t *testing.T) {
// Reference regular install by path to validate this behavior
path := installs.InstallPath(conf, plugin, version)
executables, err := ToolExecutables(conf, plugin, toolversions.Version{Type: "path", Value: path})
assert.Nil(t, err)
var filenames []string
for _, executablePath := range executables {
assert.True(t, strings.HasPrefix(executablePath, conf.DataDir))
filenames = append(filenames, filepath.Base(executablePath))
}
assert.Equal(t, filenames, []string{"dummy"})
})
t.Run("returns list of executables even when one directory printed by list-bin-paths doesn't exist", func(t *testing.T) {
// foo is first in list returned by list-bin-paths but doesn't exist, do
// we still get the executables in the bin/ dir?
repotest.WritePluginCallback(plugin.Dir, "list-bin-paths", "#!/usr/bin/env bash\necho 'foo bin'")
executables, err := ToolExecutables(conf, plugin, version)
assert.Nil(t, err)
var filenames []string
for _, executablePath := range executables {
assert.True(t, strings.HasPrefix(executablePath, conf.DataDir))
filenames = append(filenames, filepath.Base(executablePath))
}
assert.Equal(t, filenames, []string{"dummy"})
})
}
func TestExecutablePaths(t *testing.T) {
conf, plugin := generateConfig(t)
installVersion(t, conf, plugin, "1.2.3")
t.Run("returns list only containing 'bin' when list-bin-paths callback missing", func(t *testing.T) {
executables, err := ExecutablePaths(conf, plugin, toolversions.Version{Type: "version", Value: "1.2.3"})
path := executables[0]
assert.Nil(t, err)
assert.Equal(t, filepath.Base(filepath.Dir(path)), "1.2.3")
assert.Equal(t, filepath.Base(path), "bin")
})
t.Run("returns list of executable paths for tool version", func(t *testing.T) {
data := []byte("echo 'foo bar'")
err := os.WriteFile(filepath.Join(plugin.Dir, "bin", "list-bin-paths"), data, 0o777)
assert.Nil(t, err)
executables, err := ExecutablePaths(conf, plugin, toolversions.Version{Type: "version", Value: "1.2.3"})
path1 := executables[0]
path2 := executables[1]
assert.Nil(t, err)
assert.Equal(t, filepath.Base(path1), "foo")
assert.Equal(t, filepath.Base(path2), "bar")
})
}
func TestExecutableDirs(t *testing.T) {
conf, plugin := generateConfig(t)
installVersion(t, conf, plugin, "1.2.3")
t.Run("returns list only containing 'bin' when list-bin-paths callback missing", func(t *testing.T) {
executables, err := ExecutableDirs(plugin)
assert.Nil(t, err)
assert.Equal(t, executables, []string{"bin"})
})
t.Run("returns list of executable paths for tool version", func(t *testing.T) {
data := []byte("echo 'foo bar'")
err := os.WriteFile(filepath.Join(plugin.Dir, "bin", "list-bin-paths"), data, 0o777)
assert.Nil(t, err)
executables, err := ExecutableDirs(plugin)
assert.Nil(t, err)
assert.Equal(t, executables, []string{"foo", "bar"})
})
}
// Helper functions
func buildOutputs() (strings.Builder, strings.Builder) {
var stdout strings.Builder
var stderr strings.Builder
return stdout, stderr
}
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
return conf, installPlugin(t, conf, "dummy_plugin", testPluginName)
}
func installDummyExecPathScript(t *testing.T, conf config.Config, plugin plugins.Plugin, version toolversions.Version, name, script string) {
t.Helper()
execPath := filepath.Join(plugin.Dir, "bin", "exec-path")
contents := fmt.Sprintf("#!/usr/bin/env bash\n%s\n", script)
err := os.WriteFile(execPath, []byte(contents), 0o777)
assert.Nil(t, err)
installPath := installs.InstallPath(conf, plugin, version)
err = os.MkdirAll(filepath.Join(installPath, "bin", "custom"), 0o777)
assert.Nil(t, err)
err = os.WriteFile(filepath.Join(installPath, "bin", "custom", name), []byte{}, 0o777)
assert.Nil(t, err)
}
func installPlugin(t *testing.T, conf config.Config, fixture, pluginName string) plugins.Plugin {
_, err := repotest.InstallPlugin(fixture, conf.DataDir, pluginName)
assert.Nil(t, err)
return plugins.New(conf, pluginName)
}
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)
}