feat(golang-rewrite): BATS test fixes & latest command

* Get asdf info BATS tests working
* Create `versions.Installed` function
* Update `versions.Latest` to return single version
* Implement `latest` asdf command
This commit is contained in:
Trevor Brown 2024-08-20 08:15:05 -04:00
parent bd9d80f49e
commit 040df16084
6 changed files with 163 additions and 43 deletions

View File

@ -60,6 +60,22 @@ func Execute(version string) {
return installCommand(logger, args.Get(0), args.Get(1))
},
},
{
Name: "latest",
Flags: []cli.Flag{
&cli.BoolFlag{
Name: "all",
Usage: "Show latest version of all tools",
},
},
Action: func(cCtx *cli.Context) error {
tool := cCtx.Args().Get(0)
pattern := cCtx.Args().Get(1)
all := cCtx.Bool("all")
return latestCommand(logger, all, tool, pattern)
},
},
{
Name: "plugin",
Action: func(_ *cli.Context) error {
@ -313,3 +329,72 @@ func parseInstallVersion(version string) (string, string) {
return version, ""
}
func latestCommand(logger *log.Logger, all bool, toolName, pattern string) (err error) {
conf, err := config.LoadConfig()
if err != nil {
logger.Printf("error loading config: %s", err)
return err
}
if !all {
err = latestForPlugin(conf, toolName, pattern, false)
if err != nil {
os.Exit(1)
}
return err
}
plugins, err := plugins.List(conf, false, false)
if err != nil {
logger.Printf("error loading plugin list: %s", err, false)
return err
}
var maybeErr error
// loop over all plugins and show latest for each one.
for _, plugin := range plugins {
maybeErr = latestForPlugin(conf, plugin.Name, "", true)
if maybeErr != nil {
err = maybeErr
}
}
if err != nil {
os.Exit(1)
return maybeErr
}
return nil
}
func latestForPlugin(conf config.Config, toolName, pattern string, showStatus bool) error {
// show single plugin
plugin := plugins.New(conf, toolName)
latest, err := versions.Latest(plugin, pattern)
if err != nil && err.Error() != "no latest version found" {
fmt.Printf("unable to load latest version: %s\n", err)
return err
}
if latest == "" {
err := fmt.Errorf("No compatible versions available (%s %s)", toolName, pattern)
fmt.Println(err.Error())
return err
}
if showStatus {
installed := versions.Installed(conf, plugin, latest)
fmt.Printf("%s\t%s\t%s\n", plugin.Name, latest, installedStatus(installed))
} else {
fmt.Printf("%s\n", latest)
}
return nil
}
func installedStatus(installed bool) string {
if installed {
return "installed"
}
return "missing"
}

View File

@ -23,7 +23,6 @@ const (
uninstallableVersionMsg = "uninstallable version: system"
dataDirDownloads = "downloads"
dataDirInstalls = "installs"
defaultQuery = "[0-9]"
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"
)
@ -98,16 +97,10 @@ func InstallVersion(conf config.Config, plugin plugins.Plugin, version string, p
}
if version == latestVersion {
versions, err := Latest(plugin, pattern)
version, err = Latest(plugin, pattern)
if err != nil {
return err
}
if len(versions) < 1 {
return errors.New(noLatestVersionErrMsg)
}
version = versions[0]
}
return InstallOneVersion(conf, plugin, version, stdOut, stdErr)
@ -128,8 +121,7 @@ func InstallOneVersion(conf config.Config, plugin plugins.Plugin, version string
installDir := installPath(conf, plugin, version)
versionType, version := ParseString(version)
// Check if version already installed
if _, err = os.Stat(installDir); !os.IsNotExist(err) {
if Installed(conf, plugin, version) {
return fmt.Errorf("version %s of %s is already installed", version, plugin.Name)
}
@ -177,41 +169,50 @@ func InstallOneVersion(conf config.Config, plugin plugins.Plugin, version string
return nil
}
// Installed checks if a specific version of a tool is installed
func Installed(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
// provided.
func Latest(plugin plugins.Plugin, query string) (versions []string, err error) {
if query == "" {
query = defaultQuery
}
func Latest(plugin plugins.Plugin, query string) (version string, err error) {
var stdOut strings.Builder
var stdErr strings.Builder
err = plugin.RunCallback("latest-stable", []string{query}, map[string]string{}, &stdOut, &stdErr)
if err != nil {
if _, ok := err.(plugins.NoCallbackError); !ok {
return versions, err
return version, err
}
allVersions, err := AllVersionsFiltered(plugin, query)
if err != nil {
return versions, err
return version, err
}
versions = filterByRegex(allVersions, latestFilterRegex, false)
versions := filterOutByRegex(allVersions, latestFilterRegex)
if len(versions) < 1 {
return versions, nil
return version, errors.New(noLatestVersionErrMsg)
}
return []string{versions[len(versions)-1]}, nil
return versions[len(versions)-1], nil
}
// parse stdOut and return version
versions = parseVersions(stdOut.String())
return versions, nil
allVersions := parseVersions(stdOut.String())
versions := filterOutByRegex(allVersions, latestFilterRegex)
if len(versions) < 1 {
return version, errors.New(noLatestVersionErrMsg)
}
return versions[len(versions)-1], nil
}
// AllVersions returns a slice of all available versions for the tool managed by
@ -238,13 +239,23 @@ func AllVersionsFiltered(plugin plugins.Plugin, query string) (versions []string
return versions, err
}
return filterByRegex(all, query, true), err
return filterByExactMatch(all, query), err
}
func filterByRegex(allVersions []string, pattern string, include bool) (versions []string) {
func filterByExactMatch(allVersions []string, pattern string) (versions []string) {
for _, version := range allVersions {
if strings.HasPrefix(version, pattern) {
versions = append(versions, version)
}
}
return versions
}
func filterOutByRegex(allVersions []string, pattern string) (versions []string) {
for _, version := range allVersions {
match, _ := regexp.MatchString(pattern, version)
if match && include || !match && !include {
if !match {
versions = append(versions, version)
}
}

View File

@ -263,6 +263,20 @@ func TestInstallOneVersion(t *testing.T) {
})
}
func TestInstalled(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, Installed(conf, plugin, "4.0.0"))
})
t.Run("returns true when installed", func(t *testing.T) {
assert.True(t, Installed(conf, plugin, "1.0.0"))
})
}
func TestLatest(t *testing.T) {
pluginName := "latest_test"
conf, _ := generateConfig(t)
@ -276,27 +290,27 @@ func TestLatest(t *testing.T) {
assert.Nil(t, err)
plugin := plugins.New(conf, pluginName)
versions, err := Latest(plugin, "")
version, err := Latest(plugin, "")
assert.Nil(t, err)
assert.Equal(t, []string{"2.0.0"}, versions)
assert.Equal(t, "2.0.0", version)
})
t.Run("when given query matching no versions return empty slice of versions", func(t *testing.T) {
versions, err := Latest(plugin, "impossible-to-satisfy-query")
assert.Nil(t, err)
assert.Empty(t, versions)
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) {
versions, err := Latest(plugin, "")
version, err := Latest(plugin, "")
assert.Nil(t, err)
assert.Equal(t, []string{"5.1.0"}, versions)
assert.Equal(t, "5.1.0", version)
})
t.Run("when given no query returns latest version of plugin", func(t *testing.T) {
versions, err := Latest(plugin, "^4")
version, err := Latest(plugin, "4")
assert.Nil(t, err)
assert.Equal(t, []string{"4.0.0"}, versions)
assert.Equal(t, "4.0.0", version)
})
}

View File

@ -27,17 +27,17 @@ func TestBatsTests(t *testing.T) {
// runBatsFile(t, dir, "help_command.bats")
//})
//t.Run("info_command", func(t *testing.T) {
// runBatsFile(t, dir, "info_command.bats")
//})
t.Run("info_command", func(t *testing.T) {
runBatsFile(t, dir, "info_command.bats")
})
//t.Run("install_command", func(t *testing.T) {
// runBatsFile(t, dir, "install_command.bats")
//})
//t.Run("latest_command", func(t *testing.T) {
// runBatsFile(t, dir, "latest_command.bats")
//})
t.Run("latest_command", func(t *testing.T) {
runBatsFile(t, dir, "latest_command.bats")
})
//t.Run("list_command", func(t *testing.T) {
// runBatsFile(t, dir, "list_command.bats")

View File

@ -50,7 +50,7 @@ teardown() {
@test "[latest_command - dummy_legacy_plugin] No stable version should return an error" {
run asdf latest legacy-dummy 3
[ -z "$output" ]
[ "No compatible versions available (legacy-dummy 3)" = "$output" ]
[ "$status" -eq 1 ]
}

View File

@ -36,7 +36,9 @@ setup_asdf_dir() {
install_mock_plugin() {
local plugin_name=$1
local location="${2:-$ASDF_DIR}"
cp -r "$BATS_TEST_DIRNAME/fixtures/dummy_plugin" "$location/plugins/$plugin_name"
plugin_dir="$location/plugins/$plugin_name"
cp -r "$BATS_TEST_DIRNAME/fixtures/dummy_plugin" "$plugin_dir"
init_git_repo "$plugin_dir"
}
install_mock_plugin_no_download() {
@ -48,7 +50,9 @@ install_mock_plugin_no_download() {
install_mock_legacy_plugin() {
local plugin_name=$1
local location="${2:-$ASDF_DIR}"
cp -r "$BATS_TEST_DIRNAME/fixtures/dummy_legacy_plugin" "$location/plugins/$plugin_name"
plugin_dir="$location/plugins/$plugin_name"
cp -r "$BATS_TEST_DIRNAME/fixtures/dummy_legacy_plugin" "$plugin_dir"
init_git_repo "$plugin_dir"
}
install_mock_broken_plugin() {
@ -61,11 +65,17 @@ install_mock_plugin_repo() {
local plugin_name=$1
local location="${BASE_DIR}/repo-${plugin_name}"
cp -r "$BATS_TEST_DIRNAME/fixtures/dummy_plugin" "${location}"
init_git_repo "${location}"
}
init_git_repo() {
location="$1"
git -C "${location}" init -q
git -C "${location}" config user.name "Test"
git -C "${location}" config user.email "test@example.com"
git -C "${location}" add -A
git -C "${location}" commit -q -m "asdf ${plugin_name} plugin"
git -C "${location}" remote add origin "https://asdf-vm.com/fake-repo"
}
install_mock_plugin_version() {