1010using System . Reflection ;
1111using System . Threading . Tasks ;
1212using FluentAssertions ;
13+ using FluentAssertions . Execution ;
1314using Xunit ;
1415
1516namespace System . CommandLine . Tests . Binding
@@ -251,6 +252,28 @@ public async Task When_argument_type_is_not_known_until_binding_then_int_paramet
251252 received . Should ( ) . Be ( 123 ) ;
252253 }
253254
255+ [ Fact ]
256+ public void When_argument_type_is_more_specific_than_parameter_type_then_parameter_is_bound_correctly ( )
257+ {
258+ FileSystemInfo received = null ;
259+
260+ var root = new RootCommand
261+ {
262+ new Option < DirectoryInfo > ( "-f" )
263+ } ;
264+ root . Handler = CommandHandler . Create < FileSystemInfo > ( f => received = f ) ;
265+ var path = $ "{ Directory . GetCurrentDirectory ( ) } { Path . DirectorySeparatorChar } ";
266+
267+ root . Invoke ( $ "-f { path } ") ;
268+
269+ received . Should ( )
270+ . BeOfType < DirectoryInfo > ( )
271+ . Which
272+ . FullName
273+ . Should ( )
274+ . Be ( path ) ;
275+ }
276+
254277 [ Theory ]
255278 [ InlineData ( typeof ( ClassWithCtorParameter < int > ) , false ) ]
256279 [ InlineData ( typeof ( ClassWithCtorParameter < int > ) , true ) ]
@@ -260,10 +283,23 @@ public async Task When_argument_type_is_not_known_until_binding_then_int_paramet
260283 [ InlineData ( typeof ( ClassWithCtorParameter < string > ) , true ) ]
261284 [ InlineData ( typeof ( ClassWithSetter < string > ) , false ) ]
262285 [ InlineData ( typeof ( ClassWithSetter < string > ) , true ) ]
286+
263287 [ InlineData ( typeof ( FileInfo ) , false ) ]
264288 [ InlineData ( typeof ( FileInfo ) , true ) ]
265289 [ InlineData ( typeof ( FileInfo [ ] ) , false ) ]
266290 [ InlineData ( typeof ( FileInfo [ ] ) , true ) ]
291+
292+ [ InlineData ( typeof ( DirectoryInfo ) , false ) ]
293+ [ InlineData ( typeof ( DirectoryInfo ) , true ) ]
294+ [ InlineData ( typeof ( DirectoryInfo [ ] ) , false ) ]
295+ [ InlineData ( typeof ( DirectoryInfo [ ] ) , true ) ]
296+
297+ [ InlineData ( typeof ( FileSystemInfo ) , true , nameof ( ExistingFile ) ) ]
298+ [ InlineData ( typeof ( FileSystemInfo ) , true , nameof ( ExistingDirectory ) ) ]
299+ [ InlineData ( typeof ( FileSystemInfo ) , true , nameof ( NonexistentPathWithTrailingSlash ) ) ]
300+ [ InlineData ( typeof ( FileSystemInfo ) , true , nameof ( NonexistentPathWithTrailingAltSlash ) ) ]
301+ [ InlineData ( typeof ( FileSystemInfo ) , true , nameof ( NonexistentPathWithoutTrailingSlash ) ) ]
302+
267303 [ InlineData ( typeof ( string [ ] ) , false ) ]
268304 [ InlineData ( typeof ( string [ ] ) , true ) ]
269305 [ InlineData ( typeof ( List < string > ) , false ) ]
@@ -274,9 +310,10 @@ public async Task When_argument_type_is_not_known_until_binding_then_int_paramet
274310 [ InlineData ( typeof ( List < int > ) , true ) ]
275311 public async Task Handler_method_receives_option_arguments_bound_to_the_specified_type (
276312 Type type ,
277- bool useDelegate )
313+ bool useDelegate ,
314+ string variation = null )
278315 {
279- var testCase = _bindingCases [ type ] ;
316+ var testCase = _bindingCases [ ( type , variation ) ] ;
280317
281318 ICommandHandler handler ;
282319 if ( ! useDelegate )
@@ -318,7 +355,7 @@ public async Task Handler_method_receives_option_arguments_bound_to_the_specifie
318355
319356 var boundValue = ( ( BoundValueCapturer ) invocationContext . InvocationResult ) . BoundValue ;
320357
321- boundValue . Should ( ) . BeOfType ( testCase . ParameterType ) ;
358+ boundValue . Should ( ) . BeAssignableTo ( testCase . ParameterType ) ;
322359
323360 testCase . AssertBoundValue ( boundValue ) ;
324361 }
@@ -443,14 +480,93 @@ public void Apply(InvocationContext context)
443480 o => o . Value . Should ( ) . Be ( "123" ) ) ,
444481
445482 BindingTestCase . Create < FileInfo > (
446- Path . Combine ( Directory . GetCurrentDirectory ( ) , "file1.txt" ) ,
447- o => o . FullName . Should ( ) . Be ( Path . Combine ( Directory . GetCurrentDirectory ( ) , "file1.txt" ) ) ) ,
483+ Path . Combine ( ExistingDirectory ( ) , "file1.txt" ) ,
484+ o => o . FullName
485+ . Should ( )
486+ . Be ( Path . Combine ( ExistingDirectory ( ) , "file1.txt" ) ) ) ,
448487
449488 BindingTestCase . Create < FileInfo [ ] > (
450- $ "{ Path . Combine ( Directory . GetCurrentDirectory ( ) , "file1.txt" ) } { Path . Combine ( Directory . GetCurrentDirectory ( ) , "file2.txt" ) } ",
489+ $ "{ Path . Combine ( ExistingDirectory ( ) , "file1.txt" ) } { Path . Combine ( ExistingDirectory ( ) , "file2.txt" ) } ",
451490 o => o . Select ( f => f . FullName )
452491 . Should ( )
453- . BeEquivalentTo ( new [ ] { Path . Combine ( Directory . GetCurrentDirectory ( ) , "file1.txt" ) , Path . Combine ( Directory . GetCurrentDirectory ( ) , "file2.txt" ) } ) ) ,
492+ . BeEquivalentTo ( new [ ]
493+ {
494+ Path . Combine ( ExistingDirectory ( ) , "file1.txt" ) ,
495+ Path . Combine ( ExistingDirectory ( ) , "file2.txt" )
496+ } ) ) ,
497+
498+ BindingTestCase . Create < DirectoryInfo > (
499+ ExistingDirectory ( ) ,
500+ fsi => fsi . Should ( )
501+ . BeOfType < DirectoryInfo > ( )
502+ . Which
503+ . FullName
504+ . Should ( )
505+ . Be ( ExistingDirectory ( ) ) ) ,
506+
507+ BindingTestCase . Create < DirectoryInfo [ ] > (
508+ $ "{ ExistingDirectory ( ) } { ExistingDirectory ( ) } ",
509+ fsi => fsi . Should ( )
510+ . BeAssignableTo < IEnumerable < DirectoryInfo > > ( )
511+ . Which
512+ . Select ( d => d . FullName )
513+ . Should ( )
514+ . BeEquivalentTo ( new [ ]
515+ {
516+ ExistingDirectory ( ) ,
517+ ExistingDirectory ( )
518+ } ) ) ,
519+
520+ BindingTestCase . Create < FileSystemInfo > (
521+ ExistingFile ( ) ,
522+ fsi => fsi . Should ( )
523+ . BeOfType < FileInfo > ( )
524+ . Which
525+ . FullName
526+ . Should ( )
527+ . Be ( ExistingFile ( ) ) ,
528+ variationName : nameof ( ExistingFile ) ) ,
529+
530+ BindingTestCase . Create < FileSystemInfo > (
531+ ExistingDirectory ( ) ,
532+ fsi => fsi . Should ( )
533+ . BeOfType < DirectoryInfo > ( )
534+ . Which
535+ . FullName
536+ . Should ( )
537+ . Be ( ExistingDirectory ( ) ) ,
538+ variationName : nameof ( ExistingDirectory ) ) ,
539+
540+ BindingTestCase . Create < FileSystemInfo > (
541+ NonexistentPathWithTrailingSlash ( ) ,
542+ fsi => fsi . Should ( )
543+ . BeOfType < DirectoryInfo > ( )
544+ . Which
545+ . FullName
546+ . Should ( )
547+ . Be ( NonexistentPathWithTrailingSlash ( ) ) ,
548+ variationName : nameof ( NonexistentPathWithTrailingSlash ) ) ,
549+
550+ BindingTestCase . Create < FileSystemInfo > (
551+ NonexistentPathWithTrailingAltSlash ( ) ,
552+ fsi => fsi . Should ( )
553+ . BeOfType < DirectoryInfo > ( )
554+ . Which
555+ . FullName
556+ . Should ( )
557+ . Be ( NonexistentPathWithTrailingSlash ( ) ,
558+ "DirectoryInfo replaces Path.AltDirectorySeparatorChar with Path.DirectorySeparatorChar on Windows" ) ,
559+ variationName : nameof ( NonexistentPathWithTrailingAltSlash ) ) ,
560+
561+ BindingTestCase . Create < FileSystemInfo > (
562+ NonexistentPathWithoutTrailingSlash ( ) ,
563+ fsi => fsi . Should ( )
564+ . BeOfType < FileInfo > ( )
565+ . Which
566+ . FullName
567+ . Should ( )
568+ . Be ( NonexistentPathWithoutTrailingSlash ( ) ) ,
569+ variationName : nameof ( NonexistentPathWithoutTrailingSlash ) ) ,
454570
455571 BindingTestCase . Create < string [ ] > (
456572 "one two" ,
@@ -468,5 +584,23 @@ public void Apply(InvocationContext context)
468584 "1 2" ,
469585 o => o . Should ( ) . BeEquivalentTo ( new List < int > { 1 , 2 } ) )
470586 } ;
587+
588+ private static string NonexistentPathWithoutTrailingSlash ( )
589+ {
590+ return Path . Combine (
591+ ExistingDirectory ( ) ,
592+ "does-not-exist" ) ;
593+ }
594+
595+ private static string NonexistentPathWithTrailingSlash ( ) =>
596+ NonexistentPathWithoutTrailingSlash ( ) + Path . DirectorySeparatorChar ;
597+ private static string NonexistentPathWithTrailingAltSlash ( ) =>
598+ NonexistentPathWithoutTrailingSlash ( ) + Path . AltDirectorySeparatorChar ;
599+
600+ private static string ExistingFile ( ) =>
601+ Directory . GetFiles ( ExistingDirectory ( ) ) . FirstOrDefault ( ) ??
602+ throw new AssertionFailedException ( "No files found in current directory" ) ;
603+
604+ private static string ExistingDirectory ( ) => Directory . GetCurrentDirectory ( ) ;
471605 }
472606}
0 commit comments