Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion task/task.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,15 @@ import (
"github.com/google/uuid"
)

type TaskStatus int

const (
Running TaskStatus = iota
Succeeded
Stopped
Failed
)

var (
ErrAlreadyExists = errors.New("task already exists")
ErrNotExists = errors.New("task does not exist")
Expand All @@ -18,5 +27,7 @@ type Task struct {
Executable string
Args []string

Cmd *exec.Cmd
Status TaskStatus

Cmd *exec.Cmd `json:"-"`
}
6 changes: 6 additions & 0 deletions tests/helper/task.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,12 @@ func PrintFileTask(name, path string) task.Task {
}
}

func JsonDecodeTask(r io.Reader) task.Task {
var t task.Task
json.NewDecoder(r).Decode(&t)
return t
}

func JsonEncodeTask(t task.Task) io.Reader {
w := &bytes.Buffer{}
json.NewEncoder(w).Encode(t)
Expand Down
144 changes: 144 additions & 0 deletions tests/worker/get_task_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
package worker

import (
"dirigeant/task"
"dirigeant/tests/helper"
"dirigeant/worker"
"fmt"
"net/http"
"net/http/httptest"
"sync"
"testing"
"time"

"github.com/google/uuid"
"github.com/stretchr/testify/assert"
)

func TestGetTask__ShouldReturnAnErrorIfNotFound(t *testing.T) {
api := &worker.Api{
Worker: &worker.Worker{},
}
id := uuid.New()
request := helper.NewTaskGetRequest(id)
responseRecorder := httptest.NewRecorder()

api.HandleGetTask(responseRecorder, request)

assert.Equal(t, http.StatusNotFound, responseRecorder.Code, "Response status code should be 404 Not Found")
assert.Equal(t, fmt.Sprintf("A task with %s ID not found", id), responseRecorder.Body.String(), "Response body should contain error message")
}

func TestGetTask__ShouldReturnFinishedTask(t *testing.T) {
tcs := []struct {
name string
path string
responseStatus int
responseBody string
taskStatus task.TaskStatus
}{
{
name: "print-hosts-file",
path: helper.HostsFilePath,
responseStatus: http.StatusCreated,
responseBody: "",
taskStatus: task.Succeeded,
},
{
name: "print-non-existing-file",
path: "non-existing-file.txt",
responseStatus: http.StatusInternalServerError,
responseBody: "Error when executing the task: exit status 1",
taskStatus: task.Failed,
},
}

for _, tc := range tcs {
t.Run(tc.name, func(t *testing.T) {
api := &worker.Api{
Worker: worker.NewWorker(),
}
testTask := helper.PrintFileTask(tc.name, tc.path)

// 1 - create a task
createRequest := helper.NewTaskPostRequest(testTask)
createResponseRecorder := httptest.NewRecorder()

api.HandleCreateTask(createResponseRecorder, createRequest)

assert.Equal(t, tc.responseStatus, createResponseRecorder.Code)
assert.Equal(t, tc.responseBody, createResponseRecorder.Body.String())
assert.Equal(t, 1, api.Worker.LenTasks(), "Worker should have 1 task")

persistedTask := api.Worker.GetTask(testTask.ID)

assert.NotNil(t, persistedTask, "Persisted task ID should match the one from request")
assert.Equal(t, tc.taskStatus, persistedTask.Status)

// 2 - get a task
getRequest := helper.NewTaskGetRequest(testTask.ID)
getResponseRecorder := httptest.NewRecorder()

api.HandleGetTask(getResponseRecorder, getRequest)

assert.Equal(t, http.StatusOK, getResponseRecorder.Code, "Response status code should be 200 OK")

gottenTask := helper.JsonDecodeTask(getResponseRecorder.Body)

assert.Equal(t, testTask.ID, gottenTask.ID, "Gotten task ID should match the created one")
assert.Equal(t, tc.taskStatus, gottenTask.Status)
})
}
}

func TestGetTask__ShouldReturnRunningTask(t *testing.T) {
api := &worker.Api{
Worker: worker.NewWorker(),
}
testTask := helper.PingTask("ping-task", "127.0.0.1")

// 1 - Create a task
var wg sync.WaitGroup
wg.Add(1)
go func() {
defer wg.Done()

createRequest := helper.NewTaskPostRequest(testTask)
createResponseRecorder := httptest.NewRecorder()

api.HandleCreateTask(createResponseRecorder, createRequest)

assert.Equal(t, http.StatusCreated, createResponseRecorder.Code, "Response status code should be 201 Created")
assert.Empty(t, createResponseRecorder.Body, "Response body should be empty")
assert.Equal(t, 1, api.Worker.LenTasks(), "Worker should have 1 task")

persistedTask := api.Worker.GetTask(testTask.ID)

assert.NotNil(t, persistedTask, "Persisted task ID should match the one from request")
assert.Equal(t, task.Succeeded, persistedTask.Status, "Persisted task status should be Succeeded")
}()

time.Sleep(1 * time.Second)

assert.Equal(t, 1, api.Worker.LenTasks(), "Worker should have 1 task")

persistedTask := api.Worker.GetTask(testTask.ID)

assert.NotNil(t, persistedTask, "Persisted task ID should match the one from request")
assert.Equal(t, task.Running, persistedTask.Status, "Persisted task Status should be Running")

// 2 - Get a task
getRequest := helper.NewTaskGetRequest(testTask.ID)
getResponseRecorder := httptest.NewRecorder()

api.HandleGetTask(getResponseRecorder, getRequest)

assert.Equal(t, http.StatusOK, getResponseRecorder.Code, "Response status code should be 200 OK")

gottenTask := helper.JsonDecodeTask(getResponseRecorder.Body)

assert.Equal(t, testTask.ID, gottenTask.ID, "Gotten task ID should match the created one")
assert.Equal(t, task.Running, gottenTask.Status)

wg.Wait()
}
40 changes: 30 additions & 10 deletions tests/worker/start_task_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,12 @@ func TestStartTask__ShouldPersistTask(t *testing.T) {

assert.Equal(t, http.StatusCreated, responseRecorder.Code, "Response status code should be 201 Created")
assert.Empty(t, responseRecorder.Body, "Response body should be empty")
assert.Equal(t, 1, api.Worker.LenTasks(), "Tasks map should contain 1 task")
assert.NotNil(t, api.Worker.GetTask(testTask.ID), "Persisted task ID should match the one from request")
assert.Equal(t, 1, api.Worker.LenTasks(), "Worker should have 1 task")

persistedTask := api.Worker.GetTask(testTask.ID)

assert.NotNil(t, persistedTask, "Persisted task ID should match the one from request")
assert.Equal(t, task.Succeeded, persistedTask.Status, "Persisted task Status should be Succeeded")
}

func TestStartTask__ShouldReturnAnErrorIfCreatingTheSameTaskTwice(t *testing.T) {
Expand All @@ -45,8 +49,12 @@ func TestStartTask__ShouldReturnAnErrorIfCreatingTheSameTaskTwice(t *testing.T)

assert.Equal(t, http.StatusCreated, firstResponseRecorder.Code, "Response status code should be 201 Created")
assert.Empty(t, firstResponseRecorder.Body, "Response body should be empty")
assert.Equal(t, 1, api.Worker.LenTasks(), "Tasks map should contain 1 task")
assert.NotNil(t, api.Worker.GetTask(testTask.ID), "Persisted task ID should match the one from request")
assert.Equal(t, 1, api.Worker.LenTasks(), "Worker should have 1 task")

persistedTask := api.Worker.GetTask(testTask.ID)

assert.NotNil(t, persistedTask, "Persisted task ID should match the one from request")
assert.Equal(t, task.Succeeded, persistedTask.Status, "Persisted task Status should be Succeeded")

// 2 - Create the same task for the second time
secondRequest := helper.NewTaskPostRequest(testTask)
Expand All @@ -56,8 +64,11 @@ func TestStartTask__ShouldReturnAnErrorIfCreatingTheSameTaskTwice(t *testing.T)

assert.Equal(t, http.StatusConflict, secondResponseRecorder.Code, "Response status code should be 409 Conflict")
assert.Equal(t, fmt.Sprintf("Error when executing the task: %s", task.ErrAlreadyExists), secondResponseRecorder.Body.String(), "Response body should contain error message")
assert.Equal(t, 1, api.Worker.LenTasks(), "Tasks map should contain 1 task")
assert.NotNil(t, api.Worker.GetTask(testTask.ID), "Persisted task ID should match the one from request")

persistedTask = api.Worker.GetTask(testTask.ID)

assert.NotNil(t, persistedTask, "Persisted task ID should match the one from request")
assert.Equal(t, task.Succeeded, persistedTask.Status, "Persisted task Status should be Succeeded")
}

func TestStartTask__AllButOneRequestsShouldFailIfCreatingTheSameTaskSimultaneously(t *testing.T) {
Expand Down Expand Up @@ -97,8 +108,12 @@ func TestStartTask__AllButOneRequestsShouldFailIfCreatingTheSameTaskSimultaneous

assert.Equal(t, 1, succeededRequests, "There should be only 1 succeeded request")
assert.Equal(t, numOfRequests-1, conflictedRequests, "There should be only N-1 conflicted requests")
assert.Equal(t, 1, api.Worker.LenTasks(), "Tasks map should contain 1 task")
assert.NotNil(t, api.Worker.GetTask(testTask.ID), "Persisted task ID should match the one from request")
assert.Equal(t, 1, api.Worker.LenTasks(), "Worker should have 1 task")

persistedTask := api.Worker.GetTask(testTask.ID)

assert.NotNil(t, persistedTask, "Persisted task ID should match the one from request")
assert.Equal(t, task.Succeeded, persistedTask.Status, "Persisted task Status should be Succeeded")
}

func TestStartTask__ShouldHandleClientClosedRequest(t *testing.T) {
Expand All @@ -124,12 +139,17 @@ func TestStartTask__ShouldHandleClientClosedRequest(t *testing.T) {
assert.Equal(t, 499, createResponseRecorder.Code, "Response status code should be 499 Client Closed Request")
assert.Equal(t, "Error when executing the task: client closed request", createResponseRecorder.Body.String(), "Response body should contain error message")
assert.NotEmpty(t, stdout, "Task logs shouldn't be empty")
assert.Zero(t, api.Worker.LenTasks(), "Tasks map should be empty")
assert.Zero(t, api.Worker.LenTasks(), "Worker should have no tasks")
}()

time.Sleep(1 * time.Second)

assert.Equal(t, 1, api.Worker.LenTasks(), "Tasks map should contain 1 task")
assert.Equal(t, 1, api.Worker.LenTasks(), "Worker should have 1 task")

persistedTask := api.Worker.GetTask(testTask.ID)

assert.NotNil(t, persistedTask, "Persisted task ID should match the one from request")
assert.Equal(t, task.Running, persistedTask.Status, "Persisted task Status should be Running")

// 2 - Cancel a request
cancel()
Expand Down
15 changes: 10 additions & 5 deletions tests/worker/stop_task_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ func TestStopTask__ShouldStopCompletedTask(t *testing.T) {

assert.Equal(t, http.StatusCreated, responseRecorder.Code, "Response status code should be 201 Created")
assert.Empty(t, responseRecorder.Body, "Response body should be empty")
assert.Equal(t, 1, api.Worker.LenTasks(), "Tasks map should contain 1 task")
assert.Equal(t, 1, api.Worker.LenTasks(), "Worker should have 1 task")

// 2 - Delete a task
request = helper.NewTaskDeleteRequest(testTask.ID)
Expand All @@ -51,7 +51,7 @@ func TestStopTask__ShouldStopCompletedTask(t *testing.T) {

assert.Equal(t, http.StatusNoContent, responseRecorder.Code, "Response status code should be 204 No Content")
assert.Empty(t, responseRecorder.Body, "Response body should be empty")
assert.Zero(t, api.Worker.LenTasks(), "Tasks map should be empty")
assert.Zero(t, api.Worker.LenTasks(), "Worker should have no tasks")
}

func TestStopTask__ShouldStopRunningTask(t *testing.T) {
Expand All @@ -73,12 +73,17 @@ func TestStopTask__ShouldStopRunningTask(t *testing.T) {

assert.Equal(t, http.StatusInternalServerError, createResponseRecorder.Code, "Response status code should be 500 Internal Server Error")
assert.Equal(t, fmt.Sprintf("Error when executing the task: %s", helper.SignalKilledErrMessage), createResponseRecorder.Body.String(), "Response body should contain error message")
assert.Zero(t, api.Worker.LenTasks(), "Tasks map should be empty")
assert.Zero(t, api.Worker.LenTasks(), "Worker should have no tasks")
}()

time.Sleep(1 * time.Second)

assert.Equal(t, 1, api.Worker.LenTasks(), "Tasks map should contain 1 task")
assert.Equal(t, 1, api.Worker.LenTasks(), "Worker should have 1 task")

persistedTask := api.Worker.GetTask(testTask.ID)

assert.NotNil(t, persistedTask, "Persisted task ID should match the one from request")
assert.Equal(t, task.Running, persistedTask.Status, "Persisted task Status should be Running")

// 2 - Delete a task
deleteRequest := helper.NewTaskDeleteRequest(testTask.ID)
Expand All @@ -88,7 +93,7 @@ func TestStopTask__ShouldStopRunningTask(t *testing.T) {

assert.Equal(t, http.StatusNoContent, deleteResponseRecorder.Code, "Response status code should be 204 No Content")
assert.Empty(t, deleteResponseRecorder.Body, "Response body should be empty")
assert.Zero(t, api.Worker.LenTasks(), "Tasks map should be empty")
assert.Zero(t, api.Worker.LenTasks(), "Worker should have no tasks")

wg.Wait()
}
13 changes: 12 additions & 1 deletion tests/worker/task_logs_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package worker

import (
"dirigeant/task"
"dirigeant/tests/helper"
"dirigeant/worker"
"fmt"
Expand All @@ -18,20 +19,23 @@ func TestTaskLogs__PrintFile(t *testing.T) {
responseStatus int
responseBody string
stdoutRegexp string
taskStatus task.TaskStatus
}{
{
name: "print-hosts-file",
path: helper.HostsFilePath,
responseStatus: http.StatusCreated,
responseBody: "",
stdoutRegexp: "localhost",
taskStatus: task.Succeeded,
},
{
name: "print-non-existing-file",
path: "non-existing-file.txt",
responseStatus: http.StatusInternalServerError,
responseBody: "Error when executing the task: exit status 1",
stdoutRegexp: fmt.Sprintf(helper.NoFileErrMessage, "non-existing-file.txt"),
taskStatus: task.Failed,
},
}

Expand All @@ -40,7 +44,8 @@ func TestTaskLogs__PrintFile(t *testing.T) {
api := &worker.Api{
Worker: worker.NewWorker(),
}
request := helper.NewTaskPostRequest(helper.PrintFileTask(tc.name, tc.path))
testTask := helper.PrintFileTask(tc.name, tc.path)
request := helper.NewTaskPostRequest(testTask)
responseRecorder := httptest.NewRecorder()

stdout := helper.CaptureStdout(func() {
Expand All @@ -49,7 +54,13 @@ func TestTaskLogs__PrintFile(t *testing.T) {

assert.Equal(t, tc.responseStatus, responseRecorder.Code)
assert.Equal(t, tc.responseBody, responseRecorder.Body.String())
assert.Equal(t, 1, api.Worker.LenTasks(), "Worker should have 1 task")
assert.Regexp(t, tc.stdoutRegexp, stdout)

persistedTask := api.Worker.GetTask(testTask.ID)

assert.NotNil(t, persistedTask, "Persisted task ID should match the one from request")
assert.Equal(t, tc.taskStatus, persistedTask.Status)
})
}
}
6 changes: 6 additions & 0 deletions worker/worker.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,16 +53,20 @@ func (w *Worker) StartTask(t task.Task) error {
}

t.Cmd = exec.Command(t.Executable, t.Args...)
t.Status = task.Running
w.tasks[t.ID] = &t

w.Unlock()

stdout, err := t.Cmd.CombinedOutput()
os.Stdout.Write(stdout)
if err != nil {
t.Status = task.Failed
return err
}

t.Status = task.Succeeded

return nil
}

Expand All @@ -79,6 +83,8 @@ func (w *Worker) StopTask(id uuid.UUID) error {
if err := t.Cmd.Process.Kill(); err != nil {
return err
}

t.Status = task.Stopped
}

delete(w.tasks, t.ID)
Expand Down
Loading