Skip to content

Commit 12f730b

Browse files
authored
Merge pull request #7 from SimonBaeumer/bug-fixes
Bug fixes
2 parents f7002b5 + 10b26eb commit 12f730b

File tree

9 files changed

+84
-106
lines changed

9 files changed

+84
-106
lines changed

README.md

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -26,25 +26,32 @@ fmt.Println(c.Stdout())
2626
fmt.Println(c.Stderr())
2727
```
2828

29-
### Stream output to stderr and stdout
29+
### Configure the command
30+
31+
To configure the command a option function will be passed which receives the command object as an argument passed by reference.
32+
33+
Default option functions:
34+
35+
- `cmd.WithStandardStreams`
36+
- `cmd.WithTimeout(time.Duration)`
37+
- `cmd.WithoutTimeout`
38+
- `cmd.WithWorkingDir(string)`
39+
40+
#### Example
3041

3142
```go
3243
c := cmd.NewCommand("echo hello", cmd.WithStandardStreams)
3344
c.Execute()
3445
```
3546

36-
### Set custom options
47+
#### Set custom options
3748

3849
```go
39-
func SetTimeout(c *Command) {
40-
c.Timeout = 1 * time.Hour
41-
}
42-
43-
func SetWorkingDir(c *Command) {
50+
setWorkingDir := func (c *Command) {
4451
c.WorkingDir = "/tmp/test"
4552
}
4653

47-
c := cmd.NewCommand("pwd", SetTimeout, SetWorkingDir)
54+
c := cmd.NewCommand("pwd", setWorkingDir)
4855
c.Execute()
4956
```
5057

command.go

Lines changed: 42 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,6 @@ import (
66
"io"
77
"os"
88
"os/exec"
9-
"regexp"
10-
"strings"
119
"syscall"
1210
"time"
1311
)
@@ -30,6 +28,7 @@ type Command struct {
3028

3129
// NewCommand creates a new command
3230
// You can add option with variadic option argument
31+
// Default timeout is set to 30 minutes
3332
//
3433
// Example:
3534
// c := cmd.NewCommand("echo hello", function (c *Command) {
@@ -45,7 +44,7 @@ type Command struct {
4544
func NewCommand(cmd string, options ...func(*Command)) *Command {
4645
c := &Command{
4746
Command: cmd,
48-
Timeout: 1 * time.Minute,
47+
Timeout: 30 * time.Minute,
4948
executed: false,
5049
Env: []string{},
5150
}
@@ -65,57 +64,42 @@ func NewCommand(cmd string, options ...func(*Command)) *Command {
6564
//
6665
// Example:
6766
//
68-
// c := cmd.NewCommand("echo hello", cmd.WithStandardStreams)
69-
// c.Execute()
67+
// c := cmd.NewCommand("echo hello", cmd.WithStandardStreams)
68+
// c.Execute()
7069
//
7170
func WithStandardStreams(c *Command) {
7271
c.StdoutWriter = os.Stdout
7372
c.StderrWriter = os.Stderr
7473
}
7574

76-
// AddEnv adds an environment variable to the command
77-
// If a variable gets passed like ${VAR_NAME} the env variable will be read out by the current shell
78-
func (c *Command) AddEnv(key string, value string) {
79-
vars := parseEnvVariableFromShell(value)
80-
for _, v := range vars {
81-
value = strings.Replace(value, v, os.Getenv(removeEnvVarSyntax(v)), -1)
75+
// WithTimeout sets the timeout of the command
76+
//
77+
// Example:
78+
// cmd.NewCommand("sleep 10;", cmd.WithTimeout(500))
79+
//
80+
func WithTimeout(t time.Duration) func(c *Command) {
81+
return func(c *Command) {
82+
c.Timeout = t
8283
}
83-
84-
c.Env = append(c.Env, fmt.Sprintf("%s=%s", key, value))
8584
}
8685

87-
// Removes the ${...} characters at the beginng and end of the given string
88-
func removeEnvVarSyntax(v string) string {
89-
return v[2:(len(v) - 1)]
86+
// WithoutTimeout disables the timeout for the command
87+
func WithoutTimeout(c *Command) {
88+
c.Timeout = 0
9089
}
9190

92-
//Read all environment variables from the given value
93-
//with the syntax ${VAR_NAME}
94-
func parseEnvVariableFromShell(val string) []string {
95-
reg := regexp.MustCompile(`\$\{.*?\}`)
96-
matches := reg.FindAllString(val, -1)
97-
return matches
98-
}
99-
100-
//SetTimeoutMS sets the timeout in milliseconds
101-
func (c *Command) SetTimeoutMS(ms int) {
102-
if ms == 0 {
103-
c.Timeout = 1 * time.Minute
104-
return
91+
// WithWorkingDir sets the current working directory
92+
func WithWorkingDir(dir string) func(c *Command) {
93+
return func(c *Command) {
94+
c.WorkingDir = dir
10595
}
106-
c.Timeout = time.Duration(ms) * time.Millisecond
10796
}
10897

109-
// SetTimeout sets the timeout given a time unit
110-
// Example: SetTimeout("100s") sets the timeout to 100 seconds
111-
func (c *Command) SetTimeout(timeout string) error {
112-
d, err := time.ParseDuration(timeout)
113-
if err != nil {
114-
return err
115-
}
116-
117-
c.Timeout = d
118-
return nil
98+
// AddEnv adds an environment variable to the command
99+
// If a variable gets passed like ${VAR_NAME} the env variable will be read out by the current shell
100+
func (c *Command) AddEnv(key string, value string) {
101+
value = os.ExpandEnv(value)
102+
c.Env = append(c.Env, fmt.Sprintf("%s=%s", key, value))
119103
}
120104

121105
//Stdout returns the output to stdout
@@ -157,14 +141,28 @@ func (c *Command) Execute() error {
157141
cmd.Stderr = c.StderrWriter
158142
cmd.Dir = c.WorkingDir
159143

144+
// Create timer only if timeout was set > 0
145+
var timeoutChan = make(<-chan time.Time, 1)
146+
if c.Timeout != 0 {
147+
timeoutChan = time.After(c.Timeout)
148+
}
149+
160150
err := cmd.Start()
161151
if err != nil {
162152
return err
163153
}
164154

165-
done := make(chan error)
155+
done := make(chan error, 1)
156+
quit := make(chan bool, 1)
157+
defer close(quit)
158+
166159
go func() {
167-
done <- cmd.Wait()
160+
select {
161+
case <-quit:
162+
return
163+
case done <- cmd.Wait():
164+
return
165+
}
168166
}()
169167

170168
select {
@@ -174,14 +172,14 @@ func (c *Command) Execute() error {
174172
break
175173
}
176174
c.exitCode = 0
177-
case <-time.After(c.Timeout):
175+
case <-timeoutChan:
176+
quit <- true
178177
if err := cmd.Process.Kill(); err != nil {
179178
return fmt.Errorf("Timeout occurred and can not kill process with pid %v", cmd.Process.Pid)
180179
}
181180
return fmt.Errorf("Command timed out after %v", c.Timeout)
182181
}
183182

184-
//Remove leading and trailing whitespaces
185183
c.executed = true
186184

187185
return nil

command_darwin.go

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,9 @@ package cmd
22

33
import (
44
"os/exec"
5-
"strings"
65
)
76

87
func createBaseCommand(c *Command) *exec.Cmd {
98
cmd := exec.Command("/bin/sh", "-c", c.Command)
109
return cmd
1110
}
12-
13-
func (c *Command) removeLineBreaks(text string) string {
14-
return strings.Trim(text, "\n")
15-
}

command_darwin_test.go

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package cmd
33
import (
44
"github.com/stretchr/testify/assert"
55
"testing"
6+
"time"
67
)
78

89
func TestCommand_ExecuteStderr(t *testing.T) {
@@ -15,8 +16,7 @@ func TestCommand_ExecuteStderr(t *testing.T) {
1516
}
1617

1718
func TestCommand_WithTimeout(t *testing.T) {
18-
cmd := NewCommand("sleep 0.5;")
19-
cmd.SetTimeoutMS(5)
19+
cmd := NewCommand("sleep 0.5;", WithTimeout(5*time.Millisecond))
2020

2121
err := cmd.Execute()
2222

@@ -25,8 +25,7 @@ func TestCommand_WithTimeout(t *testing.T) {
2525
}
2626

2727
func TestCommand_WithValidTimeout(t *testing.T) {
28-
cmd := NewCommand("sleep 0.01;")
29-
cmd.SetTimeoutMS(500)
28+
cmd := NewCommand("sleep 0.01;", WithTimeout(500*time.Millisecond))
3029

3130
err := cmd.Execute()
3231

command_linux.go

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,9 @@ package cmd
22

33
import (
44
"os/exec"
5-
"strings"
65
)
76

87
func createBaseCommand(c *Command) *exec.Cmd {
98
cmd := exec.Command("/bin/sh", "-c", c.Command)
109
return cmd
1110
}
12-
13-
func (c *Command) removeLineBreaks(text string) string {
14-
return strings.Trim(text, "\n")
15-
}

command_linux_test.go

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"os"
77
"strings"
88
"testing"
9+
"time"
910
)
1011

1112
func TestCommand_ExecuteStderr(t *testing.T) {
@@ -18,8 +19,7 @@ func TestCommand_ExecuteStderr(t *testing.T) {
1819
}
1920

2021
func TestCommand_WithTimeout(t *testing.T) {
21-
cmd := NewCommand("sleep 0.01;")
22-
cmd.SetTimeoutMS(1)
22+
cmd := NewCommand("sleep 0.1;", WithTimeout(1*time.Millisecond))
2323

2424
err := cmd.Execute()
2525

@@ -30,8 +30,7 @@ func TestCommand_WithTimeout(t *testing.T) {
3030
}
3131

3232
func TestCommand_WithValidTimeout(t *testing.T) {
33-
cmd := NewCommand("sleep 0.01;")
34-
cmd.SetTimeoutMS(500)
33+
cmd := NewCommand("sleep 0.01;", WithTimeout(500*time.Millisecond))
3534

3635
err := cmd.Execute()
3736

@@ -66,3 +65,19 @@ func TestCommand_WithStandardStreams(t *testing.T) {
6665
assert.Nil(t, err)
6766
assert.Equal(t, "hey\n", string(r))
6867
}
68+
69+
func TestCommand_WithoutTimeout(t *testing.T) {
70+
cmd := NewCommand("sleep 0.001; echo hello", WithoutTimeout)
71+
72+
err := cmd.Execute()
73+
74+
assert.Nil(t, err)
75+
assert.Equal(t, "hello\n", cmd.Stdout())
76+
}
77+
78+
func TestCommand_WithInvalidDir(t *testing.T) {
79+
cmd := NewCommand("echo hello", WithWorkingDir("/invalid"))
80+
err := cmd.Execute()
81+
assert.NotNil(t, err)
82+
assert.Equal(t, "chdir /invalid: no such file or directory", err.Error())
83+
}

command_test.go

Lines changed: 2 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -81,8 +81,8 @@ func TestCommand_AddEnvWithShellVariable(t *testing.T) {
8181
}
8282

8383
func TestCommand_AddMultipleEnvWithShellVariable(t *testing.T) {
84-
const TestEnvKeyPlanet = "COMMANDER_TEST_PLANET"
85-
const TestEnvKeyName = "COMMANDER_TEST_NAME"
84+
const TestEnvKeyPlanet = "CMD_TEST_PLANET"
85+
const TestEnvKeyName = "CMD_TEST_NAME"
8686
os.Setenv(TestEnvKeyPlanet, "world")
8787
os.Setenv(TestEnvKeyName, "Simon")
8888
defer func() {
@@ -108,31 +108,6 @@ func getCommand() string {
108108
return command
109109
}
110110

111-
func TestCommand_SetTimeoutMS_DefaultTimeout(t *testing.T) {
112-
c := NewCommand("echo test")
113-
c.SetTimeoutMS(0)
114-
assert.Equal(t, (1 * time.Minute), c.Timeout)
115-
}
116-
117-
func TestCommand_SetTimeoutMS(t *testing.T) {
118-
c := NewCommand("echo test")
119-
c.SetTimeoutMS(100)
120-
assert.Equal(t, 100*time.Millisecond, c.Timeout)
121-
}
122-
123-
func TestCommand_SetTimeout(t *testing.T) {
124-
c := NewCommand("echo test")
125-
_ = c.SetTimeout("100s")
126-
duration, _ := time.ParseDuration("100s")
127-
assert.Equal(t, duration, c.Timeout)
128-
}
129-
130-
func TestCommand_SetInvalidTimeout(t *testing.T) {
131-
c := NewCommand("echo test")
132-
err := c.SetTimeout("1")
133-
assert.Equal(t, "time: missing unit in duration 1", err.Error())
134-
}
135-
136111
func TestCommand_SetOptions(t *testing.T) {
137112
writer := &bytes.Buffer{}
138113

command_windows.go

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,9 @@ package cmd
22

33
import (
44
"os/exec"
5-
"strings"
65
)
76

87
func createBaseCommand(c *Command) *exec.Cmd {
98
cmd := exec.Command(`C:\windows\system32\cmd.exe`, "/C", c.Command)
109
return cmd
1110
}
12-
13-
func (c *Command) removeLineBreaks(text string) string {
14-
return strings.Trim(text, "\r\n")
15-
}

command_windows_test.go

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"github.com/stretchr/testify/assert"
55
"strings"
66
"testing"
7+
"time"
78
)
89

910
func TestCommand_ExecuteStderr(t *testing.T) {
@@ -16,8 +17,7 @@ func TestCommand_ExecuteStderr(t *testing.T) {
1617
}
1718

1819
func TestCommand_WithTimeout(t *testing.T) {
19-
cmd := NewCommand("timeout 0.005;")
20-
cmd.SetTimeoutMS(5)
20+
cmd := NewCommand("timeout 0.005;", WithTimeout(5*time.Millisecond))
2121

2222
err := cmd.Execute()
2323

@@ -28,8 +28,7 @@ func TestCommand_WithTimeout(t *testing.T) {
2828
}
2929

3030
func TestCommand_WithValidTimeout(t *testing.T) {
31-
cmd := NewCommand("timeout 0.01;")
32-
cmd.SetTimeoutMS(1000)
31+
cmd := NewCommand("timeout 0.01;", WithTimeout(1000*time.Millisecond))
3332

3433
err := cmd.Execute()
3534

0 commit comments

Comments
 (0)