1 using Microsoft.Build.BackEnd.Logging; 2 using Microsoft.Build.BackEnd.SdkResolution; 3 using Microsoft.Build.Construction; 4 using Microsoft.Build.Framework; 5 using Microsoft.Build.UnitTests; 6 using System; 7 using System.Collections.Generic; 8 using System.Diagnostics; 9 using System.Linq; 10 using Microsoft.Build.Unittest; 11 using Shouldly; 12 using Xunit; 13 using SdkResolverContextBase = Microsoft.Build.Framework.SdkResolverContext; 14 using SdkResultBase = Microsoft.Build.Framework.SdkResult; 15 using SdkResultFactoryBase = Microsoft.Build.Framework.SdkResultFactory; 16 using SdkResultImpl = Microsoft.Build.BackEnd.SdkResolution.SdkResult; 17 18 namespace Microsoft.Build.Engine.UnitTests.BackEnd 19 { 20 public class SdkResolverService_Tests 21 { 22 private readonly MockLogger _logger; 23 private readonly LoggingContext _loggingContext; 24 SdkResolverService_Tests()25 public SdkResolverService_Tests() 26 { 27 _logger = new MockLogger(); 28 ILoggingService loggingService = LoggingService.CreateLoggingService(LoggerMode.Synchronous, 1); 29 loggingService.RegisterLogger(_logger); 30 31 _loggingContext = new MockLoggingContext( 32 loggingService, 33 new BuildEventContext(0, 0, BuildEventContext.InvalidProjectContextId, 0, 0)); 34 } 35 36 [Fact] AssertAllResolverErrorsLoggedWhenSdkNotResolved()37 public void AssertAllResolverErrorsLoggedWhenSdkNotResolved() 38 { 39 SdkResolverService.Instance.InitializeForTests(new MockLoaderStrategy()); 40 41 SdkReference sdk = new SdkReference("notfound", "referencedVersion", "minimumVersion"); 42 43 var result = SdkResolverService.Instance.ResolveSdk(BuildEventContext.InvalidSubmissionId, sdk, _loggingContext, new MockElementLocation("file"), "sln", "projectPath"); 44 45 result.ShouldBeNull(); 46 47 _logger.BuildMessageEvents.Select(i => i.Message).ShouldContain("MockSdkResolver1 running"); 48 _logger.BuildMessageEvents.Select(i => i.Message).ShouldContain("MockSdkResolver2 running"); 49 _logger.Errors.Select(i => i.Message).ShouldBe(new [] { "ERROR1", "ERROR2" }); 50 _logger.Warnings.Select(i => i.Message).ShouldBe(new[] { "WARNING2" }); 51 } 52 53 [Fact] AssertResolutionWarnsIfResolvedVersionIsDifferentFromReferencedVersion()54 public void AssertResolutionWarnsIfResolvedVersionIsDifferentFromReferencedVersion() 55 { 56 var sdk = new SdkReference("foo", "1.0.0", null); 57 58 SdkResolverService.Instance.InitializeForTests( 59 null, 60 new List<SdkResolver> 61 { 62 new SdkUtilities.ConfigurableMockSdkResolver( 63 new SdkResultImpl( 64 sdk, 65 "path", 66 "2.0.0", 67 Enumerable.Empty<string>() 68 )) 69 }); 70 71 var result = SdkResolverService.Instance.ResolveSdk(BuildEventContext.InvalidSubmissionId, sdk, _loggingContext, new MockElementLocation("file"), "sln", "projectPath"); 72 73 result.Path.ShouldBe("path"); 74 75 _logger.WarningCount.ShouldBe(1); 76 _logger.Warnings.First().Code.ShouldStartWith("MSB4241"); 77 } 78 79 [Fact] AssertErrorLoggedWhenResolverThrows()80 public void AssertErrorLoggedWhenResolverThrows() 81 { 82 SdkResolverService.Instance.InitializeForTests(new MockLoaderStrategy(includeErrorResolver: true)); 83 84 SdkReference sdk = new SdkReference("1sdkName", "version1", "minimumVersion"); 85 86 var result = SdkResolverService.Instance.ResolveSdk(BuildEventContext.InvalidSubmissionId, sdk, _loggingContext, new MockElementLocation("file"), "sln", "projectPath"); 87 88 result.Path.ShouldBe("resolverpath1"); 89 _logger.Warnings.Select(i => i.Message).ShouldBe(new [] { "The SDK resolver \"MockSdkResolverThrows\" failed to run. EXMESSAGE" }); 90 } 91 92 [Fact] AssertFirstResolverCanResolve()93 public void AssertFirstResolverCanResolve() 94 { 95 SdkResolverService.Instance.InitializeForTests(new MockLoaderStrategy()); 96 97 SdkReference sdk = new SdkReference("1sdkName", "referencedVersion", "minimumVersion"); 98 99 var result = SdkResolverService.Instance.ResolveSdk(BuildEventContext.InvalidSubmissionId, sdk, _loggingContext, new MockElementLocation("file"), "sln", "projectPath"); 100 101 result.Path.ShouldBe("resolverpath1"); 102 _logger.BuildMessageEvents.Select(i => i.Message).ShouldContain("MockSdkResolver1 running"); 103 } 104 105 [Fact] AssertFirstResolverErrorsSupressedWhenResolved()106 public void AssertFirstResolverErrorsSupressedWhenResolved() 107 { 108 SdkResolverService.Instance.InitializeForTests(new MockLoaderStrategy()); 109 110 // 2sdkName will cause MockSdkResolver1 to fail with an error reason. The error will not 111 // be logged because MockSdkResolver2 will succeed. 112 SdkReference sdk = new SdkReference("2sdkName", "version2", "minimumVersion"); 113 114 var result = SdkResolverService.Instance.ResolveSdk(BuildEventContext.InvalidSubmissionId, sdk, _loggingContext, new MockElementLocation("file"), "sln", "projectPath"); 115 116 result.Path.ShouldBe("resolverpath2"); 117 118 // Both resolvers should run, and no ERROR string. 119 _logger.BuildMessageEvents.Select(i => i.Message).ShouldContain("MockSdkResolver1 running"); 120 _logger.BuildMessageEvents.Select(i => i.Message).ShouldContain("MockSdkResolver2 running"); 121 122 // Resolver2 gives a warning on success or failure. 123 _logger.Warnings.Select(i => i.Message).ShouldBe(new[] { "WARNING2" }); 124 _logger.ErrorCount.ShouldBe(0); 125 } 126 127 [Fact] AssertResolverHasStatePreserved()128 public void AssertResolverHasStatePreserved() 129 { 130 const int submissionId = 5; 131 132 SdkResolverService.Instance.InitializeForTests(new MockLoaderStrategy()); 133 134 SdkReference sdk = new SdkReference("othersdk", "1.0", "minimumVersion"); 135 136 // First call should not know state 137 SdkResolverService.Instance.ResolveSdk(submissionId, sdk, _loggingContext, new MockElementLocation("file"), "sln", "projectPath").Path.ShouldBe("resolverpath"); 138 139 // Second call should have received state 140 SdkResolverService.Instance.ResolveSdk(submissionId, sdk, _loggingContext, new MockElementLocation("file"), "sln", "projectPath").Path.ShouldBe(MockSdkResolverWithState.Expected); 141 } 142 143 [Fact] AssertResolverStateNotPreserved()144 public void AssertResolverStateNotPreserved() 145 { 146 const int submissionId = BuildEventContext.InvalidSubmissionId; 147 148 SdkResolverService.Instance.InitializeForTests(new MockLoaderStrategy()); 149 150 SdkReference sdk = new SdkReference("othersdk", "1.0", "minimumVersion"); 151 152 // First call should not know state 153 SdkResolverService.Instance.ResolveSdk(submissionId, sdk, _loggingContext, new MockElementLocation("file"), "sln", "projectPath").Path.ShouldBe("resolverpath"); 154 155 // Second call should have received state 156 SdkResolverService.Instance.ResolveSdk(submissionId, sdk, _loggingContext, new MockElementLocation("file"), "sln", "projectPath").Path.ShouldBe("resolverpath"); 157 } 158 159 [Theory] 160 [InlineData(null, "1.0", true)] 161 [InlineData("1.0", "1.0", true)] 162 [InlineData("1.0-preview", "1.0-PrEvIeW", true)] 163 [InlineData("1.0", "1.0.0", false)] 164 [InlineData("1.0", "1.0.0.0", false)] 165 [InlineData("1.0.0", "1.0.0.0", false)] 166 [InlineData("1.2.0.0", "1.0.0.0", false)] 167 [InlineData("1.2.3.0", "1.2.0.0", false)] 168 [InlineData("1.2.3.4", "1.2.3.0", false)] IsReferenceSameVersionTests(string version1, string version2, bool expected)169 public void IsReferenceSameVersionTests(string version1, string version2, bool expected) 170 { 171 SdkReference sdk = new SdkReference("Microsoft.NET.Sdk", version1, null); 172 173 SdkResolverService.IsReferenceSameVersion(sdk, version2).ShouldBe(expected); 174 } 175 176 [Fact] CachingWrapperShouldWarnWhenMultipleVersionsAreReferenced()177 public void CachingWrapperShouldWarnWhenMultipleVersionsAreReferenced() 178 { 179 var sdk = new SdkReference("foo", "1.0.0", null); 180 181 var resolver = new SdkUtilities.ConfigurableMockSdkResolver( 182 new SdkResultImpl( 183 sdk, 184 "path", 185 "1.0.0", 186 Enumerable.Empty<string>() 187 )); 188 189 var service = new CachingSdkResolverService(); 190 service.InitializeForTests( 191 null, 192 new List<SdkResolver> 193 { 194 resolver 195 }); 196 197 var result = service.ResolveSdk(BuildEventContext.InvalidSubmissionId, sdk, _loggingContext, new MockElementLocation("file"), "sln", "projectPath"); 198 resolver.ResolvedCalls.Count.ShouldBe(1); 199 result.Path.ShouldBe("path"); 200 result.Version.ShouldBe("1.0.0"); 201 _logger.WarningCount.ShouldBe(0); 202 203 result = service.ResolveSdk(BuildEventContext.InvalidSubmissionId, new SdkReference("foo", "2.0.0", null), _loggingContext, new MockElementLocation("file"), "sln", "projectPath"); 204 resolver.ResolvedCalls.Count.ShouldBe(1); 205 result.Path.ShouldBe("path"); 206 result.Version.ShouldBe("1.0.0"); 207 _logger.WarningCount.ShouldBe(1); 208 _logger.Warnings.First().Code.ShouldBe("MSB4240"); 209 210 resolver.ResolvedCalls.First().Key.ShouldBe("foo"); 211 resolver.ResolvedCalls.Count.ShouldBe(1); 212 } 213 214 private class MockLoaderStrategy : SdkResolverLoader 215 { 216 private readonly bool _includeErrorResolver; 217 MockLoaderStrategy(bool includeErrorResolver = false)218 public MockLoaderStrategy(bool includeErrorResolver = false) 219 { 220 _includeErrorResolver = includeErrorResolver; 221 } 222 LoadResolvers(LoggingContext loggingContext, ElementLocation location)223 internal override IList<SdkResolver> LoadResolvers(LoggingContext loggingContext, ElementLocation location) 224 { 225 List<SdkResolver> resolvers = new List<SdkResolver> 226 { 227 new MockSdkResolver1(), 228 new MockSdkResolver2(), 229 new MockResolverReturnsNull(), 230 new MockSdkResolverWithState() 231 }; 232 233 if (_includeErrorResolver) 234 { 235 resolvers.Add(new MockSdkResolverThrows()); 236 } 237 238 return resolvers.OrderBy(i => i.Priority).ToList(); 239 } 240 } 241 242 private class MockResolverReturnsNull : SdkResolver 243 { 244 public override string Name => nameof(MockResolverReturnsNull); 245 246 public override int Priority => -1; 247 Resolve(SdkReference sdkReference, SdkResolverContextBase resolverContext, SdkResultFactoryBase factory)248 public override SdkResultBase Resolve(SdkReference sdkReference, SdkResolverContextBase resolverContext, SdkResultFactoryBase factory) => null; 249 } 250 251 private class MockSdkResolver1 : SdkResolver 252 { 253 public override string Name => nameof(MockSdkResolver1); 254 255 public override int Priority => 1; 256 Resolve(SdkReference sdk, SdkResolverContextBase resolverContext, SdkResultFactoryBase factory)257 public override SdkResultBase Resolve(SdkReference sdk, SdkResolverContextBase resolverContext, SdkResultFactoryBase factory) 258 { 259 resolverContext.Logger.LogMessage("MockSdkResolver1 running", MessageImportance.Normal); 260 261 if (sdk.Name.StartsWith("1")) 262 return factory.IndicateSuccess("resolverpath1", "version1"); 263 264 return factory.IndicateFailure(new[] {"ERROR1"}); 265 } 266 } 267 268 private class MockSdkResolver2 : SdkResolver 269 { 270 public override string Name => nameof(MockSdkResolver2); 271 272 public override int Priority => 2; 273 Resolve(SdkReference sdk, SdkResolverContextBase resolverContext, SdkResultFactoryBase factory)274 public override SdkResultBase Resolve(SdkReference sdk, SdkResolverContextBase resolverContext, SdkResultFactoryBase factory) 275 { 276 resolverContext.Logger.LogMessage("MockSdkResolver2 running", MessageImportance.Normal); 277 278 if (sdk.Name.StartsWith("2")) 279 return factory.IndicateSuccess("resolverpath2", "version2", new[] {"WARNING2"}); 280 281 return factory.IndicateFailure(new[] {"ERROR2"}, new[] {"WARNING2"}); 282 } 283 } 284 285 private class MockSdkResolverWithState : SdkResolver 286 { 287 public const string Expected = "01713226A202458F97D9074168DF2618"; 288 289 public override string Name => nameof(MockSdkResolverWithState); 290 291 public override int Priority => 3; 292 Resolve(SdkReference sdkReference, SdkResolverContextBase resolverContext, SdkResultFactoryBase factory)293 public override SdkResultBase Resolve(SdkReference sdkReference, SdkResolverContextBase resolverContext, SdkResultFactoryBase factory) 294 { 295 if (sdkReference.Name.Equals("notfound")) 296 { 297 return null; 298 } 299 if (resolverContext.State != null) 300 { 301 return factory.IndicateSuccess((string) resolverContext.State, "1.0"); 302 } 303 304 resolverContext.State = Expected; 305 306 return factory.IndicateSuccess("resolverpath", "1.0"); 307 } 308 } 309 310 private class MockSdkResolverThrows : SdkResolver 311 { 312 public override string Name => nameof(MockSdkResolverThrows); 313 public override int Priority => 0; 314 Resolve(SdkReference sdk, SdkResolverContextBase resolverContext, SdkResultFactoryBase factory)315 public override SdkResultBase Resolve(SdkReference sdk, SdkResolverContextBase resolverContext, SdkResultFactoryBase factory) 316 { 317 resolverContext.Logger.LogMessage("MockSdkResolverThrows running", MessageImportance.Normal); 318 319 throw new ArithmeticException("EXMESSAGE"); 320 } 321 } 322 } 323 } 324