|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Collections.Generic;
using System.Linq;
using Microsoft.Build.BackEnd;
using Microsoft.Build.Execution;
using Microsoft.Build.Framework;
using Microsoft.Build.Internal;
using Microsoft.Build.Shared;
using Microsoft.Build.Unittest;
using Shouldly;
using Xunit;
using static Microsoft.Build.Unittest.BuildResultUtilities;
#nullable disable
namespace Microsoft.Build.UnitTests.BackEnd
{
public class CacheAggregator_Tests
{
private CacheAggregator aggregator;
public CacheAggregator_Tests()
{
var i = BuildRequestConfiguration.InvalidConfigurationId + 1;
aggregator = new CacheAggregator(() => i++);
}
[Fact]
public void NoCachesProducesEmptyCaches()
{
var aggregation = aggregator.Aggregate();
aggregation.ConfigCache.ShouldNotBeNull();
aggregation.ConfigCache.GetEnumerator().ToEnumerable().ShouldBeEmpty();
aggregation.ResultsCache.ShouldNotBeNull();
aggregation.ResultsCache.GetEnumerator().ToEnumerable().ShouldBeEmpty();
aggregation.LastConfigurationId.ShouldBe(0);
}
[Fact]
public void CannotCallAggregateTwice()
{
aggregator.Aggregate();
using (var env = TestEnvironment.Create())
{
env.SetEnvironmentVariable("MSBUILDDONOTLAUNCHDEBUGGER", "1");
var e = Should.Throw<InternalErrorException>(() => aggregator.Aggregate());
e.Message.ShouldContain("Cannot aggregate twice");
}
}
[Fact]
public void CannotAddAfterAggregation()
{
aggregator.Aggregate();
using (var env = TestEnvironment.Create())
{
env.SetEnvironmentVariable("MSBUILDDONOTLAUNCHDEBUGGER", "1");
var e = Should.Throw<InternalErrorException>(() =>
{
aggregator.Add(new ConfigCache(), new ResultsCache());
});
e.Message.ShouldContain("Cannot add after aggregation");
}
}
[Fact]
public void RejectCachesWithMoreConfigEntriesThanResultEntries()
{
var configCache = new ConfigCache();
configCache.AddConfiguration(new BuildRequestConfiguration(configId: 1, new BuildRequestData("path", new Dictionary<string, string>() { ["p"] = "v" }, "13", new[] { "a", "b" }, null), "13"));
configCache.AddConfiguration(new BuildRequestConfiguration(configId: 2, new BuildRequestData("path2", new Dictionary<string, string>() { ["p"] = "v" }, "13", new[] { "c", "d" }, null), "13"));
var resultsCache = new ResultsCache();
var buildResult = new BuildResult(new BuildRequest(1, 2, configurationId: 1, new List<string>() { "a", "b" }, null, BuildEventContext.Invalid, null));
buildResult.AddResultsForTarget("a", GetNonEmptySucceedingTargetResult());
resultsCache.AddResult(buildResult);
aggregator.Add(configCache, resultsCache);
using (var env = TestEnvironment.Create())
{
env.SetEnvironmentVariable("MSBUILDDONOTLAUNCHDEBUGGER", "1");
var e = Should.Throw<InternalErrorException>(() => aggregator.Aggregate());
e.Message.ShouldContain("Assuming 1-to-1 mapping between configs and results. Otherwise it means the caches are either not minimal or incomplete");
}
}
[Fact]
public void RejectCachesWithMoreResultEntriesThanConfigEntries()
{
var configCache = new ConfigCache();
configCache.AddConfiguration(new BuildRequestConfiguration(configId: 1, new BuildRequestData("path", new Dictionary<string, string>() { ["p"] = "v" }, "13", new[] { "a", "b" }, null), "13"));
var resultsCache = new ResultsCache();
var buildResult = new BuildResult(new BuildRequest(1, 2, configurationId: 1, new List<string>() { "a", "b" }, null, BuildEventContext.Invalid, null));
buildResult.AddResultsForTarget("a", GetNonEmptySucceedingTargetResult());
resultsCache.AddResult(buildResult);
var buildResult2 = new BuildResult(new BuildRequest(1, 2, configurationId: 2, new List<string>() { "a", "b" }, null, BuildEventContext.Invalid, null));
buildResult2.AddResultsForTarget("a", GetNonEmptySucceedingTargetResult());
resultsCache.AddResult(buildResult2);
aggregator.Add(configCache, resultsCache);
using (var env = TestEnvironment.Create())
{
env.SetEnvironmentVariable("MSBUILDDONOTLAUNCHDEBUGGER", "1");
var e = Should.Throw<InternalErrorException>(() => aggregator.Aggregate());
e.Message.ShouldContain("Assuming 1-to-1 mapping between configs and results. Otherwise it means the caches are either not minimal or incomplete");
}
}
[Fact]
public void RejectCachesWithMismatchedIds()
{
// one entry in each cache but different config ids
var configCache = new ConfigCache();
configCache.AddConfiguration(new BuildRequestConfiguration(configId: 1, new BuildRequestData("path", new Dictionary<string, string>() { ["p"] = "v" }, "13", new[] { "a", "b" }, null), "13"));
var resultsCache = new ResultsCache();
var buildResult = new BuildResult(new BuildRequest(1, 2, configurationId: 2, new List<string>() { "a", "b" }, null, BuildEventContext.Invalid, null));
buildResult.AddResultsForTarget("a", GetNonEmptySucceedingTargetResult());
resultsCache.AddResult(buildResult);
aggregator.Add(configCache, resultsCache);
using (var env = TestEnvironment.Create())
{
env.SetEnvironmentVariable("MSBUILDDONOTLAUNCHDEBUGGER", "1");
var e = Should.Throw<InternalErrorException>(() => aggregator.Aggregate());
e.Message.ShouldContain("Each result should have a corresponding configuration. Otherwise the caches are not consistent");
}
}
[Fact]
public void RejectCollidingConfigurationsFromSeparateCaches()
{
// collides with the config id from configCache2
var configCache1 = new ConfigCache();
configCache1.AddConfiguration(new BuildRequestConfiguration(configId: 1, new BuildRequestData("path", new Dictionary<string, string>() { ["p"] = "v" }, "13", new[] { "a", "b" }, null), "13"));
var resultsCache1 = new ResultsCache();
var buildResult11 = new BuildResult(new BuildRequest(1, 2, configurationId: 1, new List<string>() { "a", "b" }, null, BuildEventContext.Invalid, null));
buildResult11.AddResultsForTarget("a", GetNonEmptySucceedingTargetResult());
resultsCache1.AddResult(buildResult11);
var configCache2 = new ConfigCache();
configCache2.AddConfiguration(new BuildRequestConfiguration(configId: 1, new BuildRequestData("path", new Dictionary<string, string>() { ["p"] = "v" }, "13", new[] { "a", "b" }, null), "13"));
var resultsCache2 = new ResultsCache();
var buildResult21 = new BuildResult(new BuildRequest(1, 2, configurationId: 1, new List<string>() { "e", "f" }, null, BuildEventContext.Invalid, null));
buildResult21.AddResultsForTarget("a", GetNonEmptySucceedingTargetResult());
resultsCache2.AddResult(buildResult21);
aggregator.Add(configCache1, resultsCache1);
aggregator.Add(configCache2, resultsCache2);
using (var env = TestEnvironment.Create())
{
env.SetEnvironmentVariable("MSBUILDDONOTLAUNCHDEBUGGER", "1");
var e = Should.Throw<InternalErrorException>(() => aggregator.Aggregate());
e.Message.ShouldContain("Input caches should not contain entries for the same configuration");
}
}
[Fact]
public void SingleEmpty()
{
var configCache = new ConfigCache();
var resultsCache = new ResultsCache();
aggregator.Add(configCache, resultsCache);
var results = aggregator.Aggregate();
AssertAggregation(new[] { (configCache, resultsCache) }, results);
}
[Fact]
public void SingleCacheWithSingleEntry()
{
var configCache = new ConfigCache();
configCache.AddConfiguration(new BuildRequestConfiguration(configId: 1, new BuildRequestData("path", new Dictionary<string, string>() { ["p"] = "v" }, "13", new[] { "a", "b" }, null), "13"));
var resultsCache = new ResultsCache();
var buildResult = new BuildResult(new BuildRequest(1, 2, configurationId: 1, new List<string>() { "a", "b" }, null, BuildEventContext.Invalid, null));
buildResult.AddResultsForTarget("a", GetNonEmptySucceedingTargetResult());
resultsCache.AddResult(buildResult);
aggregator.Add(configCache, resultsCache);
var results = aggregator.Aggregate();
AssertAggregation(new[] { (configCache, resultsCache) }, results);
}
[Fact]
public void MultipleCachesMultipleEntries()
{
var configCache1 = new ConfigCache();
configCache1.AddConfiguration(new BuildRequestConfiguration(configId: 1, new BuildRequestData("path", new Dictionary<string, string>() { ["p"] = "v" }, "13", new[] { "a", "b" }, null), "13"));
configCache1.AddConfiguration(new BuildRequestConfiguration(configId: 2, new BuildRequestData("path2", new Dictionary<string, string>() { ["p"] = "v" }, "13", new[] { "c", "d" }, null), "13"));
var resultsCache1 = new ResultsCache();
var buildResult11 = new BuildResult(new BuildRequest(1, 2, configurationId: 1, new List<string>() { "a", "b" }, null, BuildEventContext.Invalid, null));
buildResult11.AddResultsForTarget("a", GetNonEmptySucceedingTargetResult());
var buildResult12 = new BuildResult(new BuildRequest(1, 2, configurationId: 2, new List<string>() { "c", "d" }, null, BuildEventContext.Invalid, null));
buildResult12.AddResultsForTarget("a", GetNonEmptySucceedingTargetResult());
resultsCache1.AddResult(buildResult11);
resultsCache1.AddResult(buildResult12);
var configCache2 = new ConfigCache();
configCache2.AddConfiguration(new BuildRequestConfiguration(configId: 1, new BuildRequestData("path3", new Dictionary<string, string>() { ["p"] = "v" }, "13", new[] { "e", "f" }, null), "13"));
configCache2.AddConfiguration(new BuildRequestConfiguration(configId: 2, new BuildRequestData("path4", new Dictionary<string, string>() { ["p"] = "v" }, "13", new[] { "g", "h" }, null), "13"));
var resultsCache2 = new ResultsCache();
var buildResult21 = new BuildResult(new BuildRequest(1, 2, configurationId: 1, new List<string>() { "e", "f" }, null, BuildEventContext.Invalid, null));
buildResult21.AddResultsForTarget("a", GetNonEmptySucceedingTargetResult());
var buildResult22 = new BuildResult(new BuildRequest(1, 2, configurationId: 2, new List<string>() { "g", "h" }, null, BuildEventContext.Invalid, null));
buildResult22.AddResultsForTarget("a", GetNonEmptySucceedingTargetResult());
resultsCache2.AddResult(buildResult21);
resultsCache2.AddResult(buildResult22);
aggregator.Add(configCache1, resultsCache1);
aggregator.Add(configCache2, resultsCache2);
var results = aggregator.Aggregate();
AssertAggregation(new[] { (configCache1, resultsCache1), (configCache2, resultsCache2) }, results);
}
private void AssertAggregation((ConfigCache configCache, ResultsCache resultsCache)[] inputCaches, CacheAggregation aggregation)
{
var currentConfigurationIndex = 0;
var currentBuildResultIndex = 0;
var aggregatedConfigs = aggregation.ConfigCache.GetEnumerator().ToArray();
var aggregatedResults = aggregation.ResultsCache.GetEnumerator().ToArray();
foreach (var (configCache, resultsCache) in inputCaches)
{
foreach (var inputConfiguration in configCache)
{
AssertConfigurationsEquivalent(inputConfiguration, aggregatedConfigs[currentConfigurationIndex]);
currentConfigurationIndex++;
}
foreach (var inputResult in resultsCache)
{
AssertBuildResultsEquivalent(inputResult, aggregatedResults[currentBuildResultIndex]);
currentBuildResultIndex++;
}
}
currentConfigurationIndex.ShouldBe(currentBuildResultIndex);
aggregatedConfigs.Length.ShouldBe(currentBuildResultIndex);
aggregatedResults.Length.ShouldBe(currentBuildResultIndex);
aggregation.LastConfigurationId.ShouldBe(currentBuildResultIndex);
}
private void AssertBuildResultsEquivalent(BuildResult inputResult, BuildResult aggregatedBuildResult)
{
aggregatedBuildResult.ConfigurationId.ShouldNotBe(BuildRequestConfiguration.InvalidConfigurationId);
aggregatedBuildResult.ParentGlobalRequestId.ShouldBe(BuildRequest.InvalidGlobalRequestId);
aggregatedBuildResult.GlobalRequestId.ShouldBe(BuildRequest.InvalidGlobalRequestId);
aggregatedBuildResult.NodeRequestId.ShouldBe(BuildRequest.InvalidNodeRequestId);
aggregatedBuildResult.SubmissionId.ShouldBe(BuildEventContext.InvalidSubmissionId);
SdkUtilities.EngineHelpers.AssertBuildResultsEqual(inputResult, aggregatedBuildResult);
}
private void AssertConfigurationsEquivalent(BuildRequestConfiguration inputConfiguration, BuildRequestConfiguration aggregatedConfig)
{
aggregatedConfig.ConfigurationId.ShouldNotBe(BuildRequestConfiguration.InvalidConfigurationId);
aggregatedConfig.ResultsNodeId.ShouldBe(Scheduler.InvalidNodeId);
var aggregatedConfigWithInitialId = aggregatedConfig.ShallowCloneWithNewId(inputConfiguration.ConfigurationId);
inputConfiguration.ShouldBe(aggregatedConfigWithInitialId);
}
}
}
|