1 // Copyright (c) Microsoft. All rights reserved. 2 // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 //----------------------------------------------------------------------- 4 // </copyright> 5 // <summary>Tests for evaluation</summary> 6 //----------------------------------------------------------------------- 7 8 using System; 9 using System.Collections.Generic; 10 using System.Diagnostics; 11 using System.IO; 12 using System.Linq; 13 using Microsoft.Build.Evaluation; 14 using Xunit; 15 using System.Text; 16 using Microsoft.Build.Engine.UnitTests; 17 using Microsoft.Build.Internal; 18 using Microsoft.Build.Shared; 19 20 namespace Microsoft.Build.UnitTests.Evaluation 21 { 22 /// <summary> 23 /// Tests mainly for project evaluation 24 /// </summary> 25 public class ItemEvaluation_Tests : IDisposable 26 { 27 /// <summary> 28 /// Cleanup 29 /// </summary> ItemEvaluation_Tests()30 public ItemEvaluation_Tests() 31 { 32 ProjectCollection.GlobalProjectCollection.UnloadAllProjects(); 33 GC.Collect(); 34 } 35 36 /// <summary> 37 /// Cleanup 38 /// </summary> Dispose()39 public void Dispose() 40 { 41 ProjectCollection.GlobalProjectCollection.UnloadAllProjects(); 42 GC.Collect(); 43 } 44 45 [Fact] IncludeShouldPreserveIntermediaryReferences()46 public void IncludeShouldPreserveIntermediaryReferences() 47 { 48 var content = @" 49 <i2 Include='a;b;c'> 50 <m1>m1_contents</m1> 51 <m2>m2_contents</m2> 52 </i2> 53 54 <i Include='@(i2)'/> 55 56 <i2 Include='d;e;f;@(i2)'> 57 <m1>m1_updated</m1> 58 <m2>m2_updated</m2> 59 </i2>"; 60 61 IList<ProjectItem> items = ObjectModelHelpers.GetItemsFromFragment(content, allItems: true); 62 63 var mI2_1 = new Dictionary<string, string> 64 { 65 {"m1", "m1_contents"}, 66 {"m2", "m2_contents"}, 67 }; 68 69 var itemsForI = items.Where(i => i.ItemType == "i").ToList(); 70 ObjectModelHelpers.AssertItems(new [] {"a", "b", "c"}, itemsForI, mI2_1); 71 72 var mI2_2 = new Dictionary<string, string> 73 { 74 {"m1", "m1_updated"}, 75 {"m2", "m2_updated"}, 76 }; 77 78 var itemsForI2 = items.Where(i => i.ItemType == "i2").ToList(); 79 ObjectModelHelpers.AssertItems( 80 new[] { "a", "b", "c", "d", "e", "f", "a", "b", "c" }, 81 itemsForI2, 82 new [] { mI2_1, mI2_1 , mI2_1, mI2_2, mI2_2, mI2_2, mI2_2, mI2_2, mI2_2 }); 83 } 84 85 [Theory] 86 // remove the items by referencing each one 87 [InlineData( 88 @" 89 <i2 Include='a;b;c'> 90 <m1>m1_contents</m1> 91 <m2>m2_contents</m2> 92 </i2> 93 94 <i Include='@(i2)'/> 95 96 <i2 Remove='a;b;c'/>" 97 )] 98 // remove the items via a glob 99 [InlineData( 100 @" 101 <i2 Include='a;b;c'> 102 <m1>m1_contents</m1> 103 <m2>m2_contents</m2> 104 </i2> 105 106 <i Include='@(i2)'/> 107 108 <i2 Remove='*'/>" 109 )] RemoveShouldPreserveIntermediaryReferences(string content)110 public void RemoveShouldPreserveIntermediaryReferences(string content) 111 { 112 IList<ProjectItem> items = ObjectModelHelpers.GetItemsFromFragment(content, allItems: true); 113 114 var expectedMetadata = new Dictionary<string, string> 115 { 116 {"m1", "m1_contents"}, 117 {"m2", "m2_contents"} 118 }; 119 120 var itemsForI = items.Where(i => i.ItemType == "i").ToList(); 121 ObjectModelHelpers.AssertItems(new[] { "a", "b", "c" }, itemsForI, expectedMetadata); 122 123 var itemsForI2 = items.Where(i => i.ItemType == "i2").ToList(); 124 ObjectModelHelpers.AssertItems(new string[0], itemsForI2); 125 } 126 127 [Fact] UpdateShouldPreserveIntermediaryReferences()128 public void UpdateShouldPreserveIntermediaryReferences() 129 { 130 var content = @" 131 <i2 Include='a;b;c'> 132 <m1>m1_contents</m1> 133 <m2>%(Identity)</m2> 134 </i2> 135 136 <i Include='@(i2)'> 137 <m3>@(i2 -> '%(m2)')</m3> 138 <m4 Condition=""'@(i2 -> '%(m2)')' == 'a;b;c'"">m4_contents</m4> 139 </i> 140 141 <i2 Update='a;b;c'> 142 <m1>m1_updated</m1> 143 <m2>m2_updated</m2> 144 <m3>m3_updated</m3> 145 <m4>m4_updated</m4> 146 </i2>"; 147 148 IList<ProjectItem> items = ObjectModelHelpers.GetItemsFromFragment(content, allItems: true); 149 150 151 var a = new Dictionary<string, string> 152 { 153 {"m1", "m1_contents"}, 154 {"m2", "a"}, 155 {"m3", "a;b;c"}, 156 {"m4", "m4_contents"}, 157 }; 158 159 var b = new Dictionary<string, string> 160 { 161 {"m1", "m1_contents"}, 162 {"m2", "b"}, 163 {"m3", "a;b;c"}, 164 {"m4", "m4_contents"} 165 }; 166 167 var c = new Dictionary<string, string> 168 { 169 {"m1", "m1_contents"}, 170 {"m2", "c"}, 171 {"m3", "a;b;c"}, 172 {"m4", "m4_contents"}, 173 }; 174 175 var itemsForI = items.Where(i => i.ItemType == "i").ToList(); 176 ObjectModelHelpers.AssertItems(new[] { "a", "b", "c" }, itemsForI, new [] {a, b, c}); 177 178 var metadataForI2 = new Dictionary<string, string> 179 { 180 {"m1", "m1_updated"}, 181 {"m2", "m2_updated"}, 182 {"m3", "m3_updated"}, 183 {"m4", "m4_updated"} 184 }; 185 186 var itemsForI2 = items.Where(i => i.ItemType == "i2").ToList(); 187 ObjectModelHelpers.AssertItems(new[] { "a", "b", "c" }, itemsForI2, metadataForI2); 188 } 189 190 public static IEnumerable<object[]> IndirectItemReferencesTestData 191 { 192 get 193 { 194 // indirect item reference via properties in metadata 195 yield return new object[] 196 { 197 @"<Project> 198 <ItemGroup> 199 <IndirectItem Include=`1` /> 200 <IndirectItem Include=`2` /> 201 </ItemGroup> 202 <PropertyGroup> 203 <P1>@(IndirectItem)</P1> 204 <P2>$(P1)</P2> 205 </PropertyGroup> 206 207 <ItemGroup> 208 <i Include=`val`> 209 <m1>$(P1)</m1> 210 <m2>$(P2)</m2> 211 </i> 212 </ItemGroup> 213 214 </Project>", 215 new []{"val"}, 216 new Dictionary<string, string> 217 { 218 {"m1", "1;2"}, 219 {"m2", "1;2"} 220 } 221 }; 222 223 // indirect item reference via properties in metadata condition 224 yield return new object[] 225 { 226 @"<Project> 227 <ItemGroup> 228 <IndirectItem Include=`1` /> 229 <IndirectItem Include=`2` /> 230 </ItemGroup> 231 <PropertyGroup> 232 <P1>@(IndirectItem)</P1> 233 <P2>$(P1)</P2> 234 </PropertyGroup> 235 236 <ItemGroup> 237 <i Include=`val`> 238 <m1 Condition=`'$(P1)' == '1;2'`>val1</m1> 239 <m2 Condition=`'$(P2)' == '1;2'`>val2</m2> 240 </i> 241 </ItemGroup> 242 243 </Project>", 244 new []{"val"}, 245 new Dictionary<string, string> 246 { 247 {"m1", "val1"}, 248 {"m2", "val2"} 249 } 250 }; 251 252 // indirect item reference via properties in include 253 yield return new object[] 254 { 255 @"<Project> 256 <ItemGroup> 257 <IndirectItem Include=`1` /> 258 <IndirectItem Include=`2` /> 259 </ItemGroup> 260 <PropertyGroup> 261 <P1>@(IndirectItem)</P1> 262 <P2>$(P1)</P2> 263 </PropertyGroup> 264 265 <ItemGroup> 266 <i Include=`$(P1)`/> 267 <i Include=`$(P2)`/> 268 </ItemGroup> 269 270 </Project>", 271 new []{"1", "2", "1", "2"}, 272 new Dictionary<string, string>() 273 }; 274 275 // indirect item reference via properties in condition 276 yield return new object[] 277 { 278 @"<Project> 279 <ItemGroup> 280 <IndirectItem Include=`1` /> 281 <IndirectItem Include=`2` /> 282 </ItemGroup> 283 <PropertyGroup> 284 <P1>@(IndirectItem)</P1> 285 <P2>$(P1)</P2> 286 </PropertyGroup> 287 288 <ItemGroup> 289 <i Condition=`'$(P1)' == '1;2'` Include=`val1`/> 290 <i Condition=`'$(P2)' == '1;2'` Include=`val2`/> 291 </ItemGroup> 292 293 </Project>", 294 new []{"val1", "val2"}, 295 new Dictionary<string, string>() 296 }; 297 298 // indirect item reference via metadata reference in conditions and metadata 299 yield return new object[] 300 { 301 @"<Project> 302 <ItemGroup> 303 <IndirectItem Include=`1` /> 304 <IndirectItem Include=`2` /> 305 </ItemGroup> 306 <PropertyGroup> 307 <P1>@(IndirectItem)</P1> 308 <P2>$(P1)</P2> 309 </PropertyGroup> 310 311 <ItemGroup> 312 <i Include=`val`> 313 <m1 Condition=`'$(P2)' == '1;2'`>$(P2)</m1> 314 <m2 Condition=`'%(m1)' == '1;2'`>%(m1)</m2> 315 </i> 316 </ItemGroup> 317 318 </Project>", 319 new []{"val"}, 320 new Dictionary<string, string> 321 { 322 {"m1", "1;2"}, 323 {"m2", "1;2"} 324 } 325 }; 326 } 327 } 328 329 [Theory] 330 [MemberData(nameof(IndirectItemReferencesTestData))] ItemOperationsShouldExpandIndirectItemReferences(string projectContent, string[] expectedItemValues, Dictionary<string, string> expectedItemMetadata)331 public void ItemOperationsShouldExpandIndirectItemReferences(string projectContent, string[] expectedItemValues, Dictionary<string, string> expectedItemMetadata) 332 { 333 var items = ObjectModelHelpers.GetItems(projectContent); 334 335 ObjectModelHelpers.AssertItems(expectedItemValues, items, expectedItemMetadata); 336 } 337 338 [Fact] OnlyPropertyReferencesGetExpandedInPropertyFunctionArgumentsInsideIncludeAttributes()339 public void OnlyPropertyReferencesGetExpandedInPropertyFunctionArgumentsInsideIncludeAttributes() 340 { 341 var projectContent = 342 @"<Project> 343 <ItemGroup> 344 <A Include=`1`/> 345 <B Include=`$([System.String]::new('@(A)'))`/> 346 <C Include=`$([System.String]::new('$(P)'))`/> 347 </ItemGroup> 348 349 <PropertyGroup> 350 <P>@(A)</P> 351 </PropertyGroup> 352 </Project>"; 353 354 var items = ObjectModelHelpers.GetItems(projectContent, allItems: true); 355 356 ObjectModelHelpers.AssertItems(new[] { "1", "@(A)", "@(A)" }, items); 357 } 358 359 [Fact] MetadataAndPropertyReferencesGetExpandedInPropertyFunctionArgumentsInsideMetadataElements()360 public void MetadataAndPropertyReferencesGetExpandedInPropertyFunctionArgumentsInsideMetadataElements() 361 { 362 var projectContent = 363 @"<Project> 364 <ItemGroup> 365 <A Include=`1` /> 366 <B Include=`B`> 367 <M>$([System.String]::new(`%(Identity)`))</M> 368 <M2>$([System.String]::new(`%(M)`))</M2> 369 </B> 370 <C Include=`C`> 371 <M>$([System.String]::new(`$(P)`))</M> 372 <M2>$([System.String]::new(`%(M)`))</M2> 373 </C> 374 <D Include=`D`> 375 <M>$([System.String]::new(`@(A)`))</M> 376 <M2>$([System.String]::new(`%(M)`))</M2> 377 </D> 378 </ItemGroup> 379 380 <PropertyGroup> 381 <P>@(A)</P> 382 </PropertyGroup> 383 </Project>"; 384 385 var items = ObjectModelHelpers.GetItems(projectContent, allItems: true); 386 387 var expectedMetadata = new[] 388 { 389 new Dictionary<string, string>(), 390 new Dictionary<string, string> 391 { 392 {"M", "B"}, 393 {"M2", "B"} 394 }, 395 new Dictionary<string, string> 396 { 397 {"M", "@(A)"}, 398 {"M2", "@(A)"} 399 }, 400 new Dictionary<string, string> 401 { 402 {"M", "@(A)"}, 403 {"M2", "@(A)"} 404 } 405 }; 406 407 ObjectModelHelpers.AssertItems(new[] { "1", "B", "C", "D" }, items, expectedMetadata); 408 } 409 410 [Fact] ExcludeSeesIntermediaryState()411 public void ExcludeSeesIntermediaryState() 412 { 413 var projectContent = 414 @"<Project> 415 <ItemGroup> 416 <a Include=`1` /> 417 <i Include=`1;2` Exclude=`@(a)` /> 418 <a Include=`2` /> 419 <a Condition=`'@(a)' == '1;2'` Include=`3` /> 420 </ItemGroup> 421 <Target Name=`Build`> 422 <Message Text=`Done!` /> 423 </Target> 424 </Project>"; 425 426 var items = ObjectModelHelpers.GetItems(projectContent); 427 428 ObjectModelHelpers.AssertItems(new []{"2"}, items); 429 } 430 431 [Fact] MultipleInterItemDependenciesOnSameItemOperation()432 public void MultipleInterItemDependenciesOnSameItemOperation() 433 { 434 var content = @" 435 <i1 Include='i1_1;i1_2;i1_3;i1_4;i1_5'/> 436 <i1 Update='*'> 437 <m>i1</m> 438 </i1> 439 <i1 Remove='*i1_5'/> 440 441 <i_cond Condition='@(i1->Count()) == 4' Include='i1 has 4 items'/> 442 443 <i2 Include='@(i1);i2_4'/> 444 <i2 Remove='i?_4'/> 445 <i2 Update='i?_1'> 446 <m>i2</m> 447 </i2> 448 449 <i3 Include='@(i1);i3_3'/> 450 <i3 Remove='*i?_3'/> 451 452 <i1 Remove='*i1_2'/> 453 <i1 Include='i1_6'/>"; 454 455 IList<ProjectItem> items = ObjectModelHelpers.GetItemsFromFragment(content, allItems: true); 456 457 var i1BaseMetadata = new Dictionary<string, string> 458 { 459 {"m", "i1"} 460 }; 461 462 //i1 items: i1_1; i1_3; i1_4; i1_6 463 var i1Metadata = new Dictionary<string, string>[] 464 { 465 i1BaseMetadata, 466 i1BaseMetadata, 467 i1BaseMetadata, 468 new Dictionary<string, string>() 469 }; 470 471 var i1Items = items.Where(i => i.ItemType == "i1").ToList(); 472 ObjectModelHelpers.AssertItems(new[] { "i1_1", "i1_3", "i1_4", "i1_6" }, i1Items, i1Metadata); 473 474 //i2 items: i1_1; i1_2; i1_3 475 var i2Metadata = new Dictionary<string, string>[] 476 { 477 new Dictionary<string, string> 478 { 479 {"m", "i2"} 480 }, 481 i1BaseMetadata, 482 i1BaseMetadata 483 }; 484 485 var i2Items = items.Where(i => i.ItemType == "i2").ToList(); 486 ObjectModelHelpers.AssertItems(new[] { "i1_1", "i1_2", "i1_3" }, i2Items, i2Metadata); 487 488 //i3 items: i1_1; i1_2; i1_4 489 var i3Items = items.Where(i => i.ItemType == "i3").ToList(); 490 ObjectModelHelpers.AssertItems(new[] { "i1_1", "i1_2", "i1_4" }, i3Items, i1BaseMetadata); 491 492 var i_condItems = items.Where(i => i.ItemType == "i_cond").ToList(); 493 ObjectModelHelpers.AssertItems(new[] { "i1 has 4 items" }, i_condItems); 494 } 495 496 [Fact] LongIncludeChain()497 public void LongIncludeChain() 498 { 499 const int INCLUDE_COUNT = 10000; 500 501 // This was about the minimum count needed to repro a StackOverflowException 502 //const int INCLUDE_COUNT = 4000; 503 504 StringBuilder content = new StringBuilder(); 505 for (int i = 0; i < INCLUDE_COUNT; i++) 506 { 507 content.AppendLine($"<i Include='ItemValue{i}' />"); 508 } 509 510 IList<ProjectItem> items = ObjectModelHelpers.GetItemsFromFragment(content.ToString()); 511 512 Assert.Equal(INCLUDE_COUNT, items.Count); 513 } 514 515 // see https://github.com/Microsoft/msbuild/issues/2069 516 [Fact] ImmutableListBuilderBug()517 public void ImmutableListBuilderBug() 518 { 519 var content = @"<i Include=""0;x1;x2;x3;x4;x5;6;7;8;9""/> 520 <i Remove=""x*""/>"; 521 522 IList<ProjectItem> items = ObjectModelHelpers.GetItemsFromFragment(content); 523 524 Assert.Equal("0;6;7;8;9", String.Join(";", items.Select(i => i.EvaluatedInclude))); 525 } 526 527 [Fact] LazyWildcardExpansionDoesNotEvaluateWildCardsIfNotReferenced()528 public void LazyWildcardExpansionDoesNotEvaluateWildCardsIfNotReferenced() 529 { 530 var content = @" 531 <Project> 532 <Import Project=`foo/*.props`/> 533 <ItemGroup> 534 <i Include=`**/foo/**/*.cs`/> 535 <i2 Include=`**/bar/**/*.cs`/> 536 </ItemGroup> 537 538 <ItemGroup> 539 <ItemReference Include=`@(i)`/> 540 <FullPath Include=`@(i->'%(FullPath)')`/> 541 <Identity Include=`@(i->'%(Identity)')`/> 542 <RecursiveDir Include=`@(i->'%(RecursiveDir)')`/> 543 </ItemGroup> 544 </Project> 545 ".Cleanup(); 546 547 var import = @" 548 <Project> 549 <PropertyGroup> 550 <FromImport>true</FromImport> 551 </PropertyGroup> 552 </Project> 553 ".Cleanup(); 554 using (var env = TestEnvironment.Create()) 555 { 556 var projectFiles = env.CreateTestProjectWithFiles(content, new[] {"foo/extra.props", "foo/a.cs", "foo/b.cs", "bar/c.cs", "bar/d.cs"}); 557 558 File.WriteAllText(projectFiles.CreatedFiles[0], import); 559 560 env.SetEnvironmentVariable("MsBuildSkipEagerWildCardEvaluationRegexes", ".*foo.*"); 561 562 EngineFileUtilities.CaptureLazyWildcardRegexes(); 563 564 var project = new Project(projectFiles.ProjectFile); 565 566 Assert.Equal("true", project.GetPropertyValue("FromImport")); 567 Assert.Equal("**/foo/**/*.cs", project.GetConcatenatedItemsOfType("i")); 568 569 var expectedItems = "bar\\c.cs;bar\\d.cs"; 570 571 if (!NativeMethodsShared.IsWindows) 572 { 573 expectedItems = expectedItems.ToSlash(); 574 } 575 576 Assert.Equal(expectedItems, project.GetConcatenatedItemsOfType("i2")); 577 578 var fullPathItems = project.GetConcatenatedItemsOfType("FullPath"); 579 Assert.Contains("a.cs", fullPathItems); 580 Assert.Contains("b.cs", fullPathItems); 581 582 var identityItems = project.GetConcatenatedItemsOfType("Identity"); 583 Assert.Contains("a.cs", identityItems); 584 Assert.Contains("b.cs", identityItems); 585 586 // direct item references do not expand the lazy wildcard 587 Assert.Equal("**/foo/**/*.cs", project.GetConcatenatedItemsOfType("ItemReference")); 588 589 // recursive dir does not work with lazy wildcards 590 Assert.Equal(string.Empty, project.GetConcatenatedItemsOfType("RecursiveDir")); 591 } 592 } 593 594 [Theory] 595 [InlineData(true)] 596 [InlineData(false)] DifferentExcludesOnSameWildcardProduceDifferentResults(bool cacheFileEnumerations)597 public void DifferentExcludesOnSameWildcardProduceDifferentResults(bool cacheFileEnumerations) 598 { 599 var projectContents = @" 600 <Project> 601 <ItemGroup> 602 <i Include=`**/*.cs`/> 603 <i Include=`**/*.cs` Exclude=`*a.cs`/> 604 <i Include=`**/*.cs` Exclude=`a.cs;c.cs`/> 605 </ItemGroup> 606 </Project> 607 ".Cleanup(); 608 609 try 610 { 611 using (var env = TestEnvironment.Create()) 612 { 613 if (cacheFileEnumerations) 614 { 615 env.SetEnvironmentVariable("MsBuildCacheFileEnumerations", "1"); 616 } 617 618 ObjectModelHelpers.AssertItemEvaluationFromProject( 619 projectContents, 620 inputFiles: new[] {"a.cs", "b.cs", "c.cs"}, 621 expectedInclude: new[] {"a.cs", "b.cs", "c.cs", "b.cs", "c.cs", "b.cs"}); 622 } 623 } 624 finally 625 { 626 FileMatcher.ClearFileEnumerationsCache(); 627 } 628 } 629 } 630 } 631