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