├── .editorconfig ├── .travis.yml ├── README.md ├── common.go ├── common_test.go ├── group.go ├── group_test.go ├── parse.go ├── parse_test.go ├── run.go └── run_test.go /.editorconfig: -------------------------------------------------------------------------------- 1 | ; http://editorconfig.org/ 2 | 3 | root = true 4 | 5 | [*] 6 | end_of_line = lf 7 | insert_final_newline = true 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | 11 | [*.go] 12 | indent_style = tab 13 | indent_size = 4 14 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | go: 4 | - 1.x 5 | - master 6 | 7 | before_install: 8 | - go get -t -v ./... 9 | 10 | script: 11 | - go test -race -coverprofile=coverage.txt -covermode=atomic 12 | 13 | after_success: 14 | - bash <(curl -s https://codecov.io/bash) -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # cmdr 2 | [![Build Status](https://travis-ci.org/sebastianwebber/cmdr.svg?branch=master)](https://travis-ci.org/sebastianwebber/cmdr) [![Go Report Card](https://goreportcard.com/badge/github.com/sebastianwebber/cmdr)](https://goreportcard.com/report/github.com/sebastianwebber/cmdr) [![codecov](https://codecov.io/gh/sebastianwebber/cmdr/branch/master/graph/badge.svg)](https://codecov.io/gh/sebastianwebber/cmdr) 3 | [![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Fsebastianwebber%2Fcmdr.svg?type=shield)](https://app.fossa.io/projects/git%2Bgithub.com%2Fsebastianwebber%2Fcmdr?ref=badge_shield) 4 | 5 | 6 | `cmdr` (pronounced _"commander"_) is a go package to abstract and simplify execution of commands on the operation system. 7 | 8 | ## how to use it 9 | 10 | First things first: 11 | ``` 12 | go get -u -v go get github.com/sebastianwebber/cmdr 13 | ``` 14 | 15 | 16 | Basically create a `Command` and call the `Run` function. Take a look: 17 | 18 | ```golang 19 | package main 20 | 21 | import ( 22 | "fmt" 23 | 24 | "github.com/sebastianwebber/cmdr" 25 | ) 26 | 27 | func main() { 28 | // *** short version *********** 29 | out, err := cmdr.New(true, "ls", "-lh", "~/tmp2/*").Run() 30 | fmt.Println("Output:", string(out)) 31 | if err != nil { 32 | fmt.Println("OOPS:", err.Error()) 33 | } 34 | 35 | // *** verbose version *********** 36 | // New is a helper to create a Command 37 | // You can call it by a shell like bash if you want (useful to process expressions like *) 38 | cmd := cmdr.New(true, "ls", "-lh", "~/tmp/*") 39 | 40 | // You can declare the variable as well: 41 | // cmd := cmdr.Command{ } 42 | 43 | // You can also parse a command into a Command: 44 | // cmd := cmdr.Parse(`psql -At -c 'select now();'`) 45 | 46 | // Enable timeout if you want (5s by example) 47 | cmd.Options.Timeout = 5 48 | 49 | // To check if the inputed command is valid, use the IsValid function. 50 | // It checks if the command exists in PATH 51 | if cmd.IsValid() { 52 | 53 | // To execute the command, just call the Run function 54 | out, err := cmd.Run() 55 | if err != nil { 56 | panic(err) 57 | } 58 | 59 | // here comes the output 60 | fmt.Println(string(out)) 61 | } 62 | } 63 | ``` 64 | 65 | ## Grouping commands 66 | 67 | Its possible to group a list of commands: 68 | 69 | ```golang 70 | package main 71 | 72 | import ( 73 | "fmt" 74 | 75 | "github.com/sebastianwebber/cmdr" 76 | ) 77 | 78 | func main() { 79 | // Group options (experimental) 80 | total, err := cmdr.Group( 81 | cmdr.AbortOnError, 82 | cmdr.New(false, "ls", "-lh"), 83 | cmdr.New(false, "pwd 123q6236"), 84 | cmdr.New(false, "cat", "/etc/hosts"), 85 | ) 86 | fmt.Printf("%d commands executed without error. \n", total) 87 | 88 | if err != nil { 89 | fmt.Printf("Houston, we have a problem! %v\n", err) 90 | } 91 | } 92 | ``` 93 | > **This is a work in progress.** 94 | 95 | 96 | ## TODO List 97 | 98 | - [x] Add option to timeout 99 | - [x] Enable way to group commands 100 | - [ ] Print output of each command in the group (perhaps adding a `name` option?) 101 | - [ ] Pipe support 102 | - [ ] add support por multiple commands 103 | 104 | 105 | 106 | ## License 107 | [![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Fsebastianwebber%2Fcmdr.svg?type=large)](https://app.fossa.io/projects/git%2Bgithub.com%2Fsebastianwebber%2Fcmdr?ref=badge_large) -------------------------------------------------------------------------------- /common.go: -------------------------------------------------------------------------------- 1 | package cmdr 2 | 3 | // Options contains Command configuration 4 | type Options struct { 5 | CheckPath bool 6 | UseShell bool 7 | Timeout int 8 | } 9 | 10 | // Command defines how to call a program 11 | type Command struct { 12 | Command string 13 | Args []string 14 | Options Options 15 | Description string 16 | } 17 | 18 | // New creates a Command 19 | func New(useShell bool, cmd string, args ...string) *Command { 20 | return &Command{ 21 | Options: Options{UseShell: useShell}, 22 | Command: cmd, 23 | Args: args, 24 | } 25 | } 26 | 27 | // Run a Command 28 | func (c *Command) Run() ([]byte, error) { 29 | return runCmd(*c) 30 | } 31 | 32 | // IsValid validates a Command 33 | func (c *Command) IsValid() bool { 34 | return (validateCmd(*c) == nil) 35 | } 36 | -------------------------------------------------------------------------------- /common_test.go: -------------------------------------------------------------------------------- 1 | package cmdr 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | ) 7 | 8 | func TestNew(t *testing.T) { 9 | type args struct { 10 | useShell bool 11 | cmd string 12 | args []string 13 | } 14 | tests := []struct { 15 | name string 16 | args args 17 | want *Command 18 | }{ 19 | { 20 | name: "compare", 21 | args: args{ 22 | useShell: false, 23 | cmd: "ls", 24 | args: []string{"-lh"}}, 25 | want: &Command{ 26 | Options: Options{UseShell: false}, 27 | Command: "ls", 28 | Args: []string{"-lh"}}, 29 | }, 30 | } 31 | for _, tt := range tests { 32 | t.Run(tt.name, func(t *testing.T) { 33 | if got := New(tt.args.useShell, tt.args.cmd, tt.args.args...); !reflect.DeepEqual(got, tt.want) { 34 | t.Errorf("New() = %v, want %v", got, tt.want) 35 | } 36 | }) 37 | } 38 | } 39 | 40 | func TestCommand_IsValid(t *testing.T) { 41 | type fields struct { 42 | Command string 43 | Args []string 44 | Options Options 45 | } 46 | tests := []struct { 47 | name string 48 | fields fields 49 | want bool 50 | }{ 51 | { 52 | name: "invalid empty", 53 | fields: fields{ 54 | Command: "", 55 | }, 56 | want: false, 57 | }, 58 | { 59 | name: "invalid notfound", 60 | fields: fields{ 61 | Command: "jt23g6emdsbxzgmvksdyg7089 v3g4069tygkahmxzbvuweg5 t", 62 | }, 63 | want: false, 64 | }, 65 | { 66 | name: "valid", 67 | fields: fields{ 68 | Command: "ls", 69 | }, 70 | want: true, 71 | }, 72 | } 73 | for _, tt := range tests { 74 | t.Run(tt.name, func(t *testing.T) { 75 | c := &Command{ 76 | Command: tt.fields.Command, 77 | Args: tt.fields.Args, 78 | Options: tt.fields.Options, 79 | } 80 | if got := c.IsValid(); got != tt.want { 81 | t.Errorf("Command.IsValid() = %v, want %v", got, tt.want) 82 | } 83 | }) 84 | } 85 | } 86 | 87 | func TestCommand_Run(t *testing.T) { 88 | type fields struct { 89 | Command string 90 | Args []string 91 | Options Options 92 | } 93 | tests := []struct { 94 | name string 95 | fields fields 96 | want []byte 97 | wantErr bool 98 | }{ 99 | { 100 | name: "simple echo", 101 | fields: fields{ 102 | Command: "echo", 103 | Args: []string{"hello"}, 104 | }, 105 | want: []byte("hello\n"), 106 | wantErr: false, 107 | }, 108 | } 109 | for _, tt := range tests { 110 | t.Run(tt.name, func(t *testing.T) { 111 | c := &Command{ 112 | Command: tt.fields.Command, 113 | Args: tt.fields.Args, 114 | Options: tt.fields.Options, 115 | } 116 | got, err := c.Run() 117 | if (err != nil) != tt.wantErr { 118 | t.Errorf("Command.Run() error = %v, wantErr %v", err, tt.wantErr) 119 | return 120 | } 121 | if !reflect.DeepEqual(got, tt.want) { 122 | t.Errorf("Command.Run() = %v, want %v", got, tt.want) 123 | } 124 | }) 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /group.go: -------------------------------------------------------------------------------- 1 | package cmdr 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | func abortStrategy(cmdList []Command) (execCount int, err error) { 8 | for _, cmd := range cmdList { 9 | _, cmdErr := cmd.Run() 10 | 11 | if cmdErr != nil { 12 | err = fmt.Errorf("Error running a command: %v", cmdErr) 13 | break 14 | } 15 | 16 | execCount++ 17 | } 18 | return 19 | } 20 | 21 | // Strategy contains details about action to take when a 22 | // command fail inside a group execution 23 | type Strategy (func([]Command) (int, error)) 24 | 25 | var ( 26 | // AbortOnError is a strategy to run grouped commands 27 | // This stategy end the group execution resulting a error and output. 28 | // It is the default option. 29 | AbortOnError Strategy = abortStrategy 30 | 31 | // // IgnoreOnError is a strategy to run grouped commands 32 | // // This strategy ignores the problem 33 | // IgnoreOnError Strategy = "IGNORE" 34 | ) 35 | 36 | // Group allows to run lots of commands in sequence 37 | // The strategy defines the behavior when a error occours 38 | func Group(strategy Strategy, cmdList ...Command) (execCount int, err error) { 39 | return strategy(cmdList) 40 | } 41 | -------------------------------------------------------------------------------- /group_test.go: -------------------------------------------------------------------------------- 1 | package cmdr 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | var ( 8 | okCmdList = []Command{ 9 | { 10 | Command: "ls", 11 | Args: []string{"-lh"}, 12 | }, 13 | { 14 | Command: "pwd", 15 | }, 16 | } 17 | errCmdList = []Command{ 18 | { 19 | Command: "ls", 20 | Args: []string{"-lh"}, 21 | }, 22 | { 23 | Command: "agdsgsdgdsa 64323adgsgads y42382842", 24 | }, 25 | { 26 | Command: "pwd", 27 | }, 28 | } 29 | ) 30 | 31 | func Test_abortStrategy(t *testing.T) { 32 | type args struct { 33 | cmdList []Command 34 | } 35 | tests := []struct { 36 | name string 37 | args args 38 | wantExecCount int 39 | wantErr bool 40 | }{ 41 | { 42 | name: "2 commands without errors", 43 | args: args{ 44 | cmdList: okCmdList, 45 | }, 46 | wantExecCount: len(okCmdList), 47 | wantErr: false, 48 | }, 49 | { 50 | name: "3 commands 2nd will fail", 51 | args: args{ 52 | cmdList: errCmdList, 53 | }, 54 | wantExecCount: 1, 55 | wantErr: true, 56 | }, 57 | } 58 | for _, tt := range tests { 59 | t.Run(tt.name, func(t *testing.T) { 60 | gotExecCount, err := abortStrategy(tt.args.cmdList) 61 | if (err != nil) != tt.wantErr { 62 | t.Errorf("abortGroup() error = %v, wantErr %v", err, tt.wantErr) 63 | return 64 | } 65 | if gotExecCount != tt.wantExecCount { 66 | t.Errorf("abortGroup() = %v, want %v", gotExecCount, tt.wantExecCount) 67 | } 68 | }) 69 | } 70 | } 71 | 72 | func TestGroup(t *testing.T) { 73 | type args struct { 74 | strategy Strategy 75 | cmdList []Command 76 | } 77 | tests := []struct { 78 | name string 79 | args args 80 | wantExecCount int 81 | wantErr bool 82 | }{ 83 | { 84 | name: "this should be ok", 85 | args: args{ 86 | strategy: AbortOnError, 87 | cmdList: okCmdList, 88 | }, 89 | wantExecCount: len(okCmdList), 90 | wantErr: false, 91 | }, 92 | { 93 | name: "2nd command will abort", 94 | args: args{ 95 | strategy: AbortOnError, 96 | cmdList: errCmdList, 97 | }, 98 | wantExecCount: 1, 99 | wantErr: true, 100 | }, 101 | } 102 | for _, tt := range tests { 103 | t.Run(tt.name, func(t *testing.T) { 104 | gotExecCount, err := Group(tt.args.strategy, tt.args.cmdList...) 105 | if (err != nil) != tt.wantErr { 106 | t.Errorf("Group() error = %v, wantErr %v", err, tt.wantErr) 107 | return 108 | } 109 | if gotExecCount != tt.wantExecCount { 110 | t.Errorf("Group() = %v, want %v", gotExecCount, tt.wantExecCount) 111 | } 112 | }) 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /parse.go: -------------------------------------------------------------------------------- 1 | package cmdr 2 | 3 | import ( 4 | "strings" 5 | "unicode" 6 | ) 7 | 8 | // Parse parses a string 9 | // resulting into a *Command 10 | func Parse(cmd string) *Command { 11 | 12 | var ( 13 | cmdStr string 14 | parts []string 15 | args []string 16 | ) 17 | parts = parseComplex(cmd) 18 | cmdStr = parts[0] 19 | args = parts[1:len(parts)] 20 | 21 | return &Command{ 22 | Command: cmdStr, 23 | Args: args, 24 | } 25 | } 26 | 27 | // by @jg_19 on https://t.me/go_br 28 | func parseComplex(cmd string) []string { 29 | lastRune := rune(0) 30 | cControl := false 31 | startRune := rune(0) 32 | f := func(c rune) (r bool) { 33 | switch { 34 | case startRune == c: 35 | startRune = 0 36 | r = cControl 37 | case (c == rune('\'') || c == rune('"')) && startRune == 0: 38 | startRune = c 39 | cControl = unicode.IsSpace(lastRune) 40 | r = cControl 41 | default: 42 | r = unicode.IsSpace(c) && startRune == 0 43 | } 44 | lastRune = c 45 | return 46 | } 47 | return strings.FieldsFunc(cmd, f) 48 | } 49 | -------------------------------------------------------------------------------- /parse_test.go: -------------------------------------------------------------------------------- 1 | package cmdr 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | ) 7 | 8 | func TestParse(t *testing.T) { 9 | tests := []struct { 10 | name string 11 | cmd string 12 | wantOut *Command 13 | }{ 14 | {name: "simple spaces", cmd: "echo hello world", wantOut: &Command{Command: "echo", Args: []string{"hello", "world"}}}, 15 | {name: "simple single quotes", cmd: `echo 'hello world'`, wantOut: &Command{Command: "echo", Args: []string{"hello world"}}}, 16 | {name: "simple double quotes", cmd: `echo "hello world"`, wantOut: &Command{Command: "echo", Args: []string{"hello world"}}}, 17 | {name: "complex single quotes", cmd: `psql -U postgres -c 'select now() as "agora"'`, wantOut: &Command{Command: "psql", Args: []string{"-U", "postgres", "-c", `select now() as "agora"`}}}, 18 | {name: "complex double quotes", cmd: `psql -U postgres -c "select 'seba' as nome"`, wantOut: &Command{Command: "psql", Args: []string{"-U", "postgres", "-c", `select 'seba' as nome`}}}, 19 | {name: "sed complex", cmd: `sed -i 's|-Xmx.*|-Xmx3G|' teste.sh`, wantOut: &Command{Command: "sed", Args: []string{"-i", "s|-Xmx.*|-Xmx3G|", "teste.sh"}}}, 20 | {name: "exception double quotes", cmd: `/opt/wildfly-10.1.0.Final/bin/jboss-cli.sh -c --command="read-attribute server-state"`, wantOut: &Command{Command: "/opt/wildfly-10.1.0.Final/bin/jboss-cli.sh", Args: []string{`-c`, `--command="read-attribute server-state"`}}}, 21 | } 22 | for _, tt := range tests { 23 | t.Run(tt.name, func(t *testing.T) { 24 | if gotOut := Parse(tt.cmd); !reflect.DeepEqual(gotOut, tt.wantOut) { 25 | t.Errorf("Parse() = %#v, want %#v", gotOut, tt.wantOut) 26 | } 27 | }) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /run.go: -------------------------------------------------------------------------------- 1 | package cmdr 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "os" 7 | "os/exec" 8 | "regexp" 9 | "strings" 10 | "time" 11 | ) 12 | 13 | func getPATH() []string { 14 | return strings.Split(os.Getenv("PATH"), ":") 15 | } 16 | 17 | // this regex tests if the cmd starts with: ./, ../, ~/ or / 18 | var partialPathRegex = regexp.MustCompile(`^((\~|\.{1,})?\/)`) 19 | 20 | func findInPath(cmd string) (found bool) { 21 | 22 | // stops validation when a full or 23 | // partial path was inputed 24 | if partialPathRegex.Match([]byte(cmd)) { 25 | found = true 26 | return 27 | } 28 | 29 | for _, dir := range getPATH() { 30 | 31 | fullPath := fmt.Sprintf("%s/%s", dir, cmd) 32 | 33 | if fileExist(fullPath) { 34 | found = true 35 | break 36 | } 37 | } 38 | 39 | return 40 | } 41 | 42 | func fileExist(name string) bool { 43 | _, err := os.Stat(name) 44 | return !os.IsNotExist(err) 45 | } 46 | 47 | // RunCmd runs a command in the operating system 48 | func RunCmd(c Command) ([]byte, error) { 49 | return runCmd(c) 50 | } 51 | 52 | func makeCmd(c Command) (cmd *exec.Cmd) { 53 | if c.Options.UseShell { 54 | cmd = exec.Command("bash", "-c", fmt.Sprintf("%s %s", c.Command, strings.Join(c.Args, " "))) 55 | } else { 56 | cmd = exec.Command(c.Command, c.Args...) 57 | } 58 | 59 | return 60 | } 61 | 62 | func runCmd(c Command) (output []byte, err error) { 63 | 64 | if c.Options.CheckPath { 65 | if err := validateCmd(c); err != nil { 66 | return nil, fmt.Errorf("Check PATH failed: %v", err) 67 | } 68 | } 69 | 70 | cmd := makeCmd(c) 71 | 72 | var b bytes.Buffer 73 | cmd.Stdout = &b 74 | cmd.Stderr = &b 75 | 76 | err = cmd.Start() 77 | 78 | if err != nil { 79 | err = fmt.Errorf("Error starting a command: %v", err) 80 | return 81 | } 82 | 83 | var timer *time.Timer 84 | 85 | if c.Options.Timeout > 0 { 86 | 87 | execLimit := time.Duration(c.Options.Timeout) * time.Second 88 | 89 | timer = time.AfterFunc(execLimit, func() { 90 | cmd.Process.Kill() 91 | }) 92 | } 93 | 94 | err = cmd.Wait() 95 | if err != nil { 96 | err = fmt.Errorf("Error running a command: %v", err) 97 | } 98 | 99 | output = b.Bytes() 100 | 101 | if c.Options.Timeout > 0 { 102 | timer.Stop() 103 | } 104 | 105 | return 106 | } 107 | 108 | func validateCmd(c Command) (err error) { 109 | 110 | if c.Command == "" { 111 | err = fmt.Errorf("Missing command name") 112 | return 113 | } 114 | 115 | if !findInPath(c.Command) { 116 | err = fmt.Errorf("Command not found in PATH") 117 | return 118 | } 119 | 120 | return 121 | } 122 | -------------------------------------------------------------------------------- /run_test.go: -------------------------------------------------------------------------------- 1 | package cmdr 2 | 3 | import ( 4 | "os/exec" 5 | "reflect" 6 | "strings" 7 | "testing" 8 | ) 9 | 10 | func TestRunCmd(t *testing.T) { 11 | tests := []struct { 12 | name string 13 | cmd string 14 | args []string 15 | expectedError string 16 | Options 17 | }{ 18 | {name: "run empty command", cmd: "", expectedError: "Missing command name", Options: Options{CheckPath: true}}, 19 | {name: "run fake command", cmd: "xl8t23hj6__68235896253gf3", expectedError: "Command not found in PATH", Options: Options{CheckPath: true}}, 20 | {name: "run valid command with wrong args", cmd: "ls", args: []string{"-lh52313252362336", "2133266324"}, expectedError: "Error running a command"}, 21 | {name: "this should be ok", cmd: "ls", args: []string{"-lh"}}, 22 | {name: "this should be ok on bash too", cmd: "ls", args: []string{"-lh"}, Options: Options{UseShell: true}}, 23 | {name: "this should abort by timemout", cmd: "sleep", args: []string{"2"}, expectedError: "Error running a command", Options: Options{Timeout: 1}}, 24 | } 25 | for _, test := range tests { 26 | t.Run(test.name, func(t *testing.T) { 27 | 28 | cmd := Command{Command: test.cmd, Args: test.args} 29 | 30 | cmd.Options = test.Options 31 | 32 | _, err := RunCmd(cmd) 33 | 34 | if test.expectedError != "" { 35 | 36 | if err == nil { 37 | t.Errorf("Expected '%v' error", test.expectedError) 38 | } 39 | 40 | if err != nil { 41 | // first test if not starts with the message 42 | if !strings.Contains(err.Error(), test.expectedError) { 43 | // if not, then check if its different 44 | if err.Error() != test.expectedError { 45 | t.Errorf("Expected '%v' but returned '%v", test.expectedError, err) 46 | } 47 | } 48 | } 49 | 50 | } 51 | }) 52 | } 53 | } 54 | 55 | func Test_makeCmd(t *testing.T) { 56 | type args struct { 57 | c Command 58 | } 59 | tests := []struct { 60 | name string 61 | args args 62 | wantCmd *exec.Cmd 63 | }{ 64 | { 65 | name: "with shell", 66 | args: args{ 67 | c: Command{ 68 | Command: "ls", 69 | Args: []string{"-lh"}, 70 | Options: Options{ 71 | UseShell: true, 72 | }, 73 | }, 74 | }, 75 | wantCmd: exec.Command("bash", "-c", "ls -lh"), 76 | }, 77 | { 78 | name: "without shell", 79 | args: args{ 80 | c: Command{ 81 | Command: "ls", 82 | Args: []string{"-lh"}, 83 | }, 84 | }, 85 | wantCmd: exec.Command("ls", "-lh"), 86 | }, 87 | } 88 | for _, tt := range tests { 89 | t.Run(tt.name, func(t *testing.T) { 90 | if gotCmd := makeCmd(tt.args.c); !reflect.DeepEqual(gotCmd, tt.wantCmd) { 91 | t.Errorf("makeCmd() = %v, want %v", gotCmd, tt.wantCmd) 92 | } 93 | }) 94 | } 95 | } 96 | 97 | func Test_findInPath(t *testing.T) { 98 | 99 | tests := []struct { 100 | name string 101 | args string 102 | wantFound bool 103 | }{ 104 | {"partial current dir", "./superscript.sh", true}, 105 | {"partial previous path", "../superscript.sh", true}, 106 | {"partial home dir", "~/superscript.sh", true}, 107 | {"full prefix", "/app/test/superscript.sh", true}, 108 | {"random non existing script", "shfjasfhkjsafgasjajsmvxbsjty jaghh", false}, 109 | } 110 | for _, tt := range tests { 111 | t.Run(tt.name, func(t *testing.T) { 112 | if gotFound := findInPath(tt.args); gotFound != tt.wantFound { 113 | t.Errorf("findInPath() = %v, want %v", gotFound, tt.wantFound) 114 | } 115 | }) 116 | } 117 | } 118 | 119 | func Test_runCmd(t *testing.T) { 120 | type args struct { 121 | c Command 122 | } 123 | tests := []struct { 124 | name string 125 | args args 126 | wantOutput []byte 127 | wantErr bool 128 | }{ 129 | // TODO: Add test cases. 130 | } 131 | for _, tt := range tests { 132 | t.Run(tt.name, func(t *testing.T) { 133 | gotOutput, err := runCmd(tt.args.c) 134 | if (err != nil) != tt.wantErr { 135 | t.Errorf("runCmd() error = %v, wantErr %v", err, tt.wantErr) 136 | return 137 | } 138 | if !reflect.DeepEqual(gotOutput, tt.wantOutput) { 139 | t.Errorf("runCmd() = %v, want %v", gotOutput, tt.wantOutput) 140 | } 141 | }) 142 | } 143 | } 144 | --------------------------------------------------------------------------------