Sybren A. Stüvel
02fac6a4df
Change the package base name of the Go code, from `git.blender.org/flamenco` to `projects.blender.org/studio/flamenco`. The old location, `git.blender.org`, has no longer been use since the [migration to Gitea][1]. The new package names now reflect the actual location where Flamenco is hosted. [1]: https://code.blender.org/2023/02/new-blender-development-infrastructure/
393 lines
13 KiB
Go
393 lines
13 KiB
Go
package api_impl
|
|
|
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
|
|
import (
|
|
"io/fs"
|
|
"net/http"
|
|
"os"
|
|
"os/user"
|
|
"path/filepath"
|
|
"runtime"
|
|
"testing"
|
|
|
|
"github.com/golang/mock/gomock"
|
|
"github.com/labstack/echo/v4"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
"projects.blender.org/studio/flamenco/internal/manager/config"
|
|
"projects.blender.org/studio/flamenco/pkg/api"
|
|
)
|
|
|
|
func TestGetVariables(t *testing.T) {
|
|
mockCtrl := gomock.NewController(t)
|
|
defer mockCtrl.Finish()
|
|
|
|
mf := newMockedFlamenco(mockCtrl)
|
|
|
|
// Test Linux Worker.
|
|
{
|
|
resolvedVarsLinuxWorker := make(map[string]config.ResolvedVariable)
|
|
resolvedVarsLinuxWorker["jobs"] = config.ResolvedVariable{
|
|
IsTwoWay: true,
|
|
Value: "Linux value",
|
|
}
|
|
resolvedVarsLinuxWorker["blender"] = config.ResolvedVariable{
|
|
IsTwoWay: false,
|
|
Value: "/usr/local/blender",
|
|
}
|
|
|
|
mf.config.EXPECT().
|
|
ResolveVariables(config.VariableAudienceWorkers, config.VariablePlatformLinux).
|
|
Return(resolvedVarsLinuxWorker)
|
|
|
|
echoCtx := mf.prepareMockedRequest(nil)
|
|
err := mf.flamenco.GetVariables(echoCtx, api.ManagerVariableAudienceWorkers, "linux")
|
|
assert.NoError(t, err)
|
|
assertResponseJSON(t, echoCtx, http.StatusOK, api.ManagerVariables{
|
|
AdditionalProperties: map[string]api.ManagerVariable{
|
|
"blender": {Value: "/usr/local/blender", IsTwoway: false},
|
|
"jobs": {Value: "Linux value", IsTwoway: true},
|
|
},
|
|
})
|
|
}
|
|
|
|
// Test unknown platform User.
|
|
{
|
|
resolvedVarsUnknownPlatform := make(map[string]config.ResolvedVariable)
|
|
mf.config.EXPECT().
|
|
ResolveVariables(config.VariableAudienceUsers, config.VariablePlatform("troll")).
|
|
Return(resolvedVarsUnknownPlatform)
|
|
|
|
echoCtx := mf.prepareMockedRequest(nil)
|
|
err := mf.flamenco.GetVariables(echoCtx, api.ManagerVariableAudienceUsers, "troll")
|
|
assert.NoError(t, err)
|
|
assertResponseJSON(t, echoCtx, http.StatusOK, api.ManagerVariables{})
|
|
}
|
|
}
|
|
|
|
func TestGetSharedStorage(t *testing.T) {
|
|
mockCtrl := gomock.NewController(t)
|
|
defer mockCtrl.Finish()
|
|
|
|
mf := newMockedFlamenco(mockCtrl)
|
|
|
|
conf := config.GetTestConfig(func(c *config.Conf) {
|
|
// Test with a Manager on Windows.
|
|
c.MockCurrentGOOSForTests("windows")
|
|
|
|
// Set up a two-way variable to do the mapping.
|
|
c.Variables["shared_storage_mapping"] = config.Variable{
|
|
IsTwoWay: true,
|
|
Values: []config.VariableValue{
|
|
{Value: "/user/shared/storage", Platform: config.VariablePlatformLinux, Audience: config.VariableAudienceUsers},
|
|
{Value: "/worker/shared/storage", Platform: config.VariablePlatformLinux, Audience: config.VariableAudienceWorkers},
|
|
{Value: `S:\storage`, Platform: config.VariablePlatformWindows, Audience: config.VariableAudienceAll},
|
|
},
|
|
}
|
|
})
|
|
mf.config.EXPECT().Get().Return(&conf).AnyTimes()
|
|
mf.config.EXPECT().EffectiveStoragePath().Return(`S:\storage\flamenco`).AnyTimes()
|
|
|
|
{ // Test user client on Linux.
|
|
// Defer to the actual ExpandVariables() implementation of the above config.
|
|
mf.config.EXPECT().
|
|
NewVariableExpander(config.VariableAudienceUsers, config.VariablePlatformLinux).
|
|
DoAndReturn(conf.NewVariableExpander)
|
|
mf.shaman.EXPECT().IsEnabled().Return(false)
|
|
|
|
echoCtx := mf.prepareMockedRequest(nil)
|
|
err := mf.flamenco.GetSharedStorage(echoCtx, api.ManagerVariableAudienceUsers, "linux")
|
|
require.NoError(t, err)
|
|
assertResponseJSON(t, echoCtx, http.StatusOK, api.SharedStorageLocation{
|
|
Location: "/user/shared/storage/flamenco",
|
|
Audience: api.ManagerVariableAudienceUsers,
|
|
Platform: "linux",
|
|
})
|
|
}
|
|
|
|
{ // Test worker client on Linux with Shaman enabled.
|
|
// Defer to the actual ExpandVariables() implementation of the above config.
|
|
mf.config.EXPECT().
|
|
NewVariableExpander(config.VariableAudienceWorkers, config.VariablePlatformLinux).
|
|
DoAndReturn(conf.NewVariableExpander)
|
|
mf.shaman.EXPECT().IsEnabled().Return(true)
|
|
|
|
echoCtx := mf.prepareMockedRequest(nil)
|
|
err := mf.flamenco.GetSharedStorage(echoCtx, api.ManagerVariableAudienceWorkers, "linux")
|
|
require.NoError(t, err)
|
|
assertResponseJSON(t, echoCtx, http.StatusOK, api.SharedStorageLocation{
|
|
Location: "/worker/shared/storage/flamenco",
|
|
Audience: api.ManagerVariableAudienceWorkers,
|
|
Platform: "linux",
|
|
ShamanEnabled: true,
|
|
})
|
|
}
|
|
|
|
{ // Test user client on Windows.
|
|
// Defer to the actual ExpandVariables() implementation of the above config.
|
|
mf.config.EXPECT().
|
|
NewVariableExpander(config.VariableAudienceUsers, config.VariablePlatformWindows).
|
|
DoAndReturn(conf.NewVariableExpander)
|
|
mf.shaman.EXPECT().IsEnabled().Return(false)
|
|
|
|
echoCtx := mf.prepareMockedRequest(nil)
|
|
err := mf.flamenco.GetSharedStorage(echoCtx, api.ManagerVariableAudienceUsers, "windows")
|
|
require.NoError(t, err)
|
|
assertResponseJSON(t, echoCtx, http.StatusOK, api.SharedStorageLocation{
|
|
Location: `S:\storage\flamenco`,
|
|
Audience: api.ManagerVariableAudienceUsers,
|
|
Platform: "windows",
|
|
})
|
|
}
|
|
|
|
}
|
|
|
|
// Test shared storage sitting on /mnt/flamenco, where that's mapped to F:\ for Windows.
|
|
func TestGetSharedStorageDriveLetterRoot(t *testing.T) {
|
|
mockCtrl := gomock.NewController(t)
|
|
defer mockCtrl.Finish()
|
|
|
|
mf := newMockedFlamenco(mockCtrl)
|
|
|
|
conf := config.GetTestConfig(func(c *config.Conf) {
|
|
// Test with a Manager on Linux.
|
|
c.MockCurrentGOOSForTests("linux")
|
|
|
|
// Set up a two-way variable to do the mapping.
|
|
c.Variables["shared_storage_mapping"] = config.Variable{
|
|
IsTwoWay: true,
|
|
Values: []config.VariableValue{
|
|
{Value: "/mnt/flamenco", Platform: config.VariablePlatformLinux, Audience: config.VariableAudienceAll},
|
|
{Value: `F:\`, Platform: config.VariablePlatformWindows, Audience: config.VariableAudienceAll},
|
|
},
|
|
}
|
|
})
|
|
mf.config.EXPECT().Get().Return(&conf).AnyTimes()
|
|
mf.config.EXPECT().EffectiveStoragePath().Return(`/mnt/flamenco`).AnyTimes()
|
|
|
|
{ // Test user client on Linux.
|
|
mf.config.EXPECT().
|
|
NewVariableExpander(config.VariableAudienceUsers, config.VariablePlatformLinux).
|
|
DoAndReturn(conf.NewVariableExpander)
|
|
mf.shaman.EXPECT().IsEnabled().Return(false)
|
|
|
|
echoCtx := mf.prepareMockedRequest(nil)
|
|
err := mf.flamenco.GetSharedStorage(echoCtx, api.ManagerVariableAudienceUsers, "linux")
|
|
require.NoError(t, err)
|
|
assertResponseJSON(t, echoCtx, http.StatusOK, api.SharedStorageLocation{
|
|
Location: "/mnt/flamenco",
|
|
Audience: api.ManagerVariableAudienceUsers,
|
|
Platform: "linux",
|
|
})
|
|
}
|
|
|
|
{ // Test user client on Windows.
|
|
mf.config.EXPECT().
|
|
NewVariableExpander(config.VariableAudienceUsers, config.VariablePlatformWindows).
|
|
DoAndReturn(conf.NewVariableExpander)
|
|
mf.shaman.EXPECT().IsEnabled().Return(false)
|
|
|
|
echoCtx := mf.prepareMockedRequest(nil)
|
|
err := mf.flamenco.GetSharedStorage(echoCtx, api.ManagerVariableAudienceUsers, "windows")
|
|
require.NoError(t, err)
|
|
assertResponseJSON(t, echoCtx, http.StatusOK, api.SharedStorageLocation{
|
|
Location: `F:\`,
|
|
Audience: api.ManagerVariableAudienceUsers,
|
|
Platform: "windows",
|
|
})
|
|
}
|
|
|
|
}
|
|
|
|
func TestCheckSharedStoragePath(t *testing.T) {
|
|
mf, finish := metaTestFixtures(t)
|
|
defer finish()
|
|
|
|
doTest := func(path string) echo.Context {
|
|
echoCtx := mf.prepareMockedJSONRequest(
|
|
api.PathCheckInput{Path: path})
|
|
err := mf.flamenco.CheckSharedStoragePath(echoCtx)
|
|
if !assert.NoError(t, err) {
|
|
t.FailNow()
|
|
}
|
|
return echoCtx
|
|
}
|
|
|
|
// Test empty path.
|
|
echoCtx := doTest("")
|
|
assertResponseJSON(t, echoCtx, http.StatusOK, api.PathCheckResult{
|
|
Path: "",
|
|
IsUsable: false,
|
|
Cause: "An empty path is not suitable as shared storage",
|
|
})
|
|
|
|
// Test usable path (well, at least readable & writable; it may not be shared via Samba/NFS).
|
|
echoCtx = doTest(mf.tempdir)
|
|
assertResponseJSON(t, echoCtx, http.StatusOK, api.PathCheckResult{
|
|
Path: mf.tempdir,
|
|
IsUsable: true,
|
|
Cause: "Directory checked successfully",
|
|
})
|
|
files, err := filepath.Glob(filepath.Join(mf.tempdir, "*"))
|
|
if assert.NoError(t, err) {
|
|
assert.Empty(t, files, "After a query, there should not be any leftovers")
|
|
}
|
|
|
|
// Test inaccessible path.
|
|
// For some reason, this doesn't work on Windows, and creating a file in
|
|
// that directory is still allowed. The Explorer's properties panel of the
|
|
// directory also shows "Read Only (only applies to files)", so at least
|
|
// that seems consistent.
|
|
// FIXME: find another way to test with unwritable directories on Windows.
|
|
if runtime.GOOS != "windows" {
|
|
|
|
// Root can always create directories, so this test would fail with a
|
|
// confusing message. Instead it's better to refuse running as root at all.
|
|
currentUser, err := user.Current()
|
|
require.NoError(t, err)
|
|
require.NotEqual(t, "0", currentUser.Uid,
|
|
"this test requires running as normal user, not %s (%s)", currentUser.Username, currentUser.Uid)
|
|
require.NotEqual(t, "root", currentUser.Username,
|
|
"this test requires running as normal user, not %s (%s)", currentUser.Username, currentUser.Uid)
|
|
|
|
parentPath := filepath.Join(mf.tempdir, "deep")
|
|
testPath := filepath.Join(parentPath, "nesting")
|
|
if err := os.Mkdir(parentPath, fs.ModePerm); !assert.NoError(t, err) {
|
|
t.FailNow()
|
|
}
|
|
if err := os.Mkdir(testPath, fs.FileMode(0)); !assert.NoError(t, err) {
|
|
t.FailNow()
|
|
}
|
|
echoCtx := doTest(testPath)
|
|
result := api.PathCheckResult{}
|
|
getResponseJSON(t, echoCtx, http.StatusOK, &result)
|
|
assert.Equal(t, testPath, result.Path)
|
|
assert.False(t, result.IsUsable)
|
|
assert.Contains(t, result.Cause, "Unable to create a file")
|
|
}
|
|
}
|
|
|
|
func TestSaveSetupAssistantConfig(t *testing.T) {
|
|
mf, finish := metaTestFixtures(t)
|
|
defer finish()
|
|
|
|
defaultBlenderArgsVar := config.Variable{
|
|
Values: config.VariableValues{
|
|
{Platform: config.VariablePlatformAll, Value: config.DefaultBlenderArguments},
|
|
},
|
|
}
|
|
|
|
doTest := func(body api.SetupAssistantConfig) config.Conf {
|
|
// Always start the test with a clean configuration.
|
|
originalConfig := config.DefaultConfig(func(c *config.Conf) {
|
|
c.SharedStoragePath = ""
|
|
})
|
|
var savedConfig config.Conf
|
|
|
|
// Mock the loading & saving of the config.
|
|
mf.config.EXPECT().Get().Return(&originalConfig)
|
|
mf.config.EXPECT().Save().Do(func() error {
|
|
savedConfig = originalConfig
|
|
return nil
|
|
})
|
|
|
|
// Call the API.
|
|
echoCtx := mf.prepareMockedJSONRequest(body)
|
|
err := mf.flamenco.SaveSetupAssistantConfig(echoCtx)
|
|
if !assert.NoError(t, err) {
|
|
t.FailNow()
|
|
}
|
|
|
|
assertResponseNoContent(t, echoCtx)
|
|
return savedConfig
|
|
}
|
|
|
|
// Test situation where file association with .blend files resulted in a blender executable.
|
|
{
|
|
savedConfig := doTest(api.SetupAssistantConfig{
|
|
StorageLocation: mf.tempdir,
|
|
BlenderExecutable: api.BlenderPathCheckResult{
|
|
IsUsable: true,
|
|
Input: "",
|
|
Path: "/path/to/blender",
|
|
Source: api.BlenderPathSourceFileAssociation,
|
|
},
|
|
})
|
|
assert.Equal(t, mf.tempdir, savedConfig.SharedStoragePath)
|
|
expectBlenderVar := config.Variable{
|
|
Values: config.VariableValues{
|
|
{Platform: "linux", Value: "blender"},
|
|
{Platform: "windows", Value: "blender"},
|
|
{Platform: "darwin", Value: "blender"},
|
|
},
|
|
}
|
|
assert.Equal(t, expectBlenderVar, savedConfig.Variables["blender"])
|
|
assert.Equal(t, defaultBlenderArgsVar, savedConfig.Variables["blenderArgs"])
|
|
}
|
|
|
|
// Test situation where the given command could be found on $PATH.
|
|
{
|
|
savedConfig := doTest(api.SetupAssistantConfig{
|
|
StorageLocation: mf.tempdir,
|
|
BlenderExecutable: api.BlenderPathCheckResult{
|
|
IsUsable: true,
|
|
Input: "kitty",
|
|
Path: "/path/to/kitty",
|
|
Source: api.BlenderPathSourcePathEnvvar,
|
|
},
|
|
})
|
|
assert.Equal(t, mf.tempdir, savedConfig.SharedStoragePath)
|
|
expectBlenderVar := config.Variable{
|
|
Values: config.VariableValues{
|
|
{Platform: "linux", Value: "kitty"},
|
|
{Platform: "windows", Value: "kitty"},
|
|
{Platform: "darwin", Value: "kitty"},
|
|
},
|
|
}
|
|
assert.Equal(t, expectBlenderVar, savedConfig.Variables["blender"])
|
|
assert.Equal(t, defaultBlenderArgsVar, savedConfig.Variables["blenderArgs"])
|
|
}
|
|
|
|
// Test a custom command given with the full path.
|
|
{
|
|
savedConfig := doTest(api.SetupAssistantConfig{
|
|
StorageLocation: mf.tempdir,
|
|
BlenderExecutable: api.BlenderPathCheckResult{
|
|
IsUsable: true,
|
|
Input: "/bin/cat",
|
|
Path: "/bin/cat",
|
|
Source: api.BlenderPathSourceInputPath,
|
|
},
|
|
})
|
|
assert.Equal(t, mf.tempdir, savedConfig.SharedStoragePath)
|
|
expectBlenderVar := config.Variable{
|
|
Values: config.VariableValues{
|
|
{Platform: "linux", Value: "/bin/cat"},
|
|
{Platform: "windows", Value: "/bin/cat"},
|
|
{Platform: "darwin", Value: "/bin/cat"},
|
|
},
|
|
}
|
|
assert.Equal(t, expectBlenderVar, savedConfig.Variables["blender"])
|
|
assert.Equal(t, defaultBlenderArgsVar, savedConfig.Variables["blenderArgs"])
|
|
}
|
|
}
|
|
|
|
func metaTestFixtures(t *testing.T) (mockedFlamenco, func()) {
|
|
mockCtrl := gomock.NewController(t)
|
|
mf := newMockedFlamenco(mockCtrl)
|
|
|
|
tempdir, err := os.MkdirTemp("", "test-temp-dir")
|
|
if !assert.NoError(t, err) {
|
|
t.FailNow()
|
|
}
|
|
mf.tempdir = tempdir
|
|
|
|
finish := func() {
|
|
mockCtrl.Finish()
|
|
os.RemoveAll(tempdir)
|
|
}
|
|
|
|
return mf, finish
|
|
}
|