diff --git a/pkg/kubelet/cri/remote/fake/fake_runtime.go b/pkg/kubelet/cri/remote/fake/fake_runtime.go index 60957d7a3d5..54b31fe2a8f 100644 --- a/pkg/kubelet/cri/remote/fake/fake_runtime.go +++ b/pkg/kubelet/cri/remote/fake/fake_runtime.go @@ -24,7 +24,7 @@ import ( "google.golang.org/grpc" kubeapi "k8s.io/cri-api/pkg/apis/runtime/v1" apitest "k8s.io/cri-api/pkg/apis/testing" - "k8s.io/kubernetes/pkg/kubelet/cri/remote/util" + "k8s.io/kubernetes/pkg/kubelet/util" utilexec "k8s.io/utils/exec" ) diff --git a/pkg/kubelet/cri/remote/remote_image.go b/pkg/kubelet/cri/remote/remote_image.go index 6a8912e8bcd..e1c88f8014b 100644 --- a/pkg/kubelet/cri/remote/remote_image.go +++ b/pkg/kubelet/cri/remote/remote_image.go @@ -30,7 +30,7 @@ import ( internalapi "k8s.io/cri-api/pkg/apis" runtimeapi "k8s.io/cri-api/pkg/apis/runtime/v1" runtimeapiV1alpha2 "k8s.io/cri-api/pkg/apis/runtime/v1alpha2" - "k8s.io/kubernetes/pkg/kubelet/cri/remote/util" + "k8s.io/kubernetes/pkg/kubelet/util" ) // remoteImageService is a gRPC implementation of internalapi.ImageManagerService. diff --git a/pkg/kubelet/cri/remote/remote_runtime.go b/pkg/kubelet/cri/remote/remote_runtime.go index cd7b781e8c7..7dc8849fc46 100644 --- a/pkg/kubelet/cri/remote/remote_runtime.go +++ b/pkg/kubelet/cri/remote/remote_runtime.go @@ -32,7 +32,7 @@ import ( internalapi "k8s.io/cri-api/pkg/apis" runtimeapi "k8s.io/cri-api/pkg/apis/runtime/v1" runtimeapiV1alpha2 "k8s.io/cri-api/pkg/apis/runtime/v1alpha2" - "k8s.io/kubernetes/pkg/kubelet/cri/remote/util" + "k8s.io/kubernetes/pkg/kubelet/util" "k8s.io/kubernetes/pkg/probe/exec" utilexec "k8s.io/utils/exec" ) diff --git a/pkg/kubelet/cri/remote/remote_runtime_test.go b/pkg/kubelet/cri/remote/remote_runtime_test.go index ea247342a56..7cdb00ba780 100644 --- a/pkg/kubelet/cri/remote/remote_runtime_test.go +++ b/pkg/kubelet/cri/remote/remote_runtime_test.go @@ -26,7 +26,7 @@ import ( internalapi "k8s.io/cri-api/pkg/apis" apitest "k8s.io/cri-api/pkg/apis/testing" fakeremote "k8s.io/kubernetes/pkg/kubelet/cri/remote/fake" - "k8s.io/kubernetes/pkg/kubelet/cri/remote/util" + "k8s.io/kubernetes/pkg/kubelet/util" ) const ( diff --git a/pkg/kubelet/cri/remote/util/util_unix.go b/pkg/kubelet/cri/remote/util/util_unix.go deleted file mode 100644 index 28f1f2c2597..00000000000 --- a/pkg/kubelet/cri/remote/util/util_unix.go +++ /dev/null @@ -1,146 +0,0 @@ -//go:build freebsd || linux || darwin -// +build freebsd linux darwin - -/* -Copyright 2017 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package util - -import ( - "context" - "fmt" - "io/ioutil" - "net" - "net/url" - "os" - "path/filepath" - - "golang.org/x/sys/unix" - "k8s.io/klog/v2" -) - -const ( - // unixProtocol is the network protocol of unix socket. - unixProtocol = "unix" -) - -// CreateListener creates a listener on the specified endpoint. -func CreateListener(endpoint string) (net.Listener, error) { - protocol, addr, err := parseEndpointWithFallbackProtocol(endpoint, unixProtocol) - if err != nil { - return nil, err - } - if protocol != unixProtocol { - return nil, fmt.Errorf("only support unix socket endpoint") - } - - // Unlink to cleanup the previous socket file. - err = unix.Unlink(addr) - if err != nil && !os.IsNotExist(err) { - return nil, fmt.Errorf("failed to unlink socket file %q: %v", addr, err) - } - - if err := os.MkdirAll(filepath.Dir(addr), 0750); err != nil { - return nil, fmt.Errorf("error creating socket directory %q: %v", filepath.Dir(addr), err) - } - - // Create the socket on a tempfile and move it to the destination socket to handle improper cleanup - file, err := ioutil.TempFile(filepath.Dir(addr), "") - if err != nil { - return nil, fmt.Errorf("failed to create temporary file: %v", err) - } - - if err := os.Remove(file.Name()); err != nil { - return nil, fmt.Errorf("failed to remove temporary file: %v", err) - } - - l, err := net.Listen(protocol, file.Name()) - if err != nil { - return nil, err - } - - if err = os.Rename(file.Name(), addr); err != nil { - return nil, fmt.Errorf("failed to move temporary file to addr %q: %v", addr, err) - } - - return l, nil -} - -// GetAddressAndDialer returns the address parsed from the given endpoint and a context dialer. -func GetAddressAndDialer(endpoint string) (string, func(ctx context.Context, addr string) (net.Conn, error), error) { - protocol, addr, err := parseEndpointWithFallbackProtocol(endpoint, unixProtocol) - if err != nil { - return "", nil, err - } - if protocol != unixProtocol { - return "", nil, fmt.Errorf("only support unix socket endpoint") - } - - return addr, dial, nil -} - -func dial(ctx context.Context, addr string) (net.Conn, error) { - return (&net.Dialer{}).DialContext(ctx, unixProtocol, addr) -} - -func parseEndpointWithFallbackProtocol(endpoint string, fallbackProtocol string) (protocol string, addr string, err error) { - if protocol, addr, err = parseEndpoint(endpoint); err != nil && protocol == "" { - fallbackEndpoint := fallbackProtocol + "://" + endpoint - protocol, addr, err = parseEndpoint(fallbackEndpoint) - if err == nil { - klog.InfoS("Using this format as endpoint is deprecated, please consider using full url format.", "deprecatedFormat", endpoint, "fullURLFormat", fallbackEndpoint) - } - } - return -} - -func parseEndpoint(endpoint string) (string, string, error) { - u, err := url.Parse(endpoint) - if err != nil { - return "", "", err - } - - switch u.Scheme { - case "tcp": - return "tcp", u.Host, nil - - case "unix": - return "unix", u.Path, nil - - case "": - return "", "", fmt.Errorf("using %q as endpoint is deprecated, please consider using full url format", endpoint) - - default: - return u.Scheme, "", fmt.Errorf("protocol %q not supported", u.Scheme) - } -} - -// IsUnixDomainSocket returns whether a given file is a AF_UNIX socket file -func IsUnixDomainSocket(filePath string) (bool, error) { - fi, err := os.Stat(filePath) - if err != nil { - return false, fmt.Errorf("stat file %s failed: %v", filePath, err) - } - if fi.Mode()&os.ModeSocket == 0 { - return false, nil - } - return true, nil -} - -// NormalizePath is a no-op for Linux for now -func NormalizePath(path string) string { - return path -} diff --git a/pkg/kubelet/cri/remote/util/util_unix_test.go b/pkg/kubelet/cri/remote/util/util_unix_test.go deleted file mode 100644 index c536f96bd6c..00000000000 --- a/pkg/kubelet/cri/remote/util/util_unix_test.go +++ /dev/null @@ -1,136 +0,0 @@ -//go:build freebsd || linux || darwin -// +build freebsd linux darwin - -/* -Copyright 2018 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package util - -import ( - "io/ioutil" - "net" - "os" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestParseEndpoint(t *testing.T) { - tests := []struct { - endpoint string - expectError bool - expectedProtocol string - expectedAddr string - }{ - { - endpoint: "unix:///tmp/s1.sock", - expectedProtocol: "unix", - expectedAddr: "/tmp/s1.sock", - }, - { - endpoint: "tcp://localhost:15880", - expectedProtocol: "tcp", - expectedAddr: "localhost:15880", - }, - { - endpoint: "npipe://./pipe/mypipe", - expectedProtocol: "npipe", - expectError: true, - }, - { - endpoint: "tcp1://abc", - expectedProtocol: "tcp1", - expectError: true, - }, - { - endpoint: "a b c", - expectError: true, - }, - } - - for _, test := range tests { - protocol, addr, err := parseEndpoint(test.endpoint) - assert.Equal(t, test.expectedProtocol, protocol) - if test.expectError { - assert.NotNil(t, err, "Expect error during parsing %q", test.endpoint) - continue - } - assert.Nil(t, err, "Expect no error during parsing %q", test.endpoint) - assert.Equal(t, test.expectedAddr, addr) - } - -} - -func TestIsUnixDomainSocket(t *testing.T) { - tests := []struct { - label string - listenOnSocket bool - expectSocket bool - expectError bool - invalidFile bool - }{ - { - label: "Domain Socket file", - listenOnSocket: true, - expectSocket: true, - expectError: false, - }, - { - label: "Non Existent file", - invalidFile: true, - expectError: true, - }, - { - label: "Regular file", - listenOnSocket: false, - expectSocket: false, - expectError: false, - }, - } - for _, test := range tests { - f, err := ioutil.TempFile("", "test-domain-socket") - require.NoErrorf(t, err, "Failed to create file for test purposes: %v while setting up: %s", err, test.label) - addr := f.Name() - f.Close() - var ln *net.UnixListener - if test.listenOnSocket { - os.Remove(addr) - ta, err := net.ResolveUnixAddr("unix", addr) - require.NoErrorf(t, err, "Failed to ResolveUnixAddr: %v while setting up: %s", err, test.label) - ln, err = net.ListenUnix("unix", ta) - require.NoErrorf(t, err, "Failed to ListenUnix: %v while setting up: %s", err, test.label) - } - fileToTest := addr - if test.invalidFile { - fileToTest = fileToTest + ".invalid" - } - result, err := IsUnixDomainSocket(fileToTest) - if test.listenOnSocket { - // this takes care of removing the file associated with the domain socket - ln.Close() - } else { - // explicitly remove regular file - os.Remove(addr) - } - if test.expectError { - assert.NotNil(t, err, "Unexpected nil error from IsUnixDomainSocket for %s", test.label) - } else { - assert.Nil(t, err, "Unexpected error invoking IsUnixDomainSocket for %s", test.label) - } - assert.Equal(t, result, test.expectSocket, "Unexpected result from IsUnixDomainSocket: %v for %s", result, test.label) - } -} diff --git a/pkg/kubelet/cri/remote/util/util_unsupported.go b/pkg/kubelet/cri/remote/util/util_unsupported.go deleted file mode 100644 index 054c36230ef..00000000000 --- a/pkg/kubelet/cri/remote/util/util_unsupported.go +++ /dev/null @@ -1,56 +0,0 @@ -//go:build !freebsd && !linux && !windows && !darwin -// +build !freebsd,!linux,!windows,!darwin - -/* -Copyright 2017 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package util - -import ( - "context" - "fmt" - "net" - "time" -) - -// CreateListener creates a listener on the specified endpoint. -func CreateListener(endpoint string) (net.Listener, error) { - return nil, fmt.Errorf("CreateListener is unsupported in this build") -} - -// GetAddressAndDialer returns the address parsed from the given endpoint and a context dialer. -func GetAddressAndDialer(endpoint string) (string, func(ctx context.Context, addr string) (net.Conn, error), error) { - return "", nil, fmt.Errorf("GetAddressAndDialer is unsupported in this build") -} - -// LockAndCheckSubPath empty implementation -func LockAndCheckSubPath(volumePath, subPath string) ([]uintptr, error) { - return []uintptr{}, nil -} - -// UnlockPath empty implementation -func UnlockPath(fileHandles []uintptr) { -} - -// LocalEndpoint empty implementation -func LocalEndpoint(path, file string) (string, error) { - return "", fmt.Errorf("LocalEndpoints are unsupported in this build") -} - -// GetBootTime empty implementation -func GetBootTime() (time.Time, error) { - return time.Time{}, fmt.Errorf("GetBootTime is unsupported in this build") -} diff --git a/pkg/kubelet/cri/remote/util/util_windows.go b/pkg/kubelet/cri/remote/util/util_windows.go deleted file mode 100644 index 763fdd80a0d..00000000000 --- a/pkg/kubelet/cri/remote/util/util_windows.go +++ /dev/null @@ -1,150 +0,0 @@ -//go:build windows -// +build windows - -/* -Copyright 2017 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package util - -import ( - "context" - "fmt" - "net" - "net/url" - "strings" - "syscall" - "time" - - "github.com/Microsoft/go-winio" -) - -const ( - tcpProtocol = "tcp" - npipeProtocol = "npipe" -) - -// CreateListener creates a listener on the specified endpoint. -func CreateListener(endpoint string) (net.Listener, error) { - protocol, addr, err := parseEndpoint(endpoint) - if err != nil { - return nil, err - } - - switch protocol { - case tcpProtocol: - return net.Listen(tcpProtocol, addr) - - case npipeProtocol: - return winio.ListenPipe(addr, nil) - - default: - return nil, fmt.Errorf("only support tcp and npipe endpoint") - } -} - -// GetAddressAndDialer returns the address parsed from the given endpoint and a context dialer. -func GetAddressAndDialer(endpoint string) (string, func(ctx context.Context, addr string) (net.Conn, error), error) { - protocol, addr, err := parseEndpoint(endpoint) - if err != nil { - return "", nil, err - } - - if protocol == tcpProtocol { - return addr, tcpDial, nil - } - - if protocol == npipeProtocol { - return addr, npipeDial, nil - } - - return "", nil, fmt.Errorf("only support tcp and npipe endpoint") -} - -func tcpDial(ctx context.Context, addr string) (net.Conn, error) { - return (&net.Dialer{}).DialContext(ctx, tcpProtocol, addr) -} - -func npipeDial(ctx context.Context, addr string) (net.Conn, error) { - return winio.DialPipeContext(ctx, addr) -} - -func parseEndpoint(endpoint string) (string, string, error) { - // url.Parse doesn't recognize \, so replace with / first. - endpoint = strings.Replace(endpoint, "\\", "/", -1) - u, err := url.Parse(endpoint) - if err != nil { - return "", "", err - } - - if u.Scheme == "tcp" { - return "tcp", u.Host, nil - } else if u.Scheme == "npipe" { - if strings.HasPrefix(u.Path, "//./pipe") { - return "npipe", u.Path, nil - } - - // fallback host if not provided. - host := u.Host - if host == "" { - host = "." - } - return "npipe", fmt.Sprintf("//%s%s", host, u.Path), nil - } else if u.Scheme == "" { - return "", "", fmt.Errorf("using %q as endpoint is deprecated, please consider using full url format", endpoint) - } else { - return u.Scheme, "", fmt.Errorf("protocol %q not supported", u.Scheme) - } -} - -var tickCount = syscall.NewLazyDLL("kernel32.dll").NewProc("GetTickCount64") - -// GetBootTime returns the time at which the machine was started, truncated to the nearest second -func GetBootTime() (time.Time, error) { - currentTime := time.Now() - output, _, err := tickCount.Call() - if errno, ok := err.(syscall.Errno); !ok || errno != 0 { - return time.Time{}, err - } - return currentTime.Add(-time.Duration(output) * time.Millisecond).Truncate(time.Second), nil -} - -// IsUnixDomainSocket returns whether a given file is a AF_UNIX socket file -func IsUnixDomainSocket(filePath string) (bool, error) { - // Due to the absence of golang support for os.ModeSocket in Windows (https://github.com/golang/go/issues/33357) - // we need to dial the file and check if we receive an error to determine if a file is Unix Domain Socket file. - - // Note that querrying for the Reparse Points (https://docs.microsoft.com/en-us/windows/win32/fileio/reparse-points) - // for the file (using FSCTL_GET_REPARSE_POINT) and checking for reparse tag: reparseTagSocket - // does NOT work in 1809 if the socket file is created within a bind mounted directory by a container - // and the FSCTL is issued in the host by the kubelet. - - c, err := net.Dial("unix", filePath) - if err == nil { - c.Close() - return true, nil - } - return false, nil -} - -// NormalizePath converts FS paths returned by certain go frameworks (like fsnotify) -// to native Windows paths that can be passed to Windows specific code -func NormalizePath(path string) string { - path = strings.ReplaceAll(path, "/", "\\") - if strings.HasPrefix(path, "\\") { - path = "c:" + path - } - return path -} diff --git a/pkg/kubelet/cri/remote/util/util_windows_test.go b/pkg/kubelet/cri/remote/util/util_windows_test.go deleted file mode 100644 index 88bdd74137d..00000000000 --- a/pkg/kubelet/cri/remote/util/util_windows_test.go +++ /dev/null @@ -1,196 +0,0 @@ -//go:build windows -// +build windows - -/* -Copyright 2018 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package util - -import ( - "io/ioutil" - "math/rand" - "net" - "os" - "testing" - "time" - - winio "github.com/Microsoft/go-winio" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestParseEndpoint(t *testing.T) { - tests := []struct { - endpoint string - expectError bool - expectedProtocol string - expectedAddr string - }{ - { - endpoint: "unix:///tmp/s1.sock", - expectedProtocol: "unix", - expectError: true, - }, - { - endpoint: "tcp://localhost:15880", - expectedProtocol: "tcp", - expectedAddr: "localhost:15880", - }, - { - endpoint: "npipe://./pipe/mypipe", - expectedProtocol: "npipe", - expectedAddr: "//./pipe/mypipe", - }, - { - endpoint: "npipe:////./pipe/mypipe2", - expectedProtocol: "npipe", - expectedAddr: "//./pipe/mypipe2", - }, - { - endpoint: "npipe:/pipe/mypipe3", - expectedProtocol: "npipe", - expectedAddr: "//./pipe/mypipe3", - }, - { - endpoint: "npipe:\\\\.\\pipe\\mypipe4", - expectedProtocol: "npipe", - expectedAddr: "//./pipe/mypipe4", - }, - { - endpoint: "npipe:\\pipe\\mypipe5", - expectedProtocol: "npipe", - expectedAddr: "//./pipe/mypipe5", - }, - { - endpoint: "tcp1://abc", - expectedProtocol: "tcp1", - expectError: true, - }, - { - endpoint: "a b c", - expectError: true, - }, - } - - for _, test := range tests { - protocol, addr, err := parseEndpoint(test.endpoint) - assert.Equal(t, test.expectedProtocol, protocol) - if test.expectError { - assert.NotNil(t, err, "Expect error during parsing %q", test.endpoint) - continue - } - require.Nil(t, err, "Expect no error during parsing %q", test.endpoint) - assert.Equal(t, test.expectedAddr, addr) - } - -} - -func testPipe(t *testing.T, label string) { - generatePipeName := func(suffixlen int) string { - rand.Seed(time.Now().UnixNano()) - letter := []rune("abcdef0123456789") - b := make([]rune, suffixlen) - for i := range b { - b[i] = letter[rand.Intn(len(letter))] - } - return "\\\\.\\pipe\\test-pipe" + string(b) - } - testFile := generatePipeName(4) - pipeln, err := winio.ListenPipe(testFile, &winio.PipeConfig{SecurityDescriptor: "D:P(A;;GA;;;BA)(A;;GA;;;SY)"}) - defer pipeln.Close() - - require.NoErrorf(t, err, "Failed to listen on named pipe for test purposes: %v while setting up: %s", err, label) - result, err := IsUnixDomainSocket(testFile) - assert.Nil(t, err, "Unexpected error: %v from IsUnixDomainSocket for %s", err, label) - assert.False(t, result, "Unexpected result: true from IsUnixDomainSocket: %v for %s", result, label) -} - -func testRegularFile(t *testing.T, label string, exists bool) { - f, err := ioutil.TempFile("", "test-file") - require.NoErrorf(t, err, "Failed to create file for test purposes: %v while setting up: %s", err, label) - testFile := f.Name() - if !exists { - testFile = testFile + ".absent" - } - f.Close() - result, err := IsUnixDomainSocket(testFile) - os.Remove(f.Name()) - assert.Nil(t, err, "Unexpected error: %v from IsUnixDomainSocket for %s", err, label) - assert.False(t, result, "Unexpected result: true from IsUnixDomainSocket: %v for %s", result, label) -} - -func testUnixDomainSocket(t *testing.T, label string) { - f, err := ioutil.TempFile("", "test-domain-socket") - require.NoErrorf(t, err, "Failed to create file for test purposes: %v while setting up: %s", err, label) - testFile := f.Name() - f.Close() - os.Remove(testFile) - ta, err := net.ResolveUnixAddr("unix", testFile) - require.NoErrorf(t, err, "Failed to ResolveUnixAddr: %v while setting up: %s", err, label) - unixln, err := net.ListenUnix("unix", ta) - require.NoErrorf(t, err, "Failed to ListenUnix: %v while setting up: %s", err, label) - result, err := IsUnixDomainSocket(testFile) - unixln.Close() - assert.Nil(t, err, "Unexpected error: %v from IsUnixDomainSocket for %s", err, label) - assert.True(t, result, "Unexpected result: false from IsUnixDomainSocket: %v for %s", result, label) -} - -func TestIsUnixDomainSocket(t *testing.T) { - testPipe(t, "Named Pipe") - testRegularFile(t, "Regular File that Exists", true) - testRegularFile(t, "Regular File that Does Not Exist", false) - testUnixDomainSocket(t, "Unix Domain Socket File") -} - -func TestNormalizePath(t *testing.T) { - tests := []struct { - originalpath string - normalizedPath string - }{ - { - originalpath: "\\path\\to\\file", - normalizedPath: "c:\\path\\to\\file", - }, - { - originalpath: "/path/to/file", - normalizedPath: "c:\\path\\to\\file", - }, - { - originalpath: "/path/to/dir/", - normalizedPath: "c:\\path\\to\\dir\\", - }, - { - originalpath: "\\path\\to\\dir\\", - normalizedPath: "c:\\path\\to\\dir\\", - }, - { - originalpath: "/file", - normalizedPath: "c:\\file", - }, - { - originalpath: "\\file", - normalizedPath: "c:\\file", - }, - { - originalpath: "fileonly", - normalizedPath: "fileonly", - }, - } - - for _, test := range tests { - assert.Equal(t, test.normalizedPath, NormalizePath(test.originalpath)) - } -}