@@ -38,7 +38,7 @@ public SDKLookup(SharedTestState sharedState)
3838 }
3939
4040 [ Fact ]
41- public void SdkLookup_Global_Json_Single_Digit_Patch_Rollup ( )
41+ public void GlobalJson_SingleDigitPatch ( )
4242 {
4343 // Set specified SDK version = 9999.3.4-global-dummy
4444 string requestedVersion = "9999.3.4-global-dummy" ;
@@ -133,7 +133,7 @@ public void SdkLookup_Global_Json_Single_Digit_Patch_Rollup()
133133 }
134134
135135 [ Fact ]
136- public void SdkLookup_Global_Json_Two_Part_Patch_Rollup ( )
136+ public void GlobalJson_TwoPartPatch ( )
137137 {
138138 // Set specified SDK version = 9999.3.304-global-dummy
139139 string requestedVersion = "9999.3.304-global-dummy" ;
@@ -226,7 +226,7 @@ public void SdkLookup_Global_Json_Two_Part_Patch_Rollup()
226226 }
227227
228228 [ Fact ]
229- public void SdkLookup_Negative_Version ( )
229+ public void NegativeVersion ( )
230230 {
231231 GlobalJson . CreateEmpty ( SharedState . CurrentWorkingDir ) ;
232232
@@ -257,7 +257,7 @@ public void SdkLookup_Negative_Version()
257257 }
258258
259259 [ Fact ]
260- public void SdkLookup_Must_Pick_The_Highest_Semantic_Version ( )
260+ public void PickHighestSemanticVersion ( )
261261 {
262262 GlobalJson . CreateEmpty ( SharedState . CurrentWorkingDir ) ;
263263
@@ -355,7 +355,7 @@ public void SdkLookup_Must_Pick_The_Highest_Semantic_Version()
355355 [ InlineData ( "Latestfeature" ) ]
356356 [ InlineData ( "latestMINOR" ) ]
357357 [ InlineData ( "latESTMajor" ) ]
358- public void It_allows_case_insensitive_roll_forward_policy_names ( string rollForward )
358+ public void RollForwardPolicy_CaseInsensitive ( string rollForward )
359359 {
360360 const string Requested = "9999.0.100" ;
361361 AddAvailableSdkVersions ( Requested ) ;
@@ -369,7 +369,7 @@ public void It_allows_case_insensitive_roll_forward_policy_names(string rollForw
369369
370370 [ Theory ]
371371 [ MemberData ( nameof ( InvalidGlobalJsonData ) ) ]
372- public void It_falls_back_to_latest_sdk_for_invalid_global_json ( string globalJsonContents , string [ ] messages )
372+ public void InvalidGlobalJson_FallsBackToLatestSdk ( string globalJsonContents , string [ ] messages )
373373 {
374374 AddAvailableSdkVersions ( "9999.0.100" , "9999.0.300-dummy.9" , "9999.1.402" ) ;
375375
@@ -387,7 +387,7 @@ public void It_falls_back_to_latest_sdk_for_invalid_global_json(string globalJso
387387
388388 [ Theory ]
389389 [ MemberData ( nameof ( SdkRollForwardData ) ) ]
390- public void It_rolls_forward_as_expected ( string policy , string requested , bool allowPrerelease , string expected , string [ ] installed )
390+ public void RollForward ( string policy , string requested , bool allowPrerelease , string expected , string [ ] installed )
391391 {
392392 AddAvailableSdkVersions ( installed ) ;
393393
@@ -409,7 +409,7 @@ public void It_rolls_forward_as_expected(string policy, string requested, bool a
409409 }
410410
411411 [ Fact ]
412- public void It_uses_latest_stable_sdk_if_allow_prerelease_is_false ( )
412+ public void AllowPrereleaseFalse_UseLatestRelease ( )
413413 {
414414 var installed = new string [ ] {
415415 "9999.1.702" ,
@@ -437,6 +437,172 @@ public void It_uses_latest_stable_sdk_if_allow_prerelease_is_false()
437437 . And . HaveStdErrContaining ( $ "SDK path resolved to [{ Path . Combine ( ExecutableDotNet . BinPath , "sdk" , ExpectedVersion ) } ]") ;
438438 }
439439
440+ [ Fact ]
441+ public void GlobalJson_Paths ( )
442+ {
443+ GlobalJson . Sdk sdk = new ( ) { Paths = [ ] } ;
444+ string globalJsonPath = GlobalJson . Write ( SharedState . CurrentWorkingDir , sdk ) ;
445+
446+ // Add SDK versions
447+ AddAvailableSdkVersions ( "9999.0.4" ) ;
448+
449+ // Paths: none
450+ // Exe: 9999.0.4
451+ // Expected: no SDKs found
452+ RunTest ( )
453+ . Should ( ) . Fail ( )
454+ . And . FindAnySdk ( false )
455+ . And . HaveStdErrContaining ( $ "Empty search paths specified in global.json file: { globalJsonPath } ") ;
456+
457+ sdk . Paths = [ GlobalJson . HostSdkPath ] ;
458+ globalJsonPath = GlobalJson . Write ( SharedState . CurrentWorkingDir , sdk ) ;
459+
460+ // Paths: $host$
461+ // Exe: 9999.0.4
462+ // Expected: 9999.0.4 from exe dir
463+ RunTest ( )
464+ . Should ( ) . Pass ( )
465+ . And . HaveStdErrContaining ( ExpectedResolvedSdkOutput ( "9999.0.4" ) ) ;
466+
467+ using TestArtifact custom = TestArtifact . Create ( "sdkPath" ) ;
468+ AddSdkToCustomPath ( custom . Location , "9999.0.4" ) ;
469+ sdk . Paths = [ custom . Location ] ;
470+ globalJsonPath = GlobalJson . Write ( SharedState . CurrentWorkingDir , sdk ) ;
471+
472+ // Paths: custom (absolute)
473+ // Custom: 9999.0.4
474+ // Exe: 9999.0.4
475+ // Expected: 9999.0.4 from custom dir
476+ RunTest ( )
477+ . Should ( ) . Pass ( )
478+ . And . HaveStdErrContaining ( ExpectedResolvedSdkOutput ( "9999.0.4" , custom . Location ) ) ;
479+
480+ string relativePath = Path . GetRelativePath ( SharedState . CurrentWorkingDir , custom . Location ) ;
481+ sdk . Paths = [ relativePath ] ;
482+ GlobalJson . Write ( SharedState . CurrentWorkingDir , sdk ) ;
483+
484+ // Paths: custom (relative, outside current directory)
485+ // Custom: 9999.0.4
486+ // Exe: 9999.0.4
487+ // Expected: 9999.0.4 from custom dir
488+ RunTest ( )
489+ . Should ( ) . Pass ( )
490+ . And . HaveStdErrContaining ( ExpectedResolvedSdkOutput ( "9999.0.4" , custom . Location ) ) ;
491+
492+ string underCurrent = SharedState . CurrentWorkingDirArtifact . GetUniqueSubdirectory ( "sdkPath" ) ;
493+ AddSdkToCustomPath ( underCurrent , "9999.0.4" ) ;
494+
495+ relativePath = Path . GetRelativePath ( SharedState . CurrentWorkingDir , underCurrent ) ;
496+ sdk . Paths = [ relativePath ] ;
497+ GlobalJson . Write ( SharedState . CurrentWorkingDir , sdk ) ;
498+
499+ // Paths: custom (relative, under current directory)
500+ // Custom: 9999.0.4
501+ // Exe: 9999.0.4
502+ // Expected: 9999.0.4 from custom dir
503+ RunTest ( )
504+ . Should ( ) . Pass ( )
505+ . And . HaveStdErrContaining ( ExpectedResolvedSdkOutput ( "9999.0.4" , Path . Combine ( SharedState . CurrentWorkingDir , relativePath ) ) ) ;
506+ }
507+
508+ [ Fact ]
509+ public void GlobalJson_Paths_Multiple ( )
510+ {
511+ using TestArtifact custom = TestArtifact . Create ( "sdkPath" ) ;
512+ AddSdkToCustomPath ( custom . Location , "9999.0.0" ) ;
513+
514+ GlobalJson . Sdk sdk = new ( ) { Paths = [ custom . Location , GlobalJson . HostSdkPath ] } ;
515+ GlobalJson . Write ( SharedState . CurrentWorkingDir , sdk ) ;
516+
517+ // Add SDK versions
518+ AddAvailableSdkVersions ( "9999.0.4" ) ;
519+
520+ // Specified SDK
521+ // version: none
522+ // paths: custom, $host$
523+ // Custom: 9999.0.0
524+ // Exe: 9999.0.4
525+ // Expected: 9999.0.0 from custom dir
526+ RunTest ( )
527+ . Should ( ) . Pass ( )
528+ . And . HaveStdErrContaining ( ExpectedResolvedSdkOutput ( "9999.0.0" , custom . Location ) ) ;
529+
530+ sdk . Version = "9999.0.3" ;
531+ GlobalJson . Write ( SharedState . CurrentWorkingDir , sdk ) ;
532+
533+ // Specified SDK
534+ // version: 9999.0.3
535+ // paths: custom, $host$
536+ // Custom: 9999.0.0
537+ // Exe: 9999.0.4
538+ // Expected: 9999.0.4 from exe dir
539+ RunTest ( )
540+ . Should ( ) . Pass ( )
541+ . And . HaveStdErrContaining ( ExpectedResolvedSdkOutput ( "9999.0.4" ) ) ;
542+
543+ sdk . Version = "9999.0.5" ;
544+ string globalJsonPath = GlobalJson . Write ( SharedState . CurrentWorkingDir , sdk ) ;
545+
546+ // Specified SDK
547+ // version: 9999.0.5
548+ // paths: custom, $host$
549+ // Custom: 9999.0.0
550+ // Exe: 9999.0.4
551+ // Expected: no compatible version
552+ RunTest ( )
553+ . Should ( ) . Fail ( )
554+ . And . NotFindCompatibleSdk ( globalJsonPath , sdk . Version )
555+ . And . FindAnySdk ( true ) ;
556+
557+ // Verify we have the expected SDK versions
558+ RunTest ( "--list-sdks" )
559+ . Should ( ) . Pass ( )
560+ . And . HaveStdOutContaining ( $ "9999.0.0 [{ custom . Location } ")
561+ . And . HaveStdOutContaining ( $ "9999.0.4 [{ ExecutableDotNet . BinPath } ") ;
562+ }
563+
564+ [ Fact ]
565+ public void GlobalJson_Paths_FirstMatch ( )
566+ {
567+ using TestArtifact custom1 = TestArtifact . Create ( "sdkPath1" ) ;
568+ AddSdkToCustomPath ( custom1 . Location , "9999.0.0" ) ;
569+ using TestArtifact custom2 = TestArtifact . Create ( "sdkPath2" ) ;
570+ AddSdkToCustomPath ( custom2 . Location , "9999.0.2" ) ;
571+ AddAvailableSdkVersions ( "9999.0.1" ) ;
572+
573+ GlobalJson . Sdk sdk = new ( ) { Version = "9999.0.1" , Paths = [ custom1 . Location , custom2 . Location , GlobalJson . HostSdkPath ] } ;
574+ GlobalJson . Write ( SharedState . CurrentWorkingDir , sdk ) ;
575+
576+ // Specified SDK
577+ // version: none
578+ // paths: custom1, custom2, $host$
579+ // Custom1: 9999.0.0
580+ // Custom2: 9999.0.2
581+ // Exe: 9999.0.1
582+ // Expected: 9999.0.2 from custom2 - first match is used, not best match (which would be exe which is an exact match)
583+ RunTest ( )
584+ . Should ( ) . Pass ( )
585+ . And . HaveStdErrContaining ( ExpectedResolvedSdkOutput ( "9999.0.2" , custom2 . Location ) ) ;
586+
587+ // Verify we have the expected SDK versions
588+ RunTest ( "--list-sdks" )
589+ . Should ( ) . Pass ( )
590+ . And . HaveStdOutContaining ( $ "9999.0.0 [{ custom1 . Location } ")
591+ . And . HaveStdOutContaining ( $ "9999.0.2 [{ custom2 . Location } ")
592+ . And . HaveStdOutContaining ( $ "9999.0.1 [{ ExecutableDotNet . BinPath } ") ;
593+ }
594+
595+ [ Fact ]
596+ public void GlobalJson_ErrorMessage ( )
597+ {
598+ GlobalJson . Sdk sdk = new ( ) { ErrorMessage = "Custom SDK resolution error" } ;
599+ GlobalJson . Write ( SharedState . CurrentWorkingDir , sdk ) ;
600+
601+ RunTest ( )
602+ . Should ( ) . Fail ( )
603+ . And . HaveStdErrContaining ( sdk . ErrorMessage ) ;
604+ }
605+
440606 public static IEnumerable < object [ ] > InvalidGlobalJsonData
441607 {
442608 get
@@ -472,7 +638,7 @@ public static IEnumerable<object[]> InvalidGlobalJsonData
472638
473639 // Use an invalid version value
474640 yield return new object [ ] {
475- GlobalJson . FormatVersionSettings ( version : "invalid" ) ,
641+ GlobalJson . FormatSettings ( new GlobalJson . Sdk ( ) { Version = "invalid" } ) ,
476642 new [ ] {
477643 "Version 'invalid' is not valid for the 'sdk/version' value" ,
478644 IgnoringSDKSettings
@@ -490,7 +656,7 @@ public static IEnumerable<object[]> InvalidGlobalJsonData
490656
491657 // Use a policy but no version
492658 yield return new object [ ] {
493- GlobalJson . FormatVersionSettings ( policy : "latestPatch" ) ,
659+ GlobalJson . FormatSettings ( new GlobalJson . Sdk ( ) { RollForward = "latestPatch" } ) ,
494660 new [ ] {
495661 "The roll-forward policy 'latestPatch' requires a 'sdk/version' value" ,
496662 IgnoringSDKSettings
@@ -499,7 +665,7 @@ public static IEnumerable<object[]> InvalidGlobalJsonData
499665
500666 // Use an invalid policy value
501667 yield return new object [ ] {
502- GlobalJson . FormatVersionSettings ( policy : "invalid" ) ,
668+ GlobalJson . FormatSettings ( new GlobalJson . Sdk ( ) { RollForward = "invalid" } ) ,
503669 new [ ] {
504670 "The roll-forward policy 'invalid' is not supported for the 'sdk/rollForward' value" ,
505671 IgnoringSDKSettings
@@ -517,7 +683,7 @@ public static IEnumerable<object[]> InvalidGlobalJsonData
517683
518684 // Use a prerelease version and allowPrerelease = false
519685 yield return new object [ ] {
520- GlobalJson . FormatVersionSettings ( version : "9999.1.402-preview1" , allowPrerelease : false ) ,
686+ GlobalJson . FormatSettings ( new GlobalJson . Sdk ( ) { Version = "9999.1.402-preview1" , AllowPrerelease = false } ) ,
521687 new [ ] { "Ignoring the 'sdk/allowPrerelease' value" }
522688 } ;
523689 }
@@ -992,6 +1158,15 @@ public static IEnumerable<object[]> SdkRollForwardData
9921158 }
9931159 }
9941160
1161+ private static void AddSdkToCustomPath ( string sdkRoot , string version )
1162+ {
1163+ DotNetBuilder . AddMockSDK ( sdkRoot , version , version ) ;
1164+
1165+ // Add a mock framework matching the runtime version for the mock SDK
1166+ // This allows the host to successfully resolve frameworks for the SDK at the custom location
1167+ DotNetBuilder . AddMicrosoftNETCoreAppFrameworkMockHostPolicy ( sdkRoot , version ) ;
1168+ }
1169+
9951170 // This method adds a list of new sdk version folders in the specified directory.
9961171 // The actual contents are 'fake' and the minimum required for SDK discovery.
9971172 // The dotnet.runtimeconfig.json created uses a dummy framework version (9999.0.0)
@@ -1003,8 +1178,8 @@ private void AddAvailableSdkVersions(params string[] availableVersions)
10031178 }
10041179 }
10051180
1006- private string ExpectedResolvedSdkOutput ( string expectedVersion )
1007- => Path . Combine ( "Using .NET SDK dll=[" , ExecutableDotNet . BinPath , "sdk" , expectedVersion , "dotnet.dll]" ) ;
1181+ private string ExpectedResolvedSdkOutput ( string expectedVersion , string rootPath = null )
1182+ => $ "Using .NET SDK dll=[{ Path . Combine ( rootPath == null ? ExecutableDotNet . BinPath : rootPath , "sdk" , expectedVersion , "dotnet.dll" ) } ]" ;
10081183
10091184 private CommandResult RunTest ( ) => RunTest ( "help" ) ;
10101185
@@ -1021,6 +1196,7 @@ public sealed class SharedTestState : IDisposable
10211196 {
10221197 public TestArtifact BaseArtifact { get ; }
10231198
1199+ public TestArtifact CurrentWorkingDirArtifact { get ; }
10241200 public string CurrentWorkingDir { get ; }
10251201
10261202 public SharedTestState ( )
@@ -1035,10 +1211,12 @@ public SharedTestState()
10351211 . AddMockSDK ( "10000.0.0" , "9999.0.0" )
10361212 . Build ( ) ;
10371213 CurrentWorkingDir = currentWorkingSdk . BinPath ;
1214+ CurrentWorkingDirArtifact = new TestArtifact ( CurrentWorkingDir ) ;
10381215 }
10391216
10401217 public void Dispose ( )
10411218 {
1219+ CurrentWorkingDirArtifact . Dispose ( ) ;
10421220 BaseArtifact . Dispose ( ) ;
10431221 }
10441222 }
0 commit comments