From 27715ecebbe42182bc53f9c2864edd8b407c2046 Mon Sep 17 00:00:00 2001 From: Marc Cataford Date: Thu, 22 Aug 2024 23:11:00 -0400 Subject: [PATCH] refactor: extract driver into own module --- internal/commands/execute_workflow.go | 3 +- internal/{runner => driver}/driver.go | 2 +- internal/driver/mock_container_driver.go | 98 +++++++++++++++++++ internal/{runner => driver}/podman_driver.go | 2 +- .../{runner => driver}/podman_driver_test.go | 2 +- internal/runner/mock_container_driver.go | 98 ------------------- internal/runner/runner.go | 7 +- internal/runner/runner_flow_test.go | 13 +-- internal/runner/runner_job_test.go | 31 +++--- 9 files changed, 130 insertions(+), 126 deletions(-) rename internal/{runner => driver}/driver.go (98%) create mode 100644 internal/driver/mock_container_driver.go rename internal/{runner => driver}/podman_driver.go (99%) rename internal/{runner => driver}/podman_driver_test.go (99%) delete mode 100644 internal/runner/mock_container_driver.go diff --git a/internal/commands/execute_workflow.go b/internal/commands/execute_workflow.go index 311e5b7..203e7b6 100644 --- a/internal/commands/execute_workflow.go +++ b/internal/commands/execute_workflow.go @@ -1,6 +1,7 @@ package commands import ( + driver "courgette/internal/driver" logger "courgette/internal/logging" runner "courgette/internal/runner" workflow "courgette/internal/workflow" @@ -9,7 +10,7 @@ import ( ) func ExecuteWorkflow(configuration Configuration, workflowFile string) error { - driver, err := runner.NewDriver(configuration.Containers.Driver) + driver, err := driver.NewDriver(configuration.Containers.Driver) if err != nil { return err diff --git a/internal/runner/driver.go b/internal/driver/driver.go similarity index 98% rename from internal/runner/driver.go rename to internal/driver/driver.go index c37f627..12336b8 100644 --- a/internal/runner/driver.go +++ b/internal/driver/driver.go @@ -1,4 +1,4 @@ -package runner +package driver import ( "errors" diff --git a/internal/driver/mock_container_driver.go b/internal/driver/mock_container_driver.go new file mode 100644 index 0000000..a380ed2 --- /dev/null +++ b/internal/driver/mock_container_driver.go @@ -0,0 +1,98 @@ +package driver + +import ( + "fmt" + "strings" +) + +type MockCall struct { + Fname string + Args []string +} + +type MockDriver struct { + Calls map[string][]MockCall + MockedCalls map[string]map[string]CommandResult +} + +func NewMockDriver() MockDriver { + return MockDriver{ + Calls: map[string][]MockCall{}, + MockedCalls: map[string]map[string]CommandResult{}, + } +} + +func (d *MockDriver) Pull(uri string) error { + if _, init := d.Calls["Pull"]; !init { + d.Calls["Pull"] = []MockCall{} + } + + d.Calls["Pull"] = append(d.Calls["Pull"], MockCall{Fname: "Pull", Args: []string{uri}}) + + return nil +} + +func (d *MockDriver) Start(uri string, containerName string) error { + if _, init := d.Calls["Start"]; !init { + d.Calls["Start"] = []MockCall{} + } + d.Calls["Start"] = append(d.Calls["Start"], MockCall{Fname: "Start", Args: []string{uri, containerName}}) + return nil +} + +func (d *MockDriver) Stop(uri string) error { + if _, init := d.Calls["Stop"]; !init { + d.Calls["Stop"] = []MockCall{} + } + d.Calls["Stop"] = append(d.Calls["Stop"], MockCall{Fname: "Stop", Args: []string{uri}}) + return nil + +} + +func (d *MockDriver) Exec(containerName string, command string, options CommandOptions) CommandResult { + if _, init := d.Calls["Exec"]; !init { + d.Calls["Exec"] = []MockCall{} + } + + args := []string{containerName, command, options.Cwd, fmt.Sprintf("%#v", options.Env)} + d.Calls["Exec"] = append(d.Calls["Exec"], MockCall{Fname: "Exec", Args: args}) + + mockKeys := []string{ + fmt.Sprintf("nthcall::%d", len(d.Calls["Exec"])), + fmt.Sprintf("withargs::%s", strings.Join(args, " ")), + } + + for _, mockKey := range mockKeys { + if _, mocked := d.MockedCalls["Exec"][mockKey]; mocked { + return d.MockedCalls["Exec"][mockKey] + } + } + + return CommandResult{} +} + +// Mocks a call to with arguments to return . +// +// The mocked call is reused as long as it's defined within the mock driver. +func (d *MockDriver) WithMockedCall(fn string, returnValue CommandResult, args ...string) { + mockKey := fmt.Sprintf("withargs::%s", strings.Join(args, " ")) + + if _, initialized := d.MockedCalls[fn]; !initialized { + d.MockedCalls[fn] = map[string]CommandResult{} + } + + d.MockedCalls[fn][mockKey] = returnValue +} + +// Mocks the nth call to to return . +// +// The mocked call is reused as long as it's defined within the mock driver. +func (d *MockDriver) WithNthMockedCall(fn string, callIndex int, returnValue CommandResult) { + mockKey := fmt.Sprintf("nthcall::%d", callIndex) + + if _, initialized := d.MockedCalls[fn]; !initialized { + d.MockedCalls[fn] = map[string]CommandResult{} + } + + d.MockedCalls[fn][mockKey] = returnValue +} diff --git a/internal/runner/podman_driver.go b/internal/driver/podman_driver.go similarity index 99% rename from internal/runner/podman_driver.go rename to internal/driver/podman_driver.go index e05381e..58794c7 100644 --- a/internal/runner/podman_driver.go +++ b/internal/driver/podman_driver.go @@ -1,7 +1,7 @@ // Podman driver // // Abstracts interactions with Podman commands via the ContainerDriver interface. -package runner +package driver import ( logger "courgette/internal/logging" diff --git a/internal/runner/podman_driver_test.go b/internal/driver/podman_driver_test.go similarity index 99% rename from internal/runner/podman_driver_test.go rename to internal/driver/podman_driver_test.go index cce5c7b..a6c15b6 100644 --- a/internal/runner/podman_driver_test.go +++ b/internal/driver/podman_driver_test.go @@ -1,4 +1,4 @@ -package runner +package driver import ( "encoding/json" diff --git a/internal/runner/mock_container_driver.go b/internal/runner/mock_container_driver.go deleted file mode 100644 index 5e52122..0000000 --- a/internal/runner/mock_container_driver.go +++ /dev/null @@ -1,98 +0,0 @@ -package runner - -import ( - "fmt" - "strings" -) - -type MockCall struct { - fname string - args []string -} - -type MockDriver struct { - calls map[string][]MockCall - mockedCalls map[string]map[string]CommandResult -} - -func NewMockDriver() MockDriver { - return MockDriver{ - calls: map[string][]MockCall{}, - mockedCalls: map[string]map[string]CommandResult{}, - } -} - -func (d *MockDriver) Pull(uri string) error { - if _, init := d.calls["Pull"]; !init { - d.calls["Pull"] = []MockCall{} - } - - d.calls["Pull"] = append(d.calls["Pull"], MockCall{fname: "Pull", args: []string{uri}}) - - return nil -} - -func (d *MockDriver) Start(uri string, containerName string) error { - if _, init := d.calls["Start"]; !init { - d.calls["Start"] = []MockCall{} - } - d.calls["Start"] = append(d.calls["Start"], MockCall{fname: "Start", args: []string{uri, containerName}}) - return nil -} - -func (d *MockDriver) Stop(uri string) error { - if _, init := d.calls["Stop"]; !init { - d.calls["Stop"] = []MockCall{} - } - d.calls["Stop"] = append(d.calls["Stop"], MockCall{fname: "Stop", args: []string{uri}}) - return nil - -} - -func (d *MockDriver) Exec(containerName string, command string, options CommandOptions) CommandResult { - if _, init := d.calls["Exec"]; !init { - d.calls["Exec"] = []MockCall{} - } - - args := []string{containerName, command, options.Cwd, fmt.Sprintf("%#v", options.Env)} - d.calls["Exec"] = append(d.calls["Exec"], MockCall{fname: "Exec", args: args}) - - mockKeys := []string{ - fmt.Sprintf("nthcall::%d", len(d.calls["Exec"])), - fmt.Sprintf("withargs::%s", strings.Join(args, " ")), - } - - for _, mockKey := range mockKeys { - if _, mocked := d.mockedCalls["Exec"][mockKey]; mocked { - return d.mockedCalls["Exec"][mockKey] - } - } - - return CommandResult{} -} - -// Mocks a call to with arguments to return . -// -// The mocked call is reused as long as it's defined within the mock driver. -func (d *MockDriver) WithMockedCall(fn string, returnValue CommandResult, args ...string) { - mockKey := fmt.Sprintf("withargs::%s", strings.Join(args, " ")) - - if _, initialized := d.mockedCalls[fn]; !initialized { - d.mockedCalls[fn] = map[string]CommandResult{} - } - - d.mockedCalls[fn][mockKey] = returnValue -} - -// Mocks the nth call to to return . -// -// The mocked call is reused as long as it's defined within the mock driver. -func (d *MockDriver) WithNthMockedCall(fn string, callIndex int, returnValue CommandResult) { - mockKey := fmt.Sprintf("nthcall::%d", callIndex) - - if _, initialized := d.mockedCalls[fn]; !initialized { - d.mockedCalls[fn] = map[string]CommandResult{} - } - - d.mockedCalls[fn][mockKey] = returnValue -} diff --git a/internal/runner/runner.go b/internal/runner/runner.go index ccfa76f..12b8f36 100644 --- a/internal/runner/runner.go +++ b/internal/runner/runner.go @@ -2,6 +2,7 @@ package runner import ( "context" + driver "courgette/internal/driver" logger "courgette/internal/logging" workflow "courgette/internal/workflow" "errors" @@ -11,13 +12,13 @@ import ( type Runner struct { Labels map[string]string - Driver ContainerDriver + Driver driver.ContainerDriver Runs int // Deferred tasks, in order their were scheduled. deferred *DeferredTaskManager } -func NewRunner(driver ContainerDriver, labels map[string]string) Runner { +func NewRunner(driver driver.ContainerDriver, labels map[string]string) Runner { return Runner{ Driver: driver, Labels: labels, @@ -104,7 +105,7 @@ func (r Runner) runJob(jobContext context.Context, jobTracker *TaskTracker, jobW // If the command raises an error while in the container or fails to run // the command at all, an error is returned, otherwise nil. func (r *Runner) RunCommandInContainer(containerId string, command string, stepCwd string, stepEnv map[string]string) error { - result := r.Driver.Exec(containerId, command, CommandOptions{Cwd: stepCwd, Env: stepEnv}) + result := r.Driver.Exec(containerId, command, driver.CommandOptions{Cwd: stepCwd, Env: stepEnv}) if result.Error != nil { return result.Error diff --git a/internal/runner/runner_flow_test.go b/internal/runner/runner_flow_test.go index 2ac0a48..ed04a3c 100644 --- a/internal/runner/runner_flow_test.go +++ b/internal/runner/runner_flow_test.go @@ -2,6 +2,7 @@ package runner import ( "context" + driver "courgette/internal/driver" logger "courgette/internal/logging" workflow "courgette/internal/workflow" "errors" @@ -14,8 +15,8 @@ func init() { } func TestRunnerRunCommandInContainerReturnsErrorFromDriver(t *testing.T) { - mockDriver := NewMockDriver() - mockDriver.WithMockedCall("Exec", CommandResult{ExitCode: 0, Error: errors.New("test")}, "test-container", "test-command", ".", fmt.Sprintf("%#v", map[string]string{})) + mockDriver := driver.NewMockDriver() + mockDriver.WithMockedCall("Exec", driver.CommandResult{ExitCode: 0, Error: errors.New("test")}, "test-container", "test-command", ".", fmt.Sprintf("%#v", map[string]string{})) runner := Runner{ Driver: &mockDriver, @@ -29,8 +30,8 @@ func TestRunnerRunCommandInContainerReturnsErrorFromDriver(t *testing.T) { } func TestRunnerRunCommandInContainerReturnsErrorIfCommandExitCodeNonzero(t *testing.T) { - mockDriver := NewMockDriver() - mockDriver.WithMockedCall("Exec", CommandResult{ExitCode: 1, Error: nil}, "test-container", "test-command", ".", fmt.Sprintf("%#v", map[string]string{})) + mockDriver := driver.NewMockDriver() + mockDriver.WithMockedCall("Exec", driver.CommandResult{ExitCode: 1, Error: nil}, "test-container", "test-command", ".", fmt.Sprintf("%#v", map[string]string{})) runner := Runner{ Driver: &mockDriver, @@ -44,8 +45,8 @@ func TestRunnerRunCommandInContainerReturnsErrorIfCommandExitCodeNonzero(t *test } func TestRunJobInContainerSchedulesStoppingContainers(t *testing.T) { - mockDriver := NewMockDriver() - mockDriver.WithMockedCall("Exec", CommandResult{ExitCode: 1, Error: nil}, "test-container", "test-command", ".", fmt.Sprintf("%#v", map[string]string{})) + mockDriver := driver.NewMockDriver() + mockDriver.WithMockedCall("Exec", driver.CommandResult{ExitCode: 1, Error: nil}, "test-container", "test-command", ".", fmt.Sprintf("%#v", map[string]string{})) runner := NewRunner(&mockDriver, map[string]string{}) diff --git a/internal/runner/runner_job_test.go b/internal/runner/runner_job_test.go index 3a916f1..8ca6794 100644 --- a/internal/runner/runner_job_test.go +++ b/internal/runner/runner_job_test.go @@ -2,6 +2,7 @@ package runner import ( "context" + driver "courgette/internal/driver" workflow "courgette/internal/workflow" "errors" "fmt" @@ -18,14 +19,14 @@ jobs: ` workflow, _ := workflow.FromYamlBytes([]byte(workflowSample)) - mockDriver := NewMockDriver() + mockDriver := driver.NewMockDriver() runner := NewRunner(&mockDriver, map[string]string{"test": "test"}) jobContext := context.WithValue(context.Background(), "workflow", *workflow) jobContext = context.WithValue(jobContext, "currentJob", workflow.Jobs["jobA"]) - mockDriver.WithNthMockedCall("Exec", 1, CommandResult{Error: errors.New("exit 1!"), ExitCode: 1}) + mockDriver.WithNthMockedCall("Exec", 1, driver.CommandResult{Error: errors.New("exit 1!"), ExitCode: 1}) task := runner.RunWorkflow(*workflow) if !task.Failed() { @@ -43,14 +44,14 @@ jobs: ` workflow, _ := workflow.FromYamlBytes([]byte(workflowSample)) - mockDriver := NewMockDriver() + mockDriver := driver.NewMockDriver() runner := NewRunner(&mockDriver, map[string]string{"test": "test"}) jobContext := context.WithValue(context.Background(), "workflow", *workflow) jobContext = context.WithValue(jobContext, "currentJob", workflow.Jobs["jobA"]) - mockDriver.WithNthMockedCall("Exec", 1, CommandResult{Error: errors.New("exit 1!"), ExitCode: 1}) + mockDriver.WithNthMockedCall("Exec", 1, driver.CommandResult{Error: errors.New("exit 1!"), ExitCode: 1}) task := runner.RunWorkflow(*workflow) if !task.Failed() { @@ -69,9 +70,9 @@ jobs: ` workflow, _ := workflow.FromYamlBytes([]byte(workflowSample)) - mockDriver := NewMockDriver() + mockDriver := driver.NewMockDriver() - mockDriver.WithMockedCall("Exec", CommandResult{Error: errors.New("exit 1!"), ExitCode: 1}, "testContainer", "exit 1", fmt.Sprintf("%#v", map[string]string{})) + mockDriver.WithMockedCall("Exec", driver.CommandResult{Error: errors.New("exit 1!"), ExitCode: 1}, "testContainer", "exit 1", fmt.Sprintf("%#v", map[string]string{})) runner := NewRunner(&mockDriver, map[string]string{"test": "test"}) jobContext := context.WithValue(context.Background(), "workflow", *workflow) @@ -97,9 +98,9 @@ jobs: ` workflow, _ := workflow.FromYamlBytes([]byte(workflowSample)) - mockDriver := NewMockDriver() + mockDriver := driver.NewMockDriver() - mockDriver.WithMockedCall("Exec", CommandResult{Error: errors.New("exit 1!"), ExitCode: 1}, "testContainer", "exit 1", fmt.Sprintf("%#v", map[string]string{})) + mockDriver.WithMockedCall("Exec", driver.CommandResult{Error: errors.New("exit 1!"), ExitCode: 1}, "testContainer", "exit 1", fmt.Sprintf("%#v", map[string]string{})) runner := NewRunner(&mockDriver, map[string]string{"test": "test"}) jobContext := context.WithValue(context.Background(), "workflow", *workflow) @@ -111,7 +112,7 @@ jobs: t.Errorf("Did not expect error, got %+v", runErr) } - execCallCount := len(mockDriver.calls["Exec"]) + execCallCount := len(mockDriver.Calls["Exec"]) if execCallCount != 2 { t.Errorf("Expected 2 calls to Exec, got %d", execCallCount) } @@ -129,9 +130,9 @@ jobs: ` workflow, _ := workflow.FromYamlBytes([]byte(workflowSample)) - mockDriver := NewMockDriver() + mockDriver := driver.NewMockDriver() - mockDriver.WithMockedCall("Exec", CommandResult{Error: errors.New("exit 1!"), ExitCode: 1}, "testContainer", "exit 1", ".", fmt.Sprintf("%#v", map[string]string{})) + mockDriver.WithMockedCall("Exec", driver.CommandResult{Error: errors.New("exit 1!"), ExitCode: 1}, "testContainer", "exit 1", ".", fmt.Sprintf("%#v", map[string]string{})) runner := NewRunner(&mockDriver, map[string]string{"test": "test"}) jobContext := context.WithValue(context.Background(), "workflow", *workflow) @@ -143,7 +144,7 @@ jobs: t.Error("Expected error, got nil") } - execCallCount := len(mockDriver.calls["Exec"]) + execCallCount := len(mockDriver.Calls["Exec"]) if execCallCount != 1 { t.Errorf("Expected 1 calls to Exec, got %d", execCallCount) } @@ -160,9 +161,9 @@ jobs: ` workflow, _ := workflow.FromYamlBytes([]byte(workflowSample)) - mockDriver := NewMockDriver() + mockDriver := driver.NewMockDriver() - mockDriver.WithMockedCall("Exec", CommandResult{Error: errors.New("exit 1!"), ExitCode: 1}, "testContainer", "exit 1", ".", fmt.Sprintf("%#v", map[string]string{})) + mockDriver.WithMockedCall("Exec", driver.CommandResult{Error: errors.New("exit 1!"), ExitCode: 1}, "testContainer", "exit 1", ".", fmt.Sprintf("%#v", map[string]string{})) runner := NewRunner(&mockDriver, map[string]string{"test": "test"}) jobContext := context.WithValue(context.Background(), "workflow", *workflow) @@ -174,7 +175,7 @@ jobs: t.Error("Expect error, got nil") } - execCallCount := len(mockDriver.calls["Exec"]) + execCallCount := len(mockDriver.Calls["Exec"]) if execCallCount != 1 { t.Errorf("Expected 1 calls to Exec, got %d", execCallCount) }