1+ // This source file is part of the Swift.org open source project
2+ //
3+ // Copyright (c) 2025 Apple Inc. and the Swift project authors
4+ // Licensed under Apache License v2.0 with Runtime Library Exception
5+ //
6+ // See http://swift.org/LICENSE.txt for license information
7+ // See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
8+ //
9+
10+ import XCTest
11+
12+ #if os(Windows)
13+ // Import Windows C functions
14+ import WinSDK
15+
16+ // Declare _NS_getcwd function for testing
17+ @_silgen_name ( " _NS_getcwd " )
18+ func _NS_getcwd( _ buffer: UnsafeMutablePointer < CChar > , _ size: Int ) -> UnsafeMutablePointer < CChar > ?
19+ #endif
20+
21+ class TestCFPlatformGetcwd : XCTestCase {
22+
23+ #if os(Windows)
24+ func test_NS_getcwd_UNC_prefix_stripping( ) {
25+ // Test that _NS_getcwd properly strips UNC long path prefixes using PathCchStripPrefix
26+
27+ // Create a temporary directory to work with
28+ let fm = FileManager . default
29+ let tempDir = fm. temporaryDirectory. appendingPathComponent ( " test_getcwd_ \( UUID ( ) . uuidString) " )
30+
31+ do {
32+ try fm. createDirectory ( at: tempDir, withIntermediateDirectories: true )
33+ defer { try ? fm. removeItem ( at: tempDir) }
34+
35+ // Get original directory for restoration
36+ var originalBuffer = [ CChar] ( repeating: 0 , count: Int ( MAX_PATH) )
37+ guard _NS_getcwd ( & originalBuffer, originalBuffer. count) != nil else {
38+ XCTFail ( " Failed to get original directory " )
39+ return
40+ }
41+ // Create string from buffer using the traditional approach
42+ let originalDir = originalBuffer. withUnsafeBufferPointer { buffer in
43+ return String ( cString: buffer. baseAddress!)
44+ }
45+
46+ defer {
47+ // Restore original directory
48+ _ = originalDir. withCString { _chdir ( $0) }
49+ }
50+
51+ // Test with UNC long path prefix \\?\
52+ let uncLongPathPrefix = " \\ \\ ? \\ " + tempDir. path
53+ let uncLongPathCString = uncLongPathPrefix. cString ( using: . utf8) !
54+ let uncChdirResult = uncLongPathCString. withUnsafeBufferPointer { buffer in
55+ return _chdir ( buffer. baseAddress!)
56+ }
57+ XCTAssertEqual ( uncChdirResult, 0 , " Failed to change directory using UNC long path prefix " )
58+
59+ // Test _NS_getcwd directly after changing to UNC prefixed path
60+ var buffer = [ CChar] ( repeating: 0 , count: Int ( MAX_PATH) )
61+ guard let result = _NS_getcwd ( & buffer, buffer. count) else {
62+ XCTFail ( " _NS_getcwd returned null " )
63+ return
64+ }
65+
66+ let currentDir = String ( cString: result)
67+
68+ // Verify that the path doesn't contain UNC prefixes (this is the key test!)
69+ XCTAssertFalse ( currentDir. hasPrefix ( " \\ \\ ? \\ " ) , " Current directory path should not contain \\ \\ ? \\ UNC prefix after stripping " )
70+
71+ // Verify that we can still access the directory (it's a valid path)
72+ XCTAssertTrue ( fm. fileExists ( atPath: currentDir) , " Current directory path should be valid and accessible " )
73+
74+ // Verify the path ends with our test directory name
75+ XCTAssertTrue ( currentDir. hasSuffix ( tempDir. lastPathComponent) , " Current directory should end with our test directory name " )
76+
77+ // Test with a deeper nested directory using UNC prefix to ensure stripping works with longer paths
78+ let deepDir = tempDir. appendingPathComponent ( " level1 " ) . appendingPathComponent ( " level2 " ) . appendingPathComponent ( " level3 " )
79+ try fm. createDirectory ( at: deepDir, withIntermediateDirectories: true )
80+
81+ let deepUncPath = " \\ \\ ? \\ " + deepDir. path
82+ let deepUncCString = deepUncPath. cString ( using: . utf8) !
83+ let deepChdirResult = deepUncCString. withUnsafeBufferPointer { buffer in
84+ return _chdir ( buffer. baseAddress!)
85+ }
86+ XCTAssertEqual ( deepChdirResult, 0 , " Failed to change to deep directory with UNC prefix " )
87+
88+ // Test _NS_getcwd with deep UNC prefixed path
89+ var deepBuffer = [ CChar] ( repeating: 0 , count: Int ( MAX_PATH) )
90+ guard let deepResult = _NS_getcwd ( & deepBuffer, deepBuffer. count) else {
91+ XCTFail ( " _NS_getcwd returned null for deep UNC path " )
92+ return
93+ }
94+
95+ let deepCurrentDir = String ( cString: deepResult)
96+
97+ // Verify UNC prefixes are stripped from deeper paths too
98+ XCTAssertFalse ( deepCurrentDir. hasPrefix ( " \\ \\ ? \\ " ) , " Deep directory path should not contain \\ \\ ? \\ UNC prefix after stripping " )
99+ XCTAssertTrue ( fm. fileExists ( atPath: deepCurrentDir) , " Deep directory path should be valid and accessible " )
100+ XCTAssertTrue ( deepCurrentDir. hasSuffix ( " level3 " ) , " Deep directory should end with level3 " )
101+
102+ } catch {
103+ XCTFail ( " Failed to set up test environment: \( error) " )
104+ }
105+ }
106+
107+ func test_NS_getcwd_small_buffer( ) {
108+ // Test that _NS_getcwd handles small buffer correctly
109+ var smallBuffer = [ CChar] ( repeating: 0 , count: 1 )
110+ let result = _NS_getcwd ( & smallBuffer, smallBuffer. count)
111+ // This should either return null or handle the small buffer gracefully
112+ // The exact behavior depends on the implementation, but it shouldn't crash
113+ XCTAssertTrue ( result == nil || result != nil , " Function should not crash with small buffer " )
114+ }
115+ #endif
116+ }
0 commit comments