@@ -28,6 +28,57 @@ public class PathUtilitiesTests
28
28
{ " file.extension" , ".extension" }
29
29
} ;
30
30
31
+ public static TheoryData < string , string ? > TestData_GetDirectoryName => new ( )
32
+ {
33
+ { "." , "" } ,
34
+ { ".." , "" } ,
35
+ { "baz" , "" } ,
36
+ { Path . Combine ( "dir" , "baz" ) , "dir" } ,
37
+ { "dir.foo" + Path . AltDirectorySeparatorChar + "baz.txt" , "dir.foo" } ,
38
+ { Path . Combine ( "dir" , "baz" , "bar" ) , Path . Combine ( "dir" , "baz" ) } ,
39
+ { Path . Combine ( ".." , ".." , "files.txt" ) , Path . Combine ( ".." , ".." ) } ,
40
+ { Path . DirectorySeparatorChar + "foo" , Path . DirectorySeparatorChar . ToString ( ) } ,
41
+ { Path . DirectorySeparatorChar . ToString ( ) , null }
42
+ } ;
43
+
44
+ public static TheoryData < string , string ? > TestData_GetDirectoryName_Windows => new ( )
45
+ {
46
+ { @"C:\" , null } ,
47
+ { @"C:/" , null } ,
48
+ { @"C:" , null } ,
49
+ { @"dir\\baz" , "dir" } ,
50
+ { @"dir//baz" , "dir" } ,
51
+ { @"C:\foo" , @"C:\" } ,
52
+ { @"C:foo" , "C:" }
53
+ } ;
54
+
55
+ public static TheoryData < string > TestData_Spaces =>
56
+ [
57
+ " " ,
58
+ " "
59
+ ] ;
60
+
61
+ public static TheoryData < string > TestData_EmbeddedNull =>
62
+ [
63
+ "a\0 b"
64
+ ] ;
65
+
66
+ public static TheoryData < string > TestData_ControlChars =>
67
+ [
68
+ "\t " ,
69
+ "\r \n " ,
70
+ "\b " ,
71
+ "\v " ,
72
+ "\n "
73
+ ] ;
74
+
75
+ public static TheoryData < string > TestData_UnicodeWhiteSpace =>
76
+ [
77
+ "\u00A0 " , // Non-breaking Space
78
+ "\u2028 " , // Line separator
79
+ "\u2029 " , // Paragraph separator
80
+ ] ;
81
+
31
82
[ Theory , MemberData ( nameof ( TestData_GetExtension ) ) ]
32
83
public void GetExtension ( string path , string expected )
33
84
{
@@ -121,57 +172,100 @@ public static void IsPathFullyQualified_Unix_Valid(string path)
121
172
}
122
173
123
174
[ Fact ]
124
- public static void GetDirectoryName_EmptyString ( )
175
+ public void GetDirectoryName_NullReturnsNull ( )
125
176
{
126
- AssertEqual ( @"" , PathUtilities . GetDirectoryName ( @"" ) ) ;
177
+ Assert . Null ( PathUtilities . GetDirectoryName ( null ) ) ;
178
+ }
179
+
180
+ [ Theory , MemberData ( nameof ( TestData_GetDirectoryName ) ) ]
181
+ public void GetDirectoryName ( string path , string ? expected )
182
+ {
183
+ Assert . Equal ( expected , PathUtilities . GetDirectoryName ( path ) ) ;
127
184
}
128
185
129
186
[ Fact ]
130
- public static void GetDirectoryName_FilenameOnly ( )
187
+ public void GetDirectoryName_CurrentDirectory ( )
131
188
{
132
- AssertEqual ( @"" , PathUtilities . GetDirectoryName ( @"Foo.txt" ) ) ;
189
+ var curDir = Directory . GetCurrentDirectory ( ) ;
190
+ Assert . Equal ( curDir , PathUtilities . GetDirectoryName ( Path . Combine ( curDir , "baz" ) ) ) ;
191
+
192
+ Assert . Null ( PathUtilities . GetDirectoryName ( Path . GetPathRoot ( curDir ) ) ) ;
193
+ Assert . True ( PathUtilities . GetDirectoryName ( Path . GetPathRoot ( curDir ) . AsSpan ( ) ) . IsEmpty ) ;
133
194
}
134
195
135
196
[ Fact ]
136
- public static void GetDirectoryName_NetworkPath_AlternateSeparator ( )
197
+ public void GetDirectoryName_EmptyReturnsNull ( )
137
198
{
138
- AssertEqual ( "//Server/Path" , PathUtilities . GetDirectoryName ( "//Server/Path/Foo.txt" ) ) ;
199
+ // In .NET Framework this throws argument exception
200
+ Assert . Null ( PathUtilities . GetDirectoryName ( string . Empty ) ) ;
139
201
}
140
202
141
- [ ConditionalFact ( Is . AnyUnix ) ]
142
- public static void GetDirectoryName_FileAtRoot_Uni ( )
203
+ [ ConditionalTheory ( Is . Windows ) ]
204
+ [ MemberData ( nameof ( TestData_Spaces ) ) ]
205
+ public void GetDirectoryName_Spaces_Windows ( string path )
143
206
{
144
- AssertEqual ( @"/" , PathUtilities . GetDirectoryName ( @"//Foo.txt" ) ) ;
207
+ // In Windows spaces are eaten by Win32, making them effectively empty
208
+ Assert . Null ( PathUtilities . GetDirectoryName ( path ) ) ;
145
209
}
146
210
147
- [ ConditionalFact ( Is . Windows ) ]
148
- public static void GetDirectoryName_FileAtRoot ( )
211
+ [ ConditionalTheory ( Is . AnyUnix ) ]
212
+ [ MemberData ( nameof ( TestData_Spaces ) ) ]
213
+ public void GetDirectoryName_Spaces_Unix ( string path )
149
214
{
150
- AssertEqual ( @"" , PathUtilities . GetDirectoryName ( @"\\Foo.txt" ) ) ;
215
+ Assert . Empty ( PathUtilities . GetDirectoryName ( path ) ) ;
151
216
}
152
217
153
- [ ConditionalFact ( Is . Windows ) ]
154
- public static void GetDirectoryName_FileAtRoot_AlternateSeparator ( )
218
+ [ Theory , MemberData ( nameof ( TestData_Spaces ) ) ]
219
+ public void GetDirectoryName_Span_Spaces ( string path )
155
220
{
156
- AssertEqual ( @"" , PathUtilities . GetDirectoryName ( @"//Foo.txt" ) ) ;
221
+ Assert . True ( PathUtilities . GetDirectoryName ( path . AsSpan ( ) ) . IsEmpty ) ;
157
222
}
158
223
159
- [ ConditionalFact ( Is . Windows ) ]
160
- public static void GetDirectoryName_Windows ( )
224
+ [ Theory ]
225
+ [ MemberData ( nameof ( TestData_EmbeddedNull ) ) ]
226
+ [ MemberData ( nameof ( TestData_ControlChars ) ) ]
227
+ [ MemberData ( nameof ( TestData_UnicodeWhiteSpace ) ) ]
228
+ public void GetDirectoryName_NetFxInvalid ( string path )
161
229
{
162
- AssertEqual ( @"C:\Server" , PathUtilities . GetDirectoryName ( @"C:\Server\Foo.txt" ) ) ;
230
+ Assert . Empty ( PathUtilities . GetDirectoryName ( path ) ) ;
231
+ Assert . Equal ( path , PathUtilities . GetDirectoryName ( PathCombine ( path , path ) ) ) ;
232
+ Assert . True ( PathUtilities . GetDirectoryName ( path . AsSpan ( ) ) . IsEmpty ) ;
233
+ AssertEqual ( path , PathUtilities . GetDirectoryName ( PathCombine ( path , path ) . AsSpan ( ) ) ) ;
234
+
235
+ // Path.Combine on net472 throws on invalid path characters, so have to do this manually.
236
+ static string PathCombine ( string path1 , string path2 )
237
+ {
238
+ return $ """ { path1 } \{ path2 } """ ;
239
+ }
163
240
}
164
241
165
- [ ConditionalFact ( Is . Windows ) ]
166
- public static void GetDirectoryName_LocalPath_DoubleSlash ( )
242
+ [ Theory , MemberData ( nameof ( TestData_GetDirectoryName ) ) ]
243
+ public void GetDirectoryName_Span ( string path , string ? expected )
167
244
{
168
- AssertEqual ( @"C:\Server" , PathUtilities . GetDirectoryName ( @"C:\Server\\Foo.txt" ) ) ;
245
+ AssertEqual ( expected ?? ReadOnlySpan < char > . Empty , PathUtilities . GetDirectoryName ( path . AsSpan ( ) ) ) ;
169
246
}
170
247
171
- [ ConditionalFact ( Is . Windows ) ]
172
- public static void GetDirectoryName_NetworkPath ( )
248
+ [ Fact ]
249
+ public void GetDirectoryName_Span_CurrentDirectory ( )
250
+ {
251
+ var curDir = Directory . GetCurrentDirectory ( ) ;
252
+ AssertEqual ( curDir , PathUtilities . GetDirectoryName ( Path . Combine ( curDir , "baz" ) . AsSpan ( ) ) ) ;
253
+ Assert . True ( PathUtilities . GetDirectoryName ( Path . GetPathRoot ( curDir ) . AsSpan ( ) ) . IsEmpty ) ;
254
+ }
255
+
256
+ [ Theory ]
257
+ [ InlineData ( @" C:\dir/baz" , @" C:\dir" ) ]
258
+ public void GetDirectoryName_SkipSpaces ( string path , string expected )
259
+ {
260
+ // We no longer trim leading spaces for any path
261
+ Assert . Equal ( expected , PathUtilities . GetDirectoryName ( path ) ) ;
262
+ }
263
+
264
+ [ ConditionalTheory ( Is . Windows ) ]
265
+ [ MemberData ( nameof ( TestData_GetDirectoryName_Windows ) ) ]
266
+ public void GetDirectoryName_Windows ( string path , string ? expected )
173
267
{
174
- AssertEqual ( @"\\Server\Path" , PathUtilities . GetDirectoryName ( @"\\Server\Path\Foo.txt" ) ) ;
268
+ Assert . Equal ( expected , Path . GetDirectoryName ( path ) ) ;
175
269
}
176
270
177
271
private static void AssertEqual ( ReadOnlySpan < char > expected , ReadOnlySpan < char > actual )
0 commit comments