diff --git a/src/NuGet.Clients/NuGet.PackageManagement.UI/Actions/UIActionEngine.cs b/src/NuGet.Clients/NuGet.PackageManagement.UI/Actions/UIActionEngine.cs index eedfae67640..b5de55cd3ff 100644 --- a/src/NuGet.Clients/NuGet.PackageManagement.UI/Actions/UIActionEngine.cs +++ b/src/NuGet.Clients/NuGet.PackageManagement.UI/Actions/UIActionEngine.cs @@ -835,20 +835,17 @@ private async Task> GetActionsAsync( var results = new List(); Debug.Assert(userAction.PackageId != null, "Package id can never be null in a User action"); + if (userAction.Action == NuGetProjectActionType.Install) { - foreach (var target in targets) - { - var actions = await _packageManager.PreviewInstallPackageAsync( - target, - new PackageIdentity(userAction.PackageId, userAction.Version), - resolutionContext, - projectContext, - uiService.ActiveSources, - null, - token); - results.AddRange(actions.Select(a => new ResolvedAction(target, a))); - } + results.AddRange(await _packageManager.PreviewProjectsInstallPackageAsync( + targets?.ToList(), + new PackageIdentity(userAction.PackageId, userAction.Version), + resolutionContext, + projectContext, + uiService.ActiveSources?.ToList(), + token + )); } else { diff --git a/src/NuGet.Core/NuGet.PackageManagement/BuildIntegration/DependencyGraphRestoreUtility.cs b/src/NuGet.Core/NuGet.PackageManagement/BuildIntegration/DependencyGraphRestoreUtility.cs index de98fdc8101..37e4b185ab7 100644 --- a/src/NuGet.Core/NuGet.PackageManagement/BuildIntegration/DependencyGraphRestoreUtility.cs +++ b/src/NuGet.Core/NuGet.PackageManagement/BuildIntegration/DependencyGraphRestoreUtility.cs @@ -136,7 +136,7 @@ public static string GetDefaultDGSpecFileName() } /// - /// Restore without writing the lock file + /// Restore a project without writing the lock file /// internal static async Task PreviewRestoreAsync( ISolutionManager solutionManager, @@ -150,6 +150,8 @@ internal static async Task PreviewRestoreAsync( ILogger log, CancellationToken token) { + token.ThrowIfCancellationRequested(); + // Restoring packages var logger = context.Logger; @@ -185,6 +187,59 @@ internal static async Task PreviewRestoreAsync( } } + /// + /// Restore many projects without writing the lock file + /// SourceRepositories(sources) is only used for the CachingSourceProvider, the project-specific sources will still be resolved in RestoreRunner. + /// + internal static async Task> PreviewRestoreProjectsAsync( + ISolutionManager solutionManager, + IEnumerable projects, + IEnumerable updatedNugetPackageSpecs, + DependencyGraphCacheContext context, + RestoreCommandProvidersCache providerCache, + Action cacheContextModifier, + IEnumerable sources, + Guid parentId, + ILogger log, + CancellationToken token) + { + token.ThrowIfCancellationRequested(); + + // Add the new spec to the dg file and fill in the rest. + var dgFile = await GetSolutionRestoreSpec(solutionManager, context); + + dgFile = dgFile.WithoutRestores() + .WithPackageSpecs(updatedNugetPackageSpecs); + + foreach (var project in projects) + { + dgFile.AddRestore(project.MSBuildProjectPath); + } + + using (var sourceCacheContext = new SourceCacheContext()) + { + // Update cache context + cacheContextModifier(sourceCacheContext); + + // Settings passed here will be used to populate the restore requests. + var restoreContext = GetRestoreContext( + context, + providerCache, + sourceCacheContext, + sources, + dgFile, + parentId, + forceRestore: true, + isRestoreOriginalAction: false, + restoreForceEvaluate: true, + additionalMessasges: null); + + var requests = await RestoreRunner.GetRequests(restoreContext); + var results = await RestoreRunner.RunWithoutCommit(requests, restoreContext); + return results; + } + } + /// /// Restore a build integrated project(PackageReference and Project.Json only) and update the lock file /// diff --git a/src/NuGet.Core/NuGet.PackageManagement/NuGetPackageManager.cs b/src/NuGet.Core/NuGet.PackageManagement/NuGetPackageManager.cs index 7e075b1e723..49d21ab0939 100644 --- a/src/NuGet.Core/NuGet.PackageManagement/NuGetPackageManager.cs +++ b/src/NuGet.Core/NuGet.PackageManagement/NuGetPackageManager.cs @@ -42,7 +42,7 @@ public class NuGetPackageManager private Configuration.ISettings Settings { get; } - private IDictionary _buildIntegratedProjectsUpdateDict; + private HashSet _buildIntegratedProjectsUpdateSet; private DependencyGraphSpec _buildIntegratedProjectsCache; @@ -767,8 +767,8 @@ private async Task> GetPackagesToUpdateInProjectAsync( return packagesToUpdateInProject; } - private async Task> CompleteTaskAsync( - List>> updateTasks) + private async Task> CompleteTaskAsync( + List>> updateTasks) { var doneTask = await Task.WhenAny(updateTasks); updateTasks.Remove(doneTask); @@ -1529,6 +1529,99 @@ await PreviewBuildIntegratedProjectActionsAsync(buildIntegratedProject, actions, nuGetProjectContext, primarySources, secondarySources, token); } + // Preview and return ResolvedActions for many NuGetProjects. + public async Task> PreviewProjectsInstallPackageAsync( + IReadOnlyCollection nuGetProjects, + PackageIdentity packageIdentity, + ResolutionContext resolutionContext, + INuGetProjectContext nuGetProjectContext, + IReadOnlyCollection activeSources, + CancellationToken token) + { + if (nuGetProjects == null) + { + throw new ArgumentNullException(nameof(nuGetProjects)); + } + + if (packageIdentity == null) + { + throw new ArgumentNullException(nameof(packageIdentity)); + } + + if (resolutionContext == null) + { + throw new ArgumentNullException(nameof(resolutionContext)); + } + + if (nuGetProjectContext == null) + { + throw new ArgumentNullException(nameof(nuGetProjectContext)); + } + + if (activeSources == null) + { + throw new ArgumentNullException(nameof(activeSources)); + } + + if (activeSources.Count == 0) + { + throw new ArgumentException("At least 1 item expected for " + nameof(activeSources)); + } + + if (packageIdentity.Version == null) + { + throw new ArgumentNullException(nameof(packageIdentity)); + } + + var results = new List(); + + // BuildIntegratedNuGetProject type projects are now supports parallel preview action for faster performance. + var buildIntegratedProjectsToUpdate = new List(); + // Currently packages.config projects are not supported are supports parallel previews. + // Here is follow up issue to address it https://github.com/NuGet/Home/issues/9906 + var otherTargetProjectsToUpdate = new List(); + + foreach (var proj in nuGetProjects) + { + if (proj is BuildIntegratedNuGetProject buildIntegratedNuGetProject) + { + buildIntegratedProjectsToUpdate.Add(buildIntegratedNuGetProject); + } + else + { + otherTargetProjectsToUpdate.Add(proj); + } + } + + if (buildIntegratedProjectsToUpdate.Count != 0) + { + // Run build integrated project preview for all projects at the same time + var resolvedActions = await PreviewBuildIntegratedProjectsActionsAsync( + buildIntegratedProjectsToUpdate, + nugetProjectActionsLookup: null, // no nugetProjectActionsLookup so it'll be derived from packageIdentity and activeSources + packageIdentity, + activeSources, + nuGetProjectContext, + token); + results.AddRange(resolvedActions); + } + + foreach (var target in otherTargetProjectsToUpdate) + { + var actions = await PreviewInstallPackageAsync( + target, + packageIdentity, + resolutionContext, + nuGetProjectContext, + activeSources, + null, + token); + results.AddRange(actions.Select(a => new ResolvedAction(target, a))); + } + + return results; + } + public async Task> PreviewInstallPackageAsync(NuGetProject nuGetProject, PackageIdentity packageIdentity, ResolutionContext resolutionContext, INuGetProjectContext nuGetProjectContext, IEnumerable primarySources, IEnumerable secondarySources, @@ -2062,7 +2155,7 @@ public async Task ExecuteNuGetProjectActionsAsync(IEnumerable nuGe { var logger = new ProjectContextLogger(nuGetProjectContext); var referenceContext = new DependencyGraphCacheContext(logger, Settings); - _buildIntegratedProjectsUpdateDict = new Dictionary(StringComparer.OrdinalIgnoreCase); + _buildIntegratedProjectsUpdateSet = new HashSet(PathUtility.GetStringComparerBasedOnOS()); var projectUniqueNamesForBuildIntToUpdate = buildIntegratedProjectsToUpdate.ToDictionary((project) => project.MSBuildProjectPath); @@ -2071,6 +2164,10 @@ var projectUniqueNamesForBuildIntToUpdate _buildIntegratedProjectsCache = dgFile; var allSortedProjects = DependencyGraphSpec.SortPackagesByDependencyOrder(dgFile.Projects); + // cache these already evaluated(without commit) buildIntegratedProjects project ids which will be used to avoid duplicate restore as part of parent projects + _buildIntegratedProjectsUpdateSet.AddRange( + buildIntegratedProjectsToUpdate.Select(child => child.MSBuildProjectPath)); + foreach (var projectUniqueName in allSortedProjects.Select(e => e.RestoreMetadata.ProjectUniqueName)) { BuildIntegratedNuGetProject project; @@ -2079,10 +2176,6 @@ var projectUniqueNamesForBuildIntToUpdate sortedProjectsToUpdate.Add(project); } } - - // cache these projects which will be used to avoid duplicate restore as part of parent projects - _buildIntegratedProjectsUpdateDict.AddRange( - buildIntegratedProjectsToUpdate.Select(child => new KeyValuePair(child.MSBuildProjectPath, false))); } // execute all nuget project actions @@ -2093,7 +2186,7 @@ var projectUniqueNamesForBuildIntToUpdate } // clear cache which could temper with other updates - _buildIntegratedProjectsUpdateDict?.Clear(); + _buildIntegratedProjectsUpdateSet?.Clear(); _buildIntegratedProjectsCache = null; _restoreProviderCache = null; } @@ -2459,7 +2552,7 @@ await ExecuteInstallAsync( } /// - /// Run project actions for build integrated projects. + /// Run project actions for a build integrated project. /// public async Task PreviewBuildIntegratedProjectActionsAsync( BuildIntegratedNuGetProject buildIntegratedProject, @@ -2488,41 +2581,71 @@ public async Task PreviewBuildIntegratedProjectAct return null; } - var stopWatch = Stopwatch.StartNew(); - - // Find all sources used in the project actions - var sources = new HashSet( - nuGetProjectActions.Where(action => action.SourceRepository != null) - .Select(action => action.SourceRepository), - new SourceRepositoryComparer()); + var resolvedAction = await PreviewBuildIntegratedProjectsActionsAsync( + new List() { buildIntegratedProject }, + new Dictionary(PathUtility.GetStringComparerBasedOnOS()) + { + { buildIntegratedProject.MSBuildProjectPath, nuGetProjectActions.ToArray()} + }, + packageIdentity: null, // since we have nuGetProjectActions no need packageIdentity + primarySources: null, // since we have nuGetProjectActions no need primarySources + nuGetProjectContext, + token + ); - // Add all enabled sources for the existing packages - var enabledSources = SourceRepositoryProvider.GetRepositories(); + return resolvedAction.FirstOrDefault(r => r.Project == buildIntegratedProject)?.Action as BuildIntegratedProjectAction; + } - sources.UnionWith(enabledSources); - // Read the current lock file if it exists - LockFile originalLockFile = null; - var lockFileFormat = new LockFileFormat(); + /// + /// Run project actions for build integrated many projects. + /// + private async Task> PreviewBuildIntegratedProjectsActionsAsync( + IReadOnlyCollection buildIntegratedProjects, + Dictionary nugetProjectActionsLookup, + PackageIdentity packageIdentity, + IReadOnlyCollection primarySources, + INuGetProjectContext nuGetProjectContext, + CancellationToken token) + { + if (nugetProjectActionsLookup == null) + { + nugetProjectActionsLookup = new Dictionary(PathUtility.GetStringComparerBasedOnOS()); + } - var lockFilePath = await buildIntegratedProject.GetAssetsFilePathAsync(); + if (buildIntegratedProjects == null) + { + throw new ArgumentNullException(nameof(buildIntegratedProjects)); + } - if (File.Exists(lockFilePath)) + if (buildIntegratedProjects.Count == 0) { - originalLockFile = lockFileFormat.Read(lockFilePath); + // Return empty if there are no buildIntegratedProjects. + return Enumerable.Empty(); } - var logger = new ProjectContextLogger(nuGetProjectContext); - var dependencyGraphContext = new DependencyGraphCacheContext(logger, Settings); + if (nuGetProjectContext == null) + { + throw new ArgumentNullException(nameof(nuGetProjectContext)); + } - // Get Package Spec as json object - var originalPackageSpec = await DependencyGraphRestoreUtility.GetProjectSpec(buildIntegratedProject, dependencyGraphContext); + if (nugetProjectActionsLookup.Count == 0 && packageIdentity == null) + { + // Return empty if there are neither actions nor packageIdentity. + return Enumerable.Empty(); + } - // Create a copy to avoid modifying the original spec which may be shared. - var updatedPackageSpec = originalPackageSpec.Clone(); + var stopWatch = Stopwatch.StartNew(); + var logger = new ProjectContextLogger(nuGetProjectContext); + var result = new List(); + var lockFileLookup = new Dictionary(PathUtility.GetStringComparerBasedOnOS()); + var dependencyGraphContext = new DependencyGraphCacheContext(logger, Settings); var pathContext = NuGetPathContext.Create(Settings); var providerCache = new RestoreCommandProvidersCache(); + var updatedNugetPackageSpecLookup = new Dictionary(PathUtility.GetStringComparerBasedOnOS()); + var originalNugetPackageSpecLookup = new Dictionary(PathUtility.GetStringComparerBasedOnOS()); + var nuGetProjectSourceLookup = new Dictionary>(PathUtility.GetStringComparerBasedOnOS()); // For installs only use cache entries newer than the current time. // This is needed for scenarios where a new package shows up in search @@ -2532,160 +2655,246 @@ public async Task PreviewBuildIntegratedProjectAct var now = DateTimeOffset.UtcNow; void cacheModifier(SourceCacheContext cache) => cache.MaxAge = now; - // If the lock file does not exist, restore before starting the operations - if (originalLockFile == null) + // Add all enabled sources for the existing projects + var enabledSources = SourceRepositoryProvider.GetRepositories(); + var allSources = new HashSet(enabledSources, new SourceRepositoryComparer()); + + foreach (var buildIntegratedProject in buildIntegratedProjects) { - var originalRestoreResult = await DependencyGraphRestoreUtility.PreviewRestoreAsync( - SolutionManager, - buildIntegratedProject, - originalPackageSpec, - dependencyGraphContext, - providerCache, - cacheModifier, - sources, - nuGetProjectContext.OperationId, - logger, - token); + NuGetProjectAction[] nuGetProjectActions; - originalLockFile = originalRestoreResult.Result.LockFile; - } + if (packageIdentity != null) + { + if (primarySources == null || primarySources.Count == 0) + { + throw new ArgumentNullException($"Should have value in {nameof(primarySources)} if there is value for {nameof(packageIdentity)}"); + } - foreach (var action in nuGetProjectActions) - { - if (action.NuGetProjectActionType == NuGetProjectActionType.Uninstall) + var nugetAction = NuGetProjectAction.CreateInstallProjectAction(packageIdentity, primarySources.First(), buildIntegratedProject); + nuGetProjectActions = new[] { nugetAction }; + nugetProjectActionsLookup[buildIntegratedProject.MSBuildProjectPath] = nuGetProjectActions; + } + else { - // Remove the package from all frameworks and dependencies section. - PackageSpecOperations.RemoveDependency(updatedPackageSpec, action.PackageIdentity.Id); + if (!nugetProjectActionsLookup.ContainsKey(buildIntegratedProject.MSBuildProjectPath)) + { + throw new ArgumentNullException($"Either should have value in {nameof(nugetProjectActionsLookup)} for {buildIntegratedProject.MSBuildProjectPath} or {nameof(packageIdentity)} & {nameof(primarySources)}"); + } + + nuGetProjectActions = nugetProjectActionsLookup[buildIntegratedProject.MSBuildProjectPath]; + + if (nuGetProjectActions.Length == 0) + { + // Continue to next project if there are no actions for current project. + continue; + } } - else if (action.NuGetProjectActionType == NuGetProjectActionType.Install) + + // Find all sources used in the project actions + var sources = new HashSet( + nuGetProjectActions.Where(action => action.SourceRepository != null) + .Select(action => action.SourceRepository), + new SourceRepositoryComparer()); + + allSources.UnionWith(sources); + sources.UnionWith(enabledSources); + nuGetProjectSourceLookup[buildIntegratedProject.MSBuildProjectPath] = sources; + + // Read the current lock file if it exists + LockFile originalLockFile = null; + var lockFileFormat = new LockFileFormat(); + + var lockFilePath = await buildIntegratedProject.GetAssetsFilePathAsync(); + + if (File.Exists(lockFilePath)) { - if (updatedPackageSpec.RestoreMetadata.ProjectStyle == ProjectStyle.PackageReference) + originalLockFile = lockFileFormat.Read(lockFilePath); + } + + // Get Package Spec as json object + var originalPackageSpec = await DependencyGraphRestoreUtility.GetProjectSpec(buildIntegratedProject, dependencyGraphContext); + originalNugetPackageSpecLookup[buildIntegratedProject.MSBuildProjectPath] = originalPackageSpec; + + // Create a copy to avoid modifying the original spec which may be shared. + var updatedPackageSpec = originalPackageSpec.Clone(); + + // If the lock file does not exist, restore before starting the operations + if (originalLockFile == null) + { + var originalRestoreResult = await DependencyGraphRestoreUtility.PreviewRestoreAsync( + SolutionManager, + buildIntegratedProject, + originalPackageSpec, + dependencyGraphContext, + providerCache, + cacheModifier, + sources, + nuGetProjectContext.OperationId, + logger, + token); + + originalLockFile = originalRestoreResult.Result.LockFile; + } + + lockFileLookup[buildIntegratedProject.MSBuildProjectPath] = originalLockFile; + + foreach (var action in nuGetProjectActions) + { + if (action.NuGetProjectActionType == NuGetProjectActionType.Uninstall) { - PackageSpecOperations.AddOrUpdateDependency(updatedPackageSpec, action.PackageIdentity, updatedPackageSpec.TargetFrameworks.Select(e => e.FrameworkName)); + // Remove the package from all frameworks and dependencies section. + PackageSpecOperations.RemoveDependency(updatedPackageSpec, action.PackageIdentity.Id); } - else + else if (action.NuGetProjectActionType == NuGetProjectActionType.Install) { - PackageSpecOperations.AddOrUpdateDependency(updatedPackageSpec, action.PackageIdentity); + if (updatedPackageSpec.RestoreMetadata.ProjectStyle == ProjectStyle.PackageReference) + { + PackageSpecOperations.AddOrUpdateDependency(updatedPackageSpec, action.PackageIdentity, updatedPackageSpec.TargetFrameworks.Select(e => e.FrameworkName)); + } + else + { + PackageSpecOperations.AddOrUpdateDependency(updatedPackageSpec, action.PackageIdentity); + } } + + updatedNugetPackageSpecLookup[buildIntegratedProject.MSBuildProjectPath] = updatedPackageSpec; + dependencyGraphContext.PackageSpecCache[buildIntegratedProject.MSBuildProjectPath] = updatedPackageSpec; } } - // Restore based on the modified package spec. This operation does not write the lock file to disk. - var restoreResult = await DependencyGraphRestoreUtility.PreviewRestoreAsync( + // Restore based on the modified package specs for many projects. This operation does not write the lock files to disk. + var restoreResults = await DependencyGraphRestoreUtility.PreviewRestoreProjectsAsync( SolutionManager, - buildIntegratedProject, - updatedPackageSpec, + buildIntegratedProjects, + updatedNugetPackageSpecLookup.Values, dependencyGraphContext, providerCache, cacheModifier, - sources, + allSources, nuGetProjectContext.OperationId, logger, token); - var nugetProjectActionsList = nuGetProjectActions.ToList(); - - var allFrameworks = updatedPackageSpec - .TargetFrameworks - .Select(t => t.FrameworkName) - .Distinct() - .ToList(); - - var unsuccessfulFrameworks = restoreResult - .Result - .CompatibilityCheckResults - .Where(t => !t.Success) - .Select(t => t.Graph.Framework) - .Distinct() - .ToList(); - - var successfulFrameworks = allFrameworks - .Except(unsuccessfulFrameworks) - .ToList(); - - var firstAction = nugetProjectActionsList[0]; - - // If the restore failed and this was a single package install, try to install the package to a subset of - // the target frameworks. - if (nugetProjectActionsList.Count == 1 && - firstAction.NuGetProjectActionType == NuGetProjectActionType.Install && - successfulFrameworks.Any() && - unsuccessfulFrameworks.Any() && - !restoreResult.Result.Success && - // Exclude upgrades, for now we take the simplest case. - !PackageSpecOperations.HasPackage(originalPackageSpec, firstAction.PackageIdentity.Id)) - { - updatedPackageSpec = originalPackageSpec.Clone(); - - PackageSpecOperations.AddOrUpdateDependency( - updatedPackageSpec, - firstAction.PackageIdentity, - successfulFrameworks); - - restoreResult = await DependencyGraphRestoreUtility.PreviewRestoreAsync( - SolutionManager, - buildIntegratedProject, - updatedPackageSpec, - dependencyGraphContext, - providerCache, - cacheModifier, - sources, - nuGetProjectContext.OperationId, - logger, - token); - } - - // If HideWarningsAndErrors is true then restore will not display the warnings and errors. - // Further, replay errors and warnings only if restore failed because the assets file will not be committed. - // If there were only warnings then those are written to assets file and committed. The design time build will replay them. - if (updatedPackageSpec.RestoreSettings.HideWarningsAndErrors && - !restoreResult.Result.Success) + foreach (var buildIntegratedProject in buildIntegratedProjects) { - await MSBuildRestoreUtility.ReplayWarningsAndErrorsAsync(restoreResult.Result.LockFile?.LogMessages, logger); - } + var nuGetProjectActions = nugetProjectActionsLookup[buildIntegratedProject.MSBuildProjectPath]; + var nuGetProjectActionsList = nuGetProjectActions; + var updatedPackageSpec = updatedNugetPackageSpecLookup[buildIntegratedProject.MSBuildProjectPath]; + var originalPackageSpec = originalNugetPackageSpecLookup[buildIntegratedProject.MSBuildProjectPath]; + var originalLockFile = lockFileLookup[buildIntegratedProject.MSBuildProjectPath]; + var sources = nuGetProjectSourceLookup[buildIntegratedProject.MSBuildProjectPath]; - // Build the installation context - var originalFrameworks = updatedPackageSpec - .TargetFrameworks - .ToDictionary(x => x.FrameworkName, x => x.TargetAlias); + var allFrameworks = updatedPackageSpec + .TargetFrameworks + .Select(t => t.FrameworkName) + .Distinct() + .ToList(); - var installationContext = new BuildIntegratedInstallationContext( - successfulFrameworks, - unsuccessfulFrameworks, - originalFrameworks); + var restoreResult = restoreResults.Single(r => + string.Equals( + r.SummaryRequest.Request.Project.RestoreMetadata.ProjectPath, + buildIntegratedProject.MSBuildProjectPath, + StringComparison.OrdinalIgnoreCase)); + + var unsuccessfulFrameworks = restoreResult + .Result + .CompatibilityCheckResults + .Where(t => !t.Success) + .Select(t => t.Graph.Framework) + .Distinct() + .ToList(); + + var successfulFrameworks = allFrameworks + .Except(unsuccessfulFrameworks) + .ToList(); + + var firstAction = nuGetProjectActionsList[0]; + + // If the restore failed and this was a single package install, try to install the package to a subset of + // the target frameworks. + if (nuGetProjectActionsList.Length == 1 && + firstAction.NuGetProjectActionType == NuGetProjectActionType.Install && + !restoreResult.Result.Success && + successfulFrameworks.Any() && + unsuccessfulFrameworks.Any() && + // Exclude upgrades, for now we take the simplest case. + !PackageSpecOperations.HasPackage(originalPackageSpec, firstAction.PackageIdentity.Id)) + { + updatedPackageSpec = originalPackageSpec.Clone(); + + PackageSpecOperations.AddOrUpdateDependency( + updatedPackageSpec, + firstAction.PackageIdentity, + successfulFrameworks); + + restoreResult = await DependencyGraphRestoreUtility.PreviewRestoreAsync( + SolutionManager, + buildIntegratedProject, + updatedPackageSpec, + dependencyGraphContext, + providerCache, + cacheModifier, + sources, + nuGetProjectContext.OperationId, + logger, + token); + } - InstallationCompatibility.EnsurePackageCompatibility( - buildIntegratedProject, - pathContext, - nuGetProjectActions, - restoreResult.Result); + // If HideWarningsAndErrors is true then restore will not display the warnings and errors. + // Further, replay errors and warnings only if restore failed because the assets file will not be committed. + // If there were only warnings then those are written to assets file and committed. The design time build will replay them. + if (updatedPackageSpec.RestoreSettings.HideWarningsAndErrors && + !restoreResult.Result.Success) + { + await MSBuildRestoreUtility.ReplayWarningsAndErrorsAsync(restoreResult.Result.LockFile?.LogMessages, logger); + } - // If this build integrated project action represents only uninstalls, mark the entire operation - // as an uninstall. Otherwise, mark it as an install. This is important because install operations - // are a bit more sensitive to errors (thus resulting in rollbacks). - var actionType = NuGetProjectActionType.Install; - if (nuGetProjectActions.All(x => x.NuGetProjectActionType == NuGetProjectActionType.Uninstall)) - { - actionType = NuGetProjectActionType.Uninstall; + // Build the installation context + var originalFrameworks = updatedPackageSpec + .TargetFrameworks + .ToDictionary(x => x.FrameworkName, x => x.TargetAlias); + + var installationContext = new BuildIntegratedInstallationContext( + successfulFrameworks, + unsuccessfulFrameworks, + originalFrameworks); + + InstallationCompatibility.EnsurePackageCompatibility( + buildIntegratedProject, + pathContext, + nuGetProjectActions, + restoreResult.Result); + + // If this build integrated project action represents only uninstalls, mark the entire operation + // as an uninstall. Otherwise, mark it as an install. This is important because install operations + // are a bit more sensitive to errors (thus resulting in rollbacks). + var actionType = NuGetProjectActionType.Install; + if (nuGetProjectActions.All(x => x.NuGetProjectActionType == NuGetProjectActionType.Uninstall)) + { + actionType = NuGetProjectActionType.Uninstall; + } + + var nugetProjectAction = new BuildIntegratedProjectAction( + buildIntegratedProject, + nuGetProjectActions.First().PackageIdentity, + actionType, + originalLockFile, + restoreResult, + sources.ToList(), + nuGetProjectActionsList, + installationContext); + + result.Add(new ResolvedAction(buildIntegratedProject, nugetProjectAction)); } stopWatch.Stop(); - var actionTelemetryEvent = new ActionTelemetryStepEvent( nuGetProjectContext.OperationId.ToString(), TelemetryConstants.PreviewBuildIntegratedStepName, stopWatch.Elapsed.TotalSeconds); TelemetryActivity.EmitTelemetryEvent(actionTelemetryEvent); - return new BuildIntegratedProjectAction( - buildIntegratedProject, - nuGetProjectActions.First().PackageIdentity, - actionType, - originalLockFile, - restoreResult, - sources.ToList(), - nugetProjectActionsList, - installationContext); + return result; } /// @@ -2792,30 +3001,9 @@ await buildIntegratedProject.UninstallPackageAsync( var now = DateTime.UtcNow; void cacheContextModifier(SourceCacheContext c) => c.MaxAge = now; - // Check if current project is there in update cache and needs revaluation - var isProjectUpdated = false; - if (_buildIntegratedProjectsUpdateDict != null && - _buildIntegratedProjectsUpdateDict.TryGetValue( - buildIntegratedProject.MSBuildProjectPath, - out isProjectUpdated) && - isProjectUpdated) - { - await DependencyGraphRestoreUtility.RestoreProjectAsync( - SolutionManager, - buildIntegratedProject, - referenceContext, - GetRestoreProviderCache(), - cacheContextModifier, - projectAction.Sources, - nuGetProjectContext.OperationId, - logger, - token); - } - else - { - // Write out the lock file - await RestoreRunner.CommitAsync(projectAction.RestoreResultPair, token); - } + // Write out the lock file, now no need bubbling re-evaluating of parent projects when you restore from PM UI. + // We already taken account of that concern in PreviewBuildIntegratedProjectsActionsAsync method. + await RestoreRunner.CommitAsync(projectAction.RestoreResultPair, token); // add packages lock file into project if (PackagesLockFileUtilities.IsNuGetLockFileEnabled(projectAction.RestoreResult.LockFile.PackageSpec)) @@ -2883,16 +3071,13 @@ await BuildIntegratedRestoreUtility.ExecuteInitPs1ScriptsAsync( foreach (var parent in parents) { - // if this parent exists in update cache, then update it's entry to be re-evaluated - if (_buildIntegratedProjectsUpdateDict != null && - _buildIntegratedProjectsUpdateDict.ContainsKey(parent.MSBuildProjectPath)) - { - _buildIntegratedProjectsUpdateDict[parent.MSBuildProjectPath] = true; - } - else + // We only evaluate unseen parents. + if (_buildIntegratedProjectsUpdateSet == null || + !_buildIntegratedProjectsUpdateSet.Contains(parent.MSBuildProjectPath)) { // Mark project for restore dgSpecForParents.AddRestore(parent.MSBuildProjectPath); + _buildIntegratedProjectsUpdateSet?.Add(parent.MSBuildProjectPath); } } diff --git a/src/NuGet.Core/NuGet.PackageManagement/PublicAPI.Unshipped.txt b/src/NuGet.Core/NuGet.PackageManagement/PublicAPI.Unshipped.txt index 30dbf8f06a0..3f360850c1c 100644 --- a/src/NuGet.Core/NuGet.PackageManagement/PublicAPI.Unshipped.txt +++ b/src/NuGet.Core/NuGet.PackageManagement/PublicAPI.Unshipped.txt @@ -1,7 +1,8 @@ +NuGet.PackageManagement.NuGetPackageManager.PreviewProjectsInstallPackageAsync(System.Collections.Generic.IReadOnlyCollection nuGetProjects, NuGet.Packaging.Core.PackageIdentity packageIdentity, NuGet.PackageManagement.ResolutionContext resolutionContext, NuGet.ProjectManagement.INuGetProjectContext nuGetProjectContext, System.Collections.Generic.IReadOnlyCollection activeSources, System.Threading.CancellationToken token) -> System.Threading.Tasks.Task> NuGet.ProjectManagement.BuildIntegratedPackageReference.BuildIntegratedPackageReference(NuGet.LibraryModel.LibraryDependency dependency, NuGet.Frameworks.NuGetFramework projectFramework, NuGet.Packaging.Core.PackageIdentity installedVersion) -> void NuGet.ProjectManagement.MessageLevelExtensions const NuGet.ProjectManagement.ProjectBuildProperties.TargetFrameworkIdentifier = "TargetFrameworkIdentifier" -> string const NuGet.ProjectManagement.ProjectBuildProperties.TargetFrameworkProfile = "TargetFrameworkProfile" -> string const NuGet.ProjectManagement.ProjectBuildProperties.TargetFrameworkVersion = "TargetFrameworkVersion" -> string const NuGet.ProjectManagement.ProjectBuildProperties.TargetPlatformMoniker = "TargetPlatformMoniker" -> string -static NuGet.ProjectManagement.MessageLevelExtensions.ToLogLevel(this NuGet.ProjectManagement.MessageLevel messageLevel) -> NuGet.Common.LogLevel \ No newline at end of file +static NuGet.ProjectManagement.MessageLevelExtensions.ToLogLevel(this NuGet.ProjectManagement.MessageLevel messageLevel) -> NuGet.Common.LogLevel diff --git a/src/NuGet.Core/NuGet.ProjectModel/DependencyGraphSpec.cs b/src/NuGet.Core/NuGet.ProjectModel/DependencyGraphSpec.cs index 8359ad4666b..7086c45d0eb 100644 --- a/src/NuGet.Core/NuGet.ProjectModel/DependencyGraphSpec.cs +++ b/src/NuGet.Core/NuGet.ProjectModel/DependencyGraphSpec.cs @@ -484,6 +484,24 @@ public DependencyGraphSpec WithReplacedSpec(PackageSpec project) return newSpec; } + public DependencyGraphSpec WithPackageSpecs(IEnumerable packageSpecs) + { + var newSpec = new DependencyGraphSpec(); + + foreach (var packageSpec in packageSpecs) + { + newSpec.AddProject(packageSpec); + newSpec.AddRestore(packageSpec.RestoreMetadata.ProjectUniqueName); + } + + foreach (var child in Projects) + { + newSpec.AddProject(child); + } + + return newSpec; + } + public DependencyGraphSpec WithoutTools() { var newSpec = new DependencyGraphSpec(); diff --git a/src/NuGet.Core/NuGet.ProjectModel/PublicAPI.Unshipped.txt b/src/NuGet.Core/NuGet.ProjectModel/PublicAPI.Unshipped.txt index 200d8542f19..48b6576908b 100644 --- a/src/NuGet.Core/NuGet.ProjectModel/PublicAPI.Unshipped.txt +++ b/src/NuGet.Core/NuGet.ProjectModel/PublicAPI.Unshipped.txt @@ -1,3 +1,4 @@ +NuGet.ProjectModel.DependencyGraphSpec.WithPackageSpecs(System.Collections.Generic.IEnumerable packageSpecs) -> NuGet.ProjectModel.DependencyGraphSpec NuGet.ProjectModel.LockFile.GetTarget(string frameworkAlias, string runtimeIdentifier) -> NuGet.ProjectModel.LockFileTarget NuGet.ProjectModel.ProjectRestoreMetadataFrameworkInfo.TargetAlias.get -> string NuGet.ProjectModel.ProjectRestoreMetadataFrameworkInfo.TargetAlias.set -> void diff --git a/test/NuGet.Clients.Tests/NuGet.PackageManagement.VisualStudio.Test/NetCorePackageReferenceProjectTests.cs b/test/NuGet.Clients.Tests/NuGet.PackageManagement.VisualStudio.Test/NetCorePackageReferenceProjectTests.cs index 1eb28945706..4589ff9bead 100644 --- a/test/NuGet.Clients.Tests/NuGet.PackageManagement.VisualStudio.Test/NetCorePackageReferenceProjectTests.cs +++ b/test/NuGet.Clients.Tests/NuGet.PackageManagement.VisualStudio.Test/NetCorePackageReferenceProjectTests.cs @@ -2,21 +2,30 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; using FluentAssertions; +using Microsoft.VisualStudio.ProjectSystem; using Moq; using NuGet.Commands; using NuGet.Commands.Test; +using NuGet.Common; using NuGet.Configuration; +using NuGet.Frameworks; +using NuGet.LibraryModel; +using NuGet.Packaging; using NuGet.Packaging.Core; +using NuGet.ProjectManagement; using NuGet.ProjectModel; +using NuGet.Protocol.Core.Types; using NuGet.Test.Utility; using NuGet.Versioning; using NuGet.VisualStudio; +using Test.Utility; using Xunit; namespace NuGet.PackageManagement.VisualStudio.Test @@ -447,6 +456,1817 @@ public async Task GetInstalledVersion_WithAssetsFile_ChangingPackageSpec_Returns } } + [Fact] + public async Task TestPackageManager_InstallPackageForAllProjects_Success() + { + using (var testDirectory = TestDirectory.Create()) + using (var testSolutionManager = new TestSolutionManager()) + { + // Set up Package Source + var sources = new List(); + var packageA_Version100 = new SimpleTestPackageContext("packageA", "1.0.0"); + var packageB_Version100 = new SimpleTestPackageContext("packageB", "1.0.0"); + var packageA = packageA_Version100.Identity; + var packageB = packageB_Version100.Identity; + var packageSource = Path.Combine(testDirectory, "packageSource"); + await SimpleTestPackageUtility.CreateFolderFeedV3Async( + packageSource, + PackageSaveMode.Defaultv3, + packageA_Version100, + packageB_Version100 + ); + + sources.Add(new PackageSource(packageSource)); + var sourceRepositoryProvider = TestSourceRepositoryUtility.CreateSourceRepositoryProvider(sources); + + // Project + int numberOfProjects = 4; + var projectCache = new ProjectSystemCache(); + IVsProjectAdapter projectAdapter = (new Mock()).Object; + ResolutionContext resolutionContext = (new Mock()).Object; + var packageSpecs = new PackageSpec[numberOfProjects]; + var projectFullPaths = new string[numberOfProjects]; + var deleteOnRestartManager = new TestDeleteOnRestartManager(); + var nuGetPackageManager = new NuGetPackageManager( + sourceRepositoryProvider, + NullSettings.Instance, + testSolutionManager, + deleteOnRestartManager); + + var testNuGetProjectContext = new TestNuGetProjectContext(); + var netCorePackageReferenceProjects = new List(); + var prevProj = string.Empty; + PackageSpec prevPackageSpec = null; + + // Create projects + for (var i = numberOfProjects - 1; i >= 0; i--) + { + var projectName = $"project{i}"; + var projectFullPath = Path.Combine(testDirectory.Path, projectName, projectName + ".csproj"); + var project = CreateTestNetCorePackageReferenceProject(projectName, projectFullPath, projectCache); + + // We need to treat NU1605 warning as error. + project.IsNu1605Error = true; + netCorePackageReferenceProjects.Add(project); + testSolutionManager.NuGetProjects.Add(project); + + //Let new project pickup my custom package source. + project.ProjectLocalSources.AddRange(sources); + var projectNames = GetTestProjectNames(projectFullPath, projectName); + var packageSpec = GetPackageSpec( + projectName, + projectFullPath, + packageA_Version100.Version); + + if (prevPackageSpec != null) + { + packageSpec = packageSpec.WithTestProjectReference(prevPackageSpec); + } + + // Restore info + var projectRestoreInfo = ProjectJsonTestHelpers.GetDGSpecFromPackageSpecs(packageSpec); + projectCache.AddProjectRestoreInfo(projectNames, projectRestoreInfo, new List()); + projectCache.AddProject(projectNames, projectAdapter, project).Should().BeTrue(); + prevProj = projectFullPath; + packageSpecs[i] = packageSpec; + prevPackageSpec = packageSpec; + projectFullPaths[i] = projectFullPath; + } + + var initialInstalledPackages = (await netCorePackageReferenceProjects[numberOfProjects - 1].GetInstalledPackagesAsync(CancellationToken.None)).ToList(); + + for (int i = 0; i < numberOfProjects; i++) + { + // This code added because nuGetPackageManager.InstallPackageAsync doesn't do updating ProjectSystemCache + var installed = new LibraryDependency + { + LibraryRange = new LibraryRange( + name: packageB.Id, + versionRange: new VersionRange(packageB.Version), + typeConstraint: LibraryDependencyTarget.Package), + }; + + var packageSpec = packageSpecs[i]; + packageSpec.TargetFrameworks.First().Dependencies.Add(installed); + var projectRestoreInfo = ProjectJsonTestHelpers.GetDGSpecFromPackageSpecs(packageSpec); + var projectNames = GetTestProjectNames(projectFullPaths[i], $"project{i}"); + projectCache.AddProjectRestoreInfo(projectNames, projectRestoreInfo, new List()); + } + + // Act + var results = await nuGetPackageManager.PreviewProjectsInstallPackageAsync( + netCorePackageReferenceProjects, // All projects + packageB, + resolutionContext, + testNuGetProjectContext, + sourceRepositoryProvider.GetRepositories().ToList(), + CancellationToken.None); + + var actions = results.Select(a => a.Action).ToArray(); + + await nuGetPackageManager.ExecuteNuGetProjectActionsAsync( + netCorePackageReferenceProjects, + actions, + testNuGetProjectContext, + new SourceCacheContext(), + CancellationToken.None); + + // Assert + Assert.Equal(initialInstalledPackages.Count(), 1); + var builtIntegratedActions = actions.OfType().ToList(); + Assert.Equal(actions.Length, builtIntegratedActions.Count); + Assert.True(builtIntegratedActions.All(b => b.RestoreResult.Success)); + Assert.True(builtIntegratedActions.All(b => !b.RestoreResult.LogMessages.Any())); // There should be no error or warning. + foreach (var netCorePackageReferenceProject in netCorePackageReferenceProjects) + { + var finalInstalledPackages = (await netCorePackageReferenceProject.GetInstalledPackagesAsync(CancellationToken.None)).ToList(); + Assert.True(finalInstalledPackages.Any(f => f.PackageIdentity.Id == packageA.Id + && f.PackageIdentity.Version == packageA.Version)); + Assert.True(finalInstalledPackages.Any(f => f.PackageIdentity.Id == packageB.Id + && f.PackageIdentity.Version == packageB.Version)); + } + } + } + + [Fact] + public async Task TestPackageManager_UpgradePackageForAllProjects_Success() + { + using (var testDirectory = TestDirectory.Create()) + using (var testSolutionManager = new TestSolutionManager()) + { + // Set up Package Source + var sources = new List(); + var packageA_Version100 = new SimpleTestPackageContext("packageA", "1.0.0"); + var packageB_Version100 = new SimpleTestPackageContext("packageB", "1.0.0"); + var packageB_Version200 = new SimpleTestPackageContext("packageB", "2.0.0"); + var packageA = packageA_Version100.Identity; + var packageB = packageB_Version100.Identity; + var packageB_UpgradeVersion = packageB_Version200.Identity; + var packageSource = Path.Combine(testDirectory, "packageSource"); + await SimpleTestPackageUtility.CreateFolderFeedV3Async( + packageSource, + PackageSaveMode.Defaultv3, + packageA_Version100, + packageB_Version100, + packageB_Version200 + ); + + sources.Add(new PackageSource(packageSource)); + var sourceRepositoryProvider = TestSourceRepositoryUtility.CreateSourceRepositoryProvider(sources); + + // Project + int numberOfProjects = 4; + var projectCache = new ProjectSystemCache(); + IVsProjectAdapter projectAdapter = (new Mock()).Object; + ResolutionContext resolutionContext = (new Mock()).Object; + var packageSpecs = new PackageSpec[numberOfProjects]; + var projectFullPaths = new string[numberOfProjects]; + var deleteOnRestartManager = new TestDeleteOnRestartManager(); + var nuGetPackageManager = new NuGetPackageManager( + sourceRepositoryProvider, + NullSettings.Instance, + testSolutionManager, + deleteOnRestartManager); + + var testNuGetProjectContext = new TestNuGetProjectContext() { EnableLogging = true }; + var netCorePackageReferenceProjects = new List(); + var prevProj = string.Empty; + PackageSpec prevPackageSpec = null; + + // Create projects + for (var i = numberOfProjects - 1; i >= 0; i--) + { + var projectName = $"project{i}"; + var projectFullPath = Path.Combine(testDirectory.Path, projectName, projectName + ".csproj"); + var project = CreateTestNetCorePackageReferenceProject(projectName, projectFullPath, projectCache); + + // We need to treat NU1605 warning as error. + project.IsNu1605Error = true; + netCorePackageReferenceProjects.Add(project); + testSolutionManager.NuGetProjects.Add(project); + + //Let new project pickup my custom package source. + project.ProjectLocalSources.AddRange(sources); + var projectNames = GetTestProjectNames(projectFullPath, projectName); + var packageSpec = GetPackageSpec( + projectName, + projectFullPath, + packageA_Version100.Version); + + if (prevPackageSpec != null) + { + packageSpec = packageSpec.WithTestProjectReference(prevPackageSpec); + } + + // Restore info + var projectRestoreInfo = ProjectJsonTestHelpers.GetDGSpecFromPackageSpecs(packageSpec); + projectCache.AddProjectRestoreInfo(projectNames, projectRestoreInfo, new List()); + projectCache.AddProject(projectNames, projectAdapter, project).Should().BeTrue(); + prevProj = projectFullPath; + packageSpecs[i] = packageSpec; + prevPackageSpec = packageSpec; + projectFullPaths[i] = projectFullPath; + } + + for (int i = 0; i < numberOfProjects; i++) + { + // Install packageB since packageA is already there. + await nuGetPackageManager.InstallPackageAsync(netCorePackageReferenceProjects[i], packageB, new ResolutionContext(), new TestNuGetProjectContext(), + sourceRepositoryProvider.GetRepositories(), sourceRepositoryProvider.GetRepositories(), CancellationToken.None); + + // This code added because nuGetPackageManager.InstallPackageAsync doesn't do updating ProjectSystemCache + var installed = new LibraryDependency + { + LibraryRange = new LibraryRange( + name: packageB.Id, + versionRange: new VersionRange(packageB.Version), + typeConstraint: LibraryDependencyTarget.Package), + }; + + var packageSpec = packageSpecs[i]; + packageSpec.TargetFrameworks.First().Dependencies.Add(installed); + var projectRestoreInfo = ProjectJsonTestHelpers.GetDGSpecFromPackageSpecs(packageSpec); + var projectNames = GetTestProjectNames(projectFullPaths[i], $"project{i}"); + projectCache.AddProjectRestoreInfo(projectNames, projectRestoreInfo, new List()); + } + + var initialInstalledPackages = (await netCorePackageReferenceProjects[numberOfProjects - 1].GetInstalledPackagesAsync(CancellationToken.None)).ToList(); + + // Act + var results = await nuGetPackageManager.PreviewProjectsInstallPackageAsync( + netCorePackageReferenceProjects, // All projects + packageB_UpgradeVersion, + resolutionContext, + testNuGetProjectContext, + sourceRepositoryProvider.GetRepositories().ToList(), + CancellationToken.None); + var actions = results.Select(a => a.Action).ToArray(); + await nuGetPackageManager.ExecuteNuGetProjectActionsAsync( + netCorePackageReferenceProjects, + actions, + testNuGetProjectContext, + new SourceCacheContext(), + CancellationToken.None); + + // Assert + Assert.Equal(initialInstalledPackages.Count(), 2); + var builtIntegratedActions = actions.OfType().ToList(); + Assert.Equal(actions.Length, builtIntegratedActions.Count); + Assert.True(builtIntegratedActions.All(b => b.RestoreResult.Success)); + Assert.True(builtIntegratedActions.All(b => !b.RestoreResult.LogMessages.Any())); // There should be no error or warning. + // Make sure all 4 project installed packageB upgraded version. + foreach (var netCorePackageReferenceProject in netCorePackageReferenceProjects) + { + var finalInstalledPackages = (await netCorePackageReferenceProject.GetInstalledPackagesAsync(CancellationToken.None)).ToList(); + Assert.True(finalInstalledPackages.Any(f => f.PackageIdentity.Id == packageB_UpgradeVersion.Id + && f.PackageIdentity.Version == packageB_UpgradeVersion.Version)); + } + var restoringLogs = testNuGetProjectContext.Logs.Value.Where(l => l.StartsWith("Restoring packages for ")).ToList(); + var restoredLogs = testNuGetProjectContext.Logs.Value.Where(l => l.StartsWith("Restored ")).ToList(); + // Making sure project0 restored only once, not many. + Assert.Equal(restoringLogs.Count(l => l.EndsWith("project0.csproj...")), 1); + Assert.Equal(restoredLogs.Count(l => l.Contains("project0.csproj")), 1); + Assert.Equal(restoringLogs.Count(l => l.EndsWith("project1.csproj...")), 1); + Assert.Equal(restoredLogs.Count(l => l.Contains("project1.csproj")), 1); + Assert.Equal(restoringLogs.Count(l => l.EndsWith("project2.csproj...")), 1); + Assert.Equal(restoredLogs.Count(l => l.Contains("project2.csproj")), 1); + Assert.Equal(restoringLogs.Count(l => l.EndsWith("project3.csproj...")), 1); + Assert.Equal(restoredLogs.Count(l => l.Contains("project3.csproj")), 1); + var writingAssetsLogs = testNuGetProjectContext.Logs.Value.Where(l => l.StartsWith("Writing assets file to disk.")).ToList(); + // Only 4 write to assets for above 4 projects, never more than that. + Assert.Equal(writingAssetsLogs.Count, 4); + } + } + + [Fact] + public async Task TestPackageManager_UpgradePackageFor_TopParentProject_Success() + { + using (var testDirectory = TestDirectory.Create()) + using (var testSolutionManager = new TestSolutionManager()) + { + // Set up Package Source + var sources = new List(); + var packageA_Version100 = new SimpleTestPackageContext("packageA", "1.0.0"); + var packageB_Version100 = new SimpleTestPackageContext("packageB", "1.0.0"); + var packageB_Version200 = new SimpleTestPackageContext("packageB", "2.0.0"); + var packageA = packageA_Version100.Identity; + var packageB = packageB_Version100.Identity; + var packageB_UpgradeVersion = packageB_Version200.Identity; + var packageSource = Path.Combine(testDirectory, "packageSource"); + await SimpleTestPackageUtility.CreateFolderFeedV3Async( + packageSource, + PackageSaveMode.Defaultv3, + packageA_Version100, + packageB_Version100, + packageB_Version200 + ); + + sources.Add(new PackageSource(packageSource)); + var sourceRepositoryProvider = TestSourceRepositoryUtility.CreateSourceRepositoryProvider(sources); + + // Project + int numberOfProjects = 4; + var projectCache = new ProjectSystemCache(); + IVsProjectAdapter projectAdapter = (new Mock()).Object; + ResolutionContext resolutionContext = (new Mock()).Object; + var packageSpecs = new PackageSpec[numberOfProjects]; + var projectFullPaths = new string[numberOfProjects]; + var deleteOnRestartManager = new TestDeleteOnRestartManager(); + var nuGetPackageManager = new NuGetPackageManager( + sourceRepositoryProvider, + NullSettings.Instance, + testSolutionManager, + deleteOnRestartManager); + + var testNuGetProjectContext = new TestNuGetProjectContext(); + var netCorePackageReferenceProjects = new List(); + var prevProj = string.Empty; + PackageSpec prevPackageSpec = null; + + // Create projects + for (var i = numberOfProjects - 1; i >= 0; i--) + { + var projectName = $"project{i}"; + var projectFullPath = Path.Combine(testDirectory.Path, projectName, projectName + ".csproj"); + var project = CreateTestNetCorePackageReferenceProject(projectName, projectFullPath, projectCache); + + // We need to treat NU1605 warning as error. + project.IsNu1605Error = true; + netCorePackageReferenceProjects.Add(project); + testSolutionManager.NuGetProjects.Add(project); + + //Let new project pickup my custom package source. + project.ProjectLocalSources.AddRange(sources); + var projectNames = GetTestProjectNames(projectFullPath, projectName); + var packageSpec = GetPackageSpec( + projectName, + projectFullPath, + packageA_Version100.Version); + + if (prevPackageSpec != null) + { + packageSpec = packageSpec.WithTestProjectReference(prevPackageSpec); + } + + // Restore info + var projectRestoreInfo = ProjectJsonTestHelpers.GetDGSpecFromPackageSpecs(packageSpec); + projectCache.AddProjectRestoreInfo(projectNames, projectRestoreInfo, new List()); + projectCache.AddProject(projectNames, projectAdapter, project).Should().BeTrue(); + prevProj = projectFullPath; + packageSpecs[i] = packageSpec; + prevPackageSpec = packageSpec; + projectFullPaths[i] = projectFullPath; + } + + for (int i = 0; i < numberOfProjects; i++) + { + // Install packageB since packageA is already there. + await nuGetPackageManager.InstallPackageAsync(netCorePackageReferenceProjects[i], packageB, new ResolutionContext(), new TestNuGetProjectContext(), + sourceRepositoryProvider.GetRepositories(), sourceRepositoryProvider.GetRepositories(), CancellationToken.None); + + // This code added because nuGetPackageManager.InstallPackageAsync doesn't do updating ProjectSystemCache + var installed = new LibraryDependency + { + LibraryRange = new LibraryRange( + name: packageB.Id, + versionRange: new VersionRange(packageB.Version), + typeConstraint: LibraryDependencyTarget.Package), + }; + + var packageSpec = packageSpecs[i]; + packageSpec.TargetFrameworks.First().Dependencies.Add(installed); + var projectRestoreInfo = ProjectJsonTestHelpers.GetDGSpecFromPackageSpecs(packageSpec); + var projectNames = GetTestProjectNames(projectFullPaths[i], $"project{i}"); + projectCache.AddProjectRestoreInfo(projectNames, projectRestoreInfo, new List()); + } + + var initialInstalledPackages = (await netCorePackageReferenceProjects[numberOfProjects - 1].GetInstalledPackagesAsync(CancellationToken.None)).ToList(); + + var targetProjects = new List() + { + netCorePackageReferenceProjects[numberOfProjects-1] // Top parent project. + }; + + // Act + var results = await nuGetPackageManager.PreviewProjectsInstallPackageAsync( + targetProjects, + packageB_UpgradeVersion, + resolutionContext, + testNuGetProjectContext, + sourceRepositoryProvider.GetRepositories().ToList(), + CancellationToken.None); + var actions = results.Select(a => a.Action).ToArray(); + await nuGetPackageManager.ExecuteNuGetProjectActionsAsync( + netCorePackageReferenceProjects, + actions, + testNuGetProjectContext, + new SourceCacheContext(), + CancellationToken.None); + + // Assert + Assert.Equal(initialInstalledPackages.Count(), 2); + var builtIntegratedActions = actions.OfType().ToList(); + Assert.Equal(actions.Length, targetProjects.Count()); + Assert.Equal(actions.Length, builtIntegratedActions.Count); + // Uprade succeed for this top parent project(no parent but with childs). + // Keep existing Upgrade/downgrade of individual project logic and making sure that my change is not breaking it. + Assert.Equal(actions.Length, builtIntegratedActions.Count); + Assert.True(builtIntegratedActions.All(b => b.RestoreResult.Success)); + Assert.True(builtIntegratedActions.All(b => !b.RestoreResult.LogMessages.Any())); // There should be no error or warning. + // Make sure top parent project has packageB upgraded version. + var finalInstalledPackages = (await netCorePackageReferenceProjects[numberOfProjects - 1].GetInstalledPackagesAsync(CancellationToken.None)).ToList(); + Assert.True(finalInstalledPackages.Any(f => f.PackageIdentity.Id == packageB_UpgradeVersion.Id + && f.PackageIdentity.Version == packageB_UpgradeVersion.Version)); + // Make sure middle parent project still have non-upgraded version. + finalInstalledPackages = (await netCorePackageReferenceProjects[0].GetInstalledPackagesAsync(CancellationToken.None)).ToList(); + Assert.True(finalInstalledPackages.Any(f => f.PackageIdentity.Id == packageB.Id + && f.PackageIdentity.Version == packageB.Version)); + // Make sure bottom project still have non-upgraded version. + finalInstalledPackages = (await netCorePackageReferenceProjects[0].GetInstalledPackagesAsync(CancellationToken.None)).ToList(); + Assert.True(finalInstalledPackages.Any(f => f.PackageIdentity.Id == packageB.Id + && f.PackageIdentity.Version == packageB.Version)); + } + } + + [Fact] + public async Task TestPackageManager_UpgradePackageFor_MiddleParentProject_Success() + { + using (var testDirectory = TestDirectory.Create()) + using (var testSolutionManager = new TestSolutionManager()) + { + // Set up Package Source + var sources = new List(); + var packageA_Version100 = new SimpleTestPackageContext("packageA", "1.0.0"); + var packageB_Version100 = new SimpleTestPackageContext("packageB", "1.0.0"); + var packageB_Version200 = new SimpleTestPackageContext("packageB", "2.0.0"); + var packageA = packageA_Version100.Identity; + var packageB = packageB_Version100.Identity; + var packageB_UpgradeVersion = packageB_Version200.Identity; + var packageSource = Path.Combine(testDirectory, "packageSource"); + await SimpleTestPackageUtility.CreateFolderFeedV3Async( + packageSource, + PackageSaveMode.Defaultv3, + packageA_Version100, + packageB_Version100, + packageB_Version200 + ); + + sources.Add(new PackageSource(packageSource)); + var sourceRepositoryProvider = TestSourceRepositoryUtility.CreateSourceRepositoryProvider(sources); + + // Project + int numberOfProjects = 4; + var projectCache = new ProjectSystemCache(); + IVsProjectAdapter projectAdapter = (new Mock()).Object; + ResolutionContext resolutionContext = (new Mock()).Object; + var packageSpecs = new PackageSpec[numberOfProjects]; + var projectFullPaths = new string[numberOfProjects]; + var deleteOnRestartManager = new TestDeleteOnRestartManager(); + var nuGetPackageManager = new NuGetPackageManager( + sourceRepositoryProvider, + NullSettings.Instance, + testSolutionManager, + deleteOnRestartManager); + + var testNuGetProjectContext = new TestNuGetProjectContext(); + var netCorePackageReferenceProjects = new List(); + var prevProj = string.Empty; + PackageSpec prevPackageSpec = null; + + // Create projects + for (var i = numberOfProjects - 1; i >= 0; i--) + { + var projectName = $"project{i}"; + var projectFullPath = Path.Combine(testDirectory.Path, projectName, projectName + ".csproj"); + var project = CreateTestNetCorePackageReferenceProject(projectName, projectFullPath, projectCache); + + // We need to treat NU1605 warning as error. + project.IsNu1605Error = true; + netCorePackageReferenceProjects.Add(project); + testSolutionManager.NuGetProjects.Add(project); + + //Let new project pickup my custom package source. + project.ProjectLocalSources.AddRange(sources); + var projectNames = GetTestProjectNames(projectFullPath, projectName); + var packageSpec = GetPackageSpec( + projectName, + projectFullPath, + packageA_Version100.Version); + + if (prevPackageSpec != null) + { + packageSpec = packageSpec.WithTestProjectReference(prevPackageSpec); + } + + // Restore info + var projectRestoreInfo = ProjectJsonTestHelpers.GetDGSpecFromPackageSpecs(packageSpec); + projectCache.AddProjectRestoreInfo(projectNames, projectRestoreInfo, new List()); + projectCache.AddProject(projectNames, projectAdapter, project).Should().BeTrue(); + prevProj = projectFullPath; + packageSpecs[i] = packageSpec; + prevPackageSpec = packageSpec; + projectFullPaths[i] = projectFullPath; + } + + for (int i = 0; i < numberOfProjects; i++) + { + // Install packageB since packageA is already there. + await nuGetPackageManager.InstallPackageAsync(netCorePackageReferenceProjects[i], packageB, new ResolutionContext(), new TestNuGetProjectContext(), + sourceRepositoryProvider.GetRepositories(), sourceRepositoryProvider.GetRepositories(), CancellationToken.None); + + // This code added because nuGetPackageManager.InstallPackageAsync doesn't do updating ProjectSystemCache + var installed = new LibraryDependency + { + LibraryRange = new LibraryRange( + name: packageB.Id, + versionRange: new VersionRange(packageB.Version), + typeConstraint: LibraryDependencyTarget.Package), + }; + + var packageSpec = packageSpecs[i]; + packageSpec.TargetFrameworks.First().Dependencies.Add(installed); + var projectRestoreInfo = ProjectJsonTestHelpers.GetDGSpecFromPackageSpecs(packageSpec); + var projectNames = GetTestProjectNames(projectFullPaths[i], $"project{i}"); + projectCache.AddProjectRestoreInfo(projectNames, projectRestoreInfo, new List()); + } + + var initialInstalledPackages = await netCorePackageReferenceProjects[numberOfProjects - 1].GetInstalledPackagesAsync(CancellationToken.None); + + var targetProjects = new List() + { + netCorePackageReferenceProjects[numberOfProjects-2] // Middle parent project. + }; + + // Act + var results = await nuGetPackageManager.PreviewProjectsInstallPackageAsync( + targetProjects, + packageB_UpgradeVersion, + resolutionContext, + testNuGetProjectContext, + sourceRepositoryProvider.GetRepositories().ToList(), + CancellationToken.None); + var actions = results.Select(a => a.Action).ToArray(); + await nuGetPackageManager.ExecuteNuGetProjectActionsAsync( + netCorePackageReferenceProjects, + actions, + testNuGetProjectContext, + new SourceCacheContext(), + CancellationToken.None); + + // Assert + Assert.Equal(initialInstalledPackages.Count(), 2); + var builtIntegratedActions = actions.OfType().ToList(); + Assert.Equal(actions.Length, targetProjects.Count()); + Assert.Equal(actions.Length, builtIntegratedActions.Count); + // Upgrade succeed for this middle parent project(with parent and childs). + // Keep existing Upgrade/downgrade of individual project logic and making sure that my change is not breaking it. + Assert.Equal(actions.Length, builtIntegratedActions.Count); + Assert.True(builtIntegratedActions.All(b => b.RestoreResult.Success)); + Assert.True(builtIntegratedActions.All(b => !b.RestoreResult.LogMessages.Any())); // There should be no error or warning. + // Make sure top parent project still have non-upgraded version. + var finalInstalledPackages = (await netCorePackageReferenceProjects[numberOfProjects - 1].GetInstalledPackagesAsync(CancellationToken.None)).ToList(); + Assert.True(finalInstalledPackages.Any(f => f.PackageIdentity.Id == packageB.Id + && f.PackageIdentity.Version == packageB.Version)); + // Make sure middle parent project have upgraded version. + finalInstalledPackages = (await netCorePackageReferenceProjects[numberOfProjects - 2].GetInstalledPackagesAsync(CancellationToken.None)).ToList(); + Assert.True(finalInstalledPackages.Any(f => f.PackageIdentity.Id == packageB_UpgradeVersion.Id + && f.PackageIdentity.Version == packageB_UpgradeVersion.Version)); + // Make sure bottom project still have non-upgraded version. + finalInstalledPackages = (await netCorePackageReferenceProjects[0].GetInstalledPackagesAsync(CancellationToken.None)).ToList(); + Assert.True(finalInstalledPackages.Any(f => f.PackageIdentity.Id == packageB.Id + && f.PackageIdentity.Version == packageB.Version)); + } + } + + [Fact] + public async Task TestPackageManager_UpgradePackageFor_BottomProject_Success() + { + using (var testDirectory = TestDirectory.Create()) + using (var testSolutionManager = new TestSolutionManager()) + { + // Set up Package Source + var sources = new List(); + var packageA_Version100 = new SimpleTestPackageContext("packageA", "1.0.0"); + var packageB_Version100 = new SimpleTestPackageContext("packageB", "1.0.0"); + var packageB_Version200 = new SimpleTestPackageContext("packageB", "2.0.0"); + var packageA = packageA_Version100.Identity; + var packageB = packageB_Version100.Identity; + var packageB_UpgradeVersion = packageB_Version200.Identity; + var packageSource = Path.Combine(testDirectory, "packageSource"); + await SimpleTestPackageUtility.CreateFolderFeedV3Async( + packageSource, + PackageSaveMode.Defaultv3, + packageA_Version100, + packageB_Version100, + packageB_Version200 + ); + + sources.Add(new PackageSource(packageSource)); + var sourceRepositoryProvider = TestSourceRepositoryUtility.CreateSourceRepositoryProvider(sources); + + // Project + int numberOfProjects = 4; + var projectCache = new ProjectSystemCache(); + IVsProjectAdapter projectAdapter = (new Mock()).Object; + ResolutionContext resolutionContext = (new Mock()).Object; + var packageSpecs = new PackageSpec[numberOfProjects]; + var projectFullPaths = new string[numberOfProjects]; + var deleteOnRestartManager = new TestDeleteOnRestartManager(); + var nuGetPackageManager = new NuGetPackageManager( + sourceRepositoryProvider, + NullSettings.Instance, + testSolutionManager, + deleteOnRestartManager); + + var testNuGetProjectContext = new TestNuGetProjectContext(); + var netCorePackageReferenceProjects = new List(); + var prevProj = string.Empty; + PackageSpec prevPackageSpec = null; + + // Create projects + for (var i = numberOfProjects - 1; i >= 0; i--) + { + var projectName = $"project{i}"; + var projectFullPath = Path.Combine(testDirectory.Path, projectName, projectName + ".csproj"); + var project = CreateTestNetCorePackageReferenceProject(projectName, projectFullPath, projectCache); + + // We need to treat NU1605 warning as error. + project.IsNu1605Error = true; + netCorePackageReferenceProjects.Add(project); + testSolutionManager.NuGetProjects.Add(project); + + //Let new project pickup my custom package source. + project.ProjectLocalSources.AddRange(sources); + var projectNames = GetTestProjectNames(projectFullPath, projectName); + var packageSpec = GetPackageSpec( + projectName, + projectFullPath, + packageA_Version100.Version); + + if (prevPackageSpec != null) + { + packageSpec = packageSpec.WithTestProjectReference(prevPackageSpec); + } + + // Restore info + var projectRestoreInfo = ProjectJsonTestHelpers.GetDGSpecFromPackageSpecs(packageSpec); + projectCache.AddProjectRestoreInfo(projectNames, projectRestoreInfo, new List()); + projectCache.AddProject(projectNames, projectAdapter, project).Should().BeTrue(); + prevProj = projectFullPath; + packageSpecs[i] = packageSpec; + prevPackageSpec = packageSpec; + projectFullPaths[i] = projectFullPath; + } + + for (int i = 0; i < numberOfProjects; i++) + { + // Install packageB since packageA is already there. + await nuGetPackageManager.InstallPackageAsync(netCorePackageReferenceProjects[i], packageB, new ResolutionContext(), new TestNuGetProjectContext(), + sourceRepositoryProvider.GetRepositories(), sourceRepositoryProvider.GetRepositories(), CancellationToken.None); + + // This code added because nuGetPackageManager.InstallPackageAsync doesn't do updating ProjectSystemCache + var installed = new LibraryDependency + { + LibraryRange = new LibraryRange( + name: packageB.Id, + versionRange: new VersionRange(packageB.Version), + typeConstraint: LibraryDependencyTarget.Package), + }; + + var packageSpec = packageSpecs[i]; + packageSpec.TargetFrameworks.First().Dependencies.Add(installed); + var projectRestoreInfo = ProjectJsonTestHelpers.GetDGSpecFromPackageSpecs(packageSpec); + var projectNames = GetTestProjectNames(projectFullPaths[i], $"project{i}"); + projectCache.AddProjectRestoreInfo(projectNames, projectRestoreInfo, new List()); + } + + var initialInstalledPackages = await netCorePackageReferenceProjects[numberOfProjects - 1].GetInstalledPackagesAsync(CancellationToken.None); + + var targetProjects = new List() + { + netCorePackageReferenceProjects[0] // Bottom child project. + }; + + // Act + var results = await nuGetPackageManager.PreviewProjectsInstallPackageAsync( + targetProjects, + packageB_UpgradeVersion, + resolutionContext, + testNuGetProjectContext, + sourceRepositoryProvider.GetRepositories().ToList(), + CancellationToken.None); + var actions = results.Select(a => a.Action).ToArray(); + await nuGetPackageManager.ExecuteNuGetProjectActionsAsync( + netCorePackageReferenceProjects, + actions, + testNuGetProjectContext, + new SourceCacheContext(), + CancellationToken.None); + + // Assert + Assert.Equal(initialInstalledPackages.Count(), 2); + var builtIntegratedActions = actions.OfType().ToList(); + Assert.Equal(actions.Length, targetProjects.Count()); + Assert.Equal(actions.Length, builtIntegratedActions.Count); + // Upgrade succeed for this bottom project(with parent but no childs). + // Keep existing Upgrade/downgrade of individual project logic and making sure that my change is not breaking it. + Assert.Equal(actions.Length, builtIntegratedActions.Count); + Assert.True(builtIntegratedActions.All(b => b.RestoreResult.Success)); + Assert.True(builtIntegratedActions.All(b => !b.RestoreResult.LogMessages.Any())); // There should be no error or warning. + // Make sure top parent project still have non-upgraded version. + var finalInstalledPackages = (await netCorePackageReferenceProjects[numberOfProjects - 1].GetInstalledPackagesAsync(CancellationToken.None)).ToList(); + Assert.True(finalInstalledPackages.Any(f => f.PackageIdentity.Id == packageB.Id + && f.PackageIdentity.Version == packageB.Version)); + // Make sure middle parent project still have non-upgraded version. + finalInstalledPackages = (await netCorePackageReferenceProjects[numberOfProjects - 2].GetInstalledPackagesAsync(CancellationToken.None)).ToList(); + Assert.True(finalInstalledPackages.Any(f => f.PackageIdentity.Id == packageB.Id + && f.PackageIdentity.Version == packageB.Version)); + // Make sure bottom project have upgraded version. + finalInstalledPackages = (await netCorePackageReferenceProjects[0].GetInstalledPackagesAsync(CancellationToken.None)).ToList(); + Assert.True(finalInstalledPackages.Any(f => f.PackageIdentity.Id == packageB_UpgradeVersion.Id + && f.PackageIdentity.Version == packageB_UpgradeVersion.Version)); + } + } + + [Fact] + public async Task TestPackageManager_DowngradePackageForAllProjects_Success() + { + using (var testDirectory = TestDirectory.Create()) + using (var testSolutionManager = new TestSolutionManager()) + { + // Set up Package Source + var sources = new List(); + var packageA_Version100 = new SimpleTestPackageContext("packageA", "1.0.0"); + var packageB_Version100 = new SimpleTestPackageContext("packageB", "1.0.0"); + var packageB_Version200 = new SimpleTestPackageContext("packageB", "2.0.0"); + var packageA = packageA_Version100.Identity; + var packageB = packageB_Version200.Identity; + var packageB_DowngradeVersion = packageB_Version100.Identity; + var packageSource = Path.Combine(testDirectory, "packageSource"); + await SimpleTestPackageUtility.CreateFolderFeedV3Async( + packageSource, + PackageSaveMode.Defaultv3, + packageA_Version100, + packageB_Version100, + packageB_Version200 + ); + + sources.Add(new PackageSource(packageSource)); + var sourceRepositoryProvider = TestSourceRepositoryUtility.CreateSourceRepositoryProvider(sources); + + // Project + int numberOfProjects = 4; + var projectCache = new ProjectSystemCache(); + IVsProjectAdapter projectAdapter = (new Mock()).Object; + ResolutionContext resolutionContext = (new Mock()).Object; + var packageSpecs = new PackageSpec[numberOfProjects]; + var projectFullPaths = new string[numberOfProjects]; + var deleteOnRestartManager = new TestDeleteOnRestartManager(); + var nuGetPackageManager = new NuGetPackageManager( + sourceRepositoryProvider, + NullSettings.Instance, + testSolutionManager, + deleteOnRestartManager); + + var testNuGetProjectContext = new TestNuGetProjectContext(); + var netCorePackageReferenceProjects = new List(); + var prevProj = string.Empty; + PackageSpec prevPackageSpec = null; + + // Create projects + for (var i = numberOfProjects - 1; i >= 0; i--) + { + var projectName = $"project{i}"; + var projectFullPath = Path.Combine(testDirectory.Path, projectName, projectName + ".csproj"); + var project = CreateTestNetCorePackageReferenceProject(projectName, projectFullPath, projectCache); + + // We need to treat NU1605 warning as error. + project.IsNu1605Error = true; + netCorePackageReferenceProjects.Add(project); + testSolutionManager.NuGetProjects.Add(project); + + //Let new project pickup my custom package source. + project.ProjectLocalSources.AddRange(sources); + var projectNames = GetTestProjectNames(projectFullPath, projectName); + var packageSpec = GetPackageSpec( + projectName, + projectFullPath, + packageA_Version100.Version); + + if (prevPackageSpec != null) + { + packageSpec = packageSpec.WithTestProjectReference(prevPackageSpec); + } + + // Restore info + var projectRestoreInfo = ProjectJsonTestHelpers.GetDGSpecFromPackageSpecs(packageSpec); + projectCache.AddProjectRestoreInfo(projectNames, projectRestoreInfo, new List()); + projectCache.AddProject(projectNames, projectAdapter, project).Should().BeTrue(); + prevProj = projectFullPath; + packageSpecs[i] = packageSpec; + prevPackageSpec = packageSpec; + projectFullPaths[i] = projectFullPath; + } + + for (int i = 0; i < numberOfProjects; i++) + { + // Install packageB since packageA is already there. + await nuGetPackageManager.InstallPackageAsync(netCorePackageReferenceProjects[i], packageB, new ResolutionContext(), new TestNuGetProjectContext(), + sourceRepositoryProvider.GetRepositories(), sourceRepositoryProvider.GetRepositories(), CancellationToken.None); + + // This code added because nuGetPackageManager.InstallPackageAsync doesn't do updating ProjectSystemCache + var installed = new LibraryDependency + { + LibraryRange = new LibraryRange( + name: packageB.Id, + versionRange: new VersionRange(packageB.Version), + typeConstraint: LibraryDependencyTarget.Package), + }; + + var packageSpec = packageSpecs[i]; + packageSpec.TargetFrameworks.First().Dependencies.Add(installed); + var projectRestoreInfo = ProjectJsonTestHelpers.GetDGSpecFromPackageSpecs(packageSpec); + var projectNames = GetTestProjectNames(projectFullPaths[i], $"project{i}"); + projectCache.AddProjectRestoreInfo(projectNames, projectRestoreInfo, new List()); + } + + var initialInstalledPackages = await netCorePackageReferenceProjects[numberOfProjects - 1].GetInstalledPackagesAsync(CancellationToken.None); + + // Act + var results = await nuGetPackageManager.PreviewProjectsInstallPackageAsync( + netCorePackageReferenceProjects, // All projects + packageB_DowngradeVersion, + resolutionContext, + testNuGetProjectContext, + sourceRepositoryProvider.GetRepositories().ToList(), + CancellationToken.None); + + var actions = results.Select(a => a.Action).ToArray(); + + await nuGetPackageManager.ExecuteNuGetProjectActionsAsync( + netCorePackageReferenceProjects, + actions, + testNuGetProjectContext, + new SourceCacheContext(), + CancellationToken.None); + + // Assert + Assert.Equal(initialInstalledPackages.Count(), 2); + var builtIntegratedActions = actions.OfType().ToList(); + Assert.Equal(actions.Length, builtIntegratedActions.Count); + Assert.True(builtIntegratedActions.All(b => b.RestoreResult.Success)); + Assert.True(builtIntegratedActions.All(b => !b.RestoreResult.LogMessages.Any())); // There should be no error or warning. + // Make sure all 4 project installed packageB downgrade version. + foreach (var netCorePackageReferenceProject in netCorePackageReferenceProjects) + { + var finalInstalledPackages = await netCorePackageReferenceProject.GetInstalledPackagesAsync(CancellationToken.None); + Assert.True(finalInstalledPackages.Any(f => f.PackageIdentity.Id == packageB_DowngradeVersion.Id + && f.PackageIdentity.Version == packageB_DowngradeVersion.Version)); + } + } + } + + [Fact] + public async Task TestPackageManager_DowngradePackageFor_TopParentProject_Fail() + { + using (var testDirectory = TestDirectory.Create()) + using (var testSolutionManager = new TestSolutionManager()) + { + // Set up Package Source + var sources = new List(); + var packageA_Version100 = new SimpleTestPackageContext("packageA", "1.0.0"); + var packageB_Version100 = new SimpleTestPackageContext("packageB", "1.0.0"); + var packageB_Version200 = new SimpleTestPackageContext("packageB", "2.0.0"); + var packageA = packageA_Version100.Identity; + var packageB = packageB_Version200.Identity; + var packageB_DowngradeVersion = packageB_Version100.Identity; + var packageSource = Path.Combine(testDirectory, "packageSource"); + await SimpleTestPackageUtility.CreateFolderFeedV3Async( + packageSource, + PackageSaveMode.Defaultv3, + packageA_Version100, + packageB_Version100, + packageB_Version200 + ); + + sources.Add(new PackageSource(packageSource)); + var sourceRepositoryProvider = TestSourceRepositoryUtility.CreateSourceRepositoryProvider(sources); + + // Project + int numberOfProjects = 4; + var projectCache = new ProjectSystemCache(); + IVsProjectAdapter projectAdapter = (new Mock()).Object; + ResolutionContext resolutionContext = (new Mock()).Object; + var packageSpecs = new PackageSpec[numberOfProjects]; + var projectFullPaths = new string[numberOfProjects]; + var deleteOnRestartManager = new TestDeleteOnRestartManager(); + var nuGetPackageManager = new NuGetPackageManager( + sourceRepositoryProvider, + NullSettings.Instance, + testSolutionManager, + deleteOnRestartManager); + + var testNuGetProjectContext = new TestNuGetProjectContext(); + var netCorePackageReferenceProjects = new List(); + var prevProj = string.Empty; + PackageSpec prevPackageSpec = null; + + // Create projects + for (var i = numberOfProjects - 1; i >= 0; i--) + { + var projectName = $"project{i}"; + var projectFullPath = Path.Combine(testDirectory.Path, projectName, projectName + ".csproj"); + var project = CreateTestNetCorePackageReferenceProject(projectName, projectFullPath, projectCache); + + // We need to treat NU1605 warning as error. + project.IsNu1605Error = true; + netCorePackageReferenceProjects.Add(project); + testSolutionManager.NuGetProjects.Add(project); + + //Let new project pickup my custom package source. + project.ProjectLocalSources.AddRange(sources); + var projectNames = GetTestProjectNames(projectFullPath, projectName); + var packageSpec = GetPackageSpec( + projectName, + projectFullPath, + packageA_Version100.Version); + + if (prevPackageSpec != null) + { + packageSpec = packageSpec.WithTestProjectReference(prevPackageSpec); + } + + // Restore info + var projectRestoreInfo = ProjectJsonTestHelpers.GetDGSpecFromPackageSpecs(packageSpec); + projectCache.AddProjectRestoreInfo(projectNames, projectRestoreInfo, new List()); + projectCache.AddProject(projectNames, projectAdapter, project).Should().BeTrue(); + prevProj = projectFullPath; + packageSpecs[i] = packageSpec; + prevPackageSpec = packageSpec; + projectFullPaths[i] = projectFullPath; + } + + for (int i = 0; i < numberOfProjects; i++) + { + // Install packageB since packageA is already there. + await nuGetPackageManager.InstallPackageAsync(netCorePackageReferenceProjects[i], packageB, new ResolutionContext(), new TestNuGetProjectContext(), + sourceRepositoryProvider.GetRepositories(), sourceRepositoryProvider.GetRepositories(), CancellationToken.None); + + // This code added because nuGetPackageManager.InstallPackageAsync doesn't do updating ProjectSystemCache + var installed = new LibraryDependency + { + LibraryRange = new LibraryRange( + name: packageB.Id, + versionRange: new VersionRange(packageB.Version), + typeConstraint: LibraryDependencyTarget.Package), + }; + + var packageSpec = packageSpecs[i]; + packageSpec.TargetFrameworks.First().Dependencies.Add(installed); + var projectRestoreInfo = ProjectJsonTestHelpers.GetDGSpecFromPackageSpecs(packageSpec); + var projectNames = GetTestProjectNames(projectFullPaths[i], $"project{i}"); + projectCache.AddProjectRestoreInfo(projectNames, projectRestoreInfo, new List()); + } + + var initialInstalledPackages = await netCorePackageReferenceProjects[numberOfProjects - 1].GetInstalledPackagesAsync(CancellationToken.None); + + var targetProjects = new List() + { + netCorePackageReferenceProjects[numberOfProjects-1] // Top parent project. + }; + + // Act + var results = await nuGetPackageManager.PreviewProjectsInstallPackageAsync( + targetProjects, + packageB_DowngradeVersion, + resolutionContext, + testNuGetProjectContext, + sourceRepositoryProvider.GetRepositories().ToList(), + CancellationToken.None); + var actions = results.Select(a => a.Action).ToArray(); + + // Assert + Assert.Equal(initialInstalledPackages.Count(), 2); + var builtIntegratedActions = actions.OfType().ToList(); + Assert.Equal(actions.Length, targetProjects.Count()); + Assert.Equal(actions.Length, builtIntegratedActions.Count); + // Downgrade fails for this top parent project(no parent but with childs). + // Keep existing Upgrade/downgrade of individual project logic and making sure that my change is not breaking it. + Assert.False(builtIntegratedActions.All(b => b.RestoreResult.Success)); + // Should cause total 1 NU1605 for all childs. + Assert.Equal(builtIntegratedActions.Sum(b => b.RestoreResult.LogMessages.Count(l => l.Code == NuGetLogCode.NU1605)), 1); + // There should be no warning other than NU1605. + Assert.Equal(builtIntegratedActions.Sum(b => b.RestoreResult.LogMessages.Count(l => l.Code != NuGetLogCode.NU1605)), 0); + } + } + + [Fact] + public async Task TestPackageManager_DowngradePackageFor_MiddleParentProject_Fail() + { + using (var testDirectory = TestDirectory.Create()) + using (var testSolutionManager = new TestSolutionManager()) + { + // Set up Package Source + var sources = new List(); + var packageA_Version100 = new SimpleTestPackageContext("packageA", "1.0.0"); + var packageB_Version100 = new SimpleTestPackageContext("packageB", "1.0.0"); + var packageB_Version200 = new SimpleTestPackageContext("packageB", "2.0.0"); + var packageA = packageA_Version100.Identity; + var packageB = packageB_Version200.Identity; + var packageB_DowngradeVersion = packageB_Version100.Identity; + var packageSource = Path.Combine(testDirectory, "packageSource"); + await SimpleTestPackageUtility.CreateFolderFeedV3Async( + packageSource, + PackageSaveMode.Defaultv3, + packageA_Version100, + packageB_Version100, + packageB_Version200 + ); + + sources.Add(new PackageSource(packageSource)); + var sourceRepositoryProvider = TestSourceRepositoryUtility.CreateSourceRepositoryProvider(sources); + + // Project + int numberOfProjects = 4; + var projectCache = new ProjectSystemCache(); + IVsProjectAdapter projectAdapter = (new Mock()).Object; + ResolutionContext resolutionContext = (new Mock()).Object; + var packageSpecs = new PackageSpec[numberOfProjects]; + var projectFullPaths = new string[numberOfProjects]; + var deleteOnRestartManager = new TestDeleteOnRestartManager(); + var nuGetPackageManager = new NuGetPackageManager( + sourceRepositoryProvider, + NullSettings.Instance, + testSolutionManager, + deleteOnRestartManager); + + var testNuGetProjectContext = new TestNuGetProjectContext(); + var netCorePackageReferenceProjects = new List(); + var prevProj = string.Empty; + PackageSpec prevPackageSpec = null; + + // Create projects + for (var i = numberOfProjects - 1; i >= 0; i--) + { + var projectName = $"project{i}"; + var projectFullPath = Path.Combine(testDirectory.Path, projectName, projectName + ".csproj"); + var project = CreateTestNetCorePackageReferenceProject(projectName, projectFullPath, projectCache); + + // We need to treat NU1605 warning as error. + project.IsNu1605Error = true; + netCorePackageReferenceProjects.Add(project); + testSolutionManager.NuGetProjects.Add(project); + + //Let new project pickup my custom package source. + project.ProjectLocalSources.AddRange(sources); + var projectNames = GetTestProjectNames(projectFullPath, projectName); + var packageSpec = GetPackageSpec( + projectName, + projectFullPath, + packageA_Version100.Version); + + if (prevPackageSpec != null) + { + packageSpec = packageSpec.WithTestProjectReference(prevPackageSpec); + } + + // Restore info + var projectRestoreInfo = ProjectJsonTestHelpers.GetDGSpecFromPackageSpecs(packageSpec); + projectCache.AddProjectRestoreInfo(projectNames, projectRestoreInfo, new List()); + projectCache.AddProject(projectNames, projectAdapter, project).Should().BeTrue(); + prevProj = projectFullPath; + packageSpecs[i] = packageSpec; + prevPackageSpec = packageSpec; + projectFullPaths[i] = projectFullPath; + } + + for (int i = 0; i < numberOfProjects; i++) + { + // Install packageB since packageA is already there. + await nuGetPackageManager.InstallPackageAsync(netCorePackageReferenceProjects[i], packageB, new ResolutionContext(), new TestNuGetProjectContext(), + sourceRepositoryProvider.GetRepositories(), sourceRepositoryProvider.GetRepositories(), CancellationToken.None); + + // This code added because nuGetPackageManager.InstallPackageAsync doesn't do updating ProjectSystemCache here + var installed = new LibraryDependency + { + LibraryRange = new LibraryRange( + name: packageB.Id, + versionRange: new VersionRange(packageB.Version), + typeConstraint: LibraryDependencyTarget.Package), + }; + + var packageSpec = packageSpecs[i]; + packageSpec.TargetFrameworks.First().Dependencies.Add(installed); + var projectRestoreInfo = ProjectJsonTestHelpers.GetDGSpecFromPackageSpecs(packageSpec); + var projectNames = GetTestProjectNames(projectFullPaths[i], $"project{i}"); + projectCache.AddProjectRestoreInfo(projectNames, projectRestoreInfo, new List()); + } + + var initialInstalledPackages = await netCorePackageReferenceProjects[numberOfProjects - 2].GetInstalledPackagesAsync(CancellationToken.None); + + var targetProjects = new List() + { + netCorePackageReferenceProjects[numberOfProjects-2] // Middle parent project. + }; + + // Act + var results = await nuGetPackageManager.PreviewProjectsInstallPackageAsync( + targetProjects, + packageB_DowngradeVersion, + resolutionContext, + testNuGetProjectContext, + sourceRepositoryProvider.GetRepositories().ToList(), + CancellationToken.None); + var actions = results.Select(a => a.Action).ToArray(); + + // Assert + Assert.Equal(initialInstalledPackages.Count(), 2); + var builtIntegratedActions = actions.OfType().ToList(); + Assert.Equal(actions.Length, targetProjects.Count); + Assert.Equal(actions.Length, builtIntegratedActions.Count); + // Downgrade fails for this middle parent project(with both parent and child). + // Keep existing Upgrade/downgrade of individual project logic and making sure that my change is not breaking it. + Assert.False(builtIntegratedActions.All(b => b.RestoreResult.Success)); + // Should cause total 1 NU1605 for all childs. + Assert.Equal(builtIntegratedActions.Sum(b => b.RestoreResult.LogMessages.Count(l => l.Code == NuGetLogCode.NU1605)), 1); + // There should be no warning other than NU1605. + Assert.Equal(builtIntegratedActions.Sum(b => b.RestoreResult.LogMessages.Count(l => l.Code != NuGetLogCode.NU1605)), 0); + } + } + + [Fact] + public async Task TestPackageManager_DowngradePackageFor_BottomtProject_Success() + { + using (var testDirectory = TestDirectory.Create()) + using (var testSolutionManager = new TestSolutionManager()) + { + // Set up Package Source + var sources = new List(); + var packageA_Version100 = new SimpleTestPackageContext("packageA", "1.0.0"); + var packageB_Version100 = new SimpleTestPackageContext("packageB", "1.0.0"); + var packageB_Version200 = new SimpleTestPackageContext("packageB", "2.0.0"); + var packageA = packageA_Version100.Identity; + var packageB = packageB_Version200.Identity; + var packageB_DowngradeVersion = packageB_Version100.Identity; + var packageSource = Path.Combine(testDirectory, "packageSource"); + await SimpleTestPackageUtility.CreateFolderFeedV3Async( + packageSource, + PackageSaveMode.Defaultv3, + packageA_Version100, + packageB_Version100, + packageB_Version200 + ); + + sources.Add(new PackageSource(packageSource)); + var sourceRepositoryProvider = TestSourceRepositoryUtility.CreateSourceRepositoryProvider(sources); + + // Project + int numberOfProjects = 4; + var projectCache = new ProjectSystemCache(); + IVsProjectAdapter projectAdapter = (new Mock()).Object; + ResolutionContext resolutionContext = (new Mock()).Object; + var packageSpecs = new PackageSpec[numberOfProjects]; + var projectFullPaths = new string[numberOfProjects]; + var deleteOnRestartManager = new TestDeleteOnRestartManager(); + var nuGetPackageManager = new NuGetPackageManager( + sourceRepositoryProvider, + NullSettings.Instance, + testSolutionManager, + deleteOnRestartManager); + + var testNuGetProjectContext = new TestNuGetProjectContext(); + var netCorePackageReferenceProjects = new List(); + var prevProj = string.Empty; + PackageSpec prevPackageSpec = null; + + // Create projects + for (var i = numberOfProjects - 1; i >= 0; i--) + { + var projectName = $"project{i}"; + var projectFullPath = Path.Combine(testDirectory.Path, projectName, projectName + ".csproj"); + var project = CreateTestNetCorePackageReferenceProject(projectName, projectFullPath, projectCache); + + // We need to treat NU1605 warning as error. + project.IsNu1605Error = true; + netCorePackageReferenceProjects.Add(project); + testSolutionManager.NuGetProjects.Add(project); + //Let new project pickup my custom package source. + project.ProjectLocalSources.AddRange(sources); + var projectNames = GetTestProjectNames(projectFullPath, projectName); + var packageSpec = GetPackageSpec( + projectName, + projectFullPath, + packageA_Version100.Version); + + if (prevPackageSpec != null) + { + packageSpec = packageSpec.WithTestProjectReference(prevPackageSpec); + } + + // Restore info + var projectRestoreInfo = ProjectJsonTestHelpers.GetDGSpecFromPackageSpecs(packageSpec); + projectCache.AddProjectRestoreInfo(projectNames, projectRestoreInfo, new List()); + projectCache.AddProject(projectNames, projectAdapter, project).Should().BeTrue(); + prevProj = projectFullPath; + packageSpecs[i] = packageSpec; + prevPackageSpec = packageSpec; + projectFullPaths[i] = projectFullPath; + } + + for (int i = 0; i < numberOfProjects; i++) + { + // Install packageB since packageA is already there. + await nuGetPackageManager.InstallPackageAsync(netCorePackageReferenceProjects[i], packageB, new ResolutionContext(), new TestNuGetProjectContext(), + sourceRepositoryProvider.GetRepositories(), sourceRepositoryProvider.GetRepositories(), CancellationToken.None); + + // This code added because nuGetPackageManager.InstallPackageAsync doesn't do updating ProjectSystemCache here + var installed = new LibraryDependency + { + LibraryRange = new LibraryRange( + name: packageB.Id, + versionRange: new VersionRange(packageB.Version), + typeConstraint: LibraryDependencyTarget.Package), + }; + + var packageSpec = packageSpecs[i]; + packageSpec.TargetFrameworks.First().Dependencies.Add(installed); + var projectRestoreInfo = ProjectJsonTestHelpers.GetDGSpecFromPackageSpecs(packageSpec); + var projectNames = GetTestProjectNames(projectFullPaths[i], $"project{i}"); + projectCache.AddProjectRestoreInfo(projectNames, projectRestoreInfo, new List()); + } + + var initialInstalledPackages = await netCorePackageReferenceProjects[numberOfProjects - 2].GetInstalledPackagesAsync(CancellationToken.None); + + var targetProjects = new List() + { + netCorePackageReferenceProjects[0] // Bottom child project. + }; + + // Act + var results = await nuGetPackageManager.PreviewProjectsInstallPackageAsync( + targetProjects, + packageB_DowngradeVersion, + resolutionContext, + testNuGetProjectContext, + sourceRepositoryProvider.GetRepositories().ToList(), + CancellationToken.None); + var actions = results.Select(a => a.Action).ToArray(); + await nuGetPackageManager.ExecuteNuGetProjectActionsAsync( + netCorePackageReferenceProjects, + actions, + testNuGetProjectContext, + new SourceCacheContext(), + CancellationToken.None); + + // Assert + Assert.Equal(initialInstalledPackages.Count(), 2); + var builtIntegratedActions = actions.OfType().ToList(); + Assert.Equal(actions.Length, targetProjects.Count); + Assert.Equal(actions.Length, builtIntegratedActions.Count); + Assert.True(builtIntegratedActions.All(b => b.RestoreResult.Success)); + // There should be no error/warnings + Assert.Equal(builtIntegratedActions.Sum(b => b.RestoreResult.LogMessages.Count()), 0); + // Make sure top parent project still have non-downgraded version. + var finalInstalledPackages = (await netCorePackageReferenceProjects[numberOfProjects - 1].GetInstalledPackagesAsync(CancellationToken.None)).ToList(); + Assert.True(finalInstalledPackages.Any(f => f.PackageIdentity.Id == packageB.Id + && f.PackageIdentity.Version == packageB.Version)); + // Make sure middle parent project still have non-downgraded version. + finalInstalledPackages = (await netCorePackageReferenceProjects[numberOfProjects - 2].GetInstalledPackagesAsync(CancellationToken.None)).ToList(); + Assert.True(finalInstalledPackages.Any(f => f.PackageIdentity.Id == packageB.Id + && f.PackageIdentity.Version == packageB.Version)); + // Make sure bottom project have downgraded version. + finalInstalledPackages = (await netCorePackageReferenceProjects[0].GetInstalledPackagesAsync(CancellationToken.None)).ToList(); + Assert.True(finalInstalledPackages.Any(f => f.PackageIdentity.Id == packageB_DowngradeVersion.Id + && f.PackageIdentity.Version == packageB_DowngradeVersion.Version)); + } + } + + [Fact] + public async Task TestPackageManager_CancellationTokenPassed() + { + using (var testDirectory = TestDirectory.Create()) + using (var testSolutionManager = new TestSolutionManager()) + { + // Set up Package Source + var sources = new List(); + var packageA_Version100 = new SimpleTestPackageContext("packageA", "1.0.0"); + var packageA = packageA_Version100.Identity; + var packageSource = Path.Combine(testDirectory, "packageSource"); + await SimpleTestPackageUtility.CreateFolderFeedV3Async( + packageSource, + PackageSaveMode.Defaultv3, + packageA_Version100 + ); + + sources.Add(new PackageSource(packageSource)); + var sourceRepositoryProvider = TestSourceRepositoryUtility.CreateSourceRepositoryProvider(sources); + + // Project + var projectCache = new ProjectSystemCache(); + IVsProjectAdapter projectAdapter = (new Mock()).Object; + ResolutionContext resolutionContext = (new Mock()).Object; + var deleteOnRestartManager = new TestDeleteOnRestartManager(); + var nuGetPackageManager = new NuGetPackageManager( + sourceRepositoryProvider, + NullSettings.Instance, + testSolutionManager, + deleteOnRestartManager); + + var testNuGetProjectContext = new TestNuGetProjectContext(); + var netCorePackageReferenceProjects = new List(); + var prevProj = string.Empty; + PackageSpec prevPackageSpec = null; + + var source = new CancellationTokenSource(); + var token = source.Token; + // Create projects + for (var i = 1; i >= 0; i--) + { + var projectName = $"project{i}"; + var projectFullPath = Path.Combine(testDirectory.Path, projectName, projectName + ".csproj"); + var project = CreateTestNetCorePackageReferenceProject(projectName, projectFullPath, projectCache); + + // We need to treat NU1605 warning as error. + //project.IsNu1605Error = true; + netCorePackageReferenceProjects.Add(project); + testSolutionManager.NuGetProjects.Add(project); + //Let new project pickup my custom package source. + project.ProjectLocalSources.AddRange(sources); + var projectNames = GetTestProjectNames(projectFullPath, projectName); + var packageSpec = GetPackageSpec(projectName, projectFullPath, packageA_Version100.Version); + + if (prevPackageSpec != null) + { + packageSpec = packageSpec.WithTestProjectReference(prevPackageSpec); + } + + // Restore info + var projectRestoreInfo = ProjectJsonTestHelpers.GetDGSpecFromPackageSpecs(packageSpec); + projectCache.AddProjectRestoreInfo(projectNames, projectRestoreInfo, new List()); + projectCache.AddProject(projectNames, projectAdapter, project).Should().BeTrue(); + prevProj = projectFullPath; + prevPackageSpec = packageSpec; + } + + // Act + source.Cancel(); + + var exception = await Assert.ThrowsAsync( + () => nuGetPackageManager.PreviewProjectsInstallPackageAsync( + netCorePackageReferenceProjects, + packageA, + resolutionContext, + testNuGetProjectContext, + sourceRepositoryProvider.GetRepositories().ToList(), + token)); + + // Assert + Assert.NotNull(exception); + Assert.Equal(exception.Message, "The operation was canceled."); + } + } + + [Fact] + public async Task TestPackageManager_RaiseTelemetryEvents() + { + // set up telemetry service + var telemetrySession = new Mock(); + var telemetryEvents = new ConcurrentQueue(); + telemetrySession + .Setup(x => x.PostEvent(It.IsAny())) + .Callback(x => telemetryEvents.Enqueue(x)); + var telemetryService = new NuGetVSTelemetryService(telemetrySession.Object); + TelemetryActivity.NuGetTelemetryService = telemetryService; + + using (var testDirectory = TestDirectory.Create()) + using (var testSolutionManager = new TestSolutionManager()) + { + // Set up Package Source + var sources = new List(); + var packageA_Version100 = new SimpleTestPackageContext("packageA", "1.0.0"); + var packageB_Version100 = new SimpleTestPackageContext("packageB", "1.0.0"); + var packageA = packageA_Version100.Identity; + var packageB = packageB_Version100.Identity; + var packageSource = Path.Combine(testDirectory, "packageSource"); + await SimpleTestPackageUtility.CreateFolderFeedV3Async( + packageSource, + PackageSaveMode.Defaultv3, + packageA_Version100, + packageB_Version100 + ); + + sources.Add(new PackageSource(packageSource)); + var sourceRepositoryProvider = TestSourceRepositoryUtility.CreateSourceRepositoryProvider(sources); + + // Project + int numberOfProjects = 4; + var projectCache = new ProjectSystemCache(); + IVsProjectAdapter projectAdapter = (new Mock()).Object; + ResolutionContext resolutionContext = (new Mock()).Object; + var packageSpecs = new PackageSpec[numberOfProjects]; + var projectFullPaths = new string[numberOfProjects]; + var deleteOnRestartManager = new TestDeleteOnRestartManager(); + var nuGetPackageManager = new NuGetPackageManager( + sourceRepositoryProvider, + NullSettings.Instance, + testSolutionManager, + deleteOnRestartManager); + + var testNuGetProjectContext = new TestNuGetProjectContext(); + var netCorePackageReferenceProjects = new List(); + var prevProj = string.Empty; + PackageSpec prevPackageSpec = null; + + // Create projects + for (var i = numberOfProjects - 1; i >= 0; i--) + { + var projectName = $"project{i}"; + var projectFullPath = Path.Combine(testDirectory.Path, projectName, projectName + ".csproj"); + var project = CreateTestNetCorePackageReferenceProject(projectName, projectFullPath, projectCache); + + // We need to treat NU1605 warning as error. + project.IsNu1605Error = true; + netCorePackageReferenceProjects.Add(project); + testSolutionManager.NuGetProjects.Add(project); + + //Let new project pickup my custom package source. + project.ProjectLocalSources.AddRange(sources); + var projectNames = GetTestProjectNames(projectFullPath, projectName); + var packageSpec = GetPackageSpec( + projectName, + projectFullPath, + packageA_Version100.Version); + + if (prevPackageSpec != null) + { + packageSpec = packageSpec.WithTestProjectReference(prevPackageSpec); + } + + // Restore info + var projectRestoreInfo = ProjectJsonTestHelpers.GetDGSpecFromPackageSpecs(packageSpec); + projectCache.AddProjectRestoreInfo(projectNames, projectRestoreInfo, new List()); + projectCache.AddProject(projectNames, projectAdapter, project).Should().BeTrue(); + prevProj = projectFullPath; + packageSpecs[i] = packageSpec; + prevPackageSpec = packageSpec; + projectFullPaths[i] = projectFullPath; + } + + var initialInstalledPackages = await netCorePackageReferenceProjects[numberOfProjects - 1].GetInstalledPackagesAsync(CancellationToken.None); + + // Act + var results = await nuGetPackageManager.PreviewProjectsInstallPackageAsync( + netCorePackageReferenceProjects, // All projects + packageB, + resolutionContext, + testNuGetProjectContext, + sourceRepositoryProvider.GetRepositories().ToList(), + CancellationToken.None); + + var actions = results.Select(a => a.Action).ToArray(); + + await nuGetPackageManager.ExecuteNuGetProjectActionsAsync( + netCorePackageReferenceProjects, + actions, + testNuGetProjectContext, + new SourceCacheContext(), + CancellationToken.None); + + // Assert + Assert.True(telemetryEvents.Count > 1); + var actionTelemetryStepEvents = telemetryEvents.OfType(); + Assert.True(actionTelemetryStepEvents.Any(t => t.SubStepName.Contains("Preview build integrated action time"))); + var builtIntegratedActions = actions.OfType().ToList(); + Assert.Equal(actions.Length, netCorePackageReferenceProjects.Count()); + Assert.Equal(actions.Length, builtIntegratedActions.Count); + Assert.True(builtIntegratedActions.All(b => b.RestoreResult.Success)); + } + } + + [Fact] + public async Task TestPackageManager_UninstallPackageFor_TopAndMidParentProject_Success() + { + using (var testDirectory = TestDirectory.Create()) + using (var testSolutionManager = new TestSolutionManager()) + { + // Set up Package Source + var sources = new List(); + var packageA_Version100 = new SimpleTestPackageContext("packageA", "1.0.0"); + var packageB_Version100 = new SimpleTestPackageContext("packageB", "1.0.0"); + var packageA = packageA_Version100.Identity; + var packageB = packageB_Version100.Identity; + var packageSource = Path.Combine(testDirectory, "packageSource"); + await SimpleTestPackageUtility.CreateFolderFeedV3Async( + packageSource, + PackageSaveMode.Defaultv3, + packageA_Version100, + packageB_Version100 + ); + + sources.Add(new PackageSource(packageSource)); + var sourceRepositoryProvider = TestSourceRepositoryUtility.CreateSourceRepositoryProvider(sources); + + // Project + int numberOfProjects = 3; + var projectCache = new ProjectSystemCache(); + IVsProjectAdapter projectAdapter = (new Mock()).Object; + ResolutionContext resolutionContext = (new Mock()).Object; + var packageSpecs = new PackageSpec[numberOfProjects]; + var projectFullPaths = new string[numberOfProjects]; + var deleteOnRestartManager = new TestDeleteOnRestartManager(); + var nuGetPackageManager = new NuGetPackageManager( + sourceRepositoryProvider, + NullSettings.Instance, + testSolutionManager, + deleteOnRestartManager); + + var testNuGetProjectContext = new TestNuGetProjectContext() { EnableLogging = true }; + var netCorePackageReferenceProjects = new List(); + var prevProj = string.Empty; + PackageSpec prevPackageSpec = null; + + // Create projects + for (var i = numberOfProjects - 1; i >= 0; i--) + { + var projectName = $"project{i}"; + var projectFullPath = Path.Combine(testDirectory.Path, projectName, projectName + ".csproj"); + var project = CreateTestNetCorePackageReferenceProject(projectName, projectFullPath, projectCache); + + // We need to treat NU1605 warning as error. + project.IsNu1605Error = true; + netCorePackageReferenceProjects.Add(project); + testSolutionManager.NuGetProjects.Add(project); + + //Let new project pickup my custom package source. + project.ProjectLocalSources.AddRange(sources); + var projectNames = GetTestProjectNames(projectFullPath, projectName); + var packageSpec = GetPackageSpec( + projectName, + projectFullPath, + packageA_Version100.Version); + + if (prevPackageSpec != null) + { + packageSpec = packageSpec.WithTestProjectReference(prevPackageSpec); + } + + // Restore info + var projectRestoreInfo = ProjectJsonTestHelpers.GetDGSpecFromPackageSpecs(packageSpec); + projectCache.AddProjectRestoreInfo(projectNames, projectRestoreInfo, new List()); + projectCache.AddProject(projectNames, projectAdapter, project).Should().BeTrue(); + prevProj = projectFullPath; + packageSpecs[i] = packageSpec; + prevPackageSpec = packageSpec; + projectFullPaths[i] = projectFullPath; + } + + for (int i = 0; i < numberOfProjects; i++) + { + // Install packageB since packageA is already there. + await nuGetPackageManager.InstallPackageAsync(netCorePackageReferenceProjects[i], packageB, new ResolutionContext(), new TestNuGetProjectContext(), + sourceRepositoryProvider.GetRepositories(), sourceRepositoryProvider.GetRepositories(), CancellationToken.None); + + // This code added because nuGetPackageManager.InstallPackageAsync doesn't do updating ProjectSystemCache + var installed = new LibraryDependency + { + LibraryRange = new LibraryRange( + name: packageB.Id, + versionRange: new VersionRange(packageB.Version), + typeConstraint: LibraryDependencyTarget.Package), + }; + + var packageSpec = packageSpecs[i]; + packageSpec.TargetFrameworks.First().Dependencies.Add(installed); + var projectRestoreInfo = ProjectJsonTestHelpers.GetDGSpecFromPackageSpecs(packageSpec); + var projectNames = GetTestProjectNames(projectFullPaths[i], $"project{i}"); + projectCache.AddProjectRestoreInfo(projectNames, projectRestoreInfo, new List()); + } + + var initialInstalledPackages = await netCorePackageReferenceProjects[numberOfProjects - 1].GetInstalledPackagesAsync(CancellationToken.None); + + var targetProjects = new List() + { + netCorePackageReferenceProjects[numberOfProjects-1], // Top parent project. + netCorePackageReferenceProjects[numberOfProjects-2] // Middle parent project. + }; + + // Act + var uninstallationContext = new UninstallationContext( + removeDependencies: false, + forceRemove: false); + + var results = new List(); + foreach (var target in targetProjects) + { + IEnumerable resolvedActions; + + resolvedActions = await nuGetPackageManager.PreviewUninstallPackageAsync( + target, packageB.Id, uninstallationContext, testNuGetProjectContext, CancellationToken.None); + + results.AddRange(resolvedActions.Select(a => new ResolvedAction(target, a))); + } + + var actions = results.Select(a => a.Action).ToArray(); + + await nuGetPackageManager.ExecuteNuGetProjectActionsAsync( + netCorePackageReferenceProjects, + actions, + testNuGetProjectContext, + new SourceCacheContext(), + CancellationToken.None); + + // Assert + Assert.Equal(initialInstalledPackages.Count(), 2); + var builtIntegratedActions = actions.OfType().ToList(); + Assert.Equal(actions.Length, targetProjects.Count()); + Assert.Equal(actions.Length, builtIntegratedActions.Count); + Assert.True(builtIntegratedActions.All(b => b.RestoreResult.Success)); + var restoringLogs = testNuGetProjectContext.Logs.Value.Where(l => l.StartsWith("Restoring packages for ")).ToList(); + var restoredLogs = testNuGetProjectContext.Logs.Value.Where(l => l.StartsWith("Restored ")).ToList(); + // Making sure project0 restored only once, not many. + // https://github.com/NuGet/Home/issues/9932 + Assert.Equal(restoringLogs.Count(l => l.EndsWith("project0.csproj...")), 1); + Assert.Equal(restoredLogs.Count(l => l.Contains("project0.csproj")), 1); + // Making sure project1 restored only once, not many. + Assert.Equal(restoringLogs.Count(l => l.EndsWith("project1.csproj...")), 1); + Assert.Equal(restoredLogs.Count(l => l.Contains("project1.csproj")), 1); + var writingAssetsLogs = testNuGetProjectContext.Logs.Value.Where(l => l.StartsWith("Writing assets file to disk.")).ToList(); + // Only 2 write to assets for above 2 projects, not more than that. + Assert.Equal(writingAssetsLogs.Count, 2); + // There should be no warning/error. + Assert.Equal(builtIntegratedActions.Sum(b => b.RestoreResult.LogMessages.Count()), 0); + } + } + + [Fact] + public async Task TestPackageManager_UninstallPackageFor_MidParentAndBottomProject_Success() + { + using (var testDirectory = TestDirectory.Create()) + using (var testSolutionManager = new TestSolutionManager()) + { + // Set up Package Source + var sources = new List(); + var packageA_Version100 = new SimpleTestPackageContext("packageA", "1.0.0"); + var packageB_Version100 = new SimpleTestPackageContext("packageB", "1.0.0"); + var packageA = packageA_Version100.Identity; + var packageB = packageB_Version100.Identity; + var packageSource = Path.Combine(testDirectory, "packageSource"); + await SimpleTestPackageUtility.CreateFolderFeedV3Async( + packageSource, + PackageSaveMode.Defaultv3, + packageA_Version100, + packageB_Version100 + ); + + sources.Add(new PackageSource(packageSource)); + var sourceRepositoryProvider = TestSourceRepositoryUtility.CreateSourceRepositoryProvider(sources); + + // Project + int numberOfProjects = 3; + var projectCache = new ProjectSystemCache(); + IVsProjectAdapter projectAdapter = (new Mock()).Object; + ResolutionContext resolutionContext = (new Mock()).Object; + var packageSpecs = new PackageSpec[numberOfProjects]; + var projectFullPaths = new string[numberOfProjects]; + var deleteOnRestartManager = new TestDeleteOnRestartManager(); + var nuGetPackageManager = new NuGetPackageManager( + sourceRepositoryProvider, + NullSettings.Instance, + testSolutionManager, + deleteOnRestartManager); + + var testNuGetProjectContext = new TestNuGetProjectContext() { EnableLogging = true }; + var netCorePackageReferenceProjects = new List(); + var prevProj = string.Empty; + PackageSpec prevPackageSpec = null; + + // Create projects + for (var i = numberOfProjects - 1; i >= 0; i--) + { + var projectName = $"project{i}"; + var projectFullPath = Path.Combine(testDirectory.Path, projectName, projectName + ".csproj"); + var project = CreateTestNetCorePackageReferenceProject(projectName, projectFullPath, projectCache); + + // We need to treat NU1605 warning as error. + project.IsNu1605Error = true; + netCorePackageReferenceProjects.Add(project); + testSolutionManager.NuGetProjects.Add(project); + + //Let new project pickup my custom package source. + project.ProjectLocalSources.AddRange(sources); + var projectNames = GetTestProjectNames(projectFullPath, projectName); + var packageSpec = GetPackageSpec( + projectName, + projectFullPath, + packageA_Version100.Version); + + if (prevPackageSpec != null) + { + packageSpec = packageSpec.WithTestProjectReference(prevPackageSpec); + } + + // Restore info + var projectRestoreInfo = ProjectJsonTestHelpers.GetDGSpecFromPackageSpecs(packageSpec); + projectCache.AddProjectRestoreInfo(projectNames, projectRestoreInfo, new List()); + projectCache.AddProject(projectNames, projectAdapter, project).Should().BeTrue(); + prevProj = projectFullPath; + packageSpecs[i] = packageSpec; + prevPackageSpec = packageSpec; + projectFullPaths[i] = projectFullPath; + } + + for (int i = 0; i < numberOfProjects; i++) + { + // Install packageB since packageA is already there. + await nuGetPackageManager.InstallPackageAsync(netCorePackageReferenceProjects[i], packageB, new ResolutionContext(), new TestNuGetProjectContext(), + sourceRepositoryProvider.GetRepositories(), sourceRepositoryProvider.GetRepositories(), CancellationToken.None); + + // This code added because nuGetPackageManager.InstallPackageAsync doesn't do updating ProjectSystemCache + var installed = new LibraryDependency + { + LibraryRange = new LibraryRange( + name: packageB.Id, + versionRange: new VersionRange(packageB.Version), + typeConstraint: LibraryDependencyTarget.Package), + }; + + var packageSpec = packageSpecs[i]; + packageSpec.TargetFrameworks.First().Dependencies.Add(installed); + var projectRestoreInfo = ProjectJsonTestHelpers.GetDGSpecFromPackageSpecs(packageSpec); + var projectNames = GetTestProjectNames(projectFullPaths[i], $"project{i}"); + projectCache.AddProjectRestoreInfo(projectNames, projectRestoreInfo, new List()); + } + + var initialInstalledPackages = await netCorePackageReferenceProjects[numberOfProjects - 1].GetInstalledPackagesAsync(CancellationToken.None); + + var targetProjects = new List() + { + netCorePackageReferenceProjects[numberOfProjects-2], // Middle parent project. + netCorePackageReferenceProjects[numberOfProjects-3] // Bottom child project. + }; + + // Act + var uninstallationContext = new UninstallationContext( + removeDependencies: false, + forceRemove: false); + + var results = new List(); + foreach (var target in targetProjects) + { + IEnumerable resolvedActions; + + resolvedActions = await nuGetPackageManager.PreviewUninstallPackageAsync( + target, packageB.Id, uninstallationContext, testNuGetProjectContext, CancellationToken.None); + + results.AddRange(resolvedActions.Select(a => new ResolvedAction(target, a))); + } + + var actions = results.Select(a => a.Action).ToArray(); + + await nuGetPackageManager.ExecuteNuGetProjectActionsAsync( + netCorePackageReferenceProjects, + actions, + testNuGetProjectContext, + new SourceCacheContext(), + CancellationToken.None); + + // Assert + Assert.Equal(initialInstalledPackages.Count(), 2); + var builtIntegratedActions = actions.OfType().ToList(); + Assert.Equal(actions.Length, targetProjects.Count()); + Assert.Equal(actions.Length, builtIntegratedActions.Count); + Assert.True(builtIntegratedActions.All(b => b.RestoreResult.Success)); + var restoringLogs = testNuGetProjectContext.Logs.Value.Where(l => l.StartsWith("Restoring packages for ")).ToList(); + var restoredLogs = testNuGetProjectContext.Logs.Value.Where(l => l.StartsWith("Restored ")).ToList(); + // Making sure project1 restored only once, not many. + // https://github.com/NuGet/Home/issues/9932 + Assert.Equal(restoringLogs.Count(l => l.EndsWith("project1.csproj...")), 1); + Assert.Equal(restoredLogs.Count(l => l.Contains("project1.csproj")), 1); + // Making sure project2 restored only once, not many. + Assert.Equal(restoringLogs.Count(l => l.EndsWith("project2.csproj...")), 1); + Assert.Equal(restoredLogs.Count(l => l.Contains("project2.csproj")), 1); + var writingAssetsLogs = testNuGetProjectContext.Logs.Value.Where(l => l.StartsWith("Writing assets file to disk.")).ToList(); + // Only 2 write to assets for above 2 projects, never more than that. + Assert.Equal(writingAssetsLogs.Count, 2); + // There should be no warning/error. + Assert.Equal(builtIntegratedActions.Sum(b => b.RestoreResult.LogMessages.Count()), 0); + } + } + + private TestNetCorePackageReferenceProject CreateTestNetCorePackageReferenceProject(string projectName, string projectFullPath, ProjectSystemCache projectSystemCache, TestProjectSystemServices projectServices = null) + { + projectServices = projectServices == null ? new TestProjectSystemServices() : projectServices; + + return new TestNetCorePackageReferenceProject( + projectName: projectName, + projectUniqueName: projectName, + projectFullPath: projectFullPath, + projectSystemCache: projectSystemCache, + unconfiguredProject: null, + projectServices: projectServices, + projectId: projectName); + } + private NetCorePackageReferenceProject CreateNetCorePackageReferenceProject(string projectName, string projectFullPath, ProjectSystemCache projectSystemCache) { var projectServices = new TestProjectSystemServices(); @@ -534,5 +2354,179 @@ private static PackageSpec GetPackageSpecMultipleVersions(string projectName, st }"; return JsonPackageSpecReader.GetPackageSpec(referenceSpec, projectName, testDirectory).WithTestRestoreMetadata(); } + + private class TestNetCorePackageReferenceProject + : NetCorePackageReferenceProject + , IProjectScriptHostService + , IProjectSystemReferencesReader + { + public HashSet ExecuteInitScriptAsyncCalls { get; } + = new HashSet(PackageIdentity.Comparer); + + public List ProjectReferences { get; } + = new List(); + + public bool IsCacheEnabled { get; set; } + + public bool IsNu1605Error { get; set; } + + public HashSet ProjectLocalSources { get; set; } = new HashSet(); + + public TestNetCorePackageReferenceProject( + string projectName, + string projectUniqueName, + string projectFullPath, + IProjectSystemCache projectSystemCache, + UnconfiguredProject unconfiguredProject, + INuGetProjectServices projectServices, + string projectId) + : base(projectName, projectUniqueName, projectFullPath, projectSystemCache, unconfiguredProject, projectServices, projectId) + { + ProjectServices = projectServices; + } + + public override string MSBuildProjectPath => base.MSBuildProjectPath; + + public override string ProjectName => base.ProjectName; + + public override async Task> GetPackageSpecsAsync(DependencyGraphCacheContext context) + { + var packageSpecs = await base.GetPackageSpecsAsync(context); + + if (IsNu1605Error) + { + foreach (var packageSpec in packageSpecs) + { + if (packageSpec?.RestoreMetadata != null) + { + var allWarningsAsErrors = false; + var noWarn = new HashSet(); + var warnAsError = new HashSet(); + + if (packageSpec.RestoreMetadata.ProjectWideWarningProperties != null) + { + var warningProperties = packageSpec.RestoreMetadata.ProjectWideWarningProperties; + allWarningsAsErrors = warningProperties.AllWarningsAsErrors; + warnAsError.AddRange(warningProperties.WarningsAsErrors); + noWarn.AddRange(warningProperties.NoWarn); + } + + warnAsError.Add(NuGetLogCode.NU1605); + noWarn.Remove(NuGetLogCode.NU1605); + + packageSpec.RestoreMetadata.ProjectWideWarningProperties = new WarningProperties(warnAsError, noWarn, allWarningsAsErrors); + + packageSpec?.RestoreMetadata.Sources.AddRange(new List(ProjectLocalSources)); + } + } + } + + return packageSpecs; + } + + public Task ExecutePackageScriptAsync(PackageIdentity packageIdentity, string packageInstallPath, string scriptRelativePath, INuGetProjectContext projectContext, bool throwOnFailure, CancellationToken token) + { + throw new NotImplementedException(); + } + + public Task ExecutePackageInitScriptAsync(PackageIdentity packageIdentity, string packageInstallPath, INuGetProjectContext projectContext, bool throwOnFailure, CancellationToken token) + { + ExecuteInitScriptAsyncCalls.Add(packageIdentity); + return Task.FromResult(true); + } + + public Task> GetPackageReferencesAsync(NuGetFramework targetFramework, CancellationToken token) + { + throw new NotImplementedException(); + } + + public Task> GetProjectReferencesAsync(ILogger logger, CancellationToken token) + { + var projectRefs = ProjectReferences.Select(e => new ProjectRestoreReference() + { + ProjectUniqueName = e.MSBuildProjectPath, + ProjectPath = e.MSBuildProjectPath, + }); + + return Task.FromResult(projectRefs); + } + + public override Task PreProcessAsync(INuGetProjectContext nuGetProjectContext, CancellationToken token) + { + return base.PreProcessAsync(nuGetProjectContext, token); + } + + public override Task PostProcessAsync(INuGetProjectContext nuGetProjectContext, CancellationToken token) + { + return base.PostProcessAsync(nuGetProjectContext, token); + } + + public override Task GetAssetsFilePathAsync() + { + return base.GetAssetsFilePathAsync(); + } + + public override Task GetAssetsFilePathOrNullAsync() + { + return base.GetAssetsFilePathOrNullAsync(); + } + + public override Task AddFileToProjectAsync(string filePath) + { + return base.AddFileToProjectAsync(filePath); + } + + public override Task<(IReadOnlyList dgSpecs, IReadOnlyList additionalMessages)> GetPackageSpecsAndAdditionalMessagesAsync(DependencyGraphCacheContext context) + { + return base.GetPackageSpecsAndAdditionalMessagesAsync(context); + } + + public override async Task InstallPackageAsync(string packageId, VersionRange range, INuGetProjectContext nuGetProjectContext, BuildIntegratedInstallationContext installationContext, CancellationToken token) + { + var dependency = new LibraryDependency + { + LibraryRange = new LibraryRange( + name: packageId, + versionRange: range, + typeConstraint: LibraryDependencyTarget.Package), + SuppressParent = installationContext.SuppressParent, + IncludeType = installationContext.IncludeType + }; + + await ProjectServices.References.AddOrUpdatePackageReferenceAsync(dependency, token); + + return true; + } + + public override async Task UninstallPackageAsync(PackageIdentity packageIdentity, INuGetProjectContext nuGetProjectContext, CancellationToken token) + { + await ProjectServices.References.RemovePackageReferenceAsync(packageIdentity.Id); + + return true; + } + + public override Task GetCacheFilePathAsync() + { + return base.GetCacheFilePathAsync(); + } + } + + private class TestExternalProjectReference + { + public IDependencyGraphProject Project { get; set; } + + public IDependencyGraphProject[] Children { get; set; } + + public TestExternalProjectReference( + IDependencyGraphProject project, + params IDependencyGraphProject[] children) + { + Project = project; + Children = children; + MSBuildProjectPath = project.MSBuildProjectPath; + } + + public string MSBuildProjectPath { get; set; } + } } } diff --git a/test/NuGet.Core.Tests/NuGet.PackageManagement.Test/DependencyGraphSpecTests.cs b/test/NuGet.Core.Tests/NuGet.PackageManagement.Test/DependencyGraphSpecTests.cs new file mode 100644 index 00000000000..9bb6232cc4c --- /dev/null +++ b/test/NuGet.Core.Tests/NuGet.PackageManagement.Test/DependencyGraphSpecTests.cs @@ -0,0 +1,106 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Linq; +using FluentAssertions; +using NuGet.ProjectModel; +using Xunit; + +namespace NuGet.Test +{ + public class DependencyGraphSpecTests + { + [Fact] + public void WithReplacedSpec() + { + // Arrange + var packageSpecA = new PackageSpec(); + packageSpecA.Title = "A"; + packageSpecA.RestoreMetadata = new ProjectRestoreMetadata() { ProjectUniqueName = "a", CentralPackageVersionsEnabled = false }; + var dgSpec = new DependencyGraphSpec(); + var packageSpecB = new PackageSpec(); + packageSpecB.Title = "B"; + packageSpecB.RestoreMetadata = new ProjectRestoreMetadata() + { + ProjectUniqueName = "BBB" + }; + var packageSpecC = new PackageSpec(); + packageSpecC.Title = "C"; + packageSpecC.RestoreMetadata = new ProjectRestoreMetadata() + { + ProjectUniqueName = "CCC" + }; + + // Act + dgSpec = dgSpec.WithReplacedSpec(packageSpecA); + dgSpec = dgSpec.WithReplacedSpec(packageSpecB); + dgSpec = dgSpec.WithReplacedSpec(packageSpecC); + + // Assert + Assert.Equal(dgSpec.Projects.Count, 3); + Assert.Equal(dgSpec.Restore.Count, 1); + } + + [Fact] + public void WithReplacedPackageSpecs_WithASinglePackageSpec_Succeeds() + { + // Arrange + var packageSpecA = new PackageSpec + { + Title = "A", + RestoreMetadata = new ProjectRestoreMetadata() + { + ProjectUniqueName = "a", + CentralPackageVersionsEnabled = false + } + }; + var packageSpecB = new PackageSpec + { + Title = "B", + RestoreMetadata = new ProjectRestoreMetadata() + { + ProjectUniqueName = "BBB" + } + }; + var packageSpecC = new PackageSpec + { + Title = "C", + RestoreMetadata = new ProjectRestoreMetadata() + { + ProjectUniqueName = "CCC" + } + }; + var dgSpec = new DependencyGraphSpec(); + dgSpec.AddProject(packageSpecA); + dgSpec.AddProject(packageSpecB); + dgSpec.AddProject(packageSpecC); + dgSpec.AddRestore(packageSpecA.RestoreMetadata.ProjectUniqueName); + dgSpec.AddRestore(packageSpecB.RestoreMetadata.ProjectUniqueName); + dgSpec.AddRestore(packageSpecC.RestoreMetadata.ProjectUniqueName); + + // Create an updated packageSpecA + var updatedPackageA = packageSpecA.Clone(); + updatedPackageA.RestoreMetadata.ConfigFilePaths.Add("/samplePath"); + var newNugetPackageSpecs = new List() + { + updatedPackageA + }; + + // Preconditions + dgSpec.Projects.Should().HaveCount(3); + dgSpec.Restore.Should().HaveCount(3); + + // Act + var dgSpecWithReplacedPackageA = dgSpec.WithPackageSpecs(newNugetPackageSpecs); + + // Assert + dgSpecWithReplacedPackageA.Projects.Should().HaveCount(3); + dgSpecWithReplacedPackageA.Restore.Should().HaveCount(1); + + var packageSpecInAFromDgSpec = dgSpecWithReplacedPackageA.Projects.Single(e => e.Title.Equals("A")); + packageSpecInAFromDgSpec.Should().Be(updatedPackageA); + dgSpecWithReplacedPackageA.Restore.Single().Should().Be(updatedPackageA.RestoreMetadata.ProjectUniqueName); + } + } +} diff --git a/test/TestUtilities/Test.Utility/ProjectManagement/TestNuGetProjectContext.cs b/test/TestUtilities/Test.Utility/ProjectManagement/TestNuGetProjectContext.cs index f05b9ea6088..29526ff201f 100644 --- a/test/TestUtilities/Test.Utility/ProjectManagement/TestNuGetProjectContext.cs +++ b/test/TestUtilities/Test.Utility/ProjectManagement/TestNuGetProjectContext.cs @@ -17,17 +17,29 @@ public class TestNuGetProjectContext : IMSBuildNuGetProjectContext { private Guid _operationId; public TestExecutionContext TestExecutionContext { get; set; } + public Lazy> Logs { get; } = new Lazy>(); + public bool EnableLogging { get; set; } public void Log(MessageLevel level, string message, params object[] args) { // Uncomment when you want to debug tests. // Console.WriteLine(message, args); + + if (EnableLogging) + { + Logs.Value.Add(args != null ? message + " " + string.Join(",", args) : message); + } } public void Log(ILogMessage message) { // Uncomment when you want to debug tests. // Console.WriteLine(message.FormatWithCode()); + + if (EnableLogging) + { + Logs.Value.Add(message.Message); + } } public FileConflictAction ResolveFileConflict(string message)