@@ -102,3 +102,117 @@ fn test_emoji_handling() {
102102 . succeeds ( )
103103 . stdout_is ( "/🌟/emoji/path\u{0} " ) ;
104104}
105+
106+ #[ test]
107+ fn test_trailing_dot ( ) {
108+ // Basic case: path ending with /. should return parent without stripping last component
109+ // This matches GNU coreutils behavior and fixes issue #8910
110+ new_ucmd ! ( )
111+ . arg ( "/home/dos/." )
112+ . succeeds ( )
113+ . stdout_is ( "/home/dos\n " ) ;
114+
115+ // Root with dot
116+ new_ucmd ! ( ) . arg ( "/." ) . succeeds ( ) . stdout_is ( "/\n " ) ;
117+
118+ // Relative path with /.
119+ new_ucmd ! ( ) . arg ( "hello/." ) . succeeds ( ) . stdout_is ( "hello\n " ) ;
120+
121+ // Deeper path with /.
122+ new_ucmd ! ( )
123+ . arg ( "/foo/bar/baz/." )
124+ . succeeds ( )
125+ . stdout_is ( "/foo/bar/baz\n " ) ;
126+ }
127+
128+ #[ test]
129+ fn test_trailing_dot_with_zero_flag ( ) {
130+ // Test that -z flag works correctly with /. paths
131+ new_ucmd ! ( )
132+ . arg ( "-z" )
133+ . arg ( "/home/dos/." )
134+ . succeeds ( )
135+ . stdout_is ( "/home/dos\u{0} " ) ;
136+
137+ new_ucmd ! ( )
138+ . arg ( "--zero" )
139+ . arg ( "/." )
140+ . succeeds ( )
141+ . stdout_is ( "/\u{0} " ) ;
142+ }
143+
144+ #[ test]
145+ fn test_trailing_dot_multiple_paths ( ) {
146+ // Test multiple paths, some with /. suffix
147+ new_ucmd ! ( )
148+ . args ( & [ "/home/dos/." , "/var/log" , "/tmp/." ] )
149+ . succeeds ( )
150+ . stdout_is ( "/home/dos\n /var\n /tmp\n " ) ;
151+ }
152+
153+ #[ test]
154+ fn test_trailing_dot_edge_cases ( ) {
155+ // Double slash before dot (should still work)
156+ new_ucmd ! ( )
157+ . arg ( "/home/dos//." )
158+ . succeeds ( )
159+ . stdout_is ( "/home/dos/\n " ) ;
160+
161+ // Path with . in middle (should use normal logic)
162+ new_ucmd ! ( )
163+ . arg ( "/path/./to/file" )
164+ . succeeds ( )
165+ . stdout_is ( "/path/./to\n " ) ;
166+ }
167+
168+ #[ test]
169+ fn test_trailing_dot_emoji ( ) {
170+ // Emoji paths with /. suffix
171+ new_ucmd ! ( )
172+ . arg ( "/🌍/path/." )
173+ . succeeds ( )
174+ . stdout_is ( "/🌍/path\n " ) ;
175+
176+ new_ucmd ! ( ) . arg ( "/🎉/🚀/." ) . succeeds ( ) . stdout_is ( "/🎉/🚀\n " ) ;
177+ }
178+
179+ #[ test]
180+ #[ cfg( unix) ]
181+ fn test_trailing_dot_non_utf8 ( ) {
182+ use std:: ffi:: OsStr ;
183+ use std:: os:: unix:: ffi:: OsStrExt ;
184+
185+ // Create a path with non-UTF-8 bytes ending in /.
186+ let non_utf8_bytes = b"/test_\xFF \xFE /." ;
187+ let non_utf8_path = OsStr :: from_bytes ( non_utf8_bytes) ;
188+
189+ // Test that dirname handles non-UTF-8 paths with /. suffix
190+ let result = new_ucmd ! ( ) . arg ( non_utf8_path) . succeeds ( ) ;
191+
192+ // The output should be the path without the /. suffix
193+ let output = result. stdout_str_lossy ( ) ;
194+ assert ! ( !output. is_empty( ) ) ;
195+ assert ! ( output. contains( "test_" ) ) ;
196+ // Should not contain the . at the end
197+ assert ! ( !output. trim( ) . ends_with( '.' ) ) ;
198+ }
199+
200+ #[ test]
201+ fn test_existing_behavior_preserved ( ) {
202+ // Ensure we didn't break existing test cases
203+ // These tests verify backward compatibility
204+
205+ // Normal paths without /. should work as before
206+ new_ucmd ! ( ) . arg ( "/home/dos" ) . succeeds ( ) . stdout_is ( "/home\n " ) ;
207+
208+ new_ucmd ! ( )
209+ . arg ( "/home/dos/" )
210+ . succeeds ( )
211+ . stdout_is ( "/home\n " ) ;
212+
213+ // Parent directory references
214+ new_ucmd ! ( )
215+ . arg ( "/home/dos/.." )
216+ . succeeds ( )
217+ . stdout_is ( "/home/dos\n " ) ;
218+ }
0 commit comments