Files
buildkit/util/system/path_test.go
Anthony Nandaa a02c26d649 fix: util/path: CheckSystemDriveAndRemoveDriveLetter to preserve /
The call to CheckSystemDriveAndRemoveDriveLetter() does not preserve
the trailing `/` or `\\`. This happens because `filepath.Clean()`
strips away any trailing slashes. For example `/sample/` will be
`\\sample` on Windows and `/sample` on Linux.
This function was mainly written for Windows scenarios, which
have System Drive Letters like C:/, etc.

This was causing cases like `COPY testfile /testdir/` to
be intepreted as `COPY testfile /testdir`, and if `testdir` is
not explictly created before the call, it ends up being treated
as a destination file other than a directory.

Fix this by checking that if we have a trailing `/` or `\\`, we
preserve it after the call to `filepath.Clean()`.

Also refactor `CheckSystemDriveAndRemoveDriveLetter` function to take
an extra keepSlash bool param, to be consistent with what is passed
to `NormalizePath`.

The rest of the calls to this function has left keepSlash = false
as the default behavior.

Fixes #5249

PS. Also fixed for cross-building from Linux scenario, taking care
for paths like `\\sample\\` that are not changed when run
through `filepath.Clean()`.

Signed-off-by: Anthony Nandaa <profnandaa@gmail.com>
2025-02-20 22:07:01 +03:00

468 lines
13 KiB
Go

package system
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestNormalizeWorkdir(t *testing.T) {
testCases := []struct {
name string
currentWorkdir string
newWorkDir string
desiredResult string
err string
}{
{
name: "no current wd with relative wd",
currentWorkdir: "",
newWorkDir: "test",
desiredResult: `/test`,
err: "",
},
{
name: "no current wd with absolute wd",
currentWorkdir: "",
newWorkDir: `/strippedWd`,
desiredResult: `/strippedWd`,
err: "",
},
{
name: "current wd is absolute, new wd is relative",
currentWorkdir: "/test",
newWorkDir: `subdir`,
desiredResult: `/test/subdir`,
err: "",
},
{
name: "current wd is absolute, new wd is relative one folder up",
currentWorkdir: "/test",
newWorkDir: `../subdir`,
desiredResult: `/subdir`,
err: "",
},
{
name: "current wd is absolute, new wd is absolute",
currentWorkdir: "/test",
newWorkDir: `/current`,
desiredResult: `/current`,
err: "",
},
{
name: "current wd is relative, new wd is relative",
currentWorkdir: "test",
newWorkDir: `current`,
desiredResult: `/test/current`,
err: "",
},
{
name: "current wd is relative, no new wd",
currentWorkdir: "test",
newWorkDir: "",
desiredResult: `/test`,
err: "",
},
{
name: "current wd is absolute, no new wd",
currentWorkdir: "/test",
newWorkDir: "",
desiredResult: `/test`,
err: "",
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
result, err := NormalizeWorkdir(tc.currentWorkdir, tc.newWorkDir, "linux")
if tc.err != "" {
require.ErrorContains(t, err, tc.err)
} else {
require.NoError(t, err)
}
require.Equal(t, tc.desiredResult, result)
})
}
}
// TestCheckSystemDriveAndRemoveDriveLetter tests CheckSystemDriveAndRemoveDriveLetter
func TestCheckSystemDriveAndRemoveDriveLetter(t *testing.T) {
keepSlash := false
// Fails if not C drive.
_, err := CheckSystemDriveAndRemoveDriveLetter(`d:\`, "windows", keepSlash)
if err == nil || err.Error() != "The specified path is not on the system drive (C:)" {
t.Fatalf("Expected error for d:")
}
var path string
// Single character is unchanged
if path, err = CheckSystemDriveAndRemoveDriveLetter("z", "windows", keepSlash); err != nil {
t.Fatalf("Single character should pass")
}
if path != "z" {
t.Fatalf("Single character should be unchanged")
}
// Two characters without colon is unchanged
if path, err = CheckSystemDriveAndRemoveDriveLetter("AB", "windows", keepSlash); err != nil {
t.Fatalf("2 characters without colon should pass")
}
if path != "AB" {
t.Fatalf("2 characters without colon should be unchanged")
}
// Abs path without drive letter
if path, err = CheckSystemDriveAndRemoveDriveLetter(`\l`, "windows", keepSlash); err != nil {
t.Fatalf("abs path no drive letter should pass")
}
if path != `/l` {
t.Fatalf("abs path without drive letter should be unchanged")
}
// Abs path without drive letter, linux style
if path, err = CheckSystemDriveAndRemoveDriveLetter(`/l`, "windows", keepSlash); err != nil {
t.Fatalf("abs path no drive letter linux style should pass")
}
if path != `/l` {
t.Fatalf("abs path without drive letter linux failed %s", path)
}
// Drive-colon should be stripped
if path, err = CheckSystemDriveAndRemoveDriveLetter(`c:\`, "windows", keepSlash); err != nil {
t.Fatalf("An absolute path should pass")
}
if path != `/` {
t.Fatalf(`An absolute path should have been shortened to \ %s`, path)
}
// Verify with a linux-style path
if path, err = CheckSystemDriveAndRemoveDriveLetter(`c:/`, "windows", keepSlash); err != nil {
t.Fatalf("An absolute path should pass")
}
if path != `/` {
t.Fatalf(`A linux style absolute path should have been shortened to \ %s`, path)
}
// Failure on c:
if path, err = CheckSystemDriveAndRemoveDriveLetter(`c:`, "windows", keepSlash); err == nil {
t.Fatalf("c: should fail")
}
if err.Error() != `No relative path specified in "c:"` {
t.Fatalf(path, err)
}
// Failure on d:
if path, err = CheckSystemDriveAndRemoveDriveLetter(`d:`, "windows", keepSlash); err == nil {
t.Fatalf("c: should fail")
}
if err.Error() != `No relative path specified in "d:"` {
t.Fatalf(path, err)
}
// UNC path should fail.
if _, err = CheckSystemDriveAndRemoveDriveLetter(`\\.\C$\test`, "windows", keepSlash); err == nil {
t.Fatalf("UNC path should fail")
}
// also testing for keepSlash = true
keepSlash = true
origPath := "\\a\\b\\..\\c\\"
if path, err = CheckSystemDriveAndRemoveDriveLetter(origPath, "windows", keepSlash); err != nil {
t.Fatalf("windows relative paths should be cleaned and should pass")
}
// When input OS is Windows, the path should be properly cleaned
if path != "/a/c/" {
t.Fatalf("Path was not cleaned successfully")
}
if path, err = CheckSystemDriveAndRemoveDriveLetter(origPath, "windows", false); err != nil {
t.Fatalf("windows relative paths should be cleaned and should pass [keepSlash = false]")
}
// When input OS is Windows, the path should be properly cleaned
if path != "/a/c" {
t.Fatalf("Path was not cleaned successfully [keepSlash = false]")
}
// windows-style relative paths on linux
if path, err = CheckSystemDriveAndRemoveDriveLetter(origPath, "linux", keepSlash); err != nil {
t.Fatalf("windows style relative paths should be considered a valid path element in linux and should pass")
}
// When input OS is Linux, this is a valid path element name.
if path != "\\a\\b\\..\\c\\" {
t.Fatalf("Path was not cleaned successfully")
}
if path, err = CheckSystemDriveAndRemoveDriveLetter(origPath, "linux", false); err != nil {
t.Fatalf("windows style relative paths should be considered a valid path element in linux and should pass")
}
// When input OS is Linux, this is a valid path element name.
if path != "\\a\\b\\..\\c\\" {
t.Fatalf("Path was not cleaned successfully [keepSlash = false]")
}
}
// TestNormalizeWorkdirWindows tests NormalizeWorkdir
func TestNormalizeWorkdirWindows(t *testing.T) {
testCases := []struct {
name string
currentWorkdir string
newWorkDir string
desiredResult string
err string
}{
{
name: "no current wd with relative wd",
currentWorkdir: "",
newWorkDir: "test",
desiredResult: `\test`,
err: "",
},
{
name: "no current wd with stripped absolute wd",
currentWorkdir: "",
newWorkDir: `\strippedWd`,
desiredResult: `\strippedWd`,
err: "",
},
{
name: "no current wd with absolute wd",
currentWorkdir: "",
newWorkDir: `C:\withDriveLetter`,
desiredResult: `\withDriveLetter`,
err: "",
},
{
name: "no current wd with absolute wd with forward slash",
currentWorkdir: "",
newWorkDir: `C:/withDriveLetterAndForwardSlash`,
desiredResult: `\withDriveLetterAndForwardSlash`,
err: "",
},
{
name: "no current wd with absolute wd with mixed slashes",
currentWorkdir: "",
newWorkDir: `C:/first\second/third`,
desiredResult: `\first\second\third`,
err: "",
},
{
name: "current wd is relative no wd",
currentWorkdir: "testing",
newWorkDir: "",
desiredResult: `\testing`,
err: "",
},
{
name: "current wd is relative with relative wd",
currentWorkdir: "testing",
newWorkDir: "newTesting",
desiredResult: `\testing\newTesting`,
err: "",
},
{
name: "current wd is relative withMixedSlashes and relative new wd",
currentWorkdir: `testing/with\mixed/slashes`,
newWorkDir: "newTesting",
desiredResult: `\testing\with\mixed\slashes\newTesting`,
err: "",
},
{
name: "current wd is absolute withMixedSlashes and relative new wd",
currentWorkdir: `C:\testing/with\mixed/slashes`,
newWorkDir: "newTesting",
desiredResult: `\testing\with\mixed\slashes\newTesting`,
err: "",
},
{
name: "current wd is absolute withMixedSlashes and no new wd",
currentWorkdir: `C:\testing/with\mixed/slashes`,
newWorkDir: "",
desiredResult: `\testing\with\mixed\slashes`,
err: "",
},
{
name: "current wd is absolute path to non C drive",
currentWorkdir: `D:\IWillErrorOut`,
newWorkDir: "doesNotMatter",
desiredResult: "",
err: "The specified path is not on the system drive (C:)",
},
{
name: "new WD is an absolute path to illegal drive",
currentWorkdir: `C:\testing`,
newWorkDir: `D:\testing`,
desiredResult: "",
err: "The specified path is not on the system drive (C:)",
},
{
name: "current WD has no relative path to drive",
currentWorkdir: `C:`,
newWorkDir: `testing`,
desiredResult: "",
err: `No relative path specified in "C:"`,
},
{
name: "new WD has no relative path to drive",
currentWorkdir: `/test`,
newWorkDir: `C:`,
desiredResult: "",
err: `No relative path specified in "C:"`,
},
{
name: "new WD has no slash after drive letter",
currentWorkdir: `/test`,
newWorkDir: `C:testing`,
desiredResult: `\test\testing`,
err: "",
},
{
name: "current WD is an unlikely absolute path",
currentWorkdir: `C:\..\test\..\`,
newWorkDir: ``,
desiredResult: `\`,
err: "",
},
{
name: "linux style paths should work",
currentWorkdir: "/test",
newWorkDir: "relative/path",
desiredResult: `\test\relative\path`,
err: "",
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
result, err := NormalizeWorkdir(tc.currentWorkdir, tc.newWorkDir, "windows")
if tc.err != "" {
require.ErrorContains(t, err, tc.err)
} else {
require.NoError(t, err)
}
require.Equal(t, tc.desiredResult, result)
})
}
}
func TestNormalizeWorkdirUnix(t *testing.T) {
testCases := []struct {
name string
currentWorkdir string
newWorkDir string
desiredResult string
err string
}{
{
name: "no current wd with relative wd",
currentWorkdir: "",
newWorkDir: "test",
desiredResult: `/test`,
err: "",
},
{
name: "no current wd with absolute wd",
currentWorkdir: "",
newWorkDir: `/strippedWd`,
desiredResult: `/strippedWd`,
err: "",
},
{
name: "current wd is relative no wd",
currentWorkdir: "testing",
newWorkDir: "",
desiredResult: `/testing`,
err: "",
},
{
name: "current wd is relative with relative wd",
currentWorkdir: "testing",
newWorkDir: "newTesting",
desiredResult: `/testing/newTesting`,
err: "",
},
{
name: "absolute current wd with relative new wd",
currentWorkdir: "/test",
newWorkDir: "relative/path",
desiredResult: `/test/relative/path`,
err: "",
},
{
name: "absolute current wd with no new wd",
currentWorkdir: "/test",
newWorkDir: "",
desiredResult: `/test`,
err: "",
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
result, err := NormalizeWorkdir(tc.currentWorkdir, tc.newWorkDir, "linux")
if tc.err != "" {
require.ErrorContains(t, err, tc.err)
} else {
require.NoError(t, err)
}
require.Equal(t, tc.desiredResult, result)
})
}
}
func TestIsAbs(t *testing.T) {
testCases := []struct {
name string
path string
desiredResult bool
}{
{
name: "path with drive letter is absolute",
path: `C:\test`,
desiredResult: true,
},
{
name: "path with drive letter but no slash is relative",
path: `C:test`,
desiredResult: false,
},
{
name: "path with drive letter and linux style slashes is absolute",
path: `C:/test`,
desiredResult: true,
},
{
name: "path without drive letter but with leading slash is absolute",
path: `\test`,
desiredResult: true,
},
{
name: "path without drive letter but with leading forward slash is absolute",
path: `/test`,
desiredResult: true,
},
{
name: "simple relative path",
path: `test`,
desiredResult: false,
},
{
name: "deeper relative path",
path: `test/nested`,
desiredResult: false,
},
{
name: "one level up relative path",
path: `../test`,
desiredResult: false,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
result := IsAbs(tc.path, "windows")
require.Equal(t, tc.desiredResult, result)
})
}
}