Skip to content

Commit c5357f3

Browse files
authored
Merge pull request #2277 from gmittert/94.5FixFM
Fix Up FileManager Tests on Windows
2 parents 33f5603 + 264ceae commit c5357f3

File tree

5 files changed

+87
-66
lines changed

5 files changed

+87
-66
lines changed

CoreFoundation/URL.subproj/CFURL.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4292,7 +4292,7 @@ static CFStringRef URLPathToWindowsPath(CFStringRef path, CFAllocatorRef allocat
42924292
static CFStringRef _resolveFileSystemPaths(CFStringRef relativePath, CFStringRef basePath, Boolean baseIsDir, CFURLPathStyle fsType, CFAllocatorRef alloc) CF_RETURNS_RETAINED {
42934293
CFIndex baseLen = CFStringGetLength(basePath);
42944294
CFIndex relLen = CFStringGetLength(relativePath);
4295-
UniChar pathDelimiter = '/';
4295+
UniChar pathDelimiter = _CFGetSlash();
42964296
UniChar *buf = (UniChar *)CFAllocatorAllocate(alloc, sizeof(UniChar)*(relLen + baseLen + 2), 0);
42974297
CFStringGetCharacters(basePath, CFRangeMake(0, baseLen), buf);
42984298
if (baseIsDir) {

Foundation/FileManager+Win32.swift

Lines changed: 52 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -652,64 +652,69 @@ extension FileManager {
652652
var _options : FileManager.DirectoryEnumerationOptions
653653
var _errorHandler : ((URL, Error) -> Bool)?
654654
var _stack: [URL]
655-
var _current: URL?
655+
var _lastReturned: URL
656656
var _rootDepth : Int
657657

658658
init(url: URL, options: FileManager.DirectoryEnumerationOptions, errorHandler: (/* @escaping */ (URL, Error) -> Bool)?) {
659659
_options = options
660660
_errorHandler = errorHandler
661-
_stack = [url]
661+
_stack = []
662662
_rootDepth = url.pathComponents.count
663+
_lastReturned = url
663664
}
664665

665666
override func nextObject() -> Any? {
666-
func contentsOfDir(directory: URL) -> [URL]? {
667-
var ffd: WIN32_FIND_DATAW = WIN32_FIND_DATAW()
668-
guard let dirFSR = directory.withUnsafeFileSystemRepresentation({ $0.flatMap { fsr in String(utf8String: fsr) } })
669-
else { return nil }
670-
let dirPath = joinPath(prefix: dirFSR, suffix: "*")
671-
let h: HANDLE = dirPath.withCString(encodedAs: UTF16.self) {
667+
func firstValidItem() -> URL? {
668+
while let url = _stack.popLast() {
669+
if !FileManager.default.fileExists(atPath: url.path, isDirectory: nil) {
670+
guard let handler = _errorHandler,
671+
handler(url, _NSErrorWithWindowsError(GetLastError(), reading: true))
672+
else { return nil }
673+
continue
674+
}
675+
_lastReturned = url
676+
return _lastReturned
677+
}
678+
return nil
679+
}
680+
681+
// If we most recently returned a directory, decend into it
682+
var isDir: ObjCBool = false
683+
guard FileManager.default.fileExists(atPath: _lastReturned.path, isDirectory: &isDir) else {
684+
guard let handler = _errorHandler,
685+
handler(_lastReturned, _NSErrorWithWindowsError(GetLastError(), reading: true))
686+
else { return nil }
687+
return firstValidItem()
688+
}
689+
690+
if isDir.boolValue && (level == 0 || !_options.contains(.skipsSubdirectoryDescendants)) {
691+
var ffd = WIN32_FIND_DATAW()
692+
let dirPath = joinPath(prefix: _lastReturned.path, suffix: "*")
693+
let handle = dirPath.withCString(encodedAs: UTF16.self) {
672694
FindFirstFileW($0, &ffd)
673695
}
674-
guard h != INVALID_HANDLE_VALUE else { return nil }
675-
defer { FindClose(h) }
696+
guard handle != INVALID_HANDLE_VALUE else { return firstValidItem() }
697+
defer { FindClose(handle) }
676698

677-
var files: [URL] = []
678699
repeat {
679700
let fileArr = Array<WCHAR>(
680701
UnsafeBufferPointer(start: &ffd.cFileName.0,
681702
count: MemoryLayout.size(ofValue: ffd.cFileName)))
682703
let file = String(decodingCString: fileArr, as: UTF16.self)
683-
if file != "."
684-
&& file != ".."
704+
if file != "." && file != ".."
685705
&& (!_options.contains(.skipsHiddenFiles)
686706
|| (ffd.dwFileAttributes & DWORD(FILE_ATTRIBUTE_HIDDEN) == 0)) {
687-
files.append(URL(fileURLWithPath: joinPath(prefix: dirFSR, suffix: file)))
688-
}
689-
} while FindNextFileW(h, &ffd)
690-
return files
691-
}
692-
while let url = _stack.popLast() {
693-
if url.hasDirectoryPath && !_options.contains(.skipsSubdirectoryDescendants) {
694-
guard let dirContents = contentsOfDir(directory: url)?.reversed() else {
695-
if let handler = _errorHandler {
696-
let dirFSR = url.withUnsafeFileSystemRepresentation { $0.flatMap { fsr in String(utf8String: fsr) } }
697-
let keepGoing = handler(URL(fileURLWithPath: dirFSR ?? ""),
698-
_NSErrorWithWindowsError(GetLastError(), reading: true))
699-
if !keepGoing { return nil }
700-
}
701-
continue
707+
let relative = URL(fileURLWithPath: file, relativeTo: _lastReturned)
708+
_stack.append(relative)
702709
}
703-
_stack.append(contentsOf: dirContents)
704-
}
705-
_current = url
706-
return url
710+
} while FindNextFileW(handle, &ffd)
707711
}
708-
return nil
712+
713+
return firstValidItem()
709714
}
710715

711716
override var level: Int {
712-
return _rootDepth - (_current?.pathComponents.count ?? _rootDepth)
717+
return _lastReturned.pathComponents.count - _rootDepth
713718
}
714719

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

729734
extension FileManager.NSPathDirectoryEnumerator {
730735
internal func _nextObject() -> Any? {
731-
let o = innerEnumerator.nextObject()
732-
guard let url = o as? URL else {
733-
return nil
734-
}
736+
guard let url = innerEnumerator.nextObject() as? URL else { return nil }
735737

736-
var relativePath = UnsafeMutableBufferPointer<WCHAR>.allocate(capacity: Int(MAX_PATH))
737-
defer { relativePath.deallocate() }
738-
func withURLCString<Result>(url: URL, _ f: (UnsafePointer<WCHAR>) -> Result?) -> Result? {
739-
return url.withUnsafeFileSystemRepresentation { fsr in
740-
(fsr.flatMap { String(utf8String: $0) })?.withCString(encodedAs: UTF16.self) { f($0) }
741-
}
742-
}
743-
guard withURLCString(url: baseURL, { pszFrom -> Bool? in
744-
withURLCString(url: url) { pszTo in
745-
let fromAttrs = GetFileAttributesW(pszFrom)
746-
let toAttrs = GetFileAttributesW(pszTo)
738+
var relativePath: [WCHAR] = Array<WCHAR>(repeating: 0, count: Int(MAX_PATH))
739+
740+
guard baseURL._withUnsafeWideFileSystemRepresentation({ baseUrlFsr in
741+
url._withUnsafeWideFileSystemRepresentation { urlFsr in
742+
let fromAttrs = GetFileAttributesW(baseUrlFsr)
743+
let toAttrs = GetFileAttributesW(urlFsr)
747744
guard fromAttrs != INVALID_FILE_ATTRIBUTES, toAttrs != INVALID_FILE_ATTRIBUTES else {
748745
return false
749746
}
750-
return PathRelativePathToW(relativePath.baseAddress, pszFrom, fromAttrs, pszTo, toAttrs)
747+
return PathRelativePathToW(&relativePath, baseUrlFsr, fromAttrs, urlFsr, toAttrs)
751748
}
752-
}) == true, let (path, _) = String.decodeCString(relativePath.baseAddress, as: UTF16.self) else {
753-
return nil
754-
}
755-
_currentItemPath = path
749+
}) else { return nil }
750+
751+
let path = String(decodingCString: &relativePath, as: UTF16.self)
752+
// Drop the leading ".\" from the path
753+
_currentItemPath = String(path.dropFirst(2))
756754
return _currentItemPath
757755
}
758756

Foundation/NSPathUtilities.swift

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,8 +117,12 @@ extension String {
117117
return nil
118118
}
119119

120-
internal var absolutePath: Bool {
120+
internal var isAbsolutePath: Bool {
121+
#if os(Windows)
122+
return !withCString(encodedAs: UTF16.self, PathIsRelativeW)
123+
#else
121124
return hasPrefix("~") || hasPrefix("/")
125+
#endif
122126
}
123127

124128
internal func _stringByAppendingPathComponent(_ str: String, doneAppending : Bool = true) -> String {

Foundation/NSURL.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ internal let kCFURLPlatformPathStyle = kCFURLPOSIXPathStyle
2929
#endif
3030

3131
private func _standardizedPath(_ path: String) -> String {
32-
if !path.absolutePath {
32+
if !path.isAbsolutePath {
3333
return path._nsObject.standardizingPath
3434
}
3535
return path
@@ -1007,7 +1007,7 @@ extension NSURL {
10071007
}
10081008

10091009
let absolutePath: String
1010-
if selfPath.hasPrefix("/") {
1010+
if selfPath.isAbsolutePath {
10111011
absolutePath = selfPath
10121012
} else {
10131013
let workingDir = FileManager.default.currentDirectoryPath

TestFoundation/TestFileManager.swift

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,11 @@
1616
#endif
1717

1818
class TestFileManager : XCTestCase {
19+
#if os(Windows)
20+
let pathSep = "\\"
21+
#else
22+
let pathSep = "/"
23+
#endif
1924

2025
func test_createDirectory() {
2126
let fm = FileManager.default
@@ -449,11 +454,11 @@ class TestFileManager : XCTestCase {
449454
foundItems.insert(item)
450455
if item == "item" {
451456
item1FileAttributes = e.fileAttributes
452-
} else if item == "path2/item" {
457+
} else if item == "path2\(pathSep)item" {
453458
item2FileAttributes = e.fileAttributes
454459
}
455460
}
456-
XCTAssertEqual(foundItems, Set(["item", "path2", "path2/item"]))
461+
XCTAssertEqual(foundItems, Set(["item", "path2", "path2\(pathSep)item"]))
457462
} else {
458463
XCTFail()
459464
}
@@ -492,10 +497,17 @@ class TestFileManager : XCTestCase {
492497
let fm = FileManager.default
493498
let basePath = NSTemporaryDirectory() + "testdir\(NSUUID().uuidString)/"
494499
let subDirs1 = basePath + "subdir1/subdir2/.hiddenDir/subdir3/"
495-
let subDirs2 = basePath + "subdir1/subdir2/subdir4.app/subdir5./.subdir6.ext/subdir7.ext./"
496500
let itemPath1 = basePath + "itemFile1"
501+
#if os(Windows)
502+
// Filenames ending with '.' are not valid on Windows, so don't bother testing them
503+
let subDirs2 = basePath + "subdir1/subdir2/subdir4.app/subdir5/.subdir6.ext/subdir7.ext/"
504+
let itemPath2 = subDirs1 + "itemFile2"
505+
let itemPath3 = subDirs1 + "itemFile3.ext"
506+
#else
507+
let subDirs2 = basePath + "subdir1/subdir2/subdir4.app/subdir5./.subdir6.ext/subdir7.ext./"
497508
let itemPath2 = subDirs1 + "itemFile2."
498509
let itemPath3 = subDirs1 + "itemFile3.ext."
510+
#endif
499511
let hiddenItem1 = basePath + ".hiddenFile1"
500512
let hiddenItem2 = subDirs1 + ".hiddenFile2"
501513
let hiddenItem3 = subDirs2 + ".hiddenFile3"
@@ -507,17 +519,24 @@ class TestFileManager : XCTestCase {
507519
"subdir1": 1,
508520
"subdir2": 2,
509521
"subdir4.app": 3,
510-
"subdir5.": 4,
511522
".subdir6.ext": 5,
512-
"subdir7.ext.": 6,
513523
".hiddenFile4.ext": 7,
514524
".hiddenFile3": 7,
515525
".hiddenDir": 3,
516526
"subdir3": 4,
517-
"itemFile3.ext.": 5,
518-
"itemFile2.": 5,
519-
".hiddenFile2": 5
527+
".hiddenFile2": 5,
520528
]
529+
#if os(Windows)
530+
fileLevels["itemFile2"] = 5
531+
fileLevels["subdir5"] = 4
532+
fileLevels["subdir7.ext"] = 6
533+
fileLevels["itemFile3.ext"] = 5
534+
#else
535+
fileLevels["itemFile2."] = 5
536+
fileLevels["subdir5."] = 4
537+
fileLevels["subdir7.ext."] = 6
538+
fileLevels["itemFile3.ext."] = 5
539+
#endif
521540

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

0 commit comments

Comments
 (0)