Sybren A. Stüvel 9f5e4cc0cc License: license all code under "GPL-3.0-or-later"
The add-on code was copy-pasted from other addons and used the GPL v2
license, whereas by accident the LICENSE text file had the GNU "Affero" GPL
license v3 (instead of regular GPL v3).

This is now all streamlined, and all code is licensed as "GPL v3 or later".

Furthermore, the code comments just show a SPDX License Identifier
instead of an entire license block.
2022-03-07 15:26:46 +01:00

326 lines
10 KiB
Go

// Package persistence provides the database interface for Flamenco Manager.
package persistence
// SPDX-License-Identifier: GPL-3.0-or-later
import (
"testing"
"time"
"github.com/stretchr/testify/assert"
"golang.org/x/net/context"
"git.blender.org/flamenco/internal/manager/job_compilers"
"git.blender.org/flamenco/pkg/api"
)
func TestStoreAuthoredJob(t *testing.T) {
ctx, cancel, db := persistenceTestFixtures(t, 1*time.Second)
defer cancel()
job := createTestAuthoredJobWithTasks()
err := db.StoreAuthoredJob(ctx, job)
assert.NoError(t, err)
fetchedJob, err := db.FetchJob(ctx, job.JobID)
assert.NoError(t, err)
assert.NotNil(t, fetchedJob)
// Test contents of fetched job
assert.Equal(t, job.JobID, fetchedJob.UUID)
assert.Equal(t, job.Name, fetchedJob.Name)
assert.Equal(t, job.JobType, fetchedJob.JobType)
assert.Equal(t, job.Priority, fetchedJob.Priority)
assert.Equal(t, api.JobStatusUnderConstruction, fetchedJob.Status)
assert.EqualValues(t, map[string]interface{}(job.Settings), fetchedJob.Settings)
assert.EqualValues(t, map[string]string(job.Metadata), fetchedJob.Metadata)
// Fetch tasks of job.
var dbJob Job
tx := db.gormDB.Where(&Job{UUID: job.JobID}).Find(&dbJob)
assert.NoError(t, tx.Error)
var tasks []Task
tx = db.gormDB.Where("job_id = ?", dbJob.ID).Find(&tasks)
assert.NoError(t, tx.Error)
if len(tasks) != 3 {
t.Fatalf("expected 3 tasks, got %d", len(tasks))
}
// TODO: test task contents.
assert.Equal(t, api.TaskStatusQueued, tasks[0].Status)
assert.Equal(t, api.TaskStatusQueued, tasks[1].Status)
assert.Equal(t, api.TaskStatusQueued, tasks[2].Status)
}
func TestJobHasTasksInStatus(t *testing.T) {
ctx, close, db, job, _ := jobTasksTestFixtures(t)
defer close()
hasTasks, err := db.JobHasTasksInStatus(ctx, job, api.TaskStatusQueued)
assert.NoError(t, err)
assert.True(t, hasTasks, "expected freshly-created job to have queued tasks")
hasTasks, err = db.JobHasTasksInStatus(ctx, job, api.TaskStatusActive)
assert.NoError(t, err)
assert.False(t, hasTasks, "expected freshly-created job to have no active tasks")
}
func TestCountTasksOfJobInStatus(t *testing.T) {
ctx, close, db, job, authoredJob := jobTasksTestFixtures(t)
defer close()
numQueued, numTotal, err := db.CountTasksOfJobInStatus(ctx, job, api.TaskStatusQueued)
assert.NoError(t, err)
assert.Equal(t, 3, numQueued)
assert.Equal(t, 3, numTotal)
// Make one task failed.
task, err := db.FetchTask(ctx, authoredJob.Tasks[0].UUID)
assert.NoError(t, err)
task.Status = api.TaskStatusFailed
assert.NoError(t, db.SaveTask(ctx, task))
numQueued, numTotal, err = db.CountTasksOfJobInStatus(ctx, job, api.TaskStatusQueued)
assert.NoError(t, err)
assert.Equal(t, 2, numQueued)
assert.Equal(t, 3, numTotal)
numFailed, numTotal, err := db.CountTasksOfJobInStatus(ctx, job, api.TaskStatusFailed)
assert.NoError(t, err)
assert.Equal(t, 1, numFailed)
assert.Equal(t, 3, numTotal)
numActive, numTotal, err := db.CountTasksOfJobInStatus(ctx, job, api.TaskStatusActive)
assert.NoError(t, err)
assert.Equal(t, 0, numActive)
assert.Equal(t, 3, numTotal)
}
func TestUpdateJobsTaskStatuses(t *testing.T) {
ctx, close, db, job, authoredJob := jobTasksTestFixtures(t)
defer close()
err := db.UpdateJobsTaskStatuses(ctx, job, api.TaskStatusSoftFailed, "testing æctivity")
assert.NoError(t, err)
numSoftFailed, numTotal, err := db.CountTasksOfJobInStatus(ctx, job, api.TaskStatusSoftFailed)
assert.NoError(t, err)
assert.Equal(t, 3, numSoftFailed, "all tasks should have had their status changed")
assert.Equal(t, 3, numTotal)
task, err := db.FetchTask(ctx, authoredJob.Tasks[0].UUID)
assert.NoError(t, err)
assert.Equal(t, "testing æctivity", task.Activity)
// Empty status should be rejected.
err = db.UpdateJobsTaskStatuses(ctx, job, "", "testing empty status")
assert.Error(t, err)
numEmpty, _, err := db.CountTasksOfJobInStatus(ctx, job, "")
assert.NoError(t, err)
assert.Equal(t, 0, numEmpty, "tasks should not have their status changed")
numSoftFailed, _, err = db.CountTasksOfJobInStatus(ctx, job, api.TaskStatusSoftFailed)
assert.NoError(t, err)
assert.Equal(t, 3, numSoftFailed, "all tasks should still be soft-failed")
}
func TestUpdateJobsTaskStatusesConditional(t *testing.T) {
ctx, close, db, job, authoredJob := jobTasksTestFixtures(t)
defer close()
getTask := func(taskIndex int) *Task {
task, err := db.FetchTask(ctx, authoredJob.Tasks[taskIndex].UUID)
if err != nil {
t.Fatalf("Fetching task %d: %v", taskIndex, err)
}
return task
}
setTaskStatus := func(taskIndex int, taskStatus api.TaskStatus) {
task := getTask(taskIndex)
task.Status = taskStatus
if err := db.SaveTask(ctx, task); err != nil {
t.Fatalf("Setting task %d to status %s: %v", taskIndex, taskStatus, err)
}
}
setTaskStatus(0, api.TaskStatusFailed)
setTaskStatus(1, api.TaskStatusCompleted)
setTaskStatus(2, api.TaskStatusActive)
err := db.UpdateJobsTaskStatusesConditional(ctx, job,
[]api.TaskStatus{api.TaskStatusFailed, api.TaskStatusActive},
api.TaskStatusCancelRequested, "some activity")
assert.NoError(t, err)
// Task statuses should have updated for tasks 0 and 2.
assert.Equal(t, api.TaskStatusCancelRequested, getTask(0).Status)
assert.Equal(t, api.TaskStatusCompleted, getTask(1).Status)
assert.Equal(t, api.TaskStatusCancelRequested, getTask(2).Status)
err = db.UpdateJobsTaskStatusesConditional(ctx, job,
[]api.TaskStatus{api.TaskStatusFailed, api.TaskStatusActive},
"", "empty task status should be disallowed")
assert.Error(t, err)
// Task statuses should remain unchanged.
assert.Equal(t, api.TaskStatusCancelRequested, getTask(0).Status)
assert.Equal(t, api.TaskStatusCompleted, getTask(1).Status)
assert.Equal(t, api.TaskStatusCancelRequested, getTask(2).Status)
}
func TestTaskAssignToWorker(t *testing.T) {
ctx, close, db, _, authoredJob := jobTasksTestFixtures(t)
defer close()
task, err := db.FetchTask(ctx, authoredJob.Tasks[1].UUID)
assert.NoError(t, err)
w := createWorker(ctx, t, db)
assert.NoError(t, db.TaskAssignToWorker(ctx, task, w))
if task.Worker == nil {
t.Error("task.Worker == nil")
} else {
assert.Equal(t, w, task.Worker)
}
if task.WorkerID == nil {
t.Error("task.WorkerID == nil")
} else {
assert.Equal(t, w.ID, *task.WorkerID)
}
}
func TestFetchTasksOfWorkerInStatus(t *testing.T) {
ctx, close, db, _, authoredJob := jobTasksTestFixtures(t)
defer close()
task, err := db.FetchTask(ctx, authoredJob.Tasks[1].UUID)
assert.NoError(t, err)
w := createWorker(ctx, t, db)
assert.NoError(t, db.TaskAssignToWorker(ctx, task, w))
tasks, err := db.FetchTasksOfWorkerInStatus(ctx, w, task.Status)
assert.NoError(t, err)
assert.Len(t, tasks, 1, "worker should have one task in status %q", task.Status)
assert.Equal(t, task.ID, tasks[0].ID)
assert.Equal(t, task.UUID, tasks[0].UUID)
assert.NotEqual(t, api.TaskStatusCancelRequested, task.Status)
tasks, err = db.FetchTasksOfWorkerInStatus(ctx, w, api.TaskStatusCancelRequested)
assert.NoError(t, err)
assert.Empty(t, tasks, "worker should have no task in status %q", w)
}
func createTestAuthoredJobWithTasks() job_compilers.AuthoredJob {
task1 := job_compilers.AuthoredTask{
Name: "render-1-3",
Type: "blender",
UUID: "db1f5481-4ef5-4084-8571-8460c547ecaa",
Commands: []job_compilers.AuthoredCommand{
{
Name: "blender-render",
Parameters: job_compilers.AuthoredCommandParameters{
"exe": "{blender}",
"blendfile": "/path/to/file.blend",
"args": []interface{}{
"--render-output", "/path/to/output/######.png",
"--render-format", "PNG",
"--render-frame", "1-3",
},
}},
},
}
task2 := task1
task2.Name = "render-4-6"
task2.UUID = "d75ac779-151b-4bc2-b8f1-d153a9c4ac69"
task2.Commands[0].Parameters["frames"] = "4-6"
task3 := job_compilers.AuthoredTask{
Name: "preview-video",
Type: "ffmpeg",
UUID: "4915fb05-72f5-463e-a2f4-7efdb2584a1e",
Commands: []job_compilers.AuthoredCommand{
{
Name: "merge-frames-to-video",
Parameters: job_compilers.AuthoredCommandParameters{
"images": "/path/to/output/######.png",
"output": "/path/to/output/preview.mkv",
"ffmpegParams": "-c:v hevc -crf 31",
}},
},
Dependencies: []*job_compilers.AuthoredTask{&task1, &task2},
}
job := job_compilers.AuthoredJob{
JobID: "263fd47e-b9f8-4637-b726-fd7e47ecfdae",
Name: "Test job",
Status: api.JobStatusUnderConstruction,
Priority: 50,
Settings: job_compilers.JobSettings{
"frames": "1-6",
"chunk_size": 3.0, // The roundtrip to JSON in the database can make this a float.
},
Metadata: job_compilers.JobMetadata{
"author": "Sybren",
"project": "Sprite Fright",
},
Tasks: []job_compilers.AuthoredTask{task1, task2, task3},
}
return job
}
func jobTasksTestFixtures(t *testing.T) (context.Context, context.CancelFunc, *DB, *Job, job_compilers.AuthoredJob) {
ctx, cancel, db := persistenceTestFixtures(t, schedulerTestTimeout)
authoredJob := createTestAuthoredJobWithTasks()
err := db.StoreAuthoredJob(ctx, authoredJob)
if err != nil {
t.Fatalf("error storing authored job in DB: %v", err)
}
dbJob, err := db.FetchJob(ctx, authoredJob.JobID)
if err != nil {
t.Fatalf("error fetching job from DB: %v", err)
}
if dbJob == nil {
t.Fatalf("nil job obtained from DB but with no error!")
}
return ctx, cancel, db, dbJob, authoredJob
}
func createWorker(ctx context.Context, t *testing.T, db *DB) *Worker {
w := Worker{
UUID: "f0a123a9-ab05-4ce2-8577-94802cfe74a4",
Name: "дрон",
Address: "fe80::5054:ff:fede:2ad7",
LastActivity: "",
Platform: "linux",
Software: "3.0",
Status: api.WorkerStatusAwake,
SupportedTaskTypes: "blender,ffmpeg,file-management",
}
err := db.CreateWorker(ctx, &w)
if err != nil {
t.Fatalf("error creating worker: %v", err)
}
assert.NoError(t, err)
fetchedWorker, err := db.FetchWorker(ctx, w.UUID)
if err != nil {
t.Fatalf("error fetching worker: %v", err)
}
if fetchedWorker == nil {
t.Fatal("fetched worker is nil, but no error returned")
}
return fetchedWorker
}