Skip to content

Commit 7fb580d

Browse files
Add comments and test
1 parent a97d044 commit 7fb580d

File tree

7 files changed

+145
-15
lines changed

7 files changed

+145
-15
lines changed

src/Build.UnitTests/BackEnd/CacheSerialization_Tests.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,13 @@ public void OnlySerializeCacheEntryWithSmallestConfigId(object configCache, obje
117117
[MemberData(nameof(CacheData))]
118118
public void OnlySerializeResultsForSpecifiedTargets(object configCache, object resultsCache)
119119
{
120+
// Setup:
121+
// 1. Create a config with id 1 whose project is built with top-level targets target1
122+
// and target2.
123+
// 2. Send a build request and collect the BuildResults for targets target1, target2,
124+
// and target3.
125+
// 3. Ensure the BuildResult for target3 is excluded from output cache serialization
126+
// since it's not a top-level target.
120127
string cacheFile = null;
121128
try
122129
{

src/Build.UnitTests/BackEnd/Scheduler_Tests.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -726,7 +726,8 @@ public void TestDetailedSummary()
726726
/// </summary>
727727
/// <param name="configId">The configuration id.</param>
728728
/// <param name="projectFullPath">The project's full path.</param>
729-
/// <param name="configCache">The config cache in which to place to configuration.</param>
729+
/// <param name="configCache">The config cache in which to place the configuration. If
730+
/// <see cref="langword"="null" />, use the host's config cache.</param>
730731
private void CreateConfiguration(int configId, string projectFullPath, ConfigCache configCache = null)
731732
{
732733
BuildRequestData data = new(projectFullPath, new Dictionary<string, string>(), "4.0", Array.Empty<string>(), null);
@@ -748,7 +749,8 @@ private void CreateConfiguration(int configId, string projectFullPath, ConfigCac
748749
/// created and cached.</param>
749750
/// <param name="target">The target for which there will be a result.</param>
750751
/// <param name="workUnitResult">The result of executing the specified target.</param>
751-
/// <param name="resultsCache">The results cache to contain the <see cref="BuildResult"/>.</param>
752+
/// <param name="resultsCache">The results cache to contain the <see cref="BuildResult"/>.
753+
/// If <see cref="langword"="null"/>, use the host's results cache.</param>
752754
/// <returns>The build result.</returns>
753755
private BuildResult CacheBuildResult(BuildRequest request, string target, WorkUnitResult workUnitResult, ResultsCache resultsCache = null)
754756
{

src/Build.UnitTests/Graph/IsolateProjects_Tests.cs

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,14 @@
77
using System.Linq;
88
using Microsoft.Build.Execution;
99
using Microsoft.Build.Framework;
10+
using Microsoft.Build.Internal;
1011
using Microsoft.Build.Shared;
1112
using Microsoft.Build.UnitTests;
1213
using Shouldly;
1314
using Xunit;
1415
using Xunit.Abstractions;
16+
using ExpectedNodeBuildOutput = System.Collections.Generic.Dictionary<Microsoft.Build.Graph.ProjectGraphNode, string[]>;
17+
using OutputCacheDictionary = System.Collections.Generic.Dictionary<Microsoft.Build.Graph.ProjectGraphNode, string>;
1518

1619
#nullable disable
1720

@@ -263,6 +266,108 @@ public void IsolationRelatedMessageShouldBePresentInIsolatedBuildsWithMessaging(
263266
isolateProjects: ProjectIsolationMode.MessageUponIsolationViolation);
264267
}
265268

269+
[Fact]
270+
public void UndeclaredReferenceBuildResultNotPresentInOutputCache()
271+
{
272+
// Create the graph 1 -> 2 -> 3, where 2 is a declared project reference
273+
// and 3 is an undeclared project reference.
274+
// 3 outputs an item UndeclaredReferenceTargetItem that 2 outputs.
275+
// Run under ProjectIsolationMode.MessageUponIsolationViolation mode
276+
// and verify that 3's build result is not present in 2's output results
277+
// cache since, under this mode, only the results of the project
278+
// to build under isolation (2) should be serialized.
279+
// See CacheSerialization.SerializeCaches for more info.
280+
string undeclaredReferenceFile = GraphTestingUtilities.CreateProjectFile(
281+
_env,
282+
3,
283+
extraContent: @"
284+
<Target Name='UndeclaredReferenceTarget' Outputs='@(UndeclaredReferenceTargetItem)'>
285+
<ItemGroup>
286+
<UndeclaredReferenceTargetItem Include='Foo.cs' />
287+
</ItemGroup>
288+
<Message Text='Message from undeclared reference' Importance='High' />
289+
</Target>",
290+
defaultTargets: "UndeclaredReferenceTarget").Path;
291+
string declaredReferenceContents = string.Format(
292+
@"
293+
<Target Name='DeclaredReferenceTarget' Outputs='@(UndeclaredReferenceTargetItem)'>
294+
<MSBuild
295+
Projects='{0}'
296+
Targets='UndeclaredReferenceTarget'>
297+
<Output TaskParameter='TargetOutputs' ItemName='UndeclaredReferenceTargetItem' />
298+
</MSBuild>
299+
</Target>".Cleanup(),
300+
undeclaredReferenceFile).Cleanup();
301+
string declaredReferenceFile = GraphTestingUtilities.CreateProjectFile(
302+
_env,
303+
2,
304+
extraContent: declaredReferenceContents,
305+
defaultTargets: "DeclaredReferenceTarget").Path;
306+
string rootProjectContents = string.Format(
307+
@"
308+
<ItemGroup>
309+
<ProjectReference Include='{0}' />
310+
</ItemGroup>
311+
<Target Name='BuildDeclaredReference'>
312+
<MSBuild
313+
Projects='{1}'
314+
Targets='DeclaredReferenceTarget'
315+
/>
316+
</Target>".Cleanup(),
317+
declaredReferenceFile,
318+
declaredReferenceFile).Cleanup();
319+
string rootFile = GraphTestingUtilities.CreateProjectFile(
320+
_env,
321+
1,
322+
extraContent: rootProjectContents,
323+
defaultTargets: "BuildDeclaredReference").Path;
324+
var projectGraph = new ProjectGraph(
325+
rootFile,
326+
new Dictionary<string, string>(),
327+
_env.CreateProjectCollection().Collection);
328+
var expectedOutput = new ExpectedNodeBuildOutput();
329+
var outputCaches = new OutputCacheDictionary();
330+
ProjectGraphNode[] topoSortedProjectGraphNodes = projectGraph.ProjectNodesTopologicallySorted.ToArray();
331+
Dictionary<string, (BuildResult Result, MockLogger Logger)> results = ResultCacheBasedBuilds_Tests.BuildUsingCaches(
332+
_env,
333+
topoSortedProjectGraphNodes,
334+
expectedOutput,
335+
outputCaches,
336+
generateCacheFiles: true,
337+
assertBuildResults: false,
338+
projectIsolationMode: ProjectIsolationMode.MessageUponIsolationViolation);
339+
var deserializedOutputCacheDeclaredReference = CacheSerialization.DeserializeCaches(outputCaches[topoSortedProjectGraphNodes[0]]);
340+
var deserializedOutputCacheRoot = CacheSerialization.DeserializeCaches(outputCaches[topoSortedProjectGraphNodes[1]]);
341+
deserializedOutputCacheDeclaredReference.exception.ShouldBeNull();
342+
deserializedOutputCacheRoot.exception.ShouldBeNull();
343+
BuildResult[] declaredReferenceBuildResults = deserializedOutputCacheDeclaredReference.ResultsCache.GetEnumerator().ToArray();
344+
BuildResult[] rootBuildResults = deserializedOutputCacheRoot.ResultsCache.GetEnumerator().ToArray();
345+
346+
// Both the root and declared reference projects should only have one build result.
347+
declaredReferenceBuildResults.Length.ShouldBe(1);
348+
rootBuildResults.Length.ShouldBe(1);
349+
declaredReferenceBuildResults[0].OverallResult.ShouldBe(BuildResultCode.Success);
350+
rootBuildResults[0].OverallResult.ShouldBe(BuildResultCode.Success);
351+
MockLogger rootLogger = results["1"].Logger;
352+
MockLogger declaredReferenceLogger = results["2"].Logger;
353+
rootLogger.ErrorCount.ShouldBe(0);
354+
declaredReferenceLogger.ErrorCount.ShouldBe(0);
355+
rootLogger.Errors.ShouldBeEmpty();
356+
declaredReferenceLogger.Errors.ShouldBeEmpty();
357+
rootLogger.AllBuildEvents.OfType<ProjectStartedEventArgs>().Count().ShouldBe(2);
358+
declaredReferenceLogger.AllBuildEvents.OfType<ProjectStartedEventArgs>().Count().ShouldBe(2);
359+
360+
// One undeclared reference was built in isolation violation.
361+
declaredReferenceLogger.AssertMessageCount("Message from undeclared reference", 1);
362+
declaredReferenceLogger.AssertMessageCount("MSB4260", 1);
363+
364+
// The declared reference project's output item is that of the undeclared reference
365+
// project.
366+
declaredReferenceBuildResults[0]["DeclaredReferenceTarget"].Items.Length.ShouldBe(1);
367+
declaredReferenceBuildResults[0]["DeclaredReferenceTarget"].Items[0].ItemSpec.ShouldBe("Foo.cs");
368+
rootBuildResults[0]["BuildDeclaredReference"].Items.Length.ShouldBe(0);
369+
}
370+
266371
[Theory]
267372
[InlineData("BuildDeclaredReference")]
268373
[InlineData("BuildDeclaredReferenceViaTask")]

src/Build.UnitTests/Graph/ResultCacheBasedBuilds_Tests.cs

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -298,7 +298,7 @@ public void BuildProjectGraphUsingCaches(Dictionary<int, int[]> edges)
298298
var outputCaches = new OutputCacheDictionary();
299299

300300
// Build unchanged project files using caches.
301-
BuildUsingCaches(topoSortedNodes, expectedOutput, outputCaches, generateCacheFiles: true);
301+
BuildUsingCaches(_env, topoSortedNodes, expectedOutput, outputCaches, generateCacheFiles: true);
302302

303303
// Change the project files to remove all items.
304304
var collection = _env.CreateProjectCollection().Collection;
@@ -318,6 +318,7 @@ public void BuildProjectGraphUsingCaches(Dictionary<int, int[]> edges)
318318

319319
// Build again using the first caches. Project file changes from references should not be visible.
320320
BuildUsingCaches(
321+
_env,
321322
topoSortedNodes,
322323
expectedOutput,
323324
outputCaches,
@@ -343,7 +344,7 @@ public void OutputCacheShouldNotContainInformationFromInputCaches()
343344

344345
var outputCaches = new OutputCacheDictionary();
345346

346-
BuildUsingCaches(topoSortedNodes, expectedOutput, outputCaches, generateCacheFiles: true);
347+
BuildUsingCaches(_env, topoSortedNodes, expectedOutput, outputCaches, generateCacheFiles: true);
347348

348349
var rootNode = topoSortedNodes.First(n => Path.GetFileNameWithoutExtension(n.ProjectInstance.FullPath) == "1");
349350
var outputCache = outputCaches[rootNode];
@@ -381,12 +382,12 @@ public void MissingResultFromCacheShouldErrorDueToIsolatedBuildCacheEnforcement(
381382

382383
var outputCaches = new OutputCacheDictionary();
383384

384-
BuildUsingCaches(topoSortedNodes, expectedOutput, outputCaches, generateCacheFiles: true);
385+
BuildUsingCaches(_env, topoSortedNodes, expectedOutput, outputCaches, generateCacheFiles: true);
385386

386387
// remove cache for project 3 to cause a cache miss
387388
outputCaches.Remove(expectedOutput.Keys.First(n => ProjectNumber(n) == "3"));
388389

389-
var results = BuildUsingCaches(topoSortedNodes, expectedOutput, outputCaches, generateCacheFiles: false, assertBuildResults: false);
390+
var results = BuildUsingCaches(_env, topoSortedNodes, expectedOutput, outputCaches, generateCacheFiles: false, assertBuildResults: false);
390391

391392
results["3"].Result.OverallResult.ShouldBe(BuildResultCode.Success);
392393
results["2"].Result.OverallResult.ShouldBe(BuildResultCode.Success);
@@ -408,21 +409,25 @@ public void MissingResultFromCacheShouldErrorDueToIsolatedBuildCacheEnforcement(
408409
/// When it is false, it uses the filled in <param name="outputCaches"/> and <param name="expectedNodeBuildOutput"/> to simulate a fully cached build.
409410
///
410411
/// </summary>
412+
/// <param name="env">The test environment under which to run.</param>
411413
/// <param name="topoSortedNodes"></param>
412414
/// <param name="expectedNodeBuildOutput"></param>
413415
/// <param name="outputCaches"></param>
414416
/// <param name="generateCacheFiles"></param>
415417
/// <param name="assertBuildResults"></param>
416418
/// <param name="expectedOutputProducer"></param>
419+
/// <param name="projectIsolationMode">The isolation mode under which to run.</param>
417420
/// <returns></returns>
418-
private Dictionary<string, (BuildResult Result, MockLogger Logger)> BuildUsingCaches(
421+
internal static Dictionary<string, (BuildResult Result, MockLogger Logger)> BuildUsingCaches(
422+
TestEnvironment env,
419423
IReadOnlyCollection<ProjectGraphNode> topoSortedNodes,
420424
ExpectedNodeBuildOutput expectedNodeBuildOutput,
421425
OutputCacheDictionary outputCaches,
422426
bool generateCacheFiles,
423427
bool assertBuildResults = true,
424428
// (current node, expected output dictionary) -> actual expected output for current node
425-
Func<ProjectGraphNode, ExpectedNodeBuildOutput, string[]> expectedOutputProducer = null)
429+
Func<ProjectGraphNode, ExpectedNodeBuildOutput, string[]> expectedOutputProducer = null,
430+
ProjectIsolationMode projectIsolationMode = ProjectIsolationMode.False)
426431
{
427432
expectedOutputProducer ??= ((node, expectedOutputs) => expectedOutputs[node]);
428433

@@ -445,12 +450,13 @@ public void MissingResultFromCacheShouldErrorDueToIsolatedBuildCacheEnforcement(
445450

446451
var buildParameters = new BuildParameters
447452
{
448-
InputResultsCacheFiles = cacheFilesForReferences
453+
InputResultsCacheFiles = cacheFilesForReferences,
454+
ProjectIsolationMode = projectIsolationMode,
449455
};
450456

451457
if (generateCacheFiles)
452458
{
453-
outputCaches[node] = _env.DefaultTestDirectory.CreateFile($"OutputCache-{ProjectNumber(node)}").Path;
459+
outputCaches[node] = env.DefaultTestDirectory.CreateFile($"OutputCache-{ProjectNumber(node)}").Path;
454460
buildParameters.OutputResultsCacheFile = outputCaches[node];
455461
}
456462

@@ -461,7 +467,8 @@ public void MissingResultFromCacheShouldErrorDueToIsolatedBuildCacheEnforcement(
461467
var result = BuildProjectFileUsingBuildManager(
462468
node.ProjectInstance.FullPath,
463469
null,
464-
buildParameters);
470+
buildParameters,
471+
node.ProjectInstance.DefaultTargets);
465472

466473
results[ProjectNumber(node)] = (result, logger);
467474

src/Build/BackEnd/BuildManager/BuildManager.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -527,6 +527,11 @@ public void BeginBuild(BuildParameters parameters)
527527

528528
if (_buildParameters.UsesCachedResults() && parameters.ProjectIsolationMode == ProjectIsolationMode.False)
529529
{
530+
// If input or output caches are used and the project isolation mode is set to
531+
// ProjectIsolationMode.False, then set it to ProjectIsolationMode.True. The explicit
532+
// condition on ProjectIsolationMode is necessary to ensure that, if we're using input
533+
// or output caches and ProjectIsolationMode is set to ProjectIsolationMode.MessageUponIsolationViolation,
534+
// ProjectIsolationMode isn't changed to ProjectIsolationMode.True.
530535
_buildParameters.ProjectIsolationMode = ProjectIsolationMode.True;
531536
}
532537

src/Build/BackEnd/Components/Scheduler/Scheduler.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2056,8 +2056,8 @@ internal void RecordResultToCurrentCacheIfConfigNotInOverrideCache(BuildResult r
20562056
// override cache, which can happen if we are building in the project isolation mode
20572057
// ProjectIsolationMode.MessageUponIsolationViolation, and the received result was built by an
20582058
// isolation-violating dependency project.
2059-
if (_configCache is not ConfigCacheWithOverride
2060-
|| !((ConfigCacheWithOverride)_configCache).HasConfigurationInOverrideCache(result.ConfigurationId))
2059+
if (_configCache is not ConfigCacheWithOverride configCacheWithOverride
2060+
|| !configCacheWithOverride.HasConfigurationInOverrideCache(result.ConfigurationId))
20612061
{
20622062
_resultsCache.AddResult(result);
20632063
}

src/Shared/UnitTests/ObjectModelHelpers.cs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1377,7 +1377,11 @@ public static BuildResult BuildProjectContentUsingBuildManager(string content, M
13771377
}
13781378
}
13791379

1380-
public static BuildResult BuildProjectFileUsingBuildManager(string projectFile, MockLogger logger = null, BuildParameters parameters = null)
1380+
public static BuildResult BuildProjectFileUsingBuildManager(
1381+
string projectFile,
1382+
MockLogger logger = null,
1383+
BuildParameters parameters = null,
1384+
List<string> defaultTargets = null)
13811385
{
13821386
using (var buildManager = new BuildManager())
13831387
{
@@ -1394,7 +1398,7 @@ public static BuildResult BuildProjectFileUsingBuildManager(string projectFile,
13941398
projectFile,
13951399
new Dictionary<string, string>(),
13961400
MSBuildConstants.CurrentToolsVersion,
1397-
Array.Empty<string>(),
1401+
defaultTargets?.ToArray() ?? Array.Empty<string>(),
13981402
null);
13991403

14001404
var result = buildManager.Build(

0 commit comments

Comments
 (0)