@@ -1072,4 +1072,92 @@ final class FileManagerTests : XCTestCase {
1072
1072
XCTAssertEqual ( $0. contents ( atPath: " a \u{301} /test " ) , data)
1073
1073
}
1074
1074
}
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
+ }
1075
1163
}
0 commit comments