Merge pull request #58 from asdf-vm/tb/bats-test-fixes

feat(golang-rewrite): BATS test fixes & `latest` command
This commit is contained in:
Trevor Brown 2024-08-22 08:56:12 -04:00 committed by Trevor Brown
commit 99cabeaf37
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)) 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", Name: "plugin",
Action: func(_ *cli.Context) error { Action: func(_ *cli.Context) error {
@ -313,3 +329,72 @@ func parseInstallVersion(version string) (string, string) {
return version, "" 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" uninstallableVersionMsg = "uninstallable version: system"
dataDirDownloads = "downloads" dataDirDownloads = "downloads"
dataDirInstalls = "installs" 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)" 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" noLatestVersionErrMsg = "no latest version found"
) )
@ -98,16 +97,10 @@ func InstallVersion(conf config.Config, plugin plugins.Plugin, version string, p
} }
if version == latestVersion { if version == latestVersion {
versions, err := Latest(plugin, pattern) version, err = Latest(plugin, pattern)
if err != nil { if err != nil {
return err return err
} }
if len(versions) < 1 {
return errors.New(noLatestVersionErrMsg)
}
version = versions[0]
} }
return InstallOneVersion(conf, plugin, version, stdOut, stdErr) 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) installDir := installPath(conf, plugin, version)
versionType, version := ParseString(version) versionType, version := ParseString(version)
// Check if version already installed if Installed(conf, plugin, version) {
if _, err = os.Stat(installDir); !os.IsNotExist(err) {
return fmt.Errorf("version %s of %s is already installed", version, plugin.Name) 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 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 // 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 // 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 // callback and returns the last version matching the query, if a query is
// provided. // provided.
func Latest(plugin plugins.Plugin, query string) (versions []string, err error) { func Latest(plugin plugins.Plugin, query string) (version string, err error) {
if query == "" {
query = defaultQuery
}
var stdOut strings.Builder var stdOut strings.Builder
var stdErr strings.Builder var stdErr strings.Builder
err = plugin.RunCallback("latest-stable", []string{query}, map[string]string{}, &stdOut, &stdErr) err = plugin.RunCallback("latest-stable", []string{query}, map[string]string{}, &stdOut, &stdErr)
if err != nil { if err != nil {
if _, ok := err.(plugins.NoCallbackError); !ok { if _, ok := err.(plugins.NoCallbackError); !ok {
return versions, err return version, err
} }
allVersions, err := AllVersionsFiltered(plugin, query) allVersions, err := AllVersionsFiltered(plugin, query)
if err != nil { if err != nil {
return versions, err return version, err
} }
versions = filterByRegex(allVersions, latestFilterRegex, false) versions := filterOutByRegex(allVersions, latestFilterRegex)
if len(versions) < 1 { 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 // parse stdOut and return version
versions = parseVersions(stdOut.String()) allVersions := parseVersions(stdOut.String())
return versions, nil 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 // 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 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 { for _, version := range allVersions {
match, _ := regexp.MatchString(pattern, version) match, _ := regexp.MatchString(pattern, version)
if match && include || !match && !include { if !match {
versions = append(versions, version) 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) { func TestLatest(t *testing.T) {
pluginName := "latest_test" pluginName := "latest_test"
conf, _ := generateConfig(t) conf, _ := generateConfig(t)
@ -276,27 +290,27 @@ func TestLatest(t *testing.T) {
assert.Nil(t, err) assert.Nil(t, err)
plugin := plugins.New(conf, pluginName) plugin := plugins.New(conf, pluginName)
versions, err := Latest(plugin, "") version, err := Latest(plugin, "")
assert.Nil(t, err) 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) { 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") version, err := Latest(plugin, "impossible-to-satisfy-query")
assert.Nil(t, err) assert.Error(t, err, "no latest version found")
assert.Empty(t, versions) assert.Equal(t, version, "")
}) })
t.Run("when given no query returns latest version of plugin", func(t *testing.T) { 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.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) { 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.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") // runBatsFile(t, dir, "help_command.bats")
//}) //})
//t.Run("info_command", func(t *testing.T) { t.Run("info_command", func(t *testing.T) {
// runBatsFile(t, dir, "info_command.bats") runBatsFile(t, dir, "info_command.bats")
//}) })
//t.Run("install_command", func(t *testing.T) { //t.Run("install_command", func(t *testing.T) {
// runBatsFile(t, dir, "install_command.bats") // runBatsFile(t, dir, "install_command.bats")
//}) //})
//t.Run("latest_command", func(t *testing.T) { t.Run("latest_command", func(t *testing.T) {
// runBatsFile(t, dir, "latest_command.bats") runBatsFile(t, dir, "latest_command.bats")
//}) })
//t.Run("list_command", func(t *testing.T) { //t.Run("list_command", func(t *testing.T) {
// runBatsFile(t, dir, "list_command.bats") // 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" { @test "[latest_command - dummy_legacy_plugin] No stable version should return an error" {
run asdf latest legacy-dummy 3 run asdf latest legacy-dummy 3
[ -z "$output" ] [ "No compatible versions available (legacy-dummy 3)" = "$output" ]
[ "$status" -eq 1 ] [ "$status" -eq 1 ]
} }

View File

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