Skip to content

Commit

Permalink
Merge pull request #74 from asdf-vm/tb/remove-command-bats
Browse files Browse the repository at this point in the history
feat(golang-rewrite): more `asdf plugin remove` tests
  • Loading branch information
Stratus3D authored Oct 28, 2024
2 parents 3cb857f + 834b2d4 commit 519352b
Show file tree
Hide file tree
Showing 8 changed files with 150 additions and 87 deletions.
20 changes: 18 additions & 2 deletions cmd/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -392,17 +392,34 @@ func pluginAddCommand(_ *cli.Context, conf config.Config, logger *log.Logger, pl
}

func pluginRemoveCommand(_ *cli.Context, logger *log.Logger, pluginName string) error {
if pluginName == "" {
logger.Print("No plugin given")
os.Exit(1)
return nil
}

conf, err := config.LoadConfig()
if err != nil {
logger.Printf("error loading config: %s", err)
return err
}

err = plugins.Remove(conf, pluginName)
err = plugins.Remove(conf, pluginName, os.Stdout, os.Stderr)
if err != nil {
// Needed to match output of old version
logger.Printf("%s", err)
}

// This feels a little hacky but it works, to re-generate shims we delete them
// all and generate them again.
err2 := shims.RemoveAll(conf)
if err2 != nil {
logger.Printf("%s", err2)
os.Exit(1)
return err2
}

shims.GenerateAll(conf, os.Stdout, os.Stderr)
return err
}

Expand Down Expand Up @@ -646,7 +663,6 @@ func listAllCommand(logger *log.Logger, conf config.Config, toolName, filter str
var stderr strings.Builder

err := plugin.RunCallback("list-all", []string{}, map[string]string{}, &stdout, &stderr)

if err != nil {
fmt.Printf("Plugin %s's list-all callback script failed with output:\n", plugin.Name)
// Print to stderr
Expand Down
35 changes: 35 additions & 0 deletions internal/data/data.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// Package data provides constants and functions pertaining to directories and
// files in the asdf data directory on disk, specified by the $ASDF_DATA_DIR
package data

import (
"path/filepath"
)

const (
dataDirDownloads = "downloads"
dataDirInstalls = "installs"
dataDirPlugins = "plugins"
)

// DownloadDirectory returns the directory a plugin will be placing
// downloads of version source code
func DownloadDirectory(dataDir, pluginName string) string {
return filepath.Join(dataDir, dataDirDownloads, pluginName)
}

// InstallDirectory returns the path to a plugin directory
func InstallDirectory(dataDir, pluginName string) string {
return filepath.Join(dataDir, dataDirInstalls, pluginName)
}

// PluginsDirectory returns the path to the plugins directory in the data dir
func PluginsDirectory(dataDir string) string {
return filepath.Join(dataDir, dataDirPlugins)
}

// PluginDirectory returns the directory a plugin with a given name would be in
// if it were installed
func PluginDirectory(dataDir, pluginName string) string {
return filepath.Join(dataDir, dataDirPlugins, pluginName)
}
15 changes: 15 additions & 0 deletions internal/data/data_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package data

import "testing"

const testPluginName = "lua"

func TestPluginDirectory(t *testing.T) {
t.Run("returns new path with plugin name as last segment", func(t *testing.T) {
pluginDir := PluginDirectory("~/.asdf/", testPluginName)
expected := "~/.asdf/plugins/lua"
if pluginDir != expected {
t.Errorf("got %v, expected %v", pluginDir, expected)
}
})
}
16 changes: 4 additions & 12 deletions internal/installs/installs.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,14 @@ import (
"path/filepath"

"asdf/internal/config"
"asdf/internal/data"
"asdf/internal/plugins"
"asdf/internal/toolversions"
)

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)
installDirectory := data.InstallDirectory(conf.DataDir, plugin.Name)
files, err := os.ReadDir(installDirectory)
if err != nil {
if _, ok := err.(*fs.PathError); ok {
Expand All @@ -47,7 +43,7 @@ func InstallPath(conf config.Config, plugin plugins.Plugin, versionType, version
return version
}

return filepath.Join(pluginInstallPath(conf, plugin), toolversions.FormatForFS(versionType, version))
return filepath.Join(data.InstallDirectory(conf.DataDir, plugin.Name), toolversions.FormatForFS(versionType, version))
}

// DownloadPath returns the download path for a particular plugin and version
Expand All @@ -56,7 +52,7 @@ func DownloadPath(conf config.Config, plugin plugins.Plugin, versionType, versio
return ""
}

return filepath.Join(conf.DataDir, dataDirDownloads, plugin.Name, toolversions.FormatForFS(versionType, version))
return filepath.Join(data.DownloadDirectory(conf.DataDir, plugin.Name), toolversions.FormatForFS(versionType, version))
}

// IsInstalled checks if a specific version of a tool is installed
Expand All @@ -67,7 +63,3 @@ func IsInstalled(conf config.Config, plugin plugins.Plugin, versionType, version
_, 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)
}
60 changes: 31 additions & 29 deletions internal/plugins/plugins.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"strings"

"asdf/internal/config"
"asdf/internal/data"
"asdf/internal/execute"
"asdf/internal/git"
"asdf/internal/hook"
Expand Down Expand Up @@ -76,9 +77,8 @@ type Plugin struct {
// New takes config and a plugin name and returns a Plugin struct. It is
// intended for functions that need to quickly initialize a plugin.
func New(config config.Config, name string) Plugin {
pluginsDir := DataDirectory(config.DataDir)
dir := filepath.Join(pluginsDir, name)
return Plugin{Dir: dir, Name: name}
pluginsDir := data.PluginDirectory(config.DataDir, name)
return Plugin{Dir: pluginsDir, Name: name}
}

// LegacyFilenames returns a slice of filenames if the plugin contains the
Expand Down Expand Up @@ -173,7 +173,7 @@ func (p Plugin) RunCallback(name string, arguments []string, environment map[str
// List takes config and flags for what to return and builds a list of plugins
// representing the currently installed plugins on the system.
func List(config config.Config, urls, refs bool) (plugins []Plugin, err error) {
pluginsDir := DataDirectory(config.DataDir)
pluginsDir := data.PluginsDirectory(config.DataDir)
files, err := os.ReadDir(pluginsDir)
if err != nil {
if _, ok := err.(*fs.PathError); ok {
Expand Down Expand Up @@ -282,7 +282,7 @@ func Add(config config.Config, pluginName, pluginURL string) error {
return err
}

err = os.MkdirAll(PluginDownloadDirectory(config.DataDir, plugin.Name), 0o777)
err = os.MkdirAll(data.DownloadDirectory(config.DataDir, plugin.Name), 0o777)
if err != nil {
return err
}
Expand All @@ -298,12 +298,14 @@ func Add(config config.Config, pluginName, pluginURL string) error {
}

// Remove uninstalls a plugin by removing it from the file system if installed
func Remove(config config.Config, pluginName string) error {
func Remove(config config.Config, pluginName string, stdout, stderr io.Writer) error {
err := validatePluginName(pluginName)
if err != nil {
return err
}

plugin := New(config, pluginName)

exists, err := PluginExists(config.DataDir, pluginName)
if err != nil {
return fmt.Errorf("unable to check if plugin exists: %w", err)
Expand All @@ -313,17 +315,35 @@ func Remove(config config.Config, pluginName string) error {
return fmt.Errorf("No such plugin: %s", pluginName)
}

pluginDir := PluginDirectory(config.DataDir, pluginName)
downloadDir := PluginDownloadDirectory(config.DataDir, pluginName)
hook.Run(config, "pre_asdf_plugin_remove", []string{plugin.Name})
hook.Run(config, fmt.Sprintf("pre_asdf_plugin_remove_%s", plugin.Name), []string{})

env := map[string]string{
"ASDF_PLUGIN_PATH": plugin.Dir,
"ASDF_PLUGIN_SOURCE_URL": plugin.URL,
}
plugin.RunCallback("pre-plugin-remove", []string{}, env, stdout, stderr)

pluginDir := data.PluginDirectory(config.DataDir, pluginName)
downloadDir := data.DownloadDirectory(config.DataDir, pluginName)
installDir := data.InstallDirectory(config.DataDir, pluginName)

err = os.RemoveAll(downloadDir)
err2 := os.RemoveAll(pluginDir)
err3 := os.RemoveAll(installDir)

if err != nil {
return err
}

return err2
if err2 != nil {
return err2
}

hook.Run(config, "post_asdf_plugin_remove", []string{plugin.Name})
hook.Run(config, fmt.Sprintf("post_asdf_plugin_remove_%s", plugin.Name), []string{})

return err3
}

// Update a plugin to a specific ref, or if no ref provided update to latest
Expand All @@ -337,7 +357,7 @@ func Update(config config.Config, pluginName, ref string) (string, error) {
return "", fmt.Errorf("no such plugin: %s", pluginName)
}

pluginDir := PluginDirectory(config.DataDir, pluginName)
pluginDir := data.PluginDirectory(config.DataDir, pluginName)

plugin := git.NewRepo(pluginDir)

Expand All @@ -347,7 +367,7 @@ func Update(config config.Config, pluginName, ref string) (string, error) {
// PluginExists returns a boolean indicating whether or not a plugin with the
// provided name is currently installed
func PluginExists(dataDir, pluginName string) (bool, error) {
pluginDir := PluginDirectory(dataDir, pluginName)
pluginDir := data.PluginDirectory(dataDir, pluginName)
return directoryExists(pluginDir)
}

Expand All @@ -364,24 +384,6 @@ func directoryExists(dir string) (bool, error) {
return fileInfo.IsDir(), nil
}

// PluginDirectory returns the directory a plugin with a given name would be in
// if it were installed
func PluginDirectory(dataDir, pluginName string) string {
return filepath.Join(DataDirectory(dataDir), pluginName)
}

// PluginDownloadDirectory returns the directory a plugin will be placing
// downloads of version source code
func PluginDownloadDirectory(dataDir, pluginName string) string {
return filepath.Join(dataDir, "downloads", pluginName)
}

// DataDirectory returns the path to the plugin directory inside the data
// directory
func DataDirectory(dataDir string) string {
return filepath.Join(dataDir, dataDirPlugins)
}

func validatePluginName(name string) error {
match, err := regexp.MatchString("^[[:lower:][:digit:]_-]+$", name)
if err != nil {
Expand Down
41 changes: 20 additions & 21 deletions internal/plugins/plugins_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"testing"

"asdf/internal/config"
"asdf/internal/data"
"asdf/repotest"

"github.com/stretchr/testify/assert"
Expand Down Expand Up @@ -142,7 +143,7 @@ func TestAdd(t *testing.T) {
assert.Nil(t, err, "Expected to be able to add plugin")

// Assert plugin directory contains Git repo with bin directory
pluginDir := PluginDirectory(testDataDir, testPluginName)
pluginDir := data.PluginDirectory(testDataDir, testPluginName)

_, err = os.ReadDir(pluginDir + "/.git")
assert.Nil(t, err)
Expand All @@ -163,7 +164,7 @@ func TestAdd(t *testing.T) {
assert.Nil(t, err)

// Assert download dir exists
downloadDir := PluginDownloadDirectory(testDataDir, testPluginName)
downloadDir := data.DownloadDirectory(testDataDir, testPluginName)
_, err = os.Stat(downloadDir)
assert.Nil(t, err)
})
Expand All @@ -180,36 +181,44 @@ func TestRemove(t *testing.T) {
assert.Nil(t, err)

t.Run("returns error when plugin with name does not exist", func(t *testing.T) {
err := Remove(conf, "nonexistant")
var stdout strings.Builder
var stderr strings.Builder
err := Remove(conf, "nonexistant", &stdout, &stderr)
assert.NotNil(t, err)
assert.ErrorContains(t, err, "No such plugin")
})

t.Run("returns error when invalid plugin name is given", func(t *testing.T) {
err := Remove(conf, "foo/bar/baz")
var stdout strings.Builder
var stderr strings.Builder
err := Remove(conf, "foo/bar/baz", &stdout, &stderr)
assert.NotNil(t, err)
expectedErrMsg := "is invalid. Name may only contain lowercase letters, numbers, '_', and '-'"
assert.ErrorContains(t, err, expectedErrMsg)
})

t.Run("removes plugin when passed name of installed plugin", func(t *testing.T) {
err := Remove(conf, testPluginName)
var stdout strings.Builder
var stderr strings.Builder
err := Remove(conf, testPluginName, &stdout, &stderr)
assert.Nil(t, err)

pluginDir := PluginDirectory(testDataDir, testPluginName)
pluginDir := data.PluginDirectory(testDataDir, testPluginName)
_, err = os.Stat(pluginDir)
assert.NotNil(t, err)
assert.True(t, os.IsNotExist(err))
})

t.Run("removes plugin download dir when passed name of installed plugin", func(t *testing.T) {
var stdout strings.Builder
var stderr strings.Builder
err := Add(conf, testPluginName, repoPath)
assert.Nil(t, err)

err = Remove(conf, testPluginName)
err = Remove(conf, testPluginName, &stdout, &stderr)
assert.Nil(t, err)

downloadDir := PluginDownloadDirectory(testDataDir, testPluginName)
downloadDir := data.DownloadDirectory(testDataDir, testPluginName)
_, err = os.Stat(downloadDir)
assert.NotNil(t, err)
assert.True(t, os.IsNotExist(err))
Expand All @@ -227,7 +236,7 @@ func TestUpdate(t *testing.T) {
assert.Nil(t, err)

badPluginName := "badplugin"
badRepo := PluginDirectory(testDataDir, badPluginName)
badRepo := data.PluginDirectory(testDataDir, badPluginName)
err = os.MkdirAll(badRepo, 0o777)
assert.Nil(t, err)

Expand Down Expand Up @@ -307,7 +316,7 @@ func TestExists(t *testing.T) {

func TestPluginExists(t *testing.T) {
testDataDir := t.TempDir()
pluginDir := PluginDirectory(testDataDir, testPluginName)
pluginDir := data.PluginDirectory(testDataDir, testPluginName)
err := os.MkdirAll(pluginDir, 0o777)
if err != nil {
t.Errorf("got %v, expected nil", err)
Expand All @@ -326,7 +335,7 @@ func TestPluginExists(t *testing.T) {

t.Run("returns false when plugin path is file and not dir", func(t *testing.T) {
pluginName := "file"
pluginDir := PluginDirectory(testDataDir, pluginName)
pluginDir := data.PluginDirectory(testDataDir, pluginName)
err := touchFile(pluginDir)
if err != nil {
t.Errorf("got %v, expected nil", err)
Expand Down Expand Up @@ -354,16 +363,6 @@ func TestPluginExists(t *testing.T) {
})
}

func TestPluginDirectory(t *testing.T) {
t.Run("returns new path with plugin name as last segment", func(t *testing.T) {
pluginDir := PluginDirectory("~/.asdf/", testPluginName)
expected := "~/.asdf/plugins/lua"
if pluginDir != expected {
t.Errorf("got %v, expected %v", pluginDir, expected)
}
})
}

func TestValidatePluginName(t *testing.T) {
t.Run("returns no error when plugin name is valid", func(t *testing.T) {
err := validatePluginName(testPluginName)
Expand Down
Loading

0 comments on commit 519352b

Please sign in to comment.