Change cmd setup to bubble up errors over exiting (#454)

* Change cmd setup to bubble up errors over exiting

* Update main to handle execute error

* Update changelog with PR #454

* Slight cleanup tweaks
Этот коммит содержится в:
Gemini Smith 2022-01-25 16:52:57 -07:00 коммит произвёл GitHub
родитель 30de46da25
коммит 5001c4f4fe
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
6 изменённых файлов: 63 добавлений и 44 удалений

Просмотреть файл

@ -8,6 +8,12 @@ This document is formatted according to the principles of [Keep A CHANGELOG](htt
--- ---
## [Unreleased]
### Changed
- Changed underlying cobra command setup to return errors instead of calling `os.Exit` directly to enable simpler testing. ([454](https://github.com/cucumber/godog/pull/454) - [mxygem])
## [v0.12.4] ## [v0.12.4]
### Added ### Added

Просмотреть файл

@ -3,7 +3,6 @@ package internal
import ( import (
"fmt" "fmt"
"go/build" "go/build"
"os"
"path/filepath" "path/filepath"
"github.com/cucumber/godog/internal/builder" "github.com/cucumber/godog/internal/builder"
@ -28,7 +27,7 @@ package and contain buildable go source.
The test runner can be executed with the same flags as when using godog run.`, The test runner can be executed with the same flags as when using godog run.`,
Example: ` godog build Example: ` godog build
godog build -o ` + buildOutputDefault, godog build -o ` + buildOutputDefault,
Run: buildCmdRunFunc, RunE: buildCmdRunFunc,
} }
buildCmd.Flags().StringVarP(&buildOutput, "output", "o", buildOutputDefault, `compiles the test runner to the named file buildCmd.Flags().StringVarP(&buildOutput, "output", "o", buildOutputDefault, `compiles the test runner to the named file
@ -37,17 +36,15 @@ The test runner can be executed with the same flags as when using godog run.`,
return buildCmd return buildCmd
} }
func buildCmdRunFunc(cmd *cobra.Command, args []string) { func buildCmdRunFunc(cmd *cobra.Command, args []string) error {
bin, err := filepath.Abs(buildOutput) bin, err := filepath.Abs(buildOutput)
if err != nil { if err != nil {
fmt.Fprintln(os.Stderr, "could not locate absolute path for:", buildOutput, err) return fmt.Errorf("could not locate absolute path for: %q. reason: %v", buildOutput, err)
os.Exit(1)
} }
if err = builder.Build(bin); err != nil { if err = builder.Build(bin); err != nil {
fmt.Fprintln(os.Stderr, "could not build binary at:", buildOutput, err) return fmt.Errorf("could not build binary at: %q. reason: %v", buildOutput, err)
os.Exit(1)
} }
os.Exit(0) return nil
} }

Просмотреть файл

@ -1,9 +1,12 @@
package internal package internal
import ( import (
"fmt"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/spf13/pflag" "github.com/spf13/pflag"
"github.com/cucumber/godog/colors"
"github.com/cucumber/godog/internal/flags" "github.com/cucumber/godog/internal/flags"
) )
@ -18,21 +21,9 @@ func CreateRootCmd() cobra.Command {
Command should be run from the directory of tested package Command should be run from the directory of tested package
and contain buildable go source.`, and contain buildable go source.`,
Args: cobra.ArbitraryArgs, Args: cobra.ArbitraryArgs,
// Deprecated: Use godog build, godog run or godog version. // Deprecated: Use godog build, godog run or godog version.
// This is to support the legacy direct usage of the root command. // This is to support the legacy direct usage of the root command.
Run: func(cmd *cobra.Command, args []string) { RunE: runRootCmd,
if version {
versionCmdRunFunc(cmd, args)
}
if len(output) > 0 {
buildOutput = output
buildCmdRunFunc(cmd, args)
}
runCmdRunFunc(cmd, args)
},
} }
bindRootCmdFlags(rootCmd.Flags()) bindRootCmdFlags(rootCmd.Flags())
@ -40,6 +31,23 @@ and contain buildable go source.`,
return rootCmd return rootCmd
} }
func runRootCmd(cmd *cobra.Command, args []string) error {
if version {
versionCmdRunFunc(cmd, args)
return nil
}
if len(output) > 0 {
buildOutput = output
if err := buildCmdRunFunc(cmd, args); err != nil {
return err
}
}
fmt.Println(colors.Yellow("Use of godog without a sub-command is deprecated. Please use godog build, godog run or godog version."))
return runCmdRunFunc(cmd, args)
}
func bindRootCmdFlags(flagSet *pflag.FlagSet) { func bindRootCmdFlags(flagSet *pflag.FlagSet) {
flagSet.StringVarP(&output, "output", "o", "", "compiles the test runner to the named file") flagSet.StringVarP(&output, "output", "o", "", "compiles the test runner to the named file")
flagSet.BoolVar(&version, "version", false, "show current version") flagSet.BoolVar(&version, "version", false, "show current version")

Просмотреть файл

@ -32,7 +32,7 @@ buildable go source.`,
feature (*.feature) feature (*.feature)
scenario at specific line (*.feature:10) scenario at specific line (*.feature:10)
If no feature arguments are supplied, godog will use "features/" by default.`, If no feature arguments are supplied, godog will use "features/" by default.`,
Run: runCmdRunFunc, RunE: runCmdRunFunc,
} }
flags.BindRunCmdFlags("", runCmd.Flags(), &opts) flags.BindRunCmdFlags("", runCmd.Flags(), &opts)
@ -40,30 +40,28 @@ buildable go source.`,
return runCmd return runCmd
} }
func runCmdRunFunc(cmd *cobra.Command, args []string) { func runCmdRunFunc(cmd *cobra.Command, args []string) error {
osArgs := os.Args[1:] osArgs := os.Args[1:]
if len(osArgs) > 0 && osArgs[0] == "run" { if len(osArgs) > 0 && osArgs[0] == "run" {
osArgs = osArgs[1:] osArgs = osArgs[1:]
} }
status, err := buildAndRunGodog(osArgs) if err := buildAndRunGodog(osArgs); err != nil {
if err != nil { return err
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
} }
os.Exit(status) return nil
} }
func buildAndRunGodog(args []string) (_ int, err error) { func buildAndRunGodog(args []string) (err error) {
bin, err := filepath.Abs(buildOutputDefault) bin, err := filepath.Abs(buildOutputDefault)
if err != nil { if err != nil {
return 1, err return err
} }
if err = builder.Build(bin); err != nil { if err = builder.Build(bin); err != nil {
return 1, err return err
} }
defer os.Remove(bin) defer os.Remove(bin)
@ -71,7 +69,7 @@ func buildAndRunGodog(args []string) (_ int, err error) {
return runGodog(bin, args) return runGodog(bin, args)
} }
func runGodog(bin string, args []string) (_ int, err error) { func runGodog(bin string, args []string) (err error) {
cmd := exec.Command(bin, args...) cmd := exec.Command(bin, args...)
cmd.Stdout = os.Stdout cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr cmd.Stderr = os.Stderr
@ -79,25 +77,30 @@ func runGodog(bin string, args []string) (_ int, err error) {
cmd.Env = os.Environ() cmd.Env = os.Environ()
if err = cmd.Start(); err != nil { if err = cmd.Start(); err != nil {
return 0, err return err
} }
if err = cmd.Wait(); err == nil { if err = cmd.Wait(); err == nil {
return 0, nil return nil
} }
exiterr, ok := err.(*exec.ExitError) exiterr, ok := err.(*exec.ExitError)
if !ok { if !ok {
return 0, err return err
}
st, ok := exiterr.Sys().(syscall.WaitStatus)
if !ok {
return fmt.Errorf("failed to convert error to syscall wait status. original error: %w", exiterr)
} }
// This works on both Unix and Windows. Although package // This works on both Unix and Windows. Although package
// syscall is generally platform dependent, WaitStatus is // syscall is generally platform dependent, WaitStatus is
// defined for both Unix and Windows and in both cases has // defined for both Unix and Windows and in both cases has
// an ExitStatus() method with the same signature. // an ExitStatus() method with the same signature.
if st, ok := exiterr.Sys().(syscall.WaitStatus); ok { if st.ExitStatus() > 0 {
return st.ExitStatus(), nil return err
} }
return 1, nil return nil
} }

Просмотреть файл

@ -12,10 +12,9 @@ import (
// CreateVersionCmd creates the version subcommand. // CreateVersionCmd creates the version subcommand.
func CreateVersionCmd() cobra.Command { func CreateVersionCmd() cobra.Command {
versionCmd := cobra.Command{ versionCmd := cobra.Command{
Use: "version", Use: "version",
Short: "Show current version", Short: "Show current version",
Run: versionCmdRunFunc, Version: godog.Version,
DisableFlagsInUseLine: true,
} }
return versionCmd return versionCmd
@ -23,5 +22,4 @@ func CreateVersionCmd() cobra.Command {
func versionCmdRunFunc(cmd *cobra.Command, args []string) { func versionCmdRunFunc(cmd *cobra.Command, args []string) {
fmt.Fprintln(os.Stdout, "Godog version is:", godog.Version) fmt.Fprintln(os.Stdout, "Godog version is:", godog.Version)
os.Exit(0)
} }

Просмотреть файл

@ -1,6 +1,9 @@
package main package main
import ( import (
"fmt"
"os"
"github.com/cucumber/godog/cmd/godog/internal" "github.com/cucumber/godog/cmd/godog/internal"
) )
@ -11,5 +14,9 @@ func main() {
versionCmd := internal.CreateVersionCmd() versionCmd := internal.CreateVersionCmd()
rootCmd.AddCommand(&buildCmd, &runCmd, &versionCmd) rootCmd.AddCommand(&buildCmd, &runCmd, &versionCmd)
rootCmd.Execute()
if err := rootCmd.Execute(); err != nil {
fmt.Println(err)
os.Exit(1)
}
} }