From 77f1e02c758975b1902f013438d344017c1d0524 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sybren=20A=2E=20St=C3=BCvel?= Date: Tue, 22 Feb 2022 11:48:29 +0100 Subject: [PATCH] Worker: add CommandLineRunner interface for executing CLIs Not yet used, but interface is there + mocked for testing. --- internal/worker/command_exe.go | 17 ++++++-- internal/worker/command_misc_test.go | 46 ++++++++++++++------- internal/worker/mocks/cli_runner.gen.go | 55 +++++++++++++++++++++++++ 3 files changed, 99 insertions(+), 19 deletions(-) create mode 100644 internal/worker/mocks/cli_runner.gen.go diff --git a/internal/worker/command_exe.go b/internal/worker/command_exe.go index 72d822c8..aec4f90f 100644 --- a/internal/worker/command_exe.go +++ b/internal/worker/command_exe.go @@ -23,6 +23,7 @@ package worker import ( "context" "fmt" + "os/exec" "time" "github.com/rs/zerolog" @@ -42,11 +43,12 @@ type CommandListener interface { } type CommandExecutor struct { - listener CommandListener + cli CommandLineRunner + listener CommandListener + timeService TimeService + // registry maps a command name to a function that runs that command. registry map[string]commandCallable - - timeService TimeService } var _ CommandRunner = (*CommandExecutor)(nil) @@ -58,8 +60,15 @@ type TimeService interface { After(duration time.Duration) <-chan time.Time } -func NewCommandExecutor(listener CommandListener, timeService TimeService) *CommandExecutor { +//go:generate go run github.com/golang/mock/mockgen -destination mocks/cli_runner.gen.go -package mocks gitlab.com/blender/flamenco-ng-poc/internal/worker CommandLineRunner +// CommandLineRunner is an interface around exec.CommandContext(). +type CommandLineRunner interface { + CommandContext(ctx context.Context, name string, arg ...string) *exec.Cmd +} + +func NewCommandExecutor(cli CommandLineRunner, listener CommandListener, timeService TimeService) *CommandExecutor { ce := &CommandExecutor{ + cli: cli, listener: listener, timeService: timeService, } diff --git a/internal/worker/command_misc_test.go b/internal/worker/command_misc_test.go index 6080bc60..cbc86fe4 100644 --- a/internal/worker/command_misc_test.go +++ b/internal/worker/command_misc_test.go @@ -25,19 +25,38 @@ import ( "testing" "time" + "github.com/benbjohnson/clock" "github.com/golang/mock/gomock" "github.com/stretchr/testify/assert" "gitlab.com/blender/flamenco-ng-poc/internal/worker/mocks" "gitlab.com/blender/flamenco-ng-poc/pkg/api" ) +type MockCommandExecutor struct { + ce *CommandExecutor + cli *mocks.MockCommandLineRunner + listener *mocks.MockCommandListener + clock *clock.Mock +} + +func testCommandExecutor(t *testing.T, mockCtrl *gomock.Controller) *MockCommandExecutor { + cli := mocks.NewMockCommandLineRunner(mockCtrl) + listener := mocks.NewMockCommandListener(mockCtrl) + clock := mockedClock(t) + ce := NewCommandExecutor(cli, listener, clock) + + return &MockCommandExecutor{ + ce: ce, + cli: cli, + listener: listener, + clock: clock, + } +} + func TestCommandEcho(t *testing.T) { mockCtrl := gomock.NewController(t) defer mockCtrl.Finish() - - listener := mocks.NewMockCommandListener(mockCtrl) - clock := mockedClock(t) - ce := NewCommandExecutor(listener, clock) + tce := testCommandExecutor(t, mockCtrl) ctx := context.Background() message := "понављај за мном" @@ -47,19 +66,16 @@ func TestCommandEcho(t *testing.T) { Parameters: map[string]interface{}{"message": message}, } - listener.EXPECT().LogProduced(gomock.Any(), taskID, "echo: \"понављај за мном\"") + tce.listener.EXPECT().LogProduced(gomock.Any(), taskID, "echo: \"понављај за мном\"") - err := ce.Run(ctx, taskID, cmd) + err := tce.ce.Run(ctx, taskID, cmd) assert.NoError(t, err) } func TestCommandSleep(t *testing.T) { mockCtrl := gomock.NewController(t) defer mockCtrl.Finish() - - listener := mocks.NewMockCommandListener(mockCtrl) - clock := mockedClock(t) - ce := NewCommandExecutor(listener, clock) + tce := testCommandExecutor(t, mockCtrl) ctx := context.Background() taskID := "90e9d656-e201-4ef0-b6b0-c80684fafa27" @@ -68,9 +84,9 @@ func TestCommandSleep(t *testing.T) { Parameters: map[string]interface{}{"duration_in_seconds": 47}, } - listener.EXPECT().LogProduced(gomock.Any(), taskID, "slept 47s") + tce.listener.EXPECT().LogProduced(gomock.Any(), taskID, "slept 47s") - timeBefore := clock.Now() + timeBefore := tce.clock.Now() // Run the test in a goroutine, as we also need to actually increase the // mocked clock at the same time; without that, the command will sleep @@ -78,7 +94,7 @@ func TestCommandSleep(t *testing.T) { runDone := make(chan struct{}) var err error go func() { - err = ce.Run(ctx, taskID, cmd) + err = tce.ce.Run(ctx, taskID, cmd) close(runDone) }() @@ -89,12 +105,12 @@ loop: case <-runDone: break loop default: - clock.Add(timeStepSize) + tce.clock.Add(timeStepSize) } } assert.NoError(t, err) - timeAfter := clock.Now() + timeAfter := tce.clock.Now() // Within the step size is precise enough. We're testing our implementation, not the precision of `time.After()`. assert.WithinDuration(t, timeBefore.Add(47*time.Second), timeAfter, timeStepSize) } diff --git a/internal/worker/mocks/cli_runner.gen.go b/internal/worker/mocks/cli_runner.gen.go new file mode 100644 index 00000000..9e1244bb --- /dev/null +++ b/internal/worker/mocks/cli_runner.gen.go @@ -0,0 +1,55 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: gitlab.com/blender/flamenco-ng-poc/internal/worker (interfaces: CommandLineRunner) + +// Package mocks is a generated GoMock package. +package mocks + +import ( + context "context" + exec "os/exec" + reflect "reflect" + + gomock "github.com/golang/mock/gomock" +) + +// MockCommandLineRunner is a mock of CommandLineRunner interface. +type MockCommandLineRunner struct { + ctrl *gomock.Controller + recorder *MockCommandLineRunnerMockRecorder +} + +// MockCommandLineRunnerMockRecorder is the mock recorder for MockCommandLineRunner. +type MockCommandLineRunnerMockRecorder struct { + mock *MockCommandLineRunner +} + +// NewMockCommandLineRunner creates a new mock instance. +func NewMockCommandLineRunner(ctrl *gomock.Controller) *MockCommandLineRunner { + mock := &MockCommandLineRunner{ctrl: ctrl} + mock.recorder = &MockCommandLineRunnerMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockCommandLineRunner) EXPECT() *MockCommandLineRunnerMockRecorder { + return m.recorder +} + +// CommandContext mocks base method. +func (m *MockCommandLineRunner) CommandContext(arg0 context.Context, arg1 string, arg2 ...string) *exec.Cmd { + m.ctrl.T.Helper() + varargs := []interface{}{arg0, arg1} + for _, a := range arg2 { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "CommandContext", varargs...) + ret0, _ := ret[0].(*exec.Cmd) + return ret0 +} + +// CommandContext indicates an expected call of CommandContext. +func (mr *MockCommandLineRunnerMockRecorder) CommandContext(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{arg0, arg1}, arg2...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CommandContext", reflect.TypeOf((*MockCommandLineRunner)(nil).CommandContext), varargs...) +}