
If the mock tests are run by root user then this specific test of inaccessible path fails because root can write files to anywhere on the filesystem. It is not clear that Flamenco Manager test TestCheckSharedStoragePath is checking inaccessible file locations when it fails and that it should be run by an unprivileged user. Fix is to fail the permission test if the tests are run as a root user.
342 lines
11 KiB
Go
342 lines
11 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"
|
|
|
|
"git.blender.org/flamenco/internal/manager/config"
|
|
"git.blender.org/flamenco/pkg/api"
|
|
"github.com/golang/mock/gomock"
|
|
"github.com/labstack/echo/v4"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
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.
|
|
mf.config.EXPECT().
|
|
ExpandVariables(gomock.Any(), gomock.Any(), config.VariableAudienceUsers, config.VariablePlatformLinux).
|
|
Do(func(inputChannel <-chan string, outputChannel chan<- string, audience config.VariableAudience, platform config.VariablePlatform) {
|
|
// Defer to the actual ExpandVariables() implementation of the above config.
|
|
conf.ExpandVariables(inputChannel, outputChannel, audience, platform)
|
|
})
|
|
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.
|
|
mf.config.EXPECT().
|
|
ExpandVariables(gomock.Any(), gomock.Any(), config.VariableAudienceWorkers, config.VariablePlatformLinux).
|
|
Do(func(inputChannel <-chan string, outputChannel chan<- string, audience config.VariableAudience, platform config.VariablePlatform) {
|
|
// Defer to the actual ExpandVariables() implementation of the above config.
|
|
conf.ExpandVariables(inputChannel, outputChannel, audience, platform)
|
|
})
|
|
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.
|
|
mf.config.EXPECT().
|
|
ExpandVariables(gomock.Any(), gomock.Any(), config.VariableAudienceUsers, config.VariablePlatformWindows).
|
|
Do(func(inputChannel <-chan string, outputChannel chan<- string, audience config.VariableAudience, platform config.VariablePlatform) {
|
|
// Defer to the actual ExpandVariables() implementation of the above config.
|
|
conf.ExpandVariables(inputChannel, outputChannel, audience, platform)
|
|
})
|
|
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",
|
|
})
|
|
}
|
|
|
|
}
|
|
|
|
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
|
|
}
|