ToolPackageDownloaderMock.cs 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320
  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 System;
  4. using System.Collections.Generic;
  5. using System.IO;
  6. using System.Linq;
  7. using System.Text.Json;
  8. using Microsoft.DotNet.Cli;
  9. using Microsoft.DotNet.Cli.ToolPackage;
  10. using Microsoft.DotNet.Cli.Utils;
  11. using Microsoft.DotNet.ToolPackage;
  12. using Microsoft.Extensions.EnvironmentAbstractions;
  13. using Microsoft.NET.TestFramework.Utilities;
  14. using NuGet.Frameworks;
  15. using NuGet.Versioning;
  16. using LocalizableStrings = Microsoft.DotNet.Tools.Tool.Install.LocalizableStrings;
  17. namespace Microsoft.DotNet.Tools.Tests.ComponentMocks
  18. {
  19. internal class ToolPackageDownloaderMock : IToolPackageDownloader
  20. {
  21. private readonly IToolPackageStore _toolPackageStore;
  22. protected DirectoryPath _toolDownloadDir;
  23. protected readonly DirectoryPath _globalToolStageDir;
  24. protected readonly DirectoryPath _localToolDownloadDir;
  25. protected readonly DirectoryPath _localToolAssetDir;
  26. public const string FakeEntrypointName = "SimulatorEntryPoint.dll";
  27. public const string DefaultToolCommandName = "SimulatorCommand";
  28. public const string DefaultPackageName = "global.tool.console.demo";
  29. public const string DefaultPackageVersion = "1.0.4";
  30. public const string FakeCommandSettingsFileName = "FakeDotnetToolSettings.json";
  31. private const string ProjectFileName = "TempProject.csproj";
  32. private readonly IFileSystem _fileSystem;
  33. private readonly IReporter _reporter;
  34. private readonly List<MockFeed> _feeds;
  35. private readonly Dictionary<PackageId, IEnumerable<string>> _warningsMap;
  36. private readonly Dictionary<PackageId, IReadOnlyList<FilePath>> _packagedShimsMap;
  37. private readonly Dictionary<PackageId, IEnumerable<NuGetFramework>> _frameworksMap;
  38. private readonly Action _downloadCallback;
  39. public ToolPackageDownloaderMock(
  40. IToolPackageStore store,
  41. IFileSystem fileSystem,
  42. IReporter reporter = null,
  43. List<MockFeed> feeds = null,
  44. Action downloadCallback = null,
  45. Dictionary<PackageId, IEnumerable<string>> warningsMap = null,
  46. Dictionary<PackageId, IReadOnlyList<FilePath>> packagedShimsMap = null,
  47. Dictionary<PackageId, IEnumerable<NuGetFramework>> frameworksMap = null
  48. )
  49. {
  50. _toolPackageStore = store ?? throw new ArgumentNullException(nameof(store)); ;
  51. _globalToolStageDir = _toolPackageStore.GetRandomStagingDirectory();
  52. _localToolDownloadDir = new DirectoryPath(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), "nuget", "package"));
  53. _localToolAssetDir = new DirectoryPath(PathUtilities.CreateTempSubdirectory());
  54. _fileSystem = fileSystem ?? throw new ArgumentNullException(nameof(fileSystem));
  55. _reporter = reporter;
  56. _warningsMap = warningsMap ?? new Dictionary<PackageId, IEnumerable<string>>();
  57. _packagedShimsMap = packagedShimsMap ?? new Dictionary<PackageId, IReadOnlyList<FilePath>>();
  58. _frameworksMap = frameworksMap ?? new Dictionary<PackageId, IEnumerable<NuGetFramework>>();
  59. _downloadCallback = downloadCallback;
  60. if (feeds == null)
  61. {
  62. _feeds = new List<MockFeed>();
  63. _feeds.Add(new MockFeed
  64. {
  65. Type = MockFeedType.FeedFromGlobalNugetConfig,
  66. Packages = new List<MockFeedPackage>
  67. {
  68. new MockFeedPackage
  69. {
  70. PackageId = DefaultPackageName,
  71. Version = DefaultPackageVersion,
  72. ToolCommandName = DefaultToolCommandName,
  73. }
  74. }
  75. });
  76. }
  77. else
  78. {
  79. _feeds = feeds;
  80. }
  81. }
  82. public IToolPackage InstallPackage(PackageLocation packageLocation, PackageId packageId,
  83. VerbosityOptions verbosity,
  84. VersionRange versionRange = null,
  85. string targetFramework = null,
  86. bool isGlobalTool = false,
  87. bool isGlobalToolRollForward = false
  88. )
  89. {
  90. string rollbackDirectory = null;
  91. var packageRootDirectory = _toolPackageStore.GetRootPackageDirectory(packageId);
  92. return TransactionalAction.Run<IToolPackage>(
  93. action: () =>
  94. {
  95. var versionString = versionRange?.OriginalString ?? "*";
  96. versionRange = VersionRange.Parse(versionString);
  97. _toolDownloadDir = isGlobalTool ? _globalToolStageDir : _localToolDownloadDir;
  98. var assetFileDirectory = isGlobalTool ? _globalToolStageDir : _localToolAssetDir;
  99. rollbackDirectory = _toolDownloadDir.Value;
  100. if (string.IsNullOrEmpty(packageId.ToString()))
  101. {
  102. throw new ToolPackageException(LocalizableStrings.ToolInstallationRestoreFailed);
  103. }
  104. var feedPackage = GetPackage(
  105. packageId.ToString(),
  106. versionRange,
  107. packageLocation.NugetConfig,
  108. packageLocation.RootConfigDirectory);
  109. var packageVersion = feedPackage.Version;
  110. targetFramework = string.IsNullOrEmpty(targetFramework) ? "targetFramework" : targetFramework;
  111. rollbackDirectory = isGlobalTool ? _toolDownloadDir.Value : Path.Combine(_toolDownloadDir.Value, packageId.ToString(), packageVersion.ToString());
  112. var fakeExecutableSubDirectory = Path.Combine(
  113. packageId.ToString().ToLowerInvariant(),
  114. packageVersion.ToLowerInvariant(),
  115. "tools",
  116. targetFramework,
  117. Constants.AnyRid);
  118. var fakeExecutablePath = Path.Combine(fakeExecutableSubDirectory, FakeEntrypointName);
  119. _fileSystem.Directory.CreateDirectory(Path.Combine(_toolDownloadDir.Value, fakeExecutableSubDirectory));
  120. _fileSystem.File.CreateEmptyFile(Path.Combine(_toolDownloadDir.Value, fakeExecutablePath));
  121. _fileSystem.File.WriteAllText(
  122. _toolDownloadDir.WithFile("project.assets.json").Value,
  123. fakeExecutablePath);
  124. _fileSystem.File.WriteAllText(
  125. _toolDownloadDir.WithFile(FakeCommandSettingsFileName).Value,
  126. JsonSerializer.Serialize(new { Name = feedPackage.ToolCommandName }));
  127. if (_downloadCallback != null)
  128. {
  129. _downloadCallback();
  130. }
  131. var version = _toolPackageStore.GetStagedPackageVersion(_toolDownloadDir, packageId);
  132. var packageDirectory = _toolPackageStore.GetPackageDirectory(packageId, version);
  133. if (_fileSystem.Directory.Exists(packageDirectory.Value))
  134. {
  135. throw new ToolPackageException(
  136. string.Format(
  137. CommonLocalizableStrings.ToolPackageConflictPackageId,
  138. packageId,
  139. version.ToNormalizedString()));
  140. }
  141. if (!isGlobalTool)
  142. {
  143. packageDirectory = new DirectoryPath(NuGetGlobalPackagesFolder.GetLocation()).WithSubDirectories(packageId.ToString());
  144. _fileSystem.Directory.CreateDirectory(packageDirectory.Value);
  145. var executable = packageDirectory.WithFile("exe");
  146. _fileSystem.File.CreateEmptyFile(executable.Value);
  147. rollbackDirectory = Path.Combine(packageDirectory.Value, packageVersion);
  148. return new TestToolPackage
  149. {
  150. Id = packageId,
  151. Version = NuGetVersion.Parse(feedPackage.Version),
  152. Commands = new List<RestoredCommand> {
  153. new RestoredCommand(new ToolCommandName(feedPackage.ToolCommandName), "runner", executable) },
  154. Warnings = Array.Empty<string>(),
  155. PackagedShims = Array.Empty<FilePath>()
  156. };
  157. }
  158. else
  159. {
  160. var packageRootDirectory = _toolPackageStore.GetRootPackageDirectory(packageId);
  161. _fileSystem.Directory.CreateDirectory(packageRootDirectory.Value);
  162. _fileSystem.Directory.Move(_toolDownloadDir.Value, packageDirectory.Value);
  163. rollbackDirectory = packageDirectory.Value;
  164. IEnumerable<string> warnings = null;
  165. _warningsMap.TryGetValue(packageId, out warnings);
  166. IReadOnlyList<FilePath> packedShims = null;
  167. _packagedShimsMap.TryGetValue(packageId, out packedShims);
  168. IEnumerable<NuGetFramework> frameworks = null;
  169. _frameworksMap.TryGetValue(packageId, out frameworks);
  170. return new ToolPackageMock(_fileSystem, id: packageId,
  171. version: version,
  172. packageDirectory: packageDirectory,
  173. warnings: warnings, packagedShims: packedShims, frameworks: frameworks);
  174. }
  175. },
  176. rollback: () =>
  177. {
  178. if (rollbackDirectory != null && _fileSystem.Directory.Exists(rollbackDirectory))
  179. {
  180. _fileSystem.Directory.Delete(rollbackDirectory, true);
  181. }
  182. if (_fileSystem.Directory.Exists(packageRootDirectory.Value) &&
  183. !_fileSystem.Directory.EnumerateFileSystemEntries(packageRootDirectory.Value).Any())
  184. {
  185. _fileSystem.Directory.Delete(packageRootDirectory.Value, false);
  186. }
  187. });
  188. }
  189. public MockFeedPackage GetPackage(
  190. string packageId,
  191. VersionRange versionRange,
  192. FilePath? nugetConfig = null,
  193. DirectoryPath? rootConfigDirectory = null)
  194. {
  195. var allPackages = _feeds
  196. .Where(feed =>
  197. {
  198. if (nugetConfig == null)
  199. {
  200. return SimulateNugetSearchNugetConfigAndMatch(
  201. rootConfigDirectory,
  202. feed);
  203. }
  204. else
  205. {
  206. return ExcludeOtherFeeds(nugetConfig.Value, feed);
  207. }
  208. })
  209. .SelectMany(f => f.Packages)
  210. .Where(f => f.PackageId == packageId)
  211. .ToList();
  212. var bestVersion = versionRange.FindBestMatch(allPackages.Select(p => NuGetVersion.Parse(p.Version)));
  213. var package = allPackages.FirstOrDefault(p => NuGetVersion.Parse(p.Version).Equals(bestVersion));
  214. if (package == null)
  215. {
  216. _reporter?.WriteLine($"Error: failed to restore package {packageId}.");
  217. throw new ToolPackageException(LocalizableStrings.ToolInstallationRestoreFailed);
  218. }
  219. return package;
  220. }
  221. /// <summary>
  222. /// Simulate NuGet search nuget config from parent directories.
  223. /// Assume all nuget.config has Clear
  224. /// And then filter against mock feed
  225. /// </summary>
  226. private bool SimulateNugetSearchNugetConfigAndMatch(
  227. DirectoryPath? rootConfigDirectory,
  228. MockFeed feed)
  229. {
  230. if (rootConfigDirectory != null)
  231. {
  232. var probedNugetConfig = EnumerateDefaultAllPossibleNuGetConfig(rootConfigDirectory.Value)
  233. .FirstOrDefault(possibleNugetConfig =>
  234. _fileSystem.File.Exists(possibleNugetConfig.Value));
  235. if (!Equals(probedNugetConfig, default(FilePath)))
  236. {
  237. return (feed.Type == MockFeedType.FeedFromLookUpNugetConfig) ||
  238. (feed.Type == MockFeedType.ImplicitAdditionalFeed) ||
  239. (feed.Type == MockFeedType.FeedFromLookUpNugetConfig
  240. && feed.Uri == probedNugetConfig.Value);
  241. }
  242. }
  243. return feed.Type != MockFeedType.ExplicitNugetConfig
  244. && feed.Type != MockFeedType.FeedFromLookUpNugetConfig;
  245. }
  246. private static IEnumerable<FilePath> EnumerateDefaultAllPossibleNuGetConfig(DirectoryPath probStart)
  247. {
  248. DirectoryPath? currentSearchDirectory = probStart;
  249. while (currentSearchDirectory.HasValue)
  250. {
  251. var tryNugetConfig = currentSearchDirectory.Value.WithFile("nuget.config");
  252. yield return tryNugetConfig;
  253. currentSearchDirectory = currentSearchDirectory.Value.GetParentPathNullable();
  254. }
  255. }
  256. private static bool ExcludeOtherFeeds(FilePath nugetConfig, MockFeed f)
  257. {
  258. return f.Type == MockFeedType.ImplicitAdditionalFeed
  259. || (f.Type == MockFeedType.ExplicitNugetConfig && f.Uri == nugetConfig.Value);
  260. }
  261. private class TestToolPackage : IToolPackage
  262. {
  263. public PackageId Id { get; set; }
  264. public NuGetVersion Version { get; set; }
  265. public DirectoryPath PackageDirectory { get; set; }
  266. public IReadOnlyList<RestoredCommand> Commands { get; set; }
  267. public IEnumerable<string> Warnings { get; set; }
  268. public IReadOnlyList<FilePath> PackagedShims { get; set; }
  269. public IEnumerable<NuGetFramework> Frameworks => throw new NotImplementedException();
  270. }
  271. }
  272. }