diff --git a/cmd/cmd.go b/cmd/cmd.go index 60effb4f..322700dc 100644 --- a/cmd/cmd.go +++ b/cmd/cmd.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "io" + "io/fs" "log" "os" "path/filepath" @@ -211,6 +212,26 @@ func Execute(version string) { return pluginUpdateCommand(cCtx, logger, args.Get(0), args.Get(1)) }, }, + { + Name: "test", + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "asdf-tool-version", + Usage: "The tool version to use during testing", + }, + &cli.StringFlag{ + Name: "asdf-plugin-gitref", + Usage: "The plugin Git ref to test", + }, + }, + Action: func(cCtx *cli.Context) error { + toolVersion := cCtx.String("asdf-tool-version") + gitRef := cCtx.String("asdf-plugin-gitref") + args := cCtx.Args().Slice() + pluginTestCommand(logger, args, toolVersion, gitRef) + return nil + }, + }, }, }, { @@ -821,6 +842,97 @@ func pluginUpdateCommand(cCtx *cli.Context, logger *log.Logger, pluginName, ref return err } +func pluginTestCommand(l *log.Logger, args []string, _, _ string) { + conf, err := config.LoadConfig() + if err != nil { + l.Printf("error loading config: %s", err) + os.Exit(1) + return + } + + if len(args) < 2 { + failTest(l, "please provide a plugin name and url") + } + + name := args[0] + url := args[1] + testName := fmt.Sprintf("asdf-test-%s", name) + + // Install plugin + err = plugins.Add(conf, testName, url) + if err != nil { + failTest(l, fmt.Sprintf("%s was not properly installed", name)) + } + + // Remove plugin + var blackhole strings.Builder + defer plugins.Remove(conf, testName, &blackhole, &blackhole) + + // Assert callbacks are present + plugin := plugins.New(conf, testName) + files, err := os.ReadDir(filepath.Join(plugin.Dir, "bin")) + if _, ok := err.(*fs.PathError); ok { + failTest(l, "bin/ directory does not exist") + } + + callbacks := []string{} + for _, file := range files { + callbacks = append(callbacks, file.Name()) + } + + for _, expectedCallback := range []string{"download", "install", "list-all"} { + if !slices.Contains(callbacks, expectedCallback) { + failTest(l, fmt.Sprintf("missing callback %s", expectedCallback)) + } + } + + allCallbacks := []string{"download", "install", "list-all", "latest-stable", "help.overview", "help.deps", "help.config", "help.links", "list-bin-paths", "exec-env", "exec-path", "uninstall", "list-legacy-filenames", "parse-legacy-file", "post-plugin-add", "post-plugin-update", "pre-plugin-remove"} + + // Assert all callbacks present are executable + for _, file := range files { + // file is a callback... + if slices.Contains(allCallbacks, file.Name()) { + // check if it is executable + info, _ := file.Info() + if !(info.Mode()&0o111 != 0) { + failTest(l, fmt.Sprintf("callback lacks executable permission: %s", file.Name())) + } + } + } + + // Assert has license + licensePath := filepath.Join(plugin.Dir, "LICENSE") + if _, err := os.Stat(licensePath); errors.Is(err, os.ErrNotExist) { + failTest(l, "LICENSE file must be present in the plugin repository") + } + + bytes, err := os.ReadFile(licensePath) + if err != nil { + failTest(l, "LICENSE file must be present in the plugin repository") + } + + // Validate license file not empty + if len(bytes) == 0 { + failTest(l, "LICENSE file in the plugin repository must not be empty") + } + + // Validate it returns at least one available version + var output strings.Builder + err = plugin.RunCallback("list-all", []string{}, map[string]string{}, &output, &blackhole) + if err != nil { + failTest(l, "Unable to list available versions") + } + + if len(strings.Split(output.String(), " ")) < 1 { + failTest(l, "list-all did not return any version") + } +} + +func failTest(logger *log.Logger, msg string) { + logger.Printf("FAILED: %s", msg) + os.Exit(1) +} + func formatUpdateResult(logger *log.Logger, pluginName, updatedToRef string, err error) { if err != nil { logger.Printf("failed to update %s due to error: %s\n", pluginName, err) diff --git a/docs/guide/upgrading-from-v0-14-to-v0-15.md b/docs/guide/upgrading-from-v0-14-to-v0-15.md index 670a7f7e..ca32771c 100644 --- a/docs/guide/upgrading-from-v0-14-to-v0-15.md +++ b/docs/guide/upgrading-from-v0-14-to-v0-15.md @@ -25,6 +25,7 @@ versions are supported. The affected commands: * `asdf plugin-list-all` -> `asdf plugin list all` * `asdf plugin-update` -> `asdf plugin update` * `asdf plugin-remove` -> `asdf plugin remove` +* `asdf plugin-test` -> `asdf plugin test` * `asdf shim-versions` -> `asdf shimversions` ### `asdf global` and `asdf local` commands have been replaced by the `asdf set` command diff --git a/main_test.go b/main_test.go index 14add6d3..2e73eccd 100644 --- a/main_test.go +++ b/main_test.go @@ -59,9 +59,9 @@ func TestBatsTests(t *testing.T) { runBatsFile(t, dir, "plugin_remove_command.bats") }) - //t.Run("plugin_test_command", func(t *testing.T) { - // runBatsFile(t, dir, "plugin_test_command.bats") - //}) + t.Run("plugin_test_command", func(t *testing.T) { + runBatsFile(t, dir, "plugin_test_command.bats") + }) //t.Run("plugin_update_command", func(t *testing.T) { // runBatsFile(t, dir, "plugin_update_command.bats") diff --git a/test/plugin_test_command.bats b/test/plugin_test_command.bats index c725450c..389f8bb2 100644 --- a/test/plugin_test_command.bats +++ b/test/plugin_test_command.bats @@ -12,23 +12,23 @@ teardown() { } @test "plugin_test_command with no URL specified prints an error" { - run asdf plugin-test "elixir" + run asdf plugin test "elixir" [ "$status" -eq 1 ] [ "$output" = "FAILED: please provide a plugin name and url" ] } @test "plugin_test_command with no name or URL specified prints an error" { - run asdf plugin-test + run asdf plugin test [ "$status" -eq 1 ] [ "$output" = "FAILED: please provide a plugin name and url" ] } @test "plugin_test_command works with no options provided" { - run asdf plugin-test dummy "${BASE_DIR}/repo-dummy" + run asdf plugin test dummy "${BASE_DIR}/repo-dummy" [ "$status" -eq 0 ] } @test "plugin_test_command works with all options provided" { - run asdf plugin-test dummy "${BASE_DIR}/repo-dummy" --asdf-tool-version 1.0.0 --asdf-plugin-gitref master + run asdf plugin test dummy "${BASE_DIR}/repo-dummy" --asdf-tool-version 1.0.0 --asdf-plugin-gitref master [ "$status" -eq 0 ] }