diff --git a/src/Build.UnitTests/ProjectCache/ProjectCacheTests.cs b/src/Build.UnitTests/ProjectCache/ProjectCacheTests.cs index 8e9b451874c..f119d2f50e5 100644 --- a/src/Build.UnitTests/ProjectCache/ProjectCacheTests.cs +++ b/src/Build.UnitTests/ProjectCache/ProjectCacheTests.cs @@ -272,6 +272,8 @@ public override async Task GetCacheResultAsync( logger.LogMessage($"MockCache: GetCacheResultAsync for {buildRequest.ProjectFullPath}", MessageImportance.High); + buildRequest.ProjectInstance.ShouldNotBeNull("The cache plugin expects evaluated projects."); + if (_projectQuerySleepTime is not null) { await Task.Delay(_projectQuerySleepTime.Value); @@ -1349,6 +1351,86 @@ Task BuildProjectFileAsync(int projectNumber) } } + [Theory] + [InlineData(false, false)] + // TODO: Reenable when this gets into the main branch. + //[InlineData(true, true)] + public void ParallelStressTestForVsWorkaround(bool useSynchronousLogging, bool disableInprocNode) + { + var currentBuildEnvironment = BuildEnvironmentHelper.Instance; + + try + { + BuildEnvironmentHelper.ResetInstance_ForUnitTestsOnly( + new BuildEnvironment( + currentBuildEnvironment.Mode, + currentBuildEnvironment.CurrentMSBuildExePath, + currentBuildEnvironment.RunningTests, + runningInVisualStudio: true, + visualStudioPath: currentBuildEnvironment.VisualStudioInstallRootDirectory)); + + BuildManager.ProjectCacheItems.ShouldBeEmpty(); + + var referenceNumbers = Enumerable.Range(2, NativeMethodsShared.GetLogicalCoreCount() * 2).ToArray(); + + var testData = new GraphCacheResponse( + new Dictionary + { + {1, referenceNumbers} + }, + referenceNumbers.ToDictionary(k => k, k => GraphCacheResponse.SuccessfulProxyTargetResult()) + ); + + var graph = testData.CreateGraph(_env); + + // Even though the assembly cache is discovered, we'll be overriding it with a descriptor based cache. + BuildManager.ProjectCacheItems.ShouldHaveSingleItem(); + + var cache = new InstanceMockCache(testData, TimeSpan.FromMilliseconds(50)); + + using var buildSession = new Helpers.BuildManagerSession(_env, new BuildParameters + { + MaxNodeCount = NativeMethodsShared.GetLogicalCoreCount(), + ProjectCacheDescriptor = ProjectCacheDescriptor.FromInstance( + cache, + entryPoints: null, + graph), + UseSynchronousLogging = useSynchronousLogging, + DisableInProcNode = disableInprocNode + }); + + var buildResultTasks = new List>(); + + foreach (var node in graph.ProjectNodes.Where(n => referenceNumbers.Contains(GetProjectNumber(n)))) + { + var buildResultTask = buildSession.BuildProjectFileAsync( + node.ProjectInstance.FullPath, + globalProperties: + new Dictionary { { "SolutionPath", graph.GraphRoots.First().ProjectInstance.FullPath } }); + + buildResultTasks.Add(buildResultTask); + } + + foreach (var buildResultTask in buildResultTasks) + { + buildResultTask.Result.OverallResult.ShouldBe(BuildResultCode.Success); + } + + buildSession.BuildProjectFile( + graph.GraphRoots.First().ProjectInstance.FullPath, + globalProperties: + new Dictionary {{"SolutionPath", graph.GraphRoots.First().ProjectInstance.FullPath}}) + .OverallResult.ShouldBe(BuildResultCode.Success); + + cache.QueryStartStops.Count.ShouldBe(graph.ProjectNodes.Count * 2); + } + finally + { + BuildEnvironmentHelper.ResetInstance_ForUnitTestsOnly(currentBuildEnvironment); + BuildManager.ProjectCacheItems.Clear(); + } + } + [Theory] [InlineData(false, false)] [InlineData(true, true)] diff --git a/src/Build/BackEnd/Components/ProjectCache/ProjectCacheService.cs b/src/Build/BackEnd/Components/ProjectCache/ProjectCacheService.cs index 8d3484cf052..50fc14a11a0 100644 --- a/src/Build/BackEnd/Components/ProjectCache/ProjectCacheService.cs +++ b/src/Build/BackEnd/Components/ProjectCache/ProjectCacheService.cs @@ -230,7 +230,10 @@ async Task ProcessCacheRequest(CacheRequest request) await LateInitializePluginForVsWorkaround(request); } - return await GetCacheResultAsync(cacheRequest.Submission.BuildRequestData); + return await GetCacheResultAsync( + new BuildRequestData( + request.Configuration.Project, + request.Submission.BuildRequestData.TargetNames.ToArray())); } static bool IsDesignTimeBuild(ProjectInstance project) @@ -300,6 +303,8 @@ static bool MSBuildStringIsTrue(string msbuildString) => private async Task GetCacheResultAsync(BuildRequestData buildRequest) { + ErrorUtilities.VerifyThrowInternalNull(buildRequest.ProjectInstance, nameof(buildRequest.ProjectInstance)); + var queryDescription = $"{buildRequest.ProjectFullPath}" + $"\n\tTargets:[{string.Join(", ", buildRequest.TargetNames)}]" + $"\n\tGlobal Properties: {{{string.Join(",", buildRequest.GlobalProperties.Select(kvp => $"{kvp.Name}={kvp.EvaluatedValue}"))}}}"; diff --git a/src/Samples/ProjectCachePlugin/AssemblyMockCache.cs b/src/Samples/ProjectCachePlugin/AssemblyMockCache.cs index 3b26b82d942..7f049a6c699 100644 --- a/src/Samples/ProjectCachePlugin/AssemblyMockCache.cs +++ b/src/Samples/ProjectCachePlugin/AssemblyMockCache.cs @@ -2,11 +2,13 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System; +using System.Diagnostics; using System.Threading; using System.Threading.Tasks; using Microsoft.Build.Execution; using Microsoft.Build.Experimental.ProjectCache; using Microsoft.Build.Framework; +using Shouldly; namespace MockCacheFromAssembly { @@ -33,6 +35,8 @@ public override Task GetCacheResultAsync( { logger.LogMessage($"{nameof(AssemblyMockCache)}: GetCacheResultAsync for {buildRequest.ProjectFullPath}", MessageImportance.High); + buildRequest.ProjectInstance.ShouldNotBeNull("The cache plugin expects evaluated projects."); + ErrorFrom(nameof(GetCacheResultAsync), logger); return Task.FromResult(CacheResult.IndicateNonCacheHit(CacheResultType.CacheNotApplicable)); diff --git a/src/Samples/ProjectCachePlugin/ProjectCachePlugin.csproj b/src/Samples/ProjectCachePlugin/ProjectCachePlugin.csproj index df35ae1ca6b..3e08803f1c5 100644 --- a/src/Samples/ProjectCachePlugin/ProjectCachePlugin.csproj +++ b/src/Samples/ProjectCachePlugin/ProjectCachePlugin.csproj @@ -12,4 +12,7 @@ + + +