LocalToolsResolverCacheTests.cs 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396
  1. // Licensed to the .NET Foundation under one or more agreements.
  2. // The .NET Foundation licenses this file to you under the MIT license.
  3. using Microsoft.DotNet.Cli.Utils;
  4. using Microsoft.DotNet.ToolPackage;
  5. using Microsoft.Extensions.DependencyModel.Tests;
  6. using Microsoft.Extensions.EnvironmentAbstractions;
  7. using NuGet.Frameworks;
  8. using NuGet.Versioning;
  9. namespace Microsoft.DotNet.PackageInstall.Tests
  10. {
  11. public class LocalToolsResolverCacheTests : SdkTest
  12. {
  13. public LocalToolsResolverCacheTests(ITestOutputHelper log) : base(log)
  14. {
  15. }
  16. private static
  17. (DirectoryPath nuGetGlobalPackagesFolder,
  18. LocalToolsResolverCache localToolsResolverCache) Setup()
  19. {
  20. IFileSystem fileSystem = new FileSystemMockBuilder().UseCurrentSystemTemporaryDirectory().Build();
  21. DirectoryPath tempDirectory =
  22. new(fileSystem.Directory.CreateTemporaryDirectory().DirectoryPath);
  23. DirectoryPath cacheDirectory = tempDirectory.WithSubDirectories("cacheDirectory");
  24. DirectoryPath nuGetGlobalPackagesFolder = tempDirectory.WithSubDirectories("nugetGlobalPackageLocation");
  25. fileSystem.Directory.CreateDirectory(cacheDirectory.Value);
  26. const int version = 1;
  27. LocalToolsResolverCache localToolsResolverCache =
  28. new(fileSystem, cacheDirectory, version);
  29. return (nuGetGlobalPackagesFolder, localToolsResolverCache);
  30. }
  31. [Fact]
  32. public void GivenExecutableIdentifierItCanSaveAndCannotLoadWithMismatches()
  33. {
  34. (DirectoryPath nuGetGlobalPackagesFolder, LocalToolsResolverCache localToolsResolverCache) = Setup();
  35. NuGetFramework targetFramework = NuGetFramework.Parse(ToolsetInfo.CurrentTargetFramework);
  36. string runtimeIdentifier = Constants.AnyRid;
  37. PackageId packageId = new("my.toolBundle");
  38. NuGetVersion nuGetVersion = NuGetVersion.Parse("1.0.2");
  39. IReadOnlyList<RestoredCommand> restoredCommands = new[]
  40. {
  41. new RestoredCommand(new ToolCommandName("tool1"), "dotnet", nuGetGlobalPackagesFolder.WithFile("tool1.dll")),
  42. new RestoredCommand(new ToolCommandName("tool2"), "dotnet", nuGetGlobalPackagesFolder.WithFile("tool2.dll"))
  43. };
  44. localToolsResolverCache.Save(
  45. restoredCommands.ToDictionary(
  46. c => new RestoredCommandIdentifier(packageId, nuGetVersion, targetFramework, runtimeIdentifier,
  47. c.Name)));
  48. localToolsResolverCache
  49. .TryLoad(
  50. new RestoredCommandIdentifier(packageId, NuGetVersion.Parse("1.0.0-wrong-version"), targetFramework,
  51. runtimeIdentifier, restoredCommands[0].Name), out _)
  52. .Should().BeFalse();
  53. localToolsResolverCache
  54. .TryLoad(
  55. new RestoredCommandIdentifier(packageId, nuGetVersion, NuGetFramework.Parse("wrongFramework"),
  56. runtimeIdentifier, restoredCommands[0].Name), out _)
  57. .Should().BeFalse();
  58. localToolsResolverCache
  59. .TryLoad(
  60. new RestoredCommandIdentifier(packageId, nuGetVersion, targetFramework,
  61. "wrongRuntimeIdentifier", restoredCommands[0].Name),
  62. out _)
  63. .Should().BeFalse();
  64. }
  65. [Fact]
  66. public void GivenExecutableIdentifierItCanSaveAndLoad()
  67. {
  68. (DirectoryPath nuGetGlobalPackagesFolder, LocalToolsResolverCache localToolsResolverCache) = Setup();
  69. NuGetFramework targetFramework = NuGetFramework.Parse(ToolsetInfo.CurrentTargetFramework);
  70. string runtimeIdentifier = Constants.AnyRid;
  71. PackageId packageId = new("my.toolBundle");
  72. NuGetVersion nuGetVersion = NuGetVersion.Parse("1.0.2");
  73. IReadOnlyList<RestoredCommand> restoredCommands = new[]
  74. {
  75. new RestoredCommand(new ToolCommandName("tool1"), "dotnet", nuGetGlobalPackagesFolder.WithFile("tool1.dll")),
  76. new RestoredCommand(new ToolCommandName("tool2"), "dotnet", nuGetGlobalPackagesFolder.WithFile("tool2.dll"))
  77. };
  78. localToolsResolverCache.Save(
  79. restoredCommands.ToDictionary(
  80. c => new RestoredCommandIdentifier(packageId, nuGetVersion, targetFramework, runtimeIdentifier,
  81. c.Name)));
  82. localToolsResolverCache.TryLoad(
  83. new RestoredCommandIdentifier(packageId, nuGetVersion, targetFramework, runtimeIdentifier,
  84. restoredCommands[0].Name),
  85. out RestoredCommand tool1).Should().BeTrue();
  86. localToolsResolverCache.TryLoad(
  87. new RestoredCommandIdentifier(packageId, nuGetVersion, targetFramework, runtimeIdentifier,
  88. restoredCommands[1].Name),
  89. out RestoredCommand tool2).Should().BeTrue();
  90. tool1.Should().BeEquivalentTo(restoredCommands[0]);
  91. tool2.Should().BeEquivalentTo(restoredCommands[1]);
  92. }
  93. [Fact]
  94. public void GivenExecutableIdentifierItCanSaveMultipleSameAndLoadContainsOnlyOne()
  95. {
  96. (DirectoryPath nuGetGlobalPackagesFolder, LocalToolsResolverCache localToolsResolverCache) = Setup();
  97. NuGetFramework targetFramework = NuGetFramework.Parse(ToolsetInfo.CurrentTargetFramework);
  98. string runtimeIdentifier = Constants.AnyRid;
  99. PackageId packageId = new("my.toolBundle");
  100. NuGetVersion nuGetVersion = NuGetVersion.Parse("1.0.2");
  101. IReadOnlyList<RestoredCommand> restoredCommands = new[]
  102. {
  103. new RestoredCommand(new ToolCommandName("tool1"), "dotnet", nuGetGlobalPackagesFolder.WithFile("tool1.dll")),
  104. new RestoredCommand(new ToolCommandName("tool2"), "dotnet", nuGetGlobalPackagesFolder.WithFile("tool2.dll"))
  105. };
  106. localToolsResolverCache.Save(
  107. restoredCommands.ToDictionary(
  108. c => new RestoredCommandIdentifier(packageId, nuGetVersion, targetFramework, runtimeIdentifier,
  109. c.Name)));
  110. localToolsResolverCache.Save(
  111. restoredCommands.ToDictionary(
  112. c => new RestoredCommandIdentifier(packageId, nuGetVersion, targetFramework, runtimeIdentifier,
  113. c.Name)));
  114. localToolsResolverCache.TryLoad(
  115. new RestoredCommandIdentifier(packageId, nuGetVersion, targetFramework, runtimeIdentifier,
  116. restoredCommands[0].Name),
  117. out RestoredCommand tool1);
  118. localToolsResolverCache.TryLoad(
  119. new RestoredCommandIdentifier(packageId, nuGetVersion, targetFramework, runtimeIdentifier,
  120. restoredCommands[1].Name),
  121. out RestoredCommand tool2);
  122. tool1.Should().BeEquivalentTo(restoredCommands[0]);
  123. tool2.Should().BeEquivalentTo(restoredCommands[1]);
  124. }
  125. [Fact]
  126. public void ItCanSaveMultipleSameAndLoadTheHighestFromVersionRange()
  127. {
  128. (DirectoryPath nuGetGlobalPackagesFolder, LocalToolsResolverCache localToolsResolverCache) = Setup();
  129. NuGetFramework targetFramework = NuGetFramework.Parse(ToolsetInfo.CurrentTargetFramework);
  130. string runtimeIdentifier = Constants.AnyRid;
  131. PackageId packageId = new("my.toolBundle");
  132. NuGetVersion previewNuGetVersion = NuGetVersion.Parse("0.0.2");
  133. IReadOnlyList<RestoredCommand> restoredCommandsV0 = new[]
  134. {
  135. new RestoredCommand(new ToolCommandName("tool1"), "dotnet", nuGetGlobalPackagesFolder.WithFile("tool1preview.dll")),
  136. };
  137. NuGetVersion nuGetVersion = NuGetVersion.Parse("1.0.2");
  138. IReadOnlyList<RestoredCommand> restoredCommandsV1 = new[]
  139. {
  140. new RestoredCommand(new ToolCommandName("tool1"), "dotnet", nuGetGlobalPackagesFolder.WithFile("tool1.dll")),
  141. };
  142. NuGetVersion newerNuGetVersion = NuGetVersion.Parse("2.0.2");
  143. IReadOnlyList<RestoredCommand> restoredCommandsV2 = new[]
  144. {
  145. new RestoredCommand(new ToolCommandName("tool1"), "dotnet", nuGetGlobalPackagesFolder.WithFile("tool1new.dll")),
  146. };
  147. localToolsResolverCache.Save(
  148. restoredCommandsV0.ToDictionary(
  149. c => new RestoredCommandIdentifier(packageId, previewNuGetVersion, targetFramework, runtimeIdentifier,
  150. c.Name)));
  151. localToolsResolverCache.Save(
  152. restoredCommandsV1.ToDictionary(
  153. c => new RestoredCommandIdentifier(packageId, nuGetVersion, targetFramework, runtimeIdentifier,
  154. c.Name)));
  155. localToolsResolverCache.Save(
  156. restoredCommandsV2.ToDictionary(
  157. c => new RestoredCommandIdentifier(packageId, newerNuGetVersion, targetFramework, runtimeIdentifier,
  158. c.Name)));
  159. bool loadSuccess =
  160. localToolsResolverCache.TryLoadHighestVersion(
  161. new RestoredCommandIdentifierVersionRange(
  162. packageId,
  163. VersionRange.Parse("(0.0.0, 2.0.0)"),
  164. targetFramework, runtimeIdentifier, new ToolCommandName("tool1")),
  165. out RestoredCommand loadedResolverCache);
  166. loadSuccess.Should().BeTrue();
  167. loadedResolverCache.Should().BeEquivalentTo(restoredCommandsV1[0]);
  168. }
  169. [Fact]
  170. public void ItReturnsFalseWhenFailedToLoadVersionRange()
  171. {
  172. (DirectoryPath nuGetGlobalPackagesFolder, LocalToolsResolverCache localToolsResolverCache) = Setup();
  173. bool loadSuccess =
  174. localToolsResolverCache.TryLoadHighestVersion(
  175. new RestoredCommandIdentifierVersionRange(
  176. new PackageId("my.toolBundle"),
  177. VersionRange.Parse("(0.0.0, 2.0.0)"),
  178. NuGetFramework.Parse(ToolsetInfo.CurrentTargetFramework), Constants.AnyRid, new ToolCommandName("tool1")),
  179. out _);
  180. loadSuccess.Should().BeFalse();
  181. }
  182. [Fact]
  183. public void GivenExecutableIdentifierItCanSaveMultipleVersionAndLoad()
  184. {
  185. (DirectoryPath nuGetGlobalPackagesFolder, LocalToolsResolverCache localToolsResolverCache) = Setup();
  186. NuGetFramework targetFramework = NuGetFramework.Parse(ToolsetInfo.CurrentTargetFramework);
  187. string runtimeIdentifier = Constants.AnyRid;
  188. PackageId packageId = new("my.toolBundle");
  189. NuGetVersion nuGetVersion = NuGetVersion.Parse("1.0.2");
  190. IReadOnlyList<RestoredCommand> restoredCommands = new[]
  191. {
  192. new RestoredCommand(new ToolCommandName("tool1"), "dotnet", nuGetGlobalPackagesFolder.WithFile("tool1.dll")),
  193. };
  194. NuGetVersion newerNuGetVersion = NuGetVersion.Parse("2.0.2");
  195. IReadOnlyList<RestoredCommand> restoredCommandsNewer = new[]
  196. {
  197. new RestoredCommand(new ToolCommandName("tool1"), "dotnet", nuGetGlobalPackagesFolder.WithFile("tool1new.dll")),
  198. new RestoredCommand(new ToolCommandName("tool2"), "dotnet", nuGetGlobalPackagesFolder.WithFile("tool2new.dll")),
  199. };
  200. localToolsResolverCache.Save(
  201. restoredCommands.ToDictionary(
  202. c => new RestoredCommandIdentifier(packageId, nuGetVersion, targetFramework, runtimeIdentifier,
  203. c.Name)));
  204. localToolsResolverCache.Save(
  205. restoredCommandsNewer.ToDictionary(
  206. c => new RestoredCommandIdentifier(packageId, newerNuGetVersion, targetFramework, runtimeIdentifier,
  207. c.Name)));
  208. localToolsResolverCache.TryLoad(
  209. new RestoredCommandIdentifier(packageId, nuGetVersion, targetFramework, runtimeIdentifier,
  210. restoredCommands[0].Name),
  211. out RestoredCommand tool1);
  212. localToolsResolverCache.TryLoad(
  213. new RestoredCommandIdentifier(packageId, newerNuGetVersion, targetFramework, runtimeIdentifier,
  214. restoredCommandsNewer[0].Name),
  215. out RestoredCommand tool1Newer);
  216. localToolsResolverCache.TryLoad(
  217. new RestoredCommandIdentifier(packageId, newerNuGetVersion, targetFramework, runtimeIdentifier,
  218. restoredCommandsNewer[1].Name),
  219. out RestoredCommand tool2Newer);
  220. tool1.Should().BeEquivalentTo(restoredCommands[0]);
  221. tool1Newer.Should().BeEquivalentTo(restoredCommandsNewer[0]);
  222. tool2Newer.Should().BeEquivalentTo(restoredCommandsNewer[1]);
  223. }
  224. [Fact]
  225. public void WhenTheCacheIsCorruptedByAppendingLineItShouldLoadAsEmpty()
  226. {
  227. WhenTheCacheIsCorruptedItShouldLoadAsEmpty(
  228. useRealFileSystem: false,
  229. corruptCache: (fileSystem, cachePath, existingCache) =>
  230. fileSystem.File.WriteAllText(cachePath, existingCache + " !!!Corrupted")
  231. );
  232. }
  233. [Fact]
  234. public void WhenTheCacheIsCorruptedByNotAJsonItShouldLoadAsEmpty()
  235. {
  236. WhenTheCacheIsCorruptedItShouldLoadAsEmpty(
  237. useRealFileSystem: true,
  238. corruptCache: (fileSystem, cachePath, existingCache) =>
  239. {
  240. File.WriteAllBytes(cachePath, new byte[] { 0x12, 0x23, 0x34, 0x45 });
  241. }
  242. );
  243. }
  244. [Fact]
  245. public void WhenTheCacheIsCorruptedItShouldNotAffectNextSaveAndLoad()
  246. {
  247. IFileSystem fileSystem = new FileSystemMockBuilder().UseCurrentSystemTemporaryDirectory().Build();
  248. DirectoryPath tempDirectory =
  249. new(fileSystem.Directory.CreateTemporaryDirectory().DirectoryPath);
  250. DirectoryPath cacheDirectory = tempDirectory.WithSubDirectories("cacheDirectory");
  251. DirectoryPath nuGetGlobalPackagesFolder = tempDirectory.WithSubDirectories("nugetGlobalPackageLocation");
  252. fileSystem.Directory.CreateDirectory(cacheDirectory.Value);
  253. const int version = 1;
  254. LocalToolsResolverCache localToolsResolverCache =
  255. new(fileSystem, cacheDirectory, version);
  256. NuGetFramework targetFramework = NuGetFramework.Parse(ToolsetInfo.CurrentTargetFramework);
  257. string runtimeIdentifier = Constants.AnyRid;
  258. PackageId packageId = new("my.toolBundle");
  259. NuGetVersion nuGetVersion = NuGetVersion.Parse("1.0.2");
  260. IReadOnlyList<RestoredCommand> restoredCommands = new[]
  261. {
  262. new RestoredCommand(new ToolCommandName("tool1"), "dotnet", nuGetGlobalPackagesFolder.WithFile("tool1.dll")),
  263. };
  264. localToolsResolverCache.Save(
  265. restoredCommands.ToDictionary(
  266. c => new RestoredCommandIdentifier(packageId, nuGetVersion, targetFramework, runtimeIdentifier,
  267. c.Name)));
  268. var cachePath = cacheDirectory
  269. .WithSubDirectories(version.ToString())
  270. .WithSubDirectories(packageId.ToString()).Value;
  271. var existingCache =
  272. fileSystem.File.ReadAllText(
  273. cachePath);
  274. existingCache.Should().NotBeEmpty();
  275. fileSystem.File.WriteAllText(cachePath, existingCache + " !!!Corrupted");
  276. // Save after corruption
  277. localToolsResolverCache.Save(
  278. restoredCommands.ToDictionary(
  279. c => new RestoredCommandIdentifier(packageId, nuGetVersion, targetFramework, runtimeIdentifier,
  280. c.Name)));
  281. localToolsResolverCache.TryLoad(
  282. new RestoredCommandIdentifier(packageId, nuGetVersion, targetFramework, runtimeIdentifier,
  283. restoredCommands[0].Name),
  284. out RestoredCommand restoredCommand);
  285. restoredCommand.Should().BeEquivalentTo(restoredCommands[0]);
  286. }
  287. private static void WhenTheCacheIsCorruptedItShouldLoadAsEmpty(
  288. bool useRealFileSystem,
  289. Action<IFileSystem, string, string> corruptCache)
  290. {
  291. IFileSystem fileSystem =
  292. useRealFileSystem == false
  293. ? new FileSystemMockBuilder().UseCurrentSystemTemporaryDirectory().Build()
  294. : new FileSystemWrapper();
  295. DirectoryPath tempDirectory =
  296. new(fileSystem.Directory.CreateTemporaryDirectory().DirectoryPath);
  297. DirectoryPath cacheDirectory = tempDirectory.WithSubDirectories("cacheDirectory");
  298. DirectoryPath nuGetGlobalPackagesFolder = tempDirectory.WithSubDirectories("nugetGlobalPackageLocation");
  299. fileSystem.Directory.CreateDirectory(cacheDirectory.Value);
  300. const int version = 1;
  301. LocalToolsResolverCache localToolsResolverCache =
  302. new(fileSystem, cacheDirectory, version);
  303. NuGetFramework targetFramework = NuGetFramework.Parse(ToolsetInfo.CurrentTargetFramework);
  304. string runtimeIdentifier = Constants.AnyRid;
  305. PackageId packageId = new("my.toolBundle");
  306. NuGetVersion nuGetVersion = NuGetVersion.Parse("1.0.2");
  307. IReadOnlyList<RestoredCommand> restoredCommands = new[]
  308. {
  309. new RestoredCommand(new ToolCommandName("tool1"), "dotnet", nuGetGlobalPackagesFolder.WithFile("tool1.dll")),
  310. };
  311. localToolsResolverCache.Save(
  312. restoredCommands.ToDictionary(
  313. c => new RestoredCommandIdentifier(packageId, nuGetVersion, targetFramework, runtimeIdentifier,
  314. c.Name)));
  315. var cachePath = cacheDirectory
  316. .WithSubDirectories(version.ToString())
  317. .WithSubDirectories(packageId.ToString()).Value;
  318. var existingCache =
  319. fileSystem.File.ReadAllText(
  320. cachePath);
  321. existingCache.Should().NotBeEmpty();
  322. corruptCache(fileSystem, cachePath, existingCache);
  323. localToolsResolverCache.TryLoad(
  324. new RestoredCommandIdentifier(packageId, nuGetVersion, targetFramework, runtimeIdentifier,
  325. restoredCommands[0].Name),
  326. out _).Should().BeFalse("Consider corrupted file cache miss");
  327. }
  328. }
  329. }