Compare commits
3 Commits
Author | SHA1 | Date | |
---|---|---|---|
9f32c6c43f | |||
86a55d16ea | |||
24d091b787 |
@ -1,3 +1,3 @@
|
|||||||
# Go Shell Runner
|
# Tortoise
|
||||||
|
|
||||||
Library for asyncronously executing shell commands in Go
|
Library for asyncronously executing shell commands in Go
|
||||||
|
2
go.mod
2
go.mod
@ -1,3 +1,3 @@
|
|||||||
module git.iamthefij.com/iamthefij/go-shell-runner
|
module git.iamthefij.com/iamthefij/tortoise
|
||||||
|
|
||||||
go 1.21.4
|
go 1.21.4
|
||||||
|
13
main.go
13
main.go
@ -1,4 +1,4 @@
|
|||||||
package shellrunner
|
package tortoise
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
@ -45,9 +45,10 @@ func NewShellRunnerWithShell(shell string) *ShellRunner {
|
|||||||
cmdQueue: make(chan func() *CommandResult),
|
cmdQueue: make(chan func() *CommandResult),
|
||||||
results: make(chan *CommandResult, MAX_RESULTS),
|
results: make(chan *CommandResult, MAX_RESULTS),
|
||||||
stopChan: make(chan struct{}),
|
stopChan: make(chan struct{}),
|
||||||
callbacks: make(chan func(), MAX_RESULTS),
|
callbacks: make(chan func()),
|
||||||
shell: shell,
|
shell: shell,
|
||||||
activeCmds: make(map[*exec.Cmd]struct{}),
|
activeCmds: make(map[*exec.Cmd]struct{}),
|
||||||
|
isStopped: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -75,16 +76,20 @@ func (sr *ShellRunner) Start() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
sr.isStopped = false
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddCommand adds a shell command to be executed with an optional callback.
|
// AddCommand adds a shell command to be executed with an optional callback.
|
||||||
// No commands can be added if the runner has been stopped.
|
// No commands can be added if the runner has been stopped or not yet started.
|
||||||
|
// The callback is executed asynchronously after the command has completed.
|
||||||
|
// The order of command execution and callback invocation can be expected to be preserved.
|
||||||
func (sr *ShellRunner) AddCommand(command string, callback func(*CommandResult)) error {
|
func (sr *ShellRunner) AddCommand(command string, callback func(*CommandResult)) error {
|
||||||
sr.mu.Lock()
|
sr.mu.Lock()
|
||||||
defer sr.mu.Unlock()
|
defer sr.mu.Unlock()
|
||||||
|
|
||||||
if sr.isStopped {
|
if sr.isStopped {
|
||||||
return errors.New("runner has been stopped, cannot add new commands")
|
return errors.New("runner is stopped, cannot add new commands")
|
||||||
}
|
}
|
||||||
|
|
||||||
sr.cmdQueue <- func() *CommandResult {
|
sr.cmdQueue <- func() *CommandResult {
|
||||||
|
69
main_test.go
69
main_test.go
@ -1,13 +1,16 @@
|
|||||||
package shellrunner_test
|
package tortoise_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
shellrunner "git.iamthefij.com/iamthefij/go-shell-runner"
|
"git.iamthefij.com/iamthefij/tortoise"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestShellRunnerNoCallback(t *testing.T) {
|
func TestShellRunnerNoCallback(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
cases := []struct {
|
cases := []struct {
|
||||||
command string
|
command string
|
||||||
output string
|
output string
|
||||||
@ -22,7 +25,7 @@ func TestShellRunnerNoCallback(t *testing.T) {
|
|||||||
t.Run(c.command, func(t *testing.T) {
|
t.Run(c.command, func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
runner := shellrunner.NewShellRunner()
|
runner := tortoise.NewShellRunner()
|
||||||
runner.Start()
|
runner.Start()
|
||||||
|
|
||||||
// Test command without callback
|
// Test command without callback
|
||||||
@ -43,52 +46,71 @@ func TestShellRunnerNoCallback(t *testing.T) {
|
|||||||
func TestShellRunnerCallback(t *testing.T) {
|
func TestShellRunnerCallback(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
runner := shellrunner.NewShellRunner()
|
runner := tortoise.NewShellRunner()
|
||||||
runner.Start()
|
runner.Start()
|
||||||
|
|
||||||
// Test command with callback
|
// Test command with callback
|
||||||
done := make(chan struct{})
|
outputString := ""
|
||||||
|
callbackWait := sync.WaitGroup{}
|
||||||
|
|
||||||
callbackReached := false
|
callbackWait.Add(1)
|
||||||
|
|
||||||
if err := runner.AddCommand("echo callback test", func(result *shellrunner.CommandResult) {
|
if err := runner.AddCommand("echo callback a", func(result *tortoise.CommandResult) {
|
||||||
callbackReached = true
|
if result.Output != "callback a\n" {
|
||||||
if result.Output != "callback test\n" {
|
t.Fatalf("expected 'callback a', got '%s'", result.Output)
|
||||||
t.Fatalf("expected 'callback test', got '%s'", result.Output)
|
|
||||||
}
|
}
|
||||||
close(done)
|
outputString = outputString + "a"
|
||||||
|
callbackWait.Done()
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
t.Fatalf("unexpected error adding command: %v", err)
|
t.Fatalf("unexpected error adding command: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Timeout waiting for callback
|
callbackWait.Add(1)
|
||||||
|
|
||||||
|
if err := runner.AddCommand("echo callback b", func(result *tortoise.CommandResult) {
|
||||||
|
if result.Output != "callback b\n" {
|
||||||
|
t.Fatalf("expected 'callback b', got '%s'", result.Output)
|
||||||
|
}
|
||||||
|
outputString = outputString + "b"
|
||||||
|
callbackWait.Done()
|
||||||
|
}); err != nil {
|
||||||
|
t.Fatalf("unexpected error adding command: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Timeout waiting for callbacks
|
||||||
|
done := make(chan struct{})
|
||||||
|
go func() {
|
||||||
|
callbackWait.Wait()
|
||||||
|
close(done)
|
||||||
|
}()
|
||||||
|
|
||||||
select {
|
select {
|
||||||
case <-done:
|
case <-done:
|
||||||
case <-time.After(2 * time.Second):
|
case <-time.After(2 * time.Second):
|
||||||
t.Fatal("callback timed out")
|
t.Fatal("callbacks timed out")
|
||||||
}
|
}
|
||||||
|
|
||||||
if !callbackReached {
|
if outputString != "ab" {
|
||||||
t.Fatal("callback was not reached")
|
t.Fatal("callbacks was not reached in order:", outputString)
|
||||||
}
|
}
|
||||||
|
|
||||||
runner.Stop()
|
runner.Stop()
|
||||||
|
|
||||||
// Make sure stop and kill both exit gracefully after the runner is stopped
|
// Make sure stop and kill both exit gracefully after the runner is stopped
|
||||||
runner.Stop()
|
runner.Stop()
|
||||||
runner.Stop()
|
runner.Kill()
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestShellRunnerKillWithTimeout(t *testing.T) {
|
func TestShellRunnerKillWithTimeout(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
runner := shellrunner.NewShellRunner()
|
runner := tortoise.NewShellRunner()
|
||||||
runner.Start()
|
runner.Start()
|
||||||
|
|
||||||
// Test command with callback
|
// Test command with callback
|
||||||
callbackReached := false
|
callbackReached := false
|
||||||
|
|
||||||
if err := runner.AddCommand("sleep 10 && echo callback test", func(result *shellrunner.CommandResult) {
|
if err := runner.AddCommand("sleep 10 && echo callback test", func(result *tortoise.CommandResult) {
|
||||||
callbackReached = true
|
callbackReached = true
|
||||||
if result.Output != "callback test\n" {
|
if result.Output != "callback test\n" {
|
||||||
t.Fatalf("expected 'callback test', got '%s'", result.Output)
|
t.Fatalf("expected 'callback test', got '%s'", result.Output)
|
||||||
@ -110,7 +132,7 @@ func TestShellRunnerKillWithTimeout(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestStopPreventsNewCommands(t *testing.T) {
|
func TestStopPreventsNewCommands(t *testing.T) {
|
||||||
runner := shellrunner.NewShellRunner()
|
runner := tortoise.NewShellRunner()
|
||||||
runner.Start()
|
runner.Start()
|
||||||
|
|
||||||
runner.Stop()
|
runner.Stop()
|
||||||
@ -120,3 +142,12 @@ func TestStopPreventsNewCommands(t *testing.T) {
|
|||||||
t.Fatal("expected error when adding command after stop, but got none")
|
t.Fatal("expected error when adding command after stop, but got none")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestAddingPriorToStart(t *testing.T) {
|
||||||
|
runner := tortoise.NewShellRunner()
|
||||||
|
|
||||||
|
err := runner.AddCommand("echo should not run", nil)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("Should have failed to add prior to starting runner")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user