Sybren A. Stüvel e5a20425c4 Separate variables for Blender executable and its arguments.
Split "executable" from "its arguments" in blender & ffmpeg commands.

Use `{blenderArgs}` variable to hold the default Blender arguments,
instead of having both the executable and its arguments in `{blender}`.

The reason for this is to support backslashes in the Blender executable
path. These were interpreted as escape characters by the shell lexer.
The shell lexer based splitting is now only performed on the default
arguments, with the result that `C:\Program Files\Blender
Foundation\3.3\blender.exe` is now a valid value for `{blender}`.

This does mean that this is backward incompatible change, and that it
requires setting up Flamenco Manager again, and that older jobs will not
be able to be rerun.

It is recommended to remove `flamenco-manager.yaml`, restart Flamenco
Manager, and reconfigure via the setup assistant.
2022-08-30 14:58:16 +02:00

247 lines
7.2 KiB
Go

package api_impl
// SPDX-License-Identifier: GPL-3.0-or-later
import (
"io/fs"
"net/http"
"os"
"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"
)
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 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" {
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
}