Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added Cobra for the Command Line Interface #321

Merged
merged 1 commit into from
Oct 8, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,11 @@ var opts = godog.Options{
}

func init() {
// godog v0.10.0 (latest) and earlier
godog.BindFlags("godog.", flag.CommandLine, &opts)

// godog v0.11.0
godog.BindCommandLineFlags("godog.", &opts)
}

func TestMain(m *testing.M) {
Expand Down
2 changes: 1 addition & 1 deletion _examples/assert-godogs/godogs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import (
var opts = godog.Options{Output: colors.Colored(os.Stdout)}

func init() {
godog.BindFlags("godog.", flag.CommandLine, &opts)
godog.BindCommandLineFlags("godog.", &opts)
}

func TestMain(m *testing.M) {
Expand Down
2 changes: 1 addition & 1 deletion _examples/godogs/godogs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import (
var opts = godog.Options{Output: colors.Colored(os.Stdout)}

func init() {
godog.BindFlags("godog.", flag.CommandLine, &opts)
godog.BindCommandLineFlags("godog.", &opts)
}

func TestMain(m *testing.M) {
Expand Down
53 changes: 53 additions & 0 deletions cmd/godog/internal/cmd_build.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package internal

import (
"fmt"
"go/build"
"os"
"path/filepath"

"github.com/spf13/cobra"

"github.com/cucumber/godog/internal/builder"
)

var buildOutput string
var buildOutputDefault = "godog.test"

// CreateBuildCmd creates the build subcommand.
func CreateBuildCmd() cobra.Command {
if build.Default.GOOS == "windows" {
buildOutputDefault += ".exe"
}

buildCmd := cobra.Command{
Use: "build",
Short: "Compiles a test runner",
Long: `Compiles a test runner. Command should be run from the directory of tested
package and contain buildable go source.

The test runner can be executed with the same flags as when using godog run.`,
Example: ` godog build
godog build -o ` + buildOutputDefault,
Run: buildCmdRunFunc,
}

buildCmd.Flags().StringVarP(&buildOutput, "output", "o", buildOutputDefault, "compiles the test runner to the named file")

return buildCmd
}

func buildCmdRunFunc(cmd *cobra.Command, args []string) {
bin, err := filepath.Abs(buildOutput)
if err != nil {
fmt.Fprintln(os.Stderr, "could not locate absolute path for:", buildOutput, err)
os.Exit(1)
}

if err = builder.Build(bin); err != nil {
fmt.Fprintln(os.Stderr, "could not build binary at:", buildOutput, err)
os.Exit(1)
}

os.Exit(0)
}
61 changes: 61 additions & 0 deletions cmd/godog/internal/cmd_root.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package internal

import (
"github.com/spf13/cobra"
flag "github.com/spf13/pflag"

"github.com/cucumber/godog/internal/flags"
)

var version bool
var output string

// CreateRootCmd creates the root command.
func CreateRootCmd() cobra.Command {
rootCmd := cobra.Command{
Use: "godog",
Long: `Creates and runs test runner for the given feature files.
Command should be run from the directory of tested package
and contain buildable go source.`,
Args: cobra.ArbitraryArgs,

// Deprecated: Use godog build, godog run or godog version.
// This is to support the legacy direct usage of the root command.
Run: func(cmd *cobra.Command, args []string) {
if version {
versionCmdRunFunc(cmd, args)
}

if len(output) > 0 {
buildOutput = output
buildCmdRunFunc(cmd, args)
}

runCmdRunFunc(cmd, args)
},
}

bindRootCmdFlags(rootCmd.Flags())

return rootCmd
}

func bindRootCmdFlags(flagSet *flag.FlagSet) {
flagSet.StringVarP(&output, "output", "o", "", "compiles the test runner to the named file")
flagSet.BoolVar(&version, "version", false, "show current version")

flags.BindRunCmdFlags("", flagSet, &opts)

// Since using the root command directly is deprecated.
// All flags will be hidden
flagSet.MarkHidden("output")
flagSet.MarkHidden("version")
flagSet.MarkHidden("no-colors")
flagSet.MarkHidden("concurrency")
flagSet.MarkHidden("tags")
flagSet.MarkHidden("format")
flagSet.MarkHidden("definitions")
flagSet.MarkHidden("stop-on-failure")
flagSet.MarkHidden("strict")
flagSet.MarkHidden("random")
}
103 changes: 103 additions & 0 deletions cmd/godog/internal/cmd_run.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
package internal

import (
"fmt"
"os"
"os/exec"
"path/filepath"
"syscall"

"github.com/spf13/cobra"

"github.com/cucumber/godog/internal/builder"
"github.com/cucumber/godog/internal/flags"
)

var opts flags.Options

// CreateRunCmd creates the run subcommand.
func CreateRunCmd() cobra.Command {
runCmd := cobra.Command{
Use: "run [features]",
Short: "Compiles and runs a test runner",
Long: `Compiles and runs test runner for the given feature files.
Command should be run from the directory of tested package and contain
buildable go source.`,
Example: ` godog run
godog run <feature>
godog run <feature> <feature>

Optional feature(s) to run:
- dir (features/)
- feature (*.feature)
- scenario at specific line (*.feature:10)
If no feature arguments are supplied, godog will use "features/" by default.`,
Run: runCmdRunFunc,
}

flags.BindRunCmdFlags("", runCmd.Flags(), &opts)

return runCmd
}

func runCmdRunFunc(cmd *cobra.Command, args []string) {
osArgs := os.Args[1:]

if len(osArgs) > 0 && osArgs[0] == "run" {
osArgs = osArgs[1:]
}

status, err := buildAndRunGodog(osArgs)
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}

os.Exit(status)
}

func buildAndRunGodog(args []string) (_ int, err error) {
bin, err := filepath.Abs(buildOutputDefault)
if err != nil {
return 1, err
}

if err = builder.Build(bin); err != nil {
return 1, err
}

defer os.Remove(bin)

return runGodog(bin, args)
}

func runGodog(bin string, args []string) (_ int, err error) {
cmd := exec.Command(bin, args...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Stdin = os.Stdin
cmd.Env = os.Environ()

if err = cmd.Start(); err != nil {
return 0, err
}

if err = cmd.Wait(); err == nil {
return 0, nil
}

exiterr, ok := err.(*exec.ExitError)
if !ok {
return 0, err
}

// This works on both Unix and Windows. Although package
// syscall is generally platform dependent, WaitStatus is
// defined for both Unix and Windows and in both cases has
// an ExitStatus() method with the same signature.
if st, ok := exiterr.Sys().(syscall.WaitStatus); ok {
return st.ExitStatus(), nil
}

return 1, nil
}
27 changes: 27 additions & 0 deletions cmd/godog/internal/cmd_version.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package internal

import (
"fmt"
"os"

"github.com/spf13/cobra"

"github.com/cucumber/godog"
)

// CreateVersionCmd creates the version subcommand.
func CreateVersionCmd() cobra.Command {
versionCmd := cobra.Command{
Use: "version",
Short: "Show current version",
Run: versionCmdRunFunc,
DisableFlagsInUseLine: true,
}

return versionCmd
}

func versionCmdRunFunc(cmd *cobra.Command, args []string) {
fmt.Fprintln(os.Stdout, "Godog version is:", godog.Version)
os.Exit(0)
}
105 changes: 7 additions & 98 deletions cmd/godog/main.go
Original file line number Diff line number Diff line change
@@ -1,106 +1,15 @@
package main

import (
"fmt"
"go/build"
"os"
"os/exec"
"path/filepath"
"syscall"

"github.com/cucumber/godog"
"github.com/cucumber/godog/colors"
"github.com/cucumber/godog/internal/builder"
"github.com/cucumber/godog/cmd/godog/internal"
)

var parsedStatus int

func buildAndRun() (int, error) {
var status int

bin, err := filepath.Abs("godog.test")
if err != nil {
return 1, err
}
if build.Default.GOOS == "windows" {
bin += ".exe"
}
if err = builder.Build(bin); err != nil {
return 1, err
}
defer os.Remove(bin)

cmd := exec.Command(bin, os.Args[1:]...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Stdin = os.Stdin
cmd.Env = os.Environ()

if err = cmd.Start(); err != nil {
return status, err
}

if err = cmd.Wait(); err != nil {
if exiterr, ok := err.(*exec.ExitError); ok {
// The program has exited with an exit code != 0
status = 1

// This works on both Unix and Windows. Although package
// syscall is generally platform dependent, WaitStatus is
// defined for both Unix and Windows and in both cases has
// an ExitStatus() method with the same signature.
if st, ok := exiterr.Sys().(syscall.WaitStatus); ok {
status = st.ExitStatus()
}
return status, nil
}
return status, err
}
return status, nil
}

func main() {
var vers bool
var output string

opt := godog.Options{Output: colors.Colored(os.Stdout)}
flagSet := godog.FlagSet(&opt)
flagSet.BoolVar(&vers, "version", false, "Show current version.")
flagSet.StringVar(&output, "o", "", "Build and output test runner executable to given target path.")
flagSet.StringVar(&output, "output", "", "Build and output test runner executable to given target path.")

if err := flagSet.Parse(os.Args[1:]); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}

if len(output) > 0 {
bin, err := filepath.Abs(output)
if err != nil {
fmt.Fprintln(os.Stderr, "could not locate absolute path for:", output, err)
os.Exit(1)
}
if err = builder.Build(bin); err != nil {
fmt.Fprintln(os.Stderr, "could not build binary at:", output, err)
os.Exit(1)
}
os.Exit(0)
}

if vers {
fmt.Fprintln(os.Stdout, "Godog version is:", godog.Version)
os.Exit(0) // should it be 0?
}
rootCmd := internal.CreateRootCmd()
buildCmd := internal.CreateBuildCmd()
runCmd := internal.CreateRunCmd()
versionCmd := internal.CreateVersionCmd()

status, err := buildAndRun()
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
// it might be a case, that status might not be resolved
// in some OSes. this is attempt to parse it from stderr
if parsedStatus > status {
status = parsedStatus
}
os.Exit(status)
rootCmd.AddCommand(&buildCmd, &runCmd, &versionCmd)
rootCmd.Execute()
}
Loading