@@ -1072,4 +1072,92 @@ final class FileManagerTests : XCTestCase {
10721072 XCTAssertEqual ( $0. contents ( atPath: " a \u{301} /test " ) , data)
10731073 }
10741074 }
1075+
1076+ /// Tests that Foundation can correctly handle "long paths" (paths of 260 to 32767 chacters long) on Windows.
1077+ func testWindowsLongPathSupport( ) throws {
1078+ #if !os(Windows)
1079+ throw XCTSkip ( " This test is not applicable for this platform " )
1080+ #else
1081+ // Create a directory with the absolute maximum path _component_ length of 255;
1082+ // this will guarantee the full playground path is well over 260 characters.
1083+ // Throw some Unicode in there for good measure, since only wide-character APIs support it.
1084+ let dirName = String ( repeating: UUID ( ) . uuidString, count: 7 ) + " 你好! "
1085+ XCTAssertEqual ( dirName. count, 255 )
1086+ XCTAssertEqual ( dirName. utf16. count, 255 )
1087+
1088+ try FileManagerPlayground {
1089+ Directory ( dirName) {
1090+ }
1091+ } . test {
1092+ // Call every function that can call into withNTPathRepresentation with an overlong path and ensure it succeeds.
1093+ let fileName = UUID ( ) . uuidString
1094+ let cwd = try XCTUnwrap ( $0. currentDirectoryPath)
1095+
1096+ XCTAssertTrue ( $0. createFile ( atPath: dirName + " / " + fileName, contents: nil ) )
1097+
1098+ let dirURL = URL ( filePath: dirName, directoryHint: . checkFileSystem)
1099+ XCTAssertTrue ( dirURL. hasDirectoryPath)
1100+
1101+ let fileURL = URL ( filePath: dirName + " / " + fileName, directoryHint: . checkFileSystem)
1102+ XCTAssertFalse ( fileURL. hasDirectoryPath)
1103+
1104+ XCTAssertTrue ( $0. fileExists ( atPath: dirName + " / " + fileName) )
1105+ XCTAssertTrue ( $0. isReadableFile ( atPath: dirName + " / " + fileName) )
1106+ XCTAssertTrue ( $0. isWritableFile ( atPath: dirName + " / " + fileName) )
1107+
1108+ // SHGetFileInfoW is documented to be limited to MAX_PATH, but appears to support long paths anyways (or at least does for SHGFI_EXETYPE).
1109+ // https://learn.microsoft.com/en-us/windows/win32/api/shellapi/nf-shellapi-shgetfileinfow
1110+ XCTAssertNoThrow ( try Data ( ) . write ( to: URL ( fileURLWithPath: dirName + " / " + fileName + " .bat " ) ) )
1111+ XCTAssertTrue ( $0. isExecutableFile ( atPath: dirName + " / " + fileName + " .bat " ) )
1112+ XCTAssertFalse ( $0. isExecutableFile ( atPath: dirName + " / " + fileName) )
1113+
1114+ XCTAssertNoThrow ( try $0. attributesOfItem ( atPath: dirName + " / " + fileName) )
1115+ XCTAssertNoThrow ( try $0. setAttributes ( [ . modificationDate: Date ( ) ] , ofItemAtPath: dirName + " / " + fileName) )
1116+ XCTAssertNoThrow ( try $0. attributesOfFileSystem ( forPath: dirName + " / " + fileName) )
1117+
1118+ XCTAssertNoThrow ( try Data ( contentsOf: URL ( fileURLWithPath: dirName + " / " + fileName) ) )
1119+
1120+ XCTAssertNoThrow ( try Data ( " hello " . utf8) . write ( to: URL ( fileURLWithPath: dirName + " / " + fileName) ) )
1121+ XCTAssertNoThrow ( try Data ( " hello " . utf8) . write ( to: URL ( fileURLWithPath: dirName + " / " + fileName) , options: . atomic) )
1122+
1123+ XCTAssertNoThrow ( try Data ( " hello " . utf8) . write ( to: URL ( fileURLWithPath: dirName + " / " + fileName + " .v2 " ) ) )
1124+ XCTAssertTrue ( $0. contentsEqual ( atPath: dirName + " / " + fileName, andPath: dirName + " / " + fileName + " .v2 " ) )
1125+
1126+ XCTAssertEqual ( try $0. subpathsOfDirectory ( atPath: dirName) . sorted ( ) , [
1127+ fileName,
1128+ fileName + " .bat " ,
1129+ fileName + " .v2 "
1130+ ] )
1131+
1132+ XCTAssertNoThrow ( try $0. createDirectory ( at: URL ( fileURLWithPath: dirName + " / " + " subdir1 " ) , withIntermediateDirectories: false ) )
1133+
1134+ // SHCreateDirectoryExW's path argument is limited to 248 characters, and the \\?\ prefix doesn't help.
1135+ // https://learn.microsoft.com/en-us/windows/win32/api/shlobj_core/nf-shlobj_core-shcreatedirectoryexw
1136+ XCTAssertThrowsError ( try $0. createDirectory ( at: URL ( fileURLWithPath: dirName + " / " + " subdir2 " + " / " + " subdir3 " ) , withIntermediateDirectories: true ) )
1137+
1138+ // SetCurrentDirectory seems to be limited to MAX_PATH unconditionally, counter to the documentation.
1139+ // https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-setcurrentdirectory
1140+ // https://github.com/MicrosoftDocs/feedback/issues/1441
1141+ XCTAssertFalse ( $0. changeCurrentDirectoryPath ( dirName + " / " + " subdir1 " ) )
1142+
1143+ XCTAssertNoThrow ( try $0. createSymbolicLink ( atPath: dirName + " / " + " lnk " , withDestinationPath: fileName) )
1144+ XCTAssertNoThrow ( try $0. createSymbolicLink ( atPath: dirName + " / " + " lnk2 " , withDestinationPath: cwd + " / " + dirName + " / " + fileName) )
1145+ XCTAssertEqual ( try $0. destinationOfSymbolicLink ( atPath: dirName + " / " + " lnk " ) , fileName)
1146+ XCTAssertEqual ( try $0. destinationOfSymbolicLink ( atPath: dirName + " / " + " lnk2 " ) , cwd + " \\ " + dirName + " \\ " + fileName)
1147+
1148+ XCTAssertEqual ( ( cwd + " / " + dirName + " / " + " lnk " ) . resolvingSymlinksInPath, ( cwd + " / " + dirName + " / " + fileName) . resolvingSymlinksInPath)
1149+
1150+ XCTAssertNoThrow ( try $0. createDirectory ( at: URL ( fileURLWithPath: dirName + " / " + " subdir2 " ) , withIntermediateDirectories: false ) )
1151+ XCTAssertNoThrow ( try $0. createDirectory ( at: URL ( fileURLWithPath: dirName + " / " + " subdir2 " + " / " + " subdir3 " ) , withIntermediateDirectories: false ) )
1152+ XCTAssertNoThrow ( try Data ( ) . write ( to: URL ( fileURLWithPath: dirName + " / " + " subdir2 " + " / " + " subdir3 " + " / " + " somefile " ) ) )
1153+ XCTAssertNoThrow ( try Data ( ) . write ( to: URL ( fileURLWithPath: dirName + " / " + " subdir2 " + " / " + " subdir3 " + " / " + " somefile2 " ) ) )
1154+ XCTAssertNoThrow ( try $0. moveItem ( atPath: dirName + " / " + " subdir2 " + " / " + " subdir3 " + " / " + " somefile2 " , toPath: dirName + " / " + " subdir2 " + " / " + " subdir3 " + " / " + " somefile3 " ) )
1155+ XCTAssertNoThrow ( try $0. moveItem ( atPath: dirName + " / " + " subdir2 " + " / " + " subdir3 " , toPath: dirName + " / " + " subdir2 " + " / " + " subdir3.delete " ) )
1156+ XCTAssertNoThrow ( try $0. linkItem ( atPath: dirName + " / " + " subdir2 " + " / " + " subdir3.delete " , toPath: dirName + " / " + " subdir2 " + " / " + " subdir3.delete.lnk " ) )
1157+ XCTAssertNoThrow ( try $0. linkItem ( atPath: dirName + " / " + " subdir2 " , toPath: dirName + " / " + " subdir2.lnk " ) )
1158+ XCTAssertNoThrow ( try $0. removeItem ( atPath: dirName + " / " + " subdir2 " + " / " + " subdir3.delete " + " / " + " somefile3 " ) )
1159+ XCTAssertNoThrow ( try $0. removeItem ( atPath: dirName + " / " + " subdir2 " ) )
1160+ }
1161+ #endif
1162+ }
10751163}
0 commit comments