Skip to content

Fix Up FileManager Tests on Windows #2277

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
May 26, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CoreFoundation/URL.subproj/CFURL.c
Original file line number Diff line number Diff line change
Expand Up @@ -4292,7 +4292,7 @@ static CFStringRef URLPathToWindowsPath(CFStringRef path, CFAllocatorRef allocat
static CFStringRef _resolveFileSystemPaths(CFStringRef relativePath, CFStringRef basePath, Boolean baseIsDir, CFURLPathStyle fsType, CFAllocatorRef alloc) CF_RETURNS_RETAINED {
CFIndex baseLen = CFStringGetLength(basePath);
CFIndex relLen = CFStringGetLength(relativePath);
UniChar pathDelimiter = '/';
UniChar pathDelimiter = _CFGetSlash();
UniChar *buf = (UniChar *)CFAllocatorAllocate(alloc, sizeof(UniChar)*(relLen + baseLen + 2), 0);
CFStringGetCharacters(basePath, CFRangeMake(0, baseLen), buf);
if (baseIsDir) {
Expand Down
106 changes: 52 additions & 54 deletions Foundation/FileManager+Win32.swift
Original file line number Diff line number Diff line change
Expand Up @@ -652,64 +652,69 @@ extension FileManager {
var _options : FileManager.DirectoryEnumerationOptions
var _errorHandler : ((URL, Error) -> Bool)?
var _stack: [URL]
var _current: URL?
var _lastReturned: URL
var _rootDepth : Int

init(url: URL, options: FileManager.DirectoryEnumerationOptions, errorHandler: (/* @escaping */ (URL, Error) -> Bool)?) {
_options = options
_errorHandler = errorHandler
_stack = [url]
_stack = []
_rootDepth = url.pathComponents.count
_lastReturned = url
}

override func nextObject() -> Any? {
func contentsOfDir(directory: URL) -> [URL]? {
var ffd: WIN32_FIND_DATAW = WIN32_FIND_DATAW()
guard let dirFSR = directory.withUnsafeFileSystemRepresentation({ $0.flatMap { fsr in String(utf8String: fsr) } })
else { return nil }
let dirPath = joinPath(prefix: dirFSR, suffix: "*")
let h: HANDLE = dirPath.withCString(encodedAs: UTF16.self) {
func firstValidItem() -> URL? {
while let url = _stack.popLast() {
if !FileManager.default.fileExists(atPath: url.path, isDirectory: nil) {
guard let handler = _errorHandler,
handler(url, _NSErrorWithWindowsError(GetLastError(), reading: true))
else { return nil }
continue
}
_lastReturned = url
return _lastReturned
}
return nil
}

// If we most recently returned a directory, decend into it
var isDir: ObjCBool = false
guard FileManager.default.fileExists(atPath: _lastReturned.path, isDirectory: &isDir) else {
guard let handler = _errorHandler,
handler(_lastReturned, _NSErrorWithWindowsError(GetLastError(), reading: true))
else { return nil }
return firstValidItem()
}

if isDir.boolValue && (level == 0 || !_options.contains(.skipsSubdirectoryDescendants)) {
var ffd = WIN32_FIND_DATAW()
let dirPath = joinPath(prefix: _lastReturned.path, suffix: "*")
let handle = dirPath.withCString(encodedAs: UTF16.self) {
FindFirstFileW($0, &ffd)
}
guard h != INVALID_HANDLE_VALUE else { return nil }
defer { FindClose(h) }
guard handle != INVALID_HANDLE_VALUE else { return firstValidItem() }
defer { FindClose(handle) }

var files: [URL] = []
repeat {
let fileArr = Array<WCHAR>(
UnsafeBufferPointer(start: &ffd.cFileName.0,
count: MemoryLayout.size(ofValue: ffd.cFileName)))
let file = String(decodingCString: fileArr, as: UTF16.self)
if file != "."
&& file != ".."
if file != "." && file != ".."
&& (!_options.contains(.skipsHiddenFiles)
|| (ffd.dwFileAttributes & DWORD(FILE_ATTRIBUTE_HIDDEN) == 0)) {
files.append(URL(fileURLWithPath: joinPath(prefix: dirFSR, suffix: file)))
}
} while FindNextFileW(h, &ffd)
return files
}
while let url = _stack.popLast() {
if url.hasDirectoryPath && !_options.contains(.skipsSubdirectoryDescendants) {
guard let dirContents = contentsOfDir(directory: url)?.reversed() else {
if let handler = _errorHandler {
let dirFSR = url.withUnsafeFileSystemRepresentation { $0.flatMap { fsr in String(utf8String: fsr) } }
let keepGoing = handler(URL(fileURLWithPath: dirFSR ?? ""),
_NSErrorWithWindowsError(GetLastError(), reading: true))
if !keepGoing { return nil }
}
continue
let relative = URL(fileURLWithPath: file, relativeTo: _lastReturned)
_stack.append(relative)
}
_stack.append(contentsOf: dirContents)
}
_current = url
return url
} while FindNextFileW(handle, &ffd)
}
return nil

return firstValidItem()
}

override var level: Int {
return _rootDepth - (_current?.pathComponents.count ?? _rootDepth)
return _lastReturned.pathComponents.count - _rootDepth
}

override func skipDescendants() {
Expand All @@ -728,31 +733,24 @@ extension FileManager {

extension FileManager.NSPathDirectoryEnumerator {
internal func _nextObject() -> Any? {
let o = innerEnumerator.nextObject()
guard let url = o as? URL else {
return nil
}
guard let url = innerEnumerator.nextObject() as? URL else { return nil }

var relativePath = UnsafeMutableBufferPointer<WCHAR>.allocate(capacity: Int(MAX_PATH))
defer { relativePath.deallocate() }
func withURLCString<Result>(url: URL, _ f: (UnsafePointer<WCHAR>) -> Result?) -> Result? {
return url.withUnsafeFileSystemRepresentation { fsr in
(fsr.flatMap { String(utf8String: $0) })?.withCString(encodedAs: UTF16.self) { f($0) }
}
}
guard withURLCString(url: baseURL, { pszFrom -> Bool? in
withURLCString(url: url) { pszTo in
let fromAttrs = GetFileAttributesW(pszFrom)
let toAttrs = GetFileAttributesW(pszTo)
var relativePath: [WCHAR] = Array<WCHAR>(repeating: 0, count: Int(MAX_PATH))

guard baseURL._withUnsafeWideFileSystemRepresentation({ baseUrlFsr in
url._withUnsafeWideFileSystemRepresentation { urlFsr in
let fromAttrs = GetFileAttributesW(baseUrlFsr)
let toAttrs = GetFileAttributesW(urlFsr)
guard fromAttrs != INVALID_FILE_ATTRIBUTES, toAttrs != INVALID_FILE_ATTRIBUTES else {
return false
}
return PathRelativePathToW(relativePath.baseAddress, pszFrom, fromAttrs, pszTo, toAttrs)
return PathRelativePathToW(&relativePath, baseUrlFsr, fromAttrs, urlFsr, toAttrs)
}
}) == true, let (path, _) = String.decodeCString(relativePath.baseAddress, as: UTF16.self) else {
return nil
}
_currentItemPath = path
}) else { return nil }

let path = String(decodingCString: &relativePath, as: UTF16.self)
// Drop the leading ".\" from the path
_currentItemPath = String(path.dropFirst(2))
return _currentItemPath
}

Expand Down
6 changes: 5 additions & 1 deletion Foundation/NSPathUtilities.swift
Original file line number Diff line number Diff line change
Expand Up @@ -117,8 +117,12 @@ extension String {
return nil
}

internal var absolutePath: Bool {
internal var isAbsolutePath: Bool {
#if os(Windows)
return !withCString(encodedAs: UTF16.self, PathIsRelativeW)
#else
return hasPrefix("~") || hasPrefix("/")
#endif
}

internal func _stringByAppendingPathComponent(_ str: String, doneAppending : Bool = true) -> String {
Expand Down
4 changes: 2 additions & 2 deletions Foundation/NSURL.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ internal let kCFURLPlatformPathStyle = kCFURLPOSIXPathStyle
#endif

private func _standardizedPath(_ path: String) -> String {
if !path.absolutePath {
if !path.isAbsolutePath {
return path._nsObject.standardizingPath
}
return path
Expand Down Expand Up @@ -1007,7 +1007,7 @@ extension NSURL {
}

let absolutePath: String
if selfPath.hasPrefix("/") {
if selfPath.isAbsolutePath {
absolutePath = selfPath
} else {
let workingDir = FileManager.default.currentDirectoryPath
Expand Down
35 changes: 27 additions & 8 deletions TestFoundation/TestFileManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@
#endif

class TestFileManager : XCTestCase {
#if os(Windows)
let pathSep = "\\"
#else
let pathSep = "/"
#endif

func test_createDirectory() {
let fm = FileManager.default
Expand Down Expand Up @@ -449,11 +454,11 @@ class TestFileManager : XCTestCase {
foundItems.insert(item)
if item == "item" {
item1FileAttributes = e.fileAttributes
} else if item == "path2/item" {
} else if item == "path2\(pathSep)item" {
item2FileAttributes = e.fileAttributes
}
}
XCTAssertEqual(foundItems, Set(["item", "path2", "path2/item"]))
XCTAssertEqual(foundItems, Set(["item", "path2", "path2\(pathSep)item"]))
} else {
XCTFail()
}
Expand Down Expand Up @@ -492,10 +497,17 @@ class TestFileManager : XCTestCase {
let fm = FileManager.default
let basePath = NSTemporaryDirectory() + "testdir\(NSUUID().uuidString)/"
let subDirs1 = basePath + "subdir1/subdir2/.hiddenDir/subdir3/"
let subDirs2 = basePath + "subdir1/subdir2/subdir4.app/subdir5./.subdir6.ext/subdir7.ext./"
let itemPath1 = basePath + "itemFile1"
#if os(Windows)
// Filenames ending with '.' are not valid on Windows, so don't bother testing them
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@millenomi - what do you think about this?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's fine.

let subDirs2 = basePath + "subdir1/subdir2/subdir4.app/subdir5/.subdir6.ext/subdir7.ext/"
let itemPath2 = subDirs1 + "itemFile2"
let itemPath3 = subDirs1 + "itemFile3.ext"
#else
let subDirs2 = basePath + "subdir1/subdir2/subdir4.app/subdir5./.subdir6.ext/subdir7.ext./"
let itemPath2 = subDirs1 + "itemFile2."
let itemPath3 = subDirs1 + "itemFile3.ext."
#endif
let hiddenItem1 = basePath + ".hiddenFile1"
let hiddenItem2 = subDirs1 + ".hiddenFile2"
let hiddenItem3 = subDirs2 + ".hiddenFile3"
Expand All @@ -507,17 +519,24 @@ class TestFileManager : XCTestCase {
"subdir1": 1,
"subdir2": 2,
"subdir4.app": 3,
"subdir5.": 4,
".subdir6.ext": 5,
"subdir7.ext.": 6,
".hiddenFile4.ext": 7,
".hiddenFile3": 7,
".hiddenDir": 3,
"subdir3": 4,
"itemFile3.ext.": 5,
"itemFile2.": 5,
".hiddenFile2": 5
".hiddenFile2": 5,
]
#if os(Windows)
fileLevels["itemFile2"] = 5
fileLevels["subdir5"] = 4
fileLevels["subdir7.ext"] = 6
fileLevels["itemFile3.ext"] = 5
#else
fileLevels["itemFile2."] = 5
fileLevels["subdir5."] = 4
fileLevels["subdir7.ext."] = 6
fileLevels["itemFile3.ext."] = 5
#endif

func directoryItems(options: FileManager.DirectoryEnumerationOptions) -> [String: Int]? {
if let e = FileManager.default.enumerator(at: URL(fileURLWithPath: basePath), includingPropertiesForKeys: nil, options: options, errorHandler: nil) {
Expand Down