Fix #104237: fix issue with drive-only paths on Windows
Fix an issue where a shared storage path on Linux, that maps via two-way variables to a drive root on Windows, caused problems with the path translation system. Windows paths that consist only of a drive letter (`F:`) cannot just be concatenated to a relative path, as that will result in `F:path\to\file`, which is still a relative path of sorts. This is now handled correctly, and should result in `F:\path\to\file`. This fixes #104237.
This commit is contained in:
parent
7dc3def1d5
commit
dae5b1a571
@ -143,6 +143,63 @@ func TestGetSharedStorage(t *testing.T) {
|
||||
|
||||
}
|
||||
|
||||
// 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()
|
||||
|
@ -126,7 +126,7 @@ func (ve *VariableExpander) Expand(valueToExpand string) string {
|
||||
if !isValueMatch(expanded, managerValue) {
|
||||
continue
|
||||
}
|
||||
expanded = targetValue + expanded[len(managerValue):]
|
||||
expanded = ve.join(targetValue, expanded[len(managerValue):], ve.targetPlatform)
|
||||
|
||||
// Since two-way variables are meant for path replacement, we know this
|
||||
// should be a path.
|
||||
@ -136,5 +136,20 @@ func (ve *VariableExpander) Expand(valueToExpand string) string {
|
||||
if isPathValue {
|
||||
expanded = crosspath.ToPlatform(expanded, string(ve.targetPlatform))
|
||||
}
|
||||
|
||||
return expanded
|
||||
}
|
||||
|
||||
func (ve *VariableExpander) join(valueFromVariable, suffix string, platform VariablePlatform) string {
|
||||
result := valueFromVariable + suffix
|
||||
|
||||
if platform == VariablePlatformWindows {
|
||||
// 'result' may now be of the form `F:some\path\to\file`, where `F:` comes
|
||||
// from `valueFromVariable` and the rest is the suffix. This is not an
|
||||
// absolute path, and needs a separator between the drive letter and the
|
||||
// rest of the path.
|
||||
return crosspath.EnsureDriveAbsolute(result)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
@ -131,6 +131,10 @@ func ToPlatform(path, platform string) string {
|
||||
pathsep := pathSepForPlatform(platform)
|
||||
translated := strings.Join(components, pathsep)
|
||||
|
||||
if platform == "windows" {
|
||||
return EnsureDriveAbsolute(translated)
|
||||
}
|
||||
|
||||
return translated
|
||||
}
|
||||
|
||||
@ -162,3 +166,28 @@ func TrimTrailingSep(path string) string {
|
||||
}
|
||||
return trimmed
|
||||
}
|
||||
|
||||
// EnsureDriveAbsolute ensures that a Windows path that starts with a drive
|
||||
// letter also has an absolute path on that drive. For example, turns
|
||||
// `F:path\to\file` into `F:\path\to\file`.
|
||||
func EnsureDriveAbsolute(windowsPath string) string {
|
||||
runes := []rune(windowsPath)
|
||||
numRunes := len(runes)
|
||||
if numRunes < 2 {
|
||||
return windowsPath
|
||||
}
|
||||
|
||||
if !validDriveLetter(runes[0]) || runes[1] != ':' {
|
||||
return windowsPath
|
||||
}
|
||||
|
||||
pathSep := pathSepForPlatform("windows")
|
||||
if numRunes == 2 {
|
||||
return windowsPath + pathSep
|
||||
}
|
||||
if string(runes[2]) == pathSep {
|
||||
return windowsPath // Already F:\blabla
|
||||
}
|
||||
|
||||
return string(runes[:2]) + pathSep + string(runes[2:])
|
||||
}
|
||||
|
@ -185,6 +185,7 @@ func TestIsRoot(t *testing.T) {
|
||||
{"just letters", "just letters", false},
|
||||
{"subdir of root", "/subdir", false},
|
||||
{"subdir of drive", "C:\\subdir", false},
|
||||
{"relative subdir of drive", "C:subdir", false},
|
||||
|
||||
{"indirectly root", "/subdir/..", false},
|
||||
{"UNC notation", `\\NAS\Share\`, false},
|
||||
@ -221,7 +222,14 @@ func TestToPlatform(t *testing.T) {
|
||||
{"mixed-lnx", args{`F:/mixed/path\to\file.blend`, "linux"}, `F:/mixed/path/to/file.blend`},
|
||||
{"absolute-win", args{`F:/absolute/path`, "windows"}, `F:\absolute\path`},
|
||||
{"absolute-lnx", args{`/absolute/path`, "linux"}, `/absolute/path`},
|
||||
{"drive-relative-win", args{`/absolute/path`, "windows"}, `\absolute\path`},
|
||||
{"relative-win", args{`/absolute/path`, "windows"}, `\absolute\path`},
|
||||
|
||||
// Trailing path separators should not be removed if it's only a drive
|
||||
// letter, as concatenation rules are tricky there. `F:path` is not the same
|
||||
// as `F:\path`.
|
||||
{"drive-root-win", args{`F:\`, "windows"}, `F:\`},
|
||||
{"trailing-win", args{`F:\directory\`, "linux"}, `F:/directory`},
|
||||
{"trailing-lnx", args{`/dir/path/`, "windows"}, `\dir\path`},
|
||||
|
||||
// UNC notation should survive, even when it no longer makes sense (like on Linux).
|
||||
{"unc-win", args{`\\NAS\share\path`, "windows"}, `\\NAS\share\path`},
|
||||
@ -267,3 +275,34 @@ func TestTrimTrailingSep(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestEnsureDriveAbsolute(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
inputPath string
|
||||
want string
|
||||
}{
|
||||
// Windows paths expected to change:
|
||||
{"windows-drive-relative", `F:path\to\file`, `F:\path\to\file`},
|
||||
{"windows-drive-relative-mixed", "F:path/to/file", `F:\path/to/file`},
|
||||
{"windows-drive-only", "F:", `F:\`},
|
||||
|
||||
// No-op paths:
|
||||
{"empty", "", ""},
|
||||
{"linux-root", "/", "/"},
|
||||
{"linux-path", "/some/path", "/some/path"},
|
||||
{"linux-unicode-path", "/söme/path", "/söme/path"},
|
||||
{"one-letter", "F", "F"},
|
||||
{"windows-drive-invalid", `©:path\to\thing`, `©:path\to\thing`},
|
||||
{"windows-unc", `\\NAS\Flamenco\path`, `\\NAS\Flamenco\path`},
|
||||
{"unicode-one-letter", "€", "€"},
|
||||
{"windows-drive-absolute", `F:\some\path`, `F:\some\path`},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := EnsureDriveAbsolute(tt.inputPath); got != tt.want {
|
||||
t.Errorf("EnsureDriveAbsolute() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user