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 ProjectItem</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 System.Xml; 14 using Microsoft.Build.Construction; 15 using Microsoft.Build.Engine.UnitTests; 16 using Microsoft.Build.Engine.UnitTests.Globbing; 17 using Microsoft.Build.Evaluation; 18 using Microsoft.Build.Execution; 19 using Microsoft.Build.Shared; 20 using Shouldly; 21 using InvalidProjectFileException = Microsoft.Build.Exceptions.InvalidProjectFileException; 22 using Xunit; 23 24 namespace Microsoft.Build.UnitTests.OM.Definition 25 { 26 /// <summary> 27 /// Tests for ProjectItem 28 /// </summary> 29 public class ProjectItem_Tests 30 { 31 internal const string ItemWithIncludeAndExclude = @" 32 <Project xmlns='http://schemas.microsoft.com/developer/msbuild/2003' > 33 <ItemGroup> 34 <i Include='{0}' Exclude='{1}'/> 35 </ItemGroup> 36 </Project> 37 "; 38 internal const string ItemWithIncludeUpdateAndRemove= @" 39 <Project xmlns='http://schemas.microsoft.com/developer/msbuild/2003' > 40 <ItemGroup> 41 <i Include='{0}'> 42 <m>contents</m> 43 </i> 44 <i Update='{1}'> 45 <m>updated</m> 46 </i> 47 <i Remove='{2}'/> 48 </ItemGroup> 49 </Project> 50 "; 51 52 /// <summary> 53 /// Project getter 54 /// </summary> 55 [Fact] ProjectGetter()56 public void ProjectGetter() 57 { 58 Project project = new Project(); 59 ProjectItem item = project.AddItem("i", "i1")[0]; 60 61 Assert.Equal(true, Object.ReferenceEquals(project, item.Project)); 62 } 63 64 /// <summary> 65 /// No metadata, simple case 66 /// </summary> 67 [Fact] SingleItemWithNoMetadata()68 public void SingleItemWithNoMetadata() 69 { 70 string content = @" 71 <Project xmlns='http://schemas.microsoft.com/developer/msbuild/2003' > 72 <ItemGroup> 73 <i Include='i1'/> 74 </ItemGroup> 75 </Project> 76 "; 77 78 ProjectItem item = GetOneItem(content); 79 80 Assert.NotNull(item.Xml); 81 Assert.Equal("i", item.ItemType); 82 Assert.Equal("i1", item.EvaluatedInclude); 83 Assert.Equal("i1", item.UnevaluatedInclude); 84 Assert.Equal(false, item.Metadata.GetEnumerator().MoveNext()); 85 } 86 87 /// <summary> 88 /// Read off metadata 89 /// </summary> 90 [Fact] ReadMetadata()91 public void ReadMetadata() 92 { 93 string content = @" 94 <Project xmlns='http://schemas.microsoft.com/developer/msbuild/2003' > 95 <ItemGroup> 96 <i Include='i1'> 97 <m1>v1</m1> 98 <m2>v2</m2> 99 </i> 100 </ItemGroup> 101 </Project> 102 "; 103 104 ProjectItem item = GetOneItem(content); 105 106 var itemMetadata = Helpers.MakeList(item.Metadata); 107 Assert.Equal(2, itemMetadata.Count); 108 Assert.Equal("m1", itemMetadata[0].Name); 109 Assert.Equal("m2", itemMetadata[1].Name); 110 Assert.Equal("v1", itemMetadata[0].EvaluatedValue); 111 Assert.Equal("v2", itemMetadata[1].EvaluatedValue); 112 113 Assert.Equal(itemMetadata[0], item.GetMetadata("m1")); 114 Assert.Equal(itemMetadata[1], item.GetMetadata("m2")); 115 } 116 117 /// <summary> 118 /// Get metadata inherited from item definitions 119 /// </summary> 120 [Fact] GetMetadataObjectsFromDefinition()121 public void GetMetadataObjectsFromDefinition() 122 { 123 string content = @" 124 <Project xmlns='http://schemas.microsoft.com/developer/msbuild/2003' > 125 <ItemDefinitionGroup> 126 <i> 127 <m0>v0</m0> 128 <m1>v1</m1> 129 </i> 130 </ItemDefinitionGroup> 131 <ItemGroup> 132 <i Include='i1'> 133 <m1>v1b</m1> 134 <m2>v2</m2> 135 </i> 136 </ItemGroup> 137 </Project> 138 "; 139 140 Project project = new Project(XmlReader.Create(new StringReader(content))); 141 142 ProjectItem item = Helpers.GetFirst(project.GetItems("i")); 143 ProjectMetadata m0 = item.GetMetadata("m0"); 144 ProjectMetadata m1 = item.GetMetadata("m1"); 145 146 ProjectItemDefinition definition = project.ItemDefinitions["i"]; 147 ProjectMetadata idm0 = definition.GetMetadata("m0"); 148 ProjectMetadata idm1 = definition.GetMetadata("m1"); 149 150 Assert.Equal(true, Object.ReferenceEquals(m0, idm0)); 151 Assert.Equal(false, Object.ReferenceEquals(m1, idm1)); 152 } 153 154 /// <summary> 155 /// Get metadata values inherited from item definitions 156 /// </summary> 157 [Fact] GetMetadataValuesFromDefinition()158 public void GetMetadataValuesFromDefinition() 159 { 160 string content = @" 161 <Project xmlns='http://schemas.microsoft.com/developer/msbuild/2003' > 162 <ItemDefinitionGroup> 163 <i> 164 <m0>v0</m0> 165 <m1>v1</m1> 166 </i> 167 </ItemDefinitionGroup> 168 <ItemGroup> 169 <i Include='i1'> 170 <m1>v1b</m1> 171 <m2>v2</m2> 172 </i> 173 </ItemGroup> 174 </Project> 175 "; 176 177 ProjectItem item = GetOneItem(content); 178 179 Assert.Equal("v0", item.GetMetadataValue("m0")); 180 Assert.Equal("v1b", item.GetMetadataValue("m1")); 181 Assert.Equal("v2", item.GetMetadataValue("m2")); 182 } 183 184 /// <summary> 185 /// Getting nonexistent metadata should return null 186 /// </summary> 187 [Fact] GetNonexistentMetadata()188 public void GetNonexistentMetadata() 189 { 190 ProjectItem item = GetOneItemFromFragment(@"<i Include='i0'/>"); 191 192 Assert.Equal(null, item.GetMetadata("m0")); 193 } 194 195 /// <summary> 196 /// Getting value of nonexistent metadata should return String.Empty 197 /// </summary> 198 [Fact] GetNonexistentMetadataValue()199 public void GetNonexistentMetadataValue() 200 { 201 ProjectItem item = GetOneItemFromFragment(@"<i Include='i0'/>"); 202 203 Assert.Equal(String.Empty, item.GetMetadataValue("m0")); 204 } 205 206 /// <summary> 207 /// Attempting to set metadata with an invalid XML name should fail 208 /// </summary> 209 [Fact] SetInvalidXmlNameMetadata()210 public void SetInvalidXmlNameMetadata() 211 { 212 Assert.Throws<ArgumentException>(() => 213 { 214 ProjectItem item = GetOneItemFromFragment(@"<i Include='c:\foo\bar.baz'/>"); 215 216 item.SetMetadataValue("##invalid##", "x"); 217 } 218 ); 219 } 220 /// <summary> 221 /// Attempting to set built-in metadata should fail 222 /// </summary> 223 [Fact] SetInvalidBuiltInMetadata()224 public void SetInvalidBuiltInMetadata() 225 { 226 Assert.Throws<ArgumentException>(() => 227 { 228 ProjectItem item = GetOneItemFromFragment(@"<i Include='c:\foo\bar.baz'/>"); 229 230 item.SetMetadataValue("FullPath", "x"); 231 } 232 ); 233 } 234 /// <summary> 235 /// Attempting to set reserved metadata should fail 236 /// </summary> 237 [Fact] SetInvalidReservedMetadata()238 public void SetInvalidReservedMetadata() 239 { 240 Assert.Throws<InvalidOperationException>(() => 241 { 242 ProjectItem item = GetOneItemFromFragment(@"<i Include='c:\foo\bar.baz'/>"); 243 244 item.SetMetadataValue("Choose", "x"); 245 } 246 ); 247 } 248 /// <summary> 249 /// Metadata enumerator should only return custom metadata 250 /// </summary> 251 [Fact] MetadataEnumeratorExcludesBuiltInMetadata()252 public void MetadataEnumeratorExcludesBuiltInMetadata() 253 { 254 ProjectItem item = GetOneItemFromFragment(@"<i Include='c:\foo\bar.baz'/>"); 255 256 Assert.Equal(false, item.Metadata.GetEnumerator().MoveNext()); 257 } 258 259 /// <summary> 260 /// Read off built-in metadata 261 /// </summary> 262 [Fact] BuiltInMetadata()263 public void BuiltInMetadata() 264 { 265 ProjectItem item = 266 GetOneItemFromFragment( 267 NativeMethodsShared.IsWindows ? @"<i Include='c:\foo\bar.baz'/>" : @"<i Include='/foo/bar.baz'/>"); 268 269 // c:\foo\bar.baz - /foo/bar.baz %(FullPath) = full path of item 270 // c:\ - / %(RootDir) = root directory of item 271 // bar %(Filename) = item filename without extension 272 // .baz %(Extension) = item filename extension 273 // c:\foo\ - /foo/ %(RelativeDir) = item directory as given in item-spec 274 // foo\ - /foo/ %(Directory) = full path of item directory relative to root 275 // [] %(RecursiveDir) = portion of item path that matched a recursive wildcard 276 // c:\foo\bar.baz - /foo/bar.baz %(Identity) = item-spec as given 277 // [] %(ModifiedTime) = last write time of item 278 // [] %(CreatedTime) = creation time of item 279 // [] %(AccessedTime) = last access time of item 280 Assert.Equal( 281 NativeMethodsShared.IsWindows ? @"c:\foo\bar.baz" : "/foo/bar.baz", 282 item.GetMetadataValue("FullPath")); 283 Assert.Equal(NativeMethodsShared.IsWindows ? @"c:\" : "/", item.GetMetadataValue("RootDir")); 284 Assert.Equal(@"bar", item.GetMetadataValue("Filename")); 285 Assert.Equal(@".baz", item.GetMetadataValue("Extension")); 286 Assert.Equal(NativeMethodsShared.IsWindows ? @"c:\foo\" : "/foo/", item.GetMetadataValue("RelativeDir")); 287 Assert.Equal(NativeMethodsShared.IsWindows ? @"foo\" : "foo/", item.GetMetadataValue("Directory")); 288 Assert.Equal(String.Empty, item.GetMetadataValue("RecursiveDir")); 289 Assert.Equal( 290 NativeMethodsShared.IsWindows ? @"c:\foo\bar.baz" : "/foo/bar.baz", 291 item.GetMetadataValue("Identity")); 292 } 293 294 /// <summary> 295 /// Check file-timestamp related metadata 296 /// </summary> 297 [Fact] BuiltInMetadataTimes()298 public void BuiltInMetadataTimes() 299 { 300 string path = null; 301 string fileTimeFormat = "yyyy'-'MM'-'dd HH':'mm':'ss'.'fffffff"; 302 303 try 304 { 305 path = Microsoft.Build.Shared.FileUtilities.GetTemporaryFile(); 306 File.WriteAllText(path, String.Empty); 307 FileInfo info = new FileInfo(path); 308 309 ProjectItem item = GetOneItemFromFragment(@"<i Include='" + path + "'/>"); 310 311 Assert.Equal(info.LastWriteTime.ToString(fileTimeFormat), item.GetMetadataValue("ModifiedTime")); 312 Assert.Equal(info.CreationTime.ToString(fileTimeFormat), item.GetMetadataValue("CreatedTime")); 313 Assert.Equal(info.LastAccessTime.ToString(fileTimeFormat), item.GetMetadataValue("AccessedTime")); 314 } 315 finally 316 { 317 File.Delete(path); 318 } 319 } 320 321 /// <summary> 322 /// Test RecursiveDir metadata 323 /// </summary> 324 [Fact] RecursiveDirMetadata()325 public void RecursiveDirMetadata() 326 { 327 string directory = null; 328 string subdirectory = null; 329 string file = null; 330 331 try 332 { 333 directory = Path.Combine(Path.GetTempPath(), "a"); 334 if (File.Exists(directory)) 335 { 336 File.Delete(directory); 337 } 338 339 subdirectory = Path.Combine(directory, "b"); 340 if (File.Exists(subdirectory)) 341 { 342 File.Delete(subdirectory); 343 } 344 345 file = Path.Combine(subdirectory, "c"); 346 Directory.CreateDirectory(subdirectory); 347 348 File.WriteAllText(file, String.Empty); 349 350 ProjectItem item = 351 GetOneItemFromFragment( 352 "<i Include='" + directory + (NativeMethodsShared.IsWindows ? @"\**\*'/>" : "/**/*'/>")); 353 354 Assert.Equal(NativeMethodsShared.IsWindows ? @"b\" : "b/", item.GetMetadataValue("RecursiveDir")); 355 Assert.Equal("c", item.GetMetadataValue("Filename")); 356 } 357 finally 358 { 359 File.Delete(file); 360 FileUtilities.DeleteWithoutTrailingBackslash(subdirectory); 361 FileUtilities.DeleteWithoutTrailingBackslash(directory); 362 } 363 } 364 365 /// <summary> 366 /// Correctly establish the "RecursiveDir" value when the include 367 /// is semicolon separated. 368 /// (This is what requires that the original include fragment [before wildcard 369 /// expansion] is stored in the item.) 370 /// </summary> 371 [Fact] RecursiveDirWithSemicolonSeparatedInclude()372 public void RecursiveDirWithSemicolonSeparatedInclude() 373 { 374 string directory = null; 375 string subdirectory = null; 376 string file = null; 377 378 try 379 { 380 directory = Path.Combine(Path.GetTempPath(), "a"); 381 if (File.Exists(directory)) 382 { 383 File.Delete(directory); 384 } 385 386 subdirectory = Path.Combine(directory, "b"); 387 if (File.Exists(subdirectory)) 388 { 389 File.Delete(subdirectory); 390 } 391 392 file = Path.Combine(subdirectory, "c"); 393 Directory.CreateDirectory(subdirectory); 394 395 File.WriteAllText(file, String.Empty); 396 397 IList<ProjectItem> items = ObjectModelHelpers.GetItemsFromFragment("<i Include='i0;" + directory + (NativeMethodsShared.IsWindows ? @"\**\*;i2'/>" : "/**/*;i2'/>")); 398 399 Assert.Equal(3, items.Count); 400 Assert.Equal("i0", items[0].EvaluatedInclude); 401 Assert.Equal(NativeMethodsShared.IsWindows ? @"b\" : "b/", items[1].GetMetadataValue("RecursiveDir")); 402 Assert.Equal("i2", items[2].EvaluatedInclude); 403 } 404 finally 405 { 406 File.Delete(file); 407 FileUtilities.DeleteWithoutTrailingBackslash(subdirectory); 408 FileUtilities.DeleteWithoutTrailingBackslash(directory); 409 } 410 } 411 412 /// <summary> 413 /// Basic exclude case 414 /// </summary> 415 [Fact] Exclude()416 public void Exclude() 417 { 418 IList<ProjectItem> items = ObjectModelHelpers.GetItemsFromFragment("<i Include='a;b' Exclude='b;c'/>"); 419 420 Assert.Equal(1, items.Count); 421 Assert.Equal("a", items[0].EvaluatedInclude); 422 } 423 424 /// <summary> 425 /// Exclude against an include with item vectors in it 426 /// </summary> 427 [Fact] ExcludeWithIncludeVector()428 public void ExcludeWithIncludeVector() 429 { 430 string content = @" 431 <Project xmlns='http://schemas.microsoft.com/developer/msbuild/2003' > 432 <ItemGroup> 433 <i Include='a;b;c'> 434 </i> 435 </ItemGroup> 436 437 <ItemGroup> 438 <i Include='x;y;z;@(i);u;v;w' Exclude='b;y;v'> 439 </i> 440 </ItemGroup> 441 </Project> 442 "; 443 444 IList<ProjectItem> items = ObjectModelHelpers.GetItems(content); 445 446 // Should contain a, b, c, x, z, a, c, u, w 447 Assert.Equal(9, items.Count); 448 ObjectModelHelpers.AssertItems(new[] { "a", "b", "c", "x", "z", "a", "c", "u", "w" }, items); 449 } 450 451 /// <summary> 452 /// Exclude with item vectors against an include with item vectors in it 453 /// </summary> 454 [Fact] ExcludeVectorWithIncludeVector()455 public void ExcludeVectorWithIncludeVector() 456 { 457 string content = @" 458 <Project xmlns='http://schemas.microsoft.com/developer/msbuild/2003' > 459 <ItemGroup> 460 <i Include='a;b;c'> 461 </i> 462 <j Include='b;y;v' /> 463 </ItemGroup> 464 465 <ItemGroup> 466 <i Include='x;y;z;@(i);u;v;w' Exclude='x;@(j);w'> 467 </i> 468 </ItemGroup> 469 </Project> 470 "; 471 472 IList<ProjectItem> items = ObjectModelHelpers.GetItems(content); 473 474 // Should contain a, b, c, z, a, c, u 475 Assert.Equal(7, items.Count); 476 ObjectModelHelpers.AssertItems(new[] { "a", "b", "c", "z", "a", "c", "u" }, items); 477 } 478 479 [Theory] 480 // items as strings: escaped includes appear as unescaped 481 [InlineData(ItemWithIncludeAndExclude, 482 "%61;%62", 483 "b", 484 new string[0], 485 new[] { "a" })] 486 // items as strings: escaped include matches non-escaped exclude 487 [InlineData(ItemWithIncludeAndExclude, 488 "%61", 489 "a", 490 new string[0], 491 new string[0])] 492 // items as strings: non-escaped include matches escaped exclude 493 [InlineData(ItemWithIncludeAndExclude, 494 "a", 495 "%61", 496 new string[0], 497 new string[0])] 498 // items as files: non-escaped wildcard include matches escaped non-wildcard character 499 [InlineData(ItemWithIncludeAndExclude, 500 "a?b", 501 "a%40b", 502 new[] { "acb", "a@b" }, 503 new[] { "acb" })] 504 // items as files: non-escaped non-wildcard include matches escaped non-wildcard character 505 [InlineData(ItemWithIncludeAndExclude, 506 "acb;a@b", 507 "a%40b", 508 new string[0], 509 new[] { "acb" })] 510 // items as files: escaped wildcard include matches escaped non-wildcard exclude 511 [InlineData(ItemWithIncludeAndExclude, 512 "a%40*b", 513 "a%40bb", 514 new[] { "a@b", "a@ab", "a@bb" }, 515 new[] { "a@ab", "a@b" })] 516 // items as files: escaped wildcard include matches escaped wildcard exclude 517 [InlineData(ItemWithIncludeAndExclude, 518 "a%40*b", 519 "a%40?b", 520 new[] { "a@b", "a@ab", "a@bb" }, 521 new[] { "a@b" })] 522 // items as files: non-escaped recursive wildcard include matches escaped recursive wildcard exclude 523 [InlineData(ItemWithIncludeAndExclude, 524 @"**\a*b", 525 @"**\a*%78b", 526 new[] { "aab", "aaxb", @"dir\abb", @"dir\abxb" }, 527 new[] { "aab", @"dir\abb" })] 528 // items as files: include with non-escaped glob does not match exclude with escaped wildcard character. 529 // The exclude is treated as a literal, not a glob, and therefore should not match the input files 530 [InlineData(ItemWithIncludeAndExclude, 531 @"**\a*b", 532 @"**\a%2Axb", 533 new[] { "aab", "aaxb", @"dir\abb", @"dir\abxb" }, 534 new[] { "aab", "aaxb", @"dir\abb", @"dir\abxb" })] IncludeExcludeWithEscapedCharacters(string projectContents, string includeString, string excludeString, string[] inputFiles, string[] expectedInclude)535 public void IncludeExcludeWithEscapedCharacters(string projectContents, string includeString, string excludeString, string[] inputFiles, string[] expectedInclude) 536 { 537 TestIncludeExcludeWithDifferentSlashes(projectContents, includeString, excludeString, inputFiles, expectedInclude); 538 } 539 540 [Theory] 541 // items as strings: include with both escaped and unescaped glob should be treated as literal and therefore not match against files as a glob 542 [InlineData(ItemWithIncludeAndExclude, 543 @"**\a%2Axb", 544 @"foo", 545 new[] { "aab", "aaxb", @"dir\abb", @"dir\abxb" }, 546 new[] { @"**\a*xb" })] 547 // Include with both escaped and unescaped glob does not match exclude with escaped wildcard character which has a different slash orientation 548 // The presence of the escaped and unescaped glob should make things behave as strings-which-are-not-paths and not as strings-which-are-paths 549 [InlineData(ItemWithIncludeAndExclude, 550 @"**\a%2Axb", 551 @"**/a%2Axb", 552 new string[0], 553 new[] { @"**\a*xb" })] 554 // Slashes are not normalized when contents is not a path 555 [InlineData(ItemWithIncludeAndExclude, 556 @"a/b/foo::||bar;a/b/foo::||bar/;a/b/foo::||bar\;a/b\foo::||bar", 557 @"a/b/foo::||bar", 558 new string[0], 559 new [] { "a/b/foo::||bar/", @"a/b/foo::||bar\", @"a/b\foo::||bar" })] IncludeExcludeWithNonPathContents(string projectContents, string includeString, string excludeString, string[] inputFiles, string[] expectedInclude)560 public void IncludeExcludeWithNonPathContents(string projectContents, string includeString, string excludeString, string[] inputFiles, string[] expectedInclude) 561 { 562 TestIncludeExclude(projectContents, inputFiles, expectedInclude, includeString, excludeString, normalizeSlashes: false); 563 } 564 565 public static IEnumerable<object[]> IncludesAndExcludesWithWildcardsTestData => GlobbingTestData.IncludesAndExcludesWithWildcardsTestData; 566 567 [Theory] 568 [MemberData(nameof(IncludesAndExcludesWithWildcardsTestData))] ExcludeVectorWithWildCards(string includeString, string excludeString, string[] inputFiles, string[] expectedInclude, bool makeExpectedIncludeAbsolute)569 public void ExcludeVectorWithWildCards(string includeString, string excludeString, string[] inputFiles, string[] expectedInclude, bool makeExpectedIncludeAbsolute) 570 { 571 TestIncludeExcludeWithDifferentSlashes(ItemWithIncludeAndExclude, includeString, excludeString, inputFiles, expectedInclude, makeExpectedIncludeAbsolute); 572 } 573 574 [Theory] 575 [InlineData(ItemWithIncludeAndExclude, 576 @"**\*", 577 @"excludes\**.*", 578 new[] 579 { 580 @"a.cs", 581 @"excludes\b.cs", 582 @"excludes\subdir\c.cs", 583 }, 584 new[] 585 { 586 @"a.cs", 587 "build.proj", 588 @"excludes\b.cs", 589 @"excludes\subdir\c.cs", 590 })] 591 [InlineData(ItemWithIncludeAndExclude, 592 @"**\*", 593 @"excludes\**..\*", 594 new[] 595 { 596 @"a.cs", 597 @"excludes\b.cs", 598 @"excludes\subdir\c.cs", 599 }, 600 new[] 601 { 602 @"a.cs", 603 "build.proj", 604 @"excludes\b.cs", 605 @"excludes\subdir\c.cs", 606 })] 607 [InlineData(ItemWithIncludeAndExclude, 608 @"**\*", 609 @"**.*", 610 new[] 611 { 612 @"a.cs", 613 @"excludes\b.cs", 614 @"excludes\subdir\c.cs", 615 }, 616 new[] 617 { 618 @"a.cs", 619 "build.proj", 620 @"excludes\b.cs", 621 @"excludes\subdir\c.cs", 622 })] 623 [InlineData(ItemWithIncludeAndExclude, 624 "*;**a", 625 "**a", 626 new[] 627 { 628 "a", 629 }, 630 new[] 631 { 632 "a", 633 "build.proj" 634 })] 635 [InlineData(ItemWithIncludeAndExclude, 636 @"**1;**2", 637 @"**1", 638 new[] 639 { 640 @"1", 641 @"2", 642 @"excludes\1", 643 @"excludes\2", 644 @"excludes\subdir\1", 645 @"excludes\subdir\2", 646 }, 647 new[] 648 { 649 "**2" 650 })] 651 [InlineData(ItemWithIncludeAndExclude, 652 @":||;||:", 653 @"||:", 654 new string[0], 655 new[] 656 { 657 ":||" 658 })] ExcludeAndIncludeConsideredAsLiteralsWhenFilespecIsIllegal(string projectContents, string includeString, string excludeString, string[] inputFiles, string[] expectedInclude)659 public void ExcludeAndIncludeConsideredAsLiteralsWhenFilespecIsIllegal(string projectContents, string includeString, string excludeString, string[] inputFiles, string[] expectedInclude) 660 { 661 TestIncludeExclude(projectContents, inputFiles, expectedInclude, includeString, excludeString, normalizeSlashes: true); 662 } 663 664 [Theory] 665 [PlatformSpecific(TestPlatforms.Windows)] 666 [InlineData(ItemWithIncludeAndExclude, 667 @"src/**/*.cs", 668 new[] 669 { 670 @"src/a.cs", 671 @"src/a/b/b.cs", 672 }, 673 new[] 674 { 675 @"src/a.cs", 676 @"src/a/b/b.cs", 677 })] 678 [InlineData(ItemWithIncludeAndExclude, 679 @"src/test/**/*.cs", 680 new[] 681 { 682 @"src/test/a.cs", 683 @"src/test/a/b/c.cs", 684 }, 685 new[] 686 { 687 @"src/test/a.cs", 688 @"src/test/a/b/c.cs", 689 })] 690 [InlineData(ItemWithIncludeAndExclude, 691 @"src/test/**/a/b/**/*.cs", 692 new[] 693 { 694 @"src/test/dir/a/b/a.cs", 695 @"src/test/dir/a/b/c/a.cs", 696 }, 697 new[] 698 { 699 @"src/test/dir/a/b/a.cs", 700 @"src/test/dir/a/b/c/a.cs", 701 })] IncludeWithWildcardShouldNotPreserveUserSlashesInFixedDirectoryPart(string projectContents, string includeString, string[] inputFiles, string[] expectedInclude)702 public void IncludeWithWildcardShouldNotPreserveUserSlashesInFixedDirectoryPart(string projectContents, string includeString, string[] inputFiles, string[] expectedInclude) 703 { 704 Func<string, char, string> setSlashes = (s, c) => s.Replace('/', c).Replace('\\', c); 705 706 // set the include string slashes to the opposite orientation relative to the OS default slash 707 if (NativeMethodsShared.IsWindows) 708 { 709 includeString = setSlashes(includeString, '/'); 710 } 711 else 712 { 713 includeString = setSlashes(includeString, '\\'); 714 } 715 716 // all the slashes in the expected items should be platform specific 717 expectedInclude = expectedInclude.Select(p => setSlashes(p, Path.DirectorySeparatorChar)).ToArray(); 718 719 TestIncludeExclude(projectContents, inputFiles, expectedInclude, includeString, ""); 720 } 721 TestIncludeExcludeWithDifferentSlashes(string projectContents, string includeString, string excludeString, string[] inputFiles, string[] expectedInclude, bool makeExpectedIncludeAbsolute = false)722 private static void TestIncludeExcludeWithDifferentSlashes(string projectContents, string includeString, string excludeString, string[] inputFiles, string[] expectedInclude, bool makeExpectedIncludeAbsolute = false) 723 { 724 Action<string, string> runTest = (include, exclude) => 725 { 726 TestIncludeExclude(projectContents, inputFiles, expectedInclude, include, exclude, normalizeSlashes: true, makeExpectedIncludeAbsolute:makeExpectedIncludeAbsolute); 727 }; 728 729 var includeWithForwardSlash = Helpers.ToForwardSlash(includeString); 730 var excludeWithForwardSlash = Helpers.ToForwardSlash(excludeString); 731 732 runTest(includeString, excludeString); 733 runTest(includeWithForwardSlash, excludeWithForwardSlash); 734 runTest(includeString, excludeWithForwardSlash); 735 runTest(includeWithForwardSlash, excludeString); 736 } 737 TestIncludeExclude(string projectContents, string[] inputFiles, string[] expectedInclude, string include, string exclude, bool normalizeSlashes = false, bool makeExpectedIncludeAbsolute = false)738 private static void TestIncludeExclude(string projectContents, string[] inputFiles, string[] expectedInclude, string include, string exclude, bool normalizeSlashes = false, bool makeExpectedIncludeAbsolute = false) 739 { 740 var formattedProjectContents = string.Format(projectContents, include, exclude); 741 ObjectModelHelpers.AssertItemEvaluationFromProject(formattedProjectContents, inputFiles, expectedInclude, expectedMetadataPerItem: null, normalizeSlashes: normalizeSlashes, makeExpectedIncludeAbsolute: makeExpectedIncludeAbsolute); 742 } 743 744 [Theory] 745 // exclude matches include; file is next to project file 746 [InlineData(ItemWithIncludeAndExclude, 747 @"a", // include item 748 @"", //path relative from projectFile. Empty string if current directory 749 750 @"a", //exclude item 751 "", //path relative from projectFile. Empty string if current directory 752 753 new[] //files relative to this test's root directory. The project is one level deeper than the root. 754 { 755 @"project\a", 756 }, 757 false // whether the include survives the exclude (true) or not (false) 758 )] 759 // exclude matches include; file is below the project file 760 [InlineData(ItemWithIncludeAndExclude, 761 @"a", 762 @"dir", 763 764 @"a", 765 "dir", 766 767 new[] 768 { 769 @"project\dir\a", 770 }, 771 false 772 )] 773 // exclude matches include; file is above the project file 774 [InlineData(ItemWithIncludeAndExclude, 775 @"a", 776 @"..", 777 778 @"a", 779 "..", 780 781 new[] 782 { 783 @"a", 784 }, 785 false 786 )] 787 // exclude does not match include; file is next to project file; exclude points above the project file 788 [InlineData(ItemWithIncludeAndExclude, 789 "a", 790 "", 791 792 "a", 793 "..", 794 795 new[] 796 { 797 "a", 798 }, 799 true 800 )] 801 // exclude does not match include; file is below the project file; exclude points next to the project file 802 [InlineData(ItemWithIncludeAndExclude, 803 "a", 804 "dir", 805 806 "a", 807 "", 808 809 new[] 810 { 811 @"project\dir\a", 812 }, 813 true 814 )] 815 // exclude does not match include; file is above the project file; exclude points next to the project file 816 [InlineData(ItemWithIncludeAndExclude, 817 "a", 818 "..", 819 820 "a", 821 "", 822 823 new[] 824 { 825 "a", 826 }, 827 true 828 )] IncludeAndExcludeWorkWithRelativeAndAbsolutePaths( string projectContents, string includeItem, string includeRelativePath, string excludeItem, string excludeRelativePath, string[] inputFiles, bool includeSurvivesExclude)829 public void IncludeAndExcludeWorkWithRelativeAndAbsolutePaths( 830 string projectContents, 831 string includeItem, 832 string includeRelativePath, 833 string excludeItem, 834 string excludeRelativePath, 835 string[] inputFiles, 836 bool includeSurvivesExclude) 837 { 838 Func<bool, string, string, string, string> adjustFilePath = (isAbsolute, testRoot, relativeFragmentFromRootToFile, file) => 839 isAbsolute 840 ? Path.GetFullPath(Path.Combine(testRoot, relativeFragmentFromRootToFile, file)) 841 : Path.Combine(relativeFragmentFromRootToFile, file); 842 843 Action<bool, bool> runTest = (includeIsAbsolute, excludeIsAbsolute) => 844 { 845 using (var env = TestEnvironment.Create()) 846 { 847 var projectFile = env 848 .CreateTestProjectWithFiles(projectContents, inputFiles, "project") 849 .ProjectFile; 850 851 var projectFileDir = Path.GetDirectoryName(projectFile); 852 853 var include = adjustFilePath(includeIsAbsolute, projectFileDir, includeRelativePath, includeItem); 854 var exclude = adjustFilePath(excludeIsAbsolute, projectFileDir, excludeRelativePath, excludeItem); 855 856 // includes and exclude may be absolute, so we can only format the project after we have the test directory paths 857 var formattedProject = string.Format(projectContents, include, exclude); 858 File.WriteAllText(projectFile, formattedProject); 859 860 var expectedInclude = includeSurvivesExclude ? new[] { include } : new string[0]; 861 862 ObjectModelHelpers.AssertItems(expectedInclude, new Project(projectFile).Items.ToList()); 863 } 864 }; 865 866 runTest(true, false); 867 runTest(false, true); 868 runTest(true, true); 869 } 870 871 [Theory] 872 // exclude globbing cone at project level; 873 [InlineData( 874 "../a.cs;b.cs", // include string 875 "**/*.cs", // exclude string 876 new[] {"a.cs", "ProjectDir/b.cs"}, // files to create relative to the test root dir 877 "ProjectDir", // relative path from test root to project 878 new[] {"../a.cs"} // expected items 879 )] 880 // exclude globbing cone below project level; 881 [InlineData( 882 "a.cs;a/b.cs", 883 "a/**/*.cs", 884 new[] { "a.cs", "a/b.cs" }, 885 "", 886 new[] {"a.cs"} 887 )] 888 // exclude globbing above project level; 889 [InlineData( 890 "a.cs;../b.cs;../../c.cs", 891 "../**/*.cs", 892 new[] { "a/ProjectDir/a.cs", "a/b.cs", "c.cs"}, 893 "a/ProjectDir", 894 new[] { "../../c.cs" } 895 )] ExcludeWithMissmatchingGlobCones(string includeString, string excludeString, string[] files, string relativePathFromRootToProject, string[] expectedInclude)896 public void ExcludeWithMissmatchingGlobCones(string includeString, string excludeString, string[] files, string relativePathFromRootToProject, string[] expectedInclude) 897 { 898 var projectContents = string.Format(ItemWithIncludeAndExclude, includeString, excludeString); 899 900 using (var env = TestEnvironment.Create()) 901 using (var projectCollection = new ProjectCollection()) 902 { 903 var testFiles = env.CreateTestProjectWithFiles(projectContents, files,relativePathFromRootToProject); 904 ObjectModelHelpers.AssertItems(expectedInclude, new Project(testFiles.ProjectFile, new Dictionary<string, string>(), MSBuildConstants.CurrentToolsVersion, projectCollection).Items.ToList()); 905 } 906 907 } 908 909 [Theory(Skip = "https://github.com/Microsoft/msbuild/issues/1576")] 910 [InlineData( 911 "../**/*.cs", // include string 912 "a.cs", // exclude string 913 new[] {"ProjectDir/a.cs", "b.cs"}, // files to create relative to the test root dir 914 "ProjectDir", // relative path from test root to project 915 new[] {"../b.cs"} // expected items 916 )] ExcludingRelativeItemToCurrentDirectoryShouldWorkWithAboveTheConeIncludes(string includeString, string excludeString, string[] files, string relativePathFromRootToProject, string[] expectedInclude)917 public void ExcludingRelativeItemToCurrentDirectoryShouldWorkWithAboveTheConeIncludes(string includeString, string excludeString, string[] files, string relativePathFromRootToProject, string[] expectedInclude) 918 { 919 var projectContents = string.Format(ItemWithIncludeAndExclude, includeString, excludeString); 920 921 using (var env = TestEnvironment.Create()) 922 using (var projectCollection = new ProjectCollection()) 923 { 924 var testFiles = env.CreateTestProjectWithFiles(projectContents, files, relativePathFromRootToProject); 925 ObjectModelHelpers.AssertItems(expectedInclude, new Project(testFiles.ProjectFile, new Dictionary<string, string>(), MSBuildConstants.CurrentToolsVersion, projectCollection).Items.ToList()); 926 } 927 928 } 929 930 /// <summary> 931 /// Expression like @(x) should clone metadata, but metadata should still point at the original XML objects 932 /// </summary> 933 [Fact] CopyFromWithItemListExpressionClonesMetadata()934 public void CopyFromWithItemListExpressionClonesMetadata() 935 { 936 string content = @" 937 <Project xmlns='http://schemas.microsoft.com/developer/msbuild/2003' > 938 <ItemGroup> 939 <i Include='i1'> 940 <m>m1</m> 941 </i> 942 <j Include='@(i)'/> 943 </ItemGroup> 944 </Project> 945 "; 946 947 Project project = new Project(XmlReader.Create(new StringReader(content))); 948 949 project.GetItems("i").First().SetMetadataValue("m", "m2"); 950 951 ProjectItem item1 = project.GetItems("i").First(); 952 ProjectItem item2 = project.GetItems("j").First(); 953 954 Assert.Equal("m2", item1.GetMetadataValue("m")); 955 Assert.Equal("m1", item2.GetMetadataValue("m")); 956 957 // Should still point at the same XML items 958 Assert.Equal(true, Object.ReferenceEquals(item1.GetMetadata("m").Xml, item2.GetMetadata("m").Xml)); 959 } 960 961 /// <summary> 962 /// Expression like @(x) should not clone metadata, even if the item type is different. 963 /// It's obvious that it shouldn't clone it if the item type is the same. 964 /// If it is different, it doesn't clone it for performance; even if the item definition metadata 965 /// changes later (this is design time), the inheritors of that item definition type 966 /// (even those that have subsequently been transformed to a different itemtype) should see 967 /// the changes, by design. 968 /// Just to make sure we don't change that behavior, we test it here. 969 /// </summary> 970 [Fact] CopyFromWithItemListExpressionDoesNotCloneDefinitionMetadata()971 public void CopyFromWithItemListExpressionDoesNotCloneDefinitionMetadata() 972 { 973 string content = @" 974 <Project xmlns='http://schemas.microsoft.com/developer/msbuild/2003' > 975 <ItemDefinitionGroup> 976 <i> 977 <m>m1</m> 978 </i> 979 </ItemDefinitionGroup> 980 <ItemGroup> 981 <i Include='i1'/> 982 <i Include='@(i)'/> 983 <i Include=""@(i->'%(identity)')"" /><!-- this will have two items, so setting metadata will split it --> 984 <j Include='@(i)'/> 985 </ItemGroup> 986 </Project> 987 "; 988 989 Project project = new Project(XmlReader.Create(new StringReader(content))); 990 991 ProjectItem item1 = project.GetItems("i").First(); 992 ProjectItem item1b = project.GetItems("i").ElementAt(1); 993 ProjectItem item1c = project.GetItems("i").ElementAt(2); 994 ProjectItem item2 = project.GetItems("j").First(); 995 996 Assert.Equal("m1", item1.GetMetadataValue("m")); 997 Assert.Equal("m1", item1b.GetMetadataValue("m")); 998 Assert.Equal("m1", item1c.GetMetadataValue("m")); 999 Assert.Equal("m1", item2.GetMetadataValue("m")); 1000 1001 project.ItemDefinitions["i"].SetMetadataValue("m", "m2"); 1002 1003 // All the items will see this change 1004 Assert.Equal("m2", item1.GetMetadataValue("m")); 1005 Assert.Equal("m2", item1b.GetMetadataValue("m")); 1006 Assert.Equal("m2", item1c.GetMetadataValue("m")); 1007 Assert.Equal("m2", item2.GetMetadataValue("m")); 1008 1009 // And verify we're not still pointing to the definition metadata objects 1010 item1.SetMetadataValue("m", "m3"); 1011 item1b.SetMetadataValue("m", "m4"); 1012 item1c.SetMetadataValue("m", "m5"); 1013 item2.SetMetadataValue("m", "m6"); 1014 1015 Assert.Equal("m2", project.ItemDefinitions["i"].GetMetadataValue("m")); // Should not have been affected 1016 } 1017 1018 /// <summary> 1019 /// Expression like @(x) should not clone metadata, for perf. See comment on test above. 1020 /// </summary> 1021 [Fact] CopyFromWithItemListExpressionClonesDefinitionMetadata_Variation()1022 public void CopyFromWithItemListExpressionClonesDefinitionMetadata_Variation() 1023 { 1024 string content = @" 1025 <Project xmlns='http://schemas.microsoft.com/developer/msbuild/2003' > 1026 <ItemDefinitionGroup> 1027 <i> 1028 <m>m1</m> 1029 </i> 1030 </ItemDefinitionGroup> 1031 <ItemGroup> 1032 <i Include='i1'/> 1033 <i Include=""@(i->'%(identity)')"" /><!-- this will have one item--> 1034 <j Include='@(i)'/> 1035 </ItemGroup> 1036 </Project> 1037 "; 1038 1039 Project project = new Project(XmlReader.Create(new StringReader(content))); 1040 1041 ProjectItem item1 = project.GetItems("i").First(); 1042 ProjectItem item1b = project.GetItems("i").ElementAt(1); 1043 ProjectItem item2 = project.GetItems("j").First(); 1044 1045 Assert.Equal("m1", item1.GetMetadataValue("m")); 1046 Assert.Equal("m1", item1b.GetMetadataValue("m")); 1047 Assert.Equal("m1", item2.GetMetadataValue("m")); 1048 1049 project.ItemDefinitions["i"].SetMetadataValue("m", "m2"); 1050 1051 // The items should all see this change 1052 Assert.Equal("m2", item1.GetMetadataValue("m")); 1053 Assert.Equal("m2", item1b.GetMetadataValue("m")); 1054 Assert.Equal("m2", item2.GetMetadataValue("m")); 1055 1056 // And verify we're not still pointing to the definition metadata objects 1057 item1.SetMetadataValue("m", "m3"); 1058 item1b.SetMetadataValue("m", "m4"); 1059 item2.SetMetadataValue("m", "m6"); 1060 1061 Assert.Equal("m2", project.ItemDefinitions["i"].GetMetadataValue("m")); // Should not have been affected 1062 } 1063 1064 /// <summary> 1065 /// Repeated copying of items with item definitions should cause the following order of precedence: 1066 /// 1) direct metadata on the item 1067 /// 2) item definition metadata on the very first item in the chain 1068 /// 3) item definition on the next item, and so on until 1069 /// 4) item definition metadata on the destination item itself 1070 /// </summary> 1071 [Fact] CopyWithItemDefinition()1072 public void CopyWithItemDefinition() 1073 { 1074 string content = @" 1075 <Project xmlns='http://schemas.microsoft.com/developer/msbuild/2003' > 1076 <ItemDefinitionGroup> 1077 <i> 1078 <l>l1</l> 1079 <m>m1</m> 1080 <n>n1</n> 1081 </i> 1082 <j> 1083 <m>m2</m> 1084 <o>o2</o> 1085 <p>p2</p> 1086 </j> 1087 <k> 1088 <n>n3</n> 1089 </k> 1090 <l/> 1091 </ItemDefinitionGroup> 1092 <ItemGroup> 1093 <i Include='i1'> 1094 <l>l0</l> 1095 </i> 1096 <j Include='@(i)'/> 1097 <k Include='@(j)'> 1098 <p>p4</p> 1099 </k> 1100 <l Include='@(k);l1'/> 1101 <m Include='@(l)'> 1102 <o>o4</o> 1103 </m> 1104 </ItemGroup> 1105 </Project> 1106 "; 1107 1108 Project project = new Project(XmlReader.Create(new StringReader(content))); 1109 1110 Assert.Equal("l0", project.GetItems("i").First().GetMetadataValue("l")); 1111 Assert.Equal("m1", project.GetItems("i").First().GetMetadataValue("m")); 1112 Assert.Equal("n1", project.GetItems("i").First().GetMetadataValue("n")); 1113 Assert.Equal("", project.GetItems("i").First().GetMetadataValue("o")); 1114 Assert.Equal("", project.GetItems("i").First().GetMetadataValue("p")); 1115 1116 Assert.Equal("l0", project.GetItems("j").First().GetMetadataValue("l")); 1117 Assert.Equal("m1", project.GetItems("j").First().GetMetadataValue("m")); 1118 Assert.Equal("n1", project.GetItems("j").First().GetMetadataValue("n")); 1119 Assert.Equal("o2", project.GetItems("j").First().GetMetadataValue("o")); 1120 Assert.Equal("p2", project.GetItems("j").First().GetMetadataValue("p")); 1121 1122 Assert.Equal("l0", project.GetItems("k").First().GetMetadataValue("l")); 1123 Assert.Equal("m1", project.GetItems("k").First().GetMetadataValue("m")); 1124 Assert.Equal("n1", project.GetItems("k").First().GetMetadataValue("n")); 1125 Assert.Equal("o2", project.GetItems("k").First().GetMetadataValue("o")); 1126 Assert.Equal("p4", project.GetItems("k").First().GetMetadataValue("p")); 1127 1128 Assert.Equal("l0", project.GetItems("l").First().GetMetadataValue("l")); 1129 Assert.Equal("m1", project.GetItems("l").First().GetMetadataValue("m")); 1130 Assert.Equal("n1", project.GetItems("l").First().GetMetadataValue("n")); 1131 Assert.Equal("o2", project.GetItems("l").First().GetMetadataValue("o")); 1132 Assert.Equal("p4", project.GetItems("l").First().GetMetadataValue("p")); 1133 1134 Assert.Equal("", project.GetItems("l").ElementAt(1).GetMetadataValue("l")); 1135 Assert.Equal("", project.GetItems("l").ElementAt(1).GetMetadataValue("m")); 1136 Assert.Equal("", project.GetItems("l").ElementAt(1).GetMetadataValue("n")); 1137 Assert.Equal("", project.GetItems("l").ElementAt(1).GetMetadataValue("o")); 1138 Assert.Equal("", project.GetItems("l").ElementAt(1).GetMetadataValue("p")); 1139 1140 Assert.Equal("l0", project.GetItems("m").First().GetMetadataValue("l")); 1141 Assert.Equal("m1", project.GetItems("m").First().GetMetadataValue("m")); 1142 Assert.Equal("n1", project.GetItems("m").First().GetMetadataValue("n")); 1143 Assert.Equal("o4", project.GetItems("m").First().GetMetadataValue("o")); 1144 Assert.Equal("p4", project.GetItems("m").First().GetMetadataValue("p")); 1145 1146 Assert.Equal("", project.GetItems("m").ElementAt(1).GetMetadataValue("l")); 1147 Assert.Equal("", project.GetItems("m").ElementAt(1).GetMetadataValue("m")); 1148 Assert.Equal("", project.GetItems("m").ElementAt(1).GetMetadataValue("n")); 1149 Assert.Equal("o4", project.GetItems("m").ElementAt(1).GetMetadataValue("o")); 1150 Assert.Equal("", project.GetItems("m").ElementAt(1).GetMetadataValue("p")); 1151 1152 // Should still point at the same XML metadata 1153 Assert.Equal(true, Object.ReferenceEquals(project.GetItems("i").First().GetMetadata("l").Xml, project.GetItems("m").First().GetMetadata("l").Xml)); 1154 Assert.Equal(true, Object.ReferenceEquals(project.GetItems("i").First().GetMetadata("m").Xml, project.GetItems("m").First().GetMetadata("m").Xml)); 1155 Assert.Equal(true, Object.ReferenceEquals(project.GetItems("i").First().GetMetadata("n").Xml, project.GetItems("m").First().GetMetadata("n").Xml)); 1156 Assert.Equal(true, Object.ReferenceEquals(project.GetItems("j").First().GetMetadata("o").Xml, project.GetItems("k").First().GetMetadata("o").Xml)); 1157 Assert.Equal(true, Object.ReferenceEquals(project.GetItems("k").First().GetMetadata("p").Xml, project.GetItems("m").First().GetMetadata("p").Xml)); 1158 Assert.Equal(true, !Object.ReferenceEquals(project.GetItems("j").First().GetMetadata("p").Xml, project.GetItems("m").First().GetMetadata("p").Xml)); 1159 } 1160 1161 /// <summary> 1162 /// Repeated copying of items with item definitions should cause the following order of precedence: 1163 /// 1) direct metadata on the item 1164 /// 2) item definition metadata on the very first item in the chain 1165 /// 3) item definition on the next item, and so on until 1166 /// 4) item definition metadata on the destination item itself 1167 /// </summary> 1168 [Fact] CopyWithItemDefinition2()1169 public void CopyWithItemDefinition2() 1170 { 1171 string content = @" 1172 <Project xmlns='http://schemas.microsoft.com/developer/msbuild/2003' > 1173 <ItemDefinitionGroup> 1174 <i> 1175 <l>l1</l> 1176 <m>m1</m> 1177 <n>n1</n> 1178 </i> 1179 <j> 1180 <m>m2</m> 1181 <o>o2</o> 1182 <p>p2</p> 1183 </j> 1184 <k> 1185 <n>n3</n> 1186 </k> 1187 <l/> 1188 </ItemDefinitionGroup> 1189 <ItemGroup> 1190 <i Include='i1'> 1191 <l>l0</l> 1192 </i> 1193 <j Include='@(i)'/> 1194 <k Include='@(j)'> 1195 <p>p4</p> 1196 </k> 1197 <l Include='@(k);l1'/> 1198 <m Include='@(l)'> 1199 <o>o4</o> 1200 </m> 1201 </ItemGroup> 1202 </Project> 1203 "; 1204 1205 Project project = new Project(XmlReader.Create(new StringReader(content))); 1206 1207 Assert.Equal("l0", project.GetItems("i").First().GetMetadataValue("l")); 1208 Assert.Equal("m1", project.GetItems("i").First().GetMetadataValue("m")); 1209 Assert.Equal("n1", project.GetItems("i").First().GetMetadataValue("n")); 1210 Assert.Equal("", project.GetItems("i").First().GetMetadataValue("o")); 1211 Assert.Equal("", project.GetItems("i").First().GetMetadataValue("p")); 1212 1213 Assert.Equal("l0", project.GetItems("j").First().GetMetadataValue("l")); 1214 Assert.Equal("m1", project.GetItems("j").First().GetMetadataValue("m")); 1215 Assert.Equal("n1", project.GetItems("j").First().GetMetadataValue("n")); 1216 Assert.Equal("o2", project.GetItems("j").First().GetMetadataValue("o")); 1217 Assert.Equal("p2", project.GetItems("j").First().GetMetadataValue("p")); 1218 1219 Assert.Equal("l0", project.GetItems("k").First().GetMetadataValue("l")); 1220 Assert.Equal("m1", project.GetItems("k").First().GetMetadataValue("m")); 1221 Assert.Equal("n1", project.GetItems("k").First().GetMetadataValue("n")); 1222 Assert.Equal("o2", project.GetItems("k").First().GetMetadataValue("o")); 1223 Assert.Equal("p4", project.GetItems("k").First().GetMetadataValue("p")); 1224 1225 Assert.Equal("l0", project.GetItems("l").First().GetMetadataValue("l")); 1226 Assert.Equal("m1", project.GetItems("l").First().GetMetadataValue("m")); 1227 Assert.Equal("n1", project.GetItems("l").First().GetMetadataValue("n")); 1228 Assert.Equal("o2", project.GetItems("l").First().GetMetadataValue("o")); 1229 Assert.Equal("p4", project.GetItems("l").First().GetMetadataValue("p")); 1230 1231 Assert.Equal("", project.GetItems("l").ElementAt(1).GetMetadataValue("l")); 1232 Assert.Equal("", project.GetItems("l").ElementAt(1).GetMetadataValue("m")); 1233 Assert.Equal("", project.GetItems("l").ElementAt(1).GetMetadataValue("n")); 1234 Assert.Equal("", project.GetItems("l").ElementAt(1).GetMetadataValue("o")); 1235 Assert.Equal("", project.GetItems("l").ElementAt(1).GetMetadataValue("p")); 1236 1237 Assert.Equal("l0", project.GetItems("m").First().GetMetadataValue("l")); 1238 Assert.Equal("m1", project.GetItems("m").First().GetMetadataValue("m")); 1239 Assert.Equal("n1", project.GetItems("m").First().GetMetadataValue("n")); 1240 Assert.Equal("o4", project.GetItems("m").First().GetMetadataValue("o")); 1241 Assert.Equal("p4", project.GetItems("m").First().GetMetadataValue("p")); 1242 1243 Assert.Equal("", project.GetItems("m").ElementAt(1).GetMetadataValue("l")); 1244 Assert.Equal("", project.GetItems("m").ElementAt(1).GetMetadataValue("m")); 1245 Assert.Equal("", project.GetItems("m").ElementAt(1).GetMetadataValue("n")); 1246 Assert.Equal("o4", project.GetItems("m").ElementAt(1).GetMetadataValue("o")); 1247 Assert.Equal("", project.GetItems("m").ElementAt(1).GetMetadataValue("p")); 1248 1249 // Should still point at the same XML metadata 1250 Assert.Equal(true, Object.ReferenceEquals(project.GetItems("i").First().GetMetadata("l").Xml, project.GetItems("m").First().GetMetadata("l").Xml)); 1251 Assert.Equal(true, Object.ReferenceEquals(project.GetItems("i").First().GetMetadata("m").Xml, project.GetItems("m").First().GetMetadata("m").Xml)); 1252 Assert.Equal(true, Object.ReferenceEquals(project.GetItems("i").First().GetMetadata("n").Xml, project.GetItems("m").First().GetMetadata("n").Xml)); 1253 Assert.Equal(true, Object.ReferenceEquals(project.GetItems("j").First().GetMetadata("o").Xml, project.GetItems("k").First().GetMetadata("o").Xml)); 1254 Assert.Equal(true, Object.ReferenceEquals(project.GetItems("k").First().GetMetadata("p").Xml, project.GetItems("m").First().GetMetadata("p").Xml)); 1255 Assert.Equal(true, !Object.ReferenceEquals(project.GetItems("j").First().GetMetadata("p").Xml, project.GetItems("m").First().GetMetadata("p").Xml)); 1256 } 1257 1258 /// <summary> 1259 /// Metadata on items can refer to metadata above 1260 /// </summary> 1261 [Fact] MetadataReferringToMetadataAbove()1262 public void MetadataReferringToMetadataAbove() 1263 { 1264 string content = @" 1265 <Project xmlns='http://schemas.microsoft.com/developer/msbuild/2003' > 1266 <ItemGroup> 1267 <i Include='i1'> 1268 <m1>v1</m1> 1269 <m2>%(m1);v2;%(m0)</m2> 1270 </i> 1271 </ItemGroup> 1272 </Project> 1273 "; 1274 1275 ProjectItem item = GetOneItem(content); 1276 1277 var itemMetadata = Helpers.MakeList(item.Metadata); 1278 Assert.Equal(2, itemMetadata.Count); 1279 Assert.Equal("v1;v2;", item.GetMetadataValue("m2")); 1280 } 1281 1282 /// <summary> 1283 /// Built-in metadata should work, too. 1284 /// NOTE: To work properly, this should batch. This is a temporary "patch" to make it work for now. 1285 /// It will only give correct results if there is exactly one item in the Include. Otherwise Batching would be needed. 1286 /// </summary> 1287 [Fact] BuiltInMetadataExpression()1288 public void BuiltInMetadataExpression() 1289 { 1290 string content = @" 1291 <Project xmlns='http://schemas.microsoft.com/developer/msbuild/2003' > 1292 <ItemGroup> 1293 <i Include='i1'> 1294 <m>%(Identity)</m> 1295 </i> 1296 </ItemGroup> 1297 </Project> 1298 "; 1299 1300 ProjectItem item = GetOneItem(content); 1301 1302 Assert.Equal("i1", item.GetMetadataValue("m")); 1303 } 1304 1305 /// <summary> 1306 /// Qualified built in metadata should work 1307 /// </summary> 1308 [Fact] BuiltInQualifiedMetadataExpression()1309 public void BuiltInQualifiedMetadataExpression() 1310 { 1311 string content = @" 1312 <Project xmlns='http://schemas.microsoft.com/developer/msbuild/2003' > 1313 <ItemGroup> 1314 <i Include='i1'> 1315 <m>%(i.Identity)</m> 1316 </i> 1317 </ItemGroup> 1318 </Project> 1319 "; 1320 1321 ProjectItem item = GetOneItem(content); 1322 1323 Assert.Equal("i1", item.GetMetadataValue("m")); 1324 } 1325 1326 /// <summary> 1327 /// Mis-qualified built in metadata should not work 1328 /// </summary> 1329 [Fact] BuiltInMisqualifiedMetadataExpression()1330 public void BuiltInMisqualifiedMetadataExpression() 1331 { 1332 string content = @" 1333 <Project xmlns='http://schemas.microsoft.com/developer/msbuild/2003' > 1334 <ItemGroup> 1335 <i Include='i1'> 1336 <m>%(j.Identity)</m> 1337 </i> 1338 </ItemGroup> 1339 </Project> 1340 "; 1341 1342 ProjectItem item = GetOneItem(content); 1343 1344 Assert.Equal(String.Empty, item.GetMetadataValue("m")); 1345 } 1346 1347 /// <summary> 1348 /// Metadata condition should work correctly with built-in metadata 1349 /// </summary> 1350 [Fact] BuiltInMetadataInMetadataCondition()1351 public void BuiltInMetadataInMetadataCondition() 1352 { 1353 string content = @" 1354 <Project xmlns='http://schemas.microsoft.com/developer/msbuild/2003' > 1355 <ItemGroup> 1356 <i Include='i1'> 1357 <m Condition=""'%(Identity)'=='i1'"">m1</m> 1358 <n Condition=""'%(Identity)'=='i2'"">n1</n> 1359 </i> 1360 </ItemGroup> 1361 </Project> 1362 "; 1363 1364 ProjectItem item = GetOneItem(content); 1365 1366 Assert.Equal("m1", item.GetMetadataValue("m")); 1367 Assert.Equal(String.Empty, item.GetMetadataValue("n")); 1368 } 1369 1370 /// <summary> 1371 /// Metadata on item condition not allowed (currently) 1372 /// </summary> 1373 [Fact] BuiltInMetadataInItemCondition()1374 public void BuiltInMetadataInItemCondition() 1375 { 1376 Assert.Throws<InvalidProjectFileException>(() => 1377 { 1378 string content = @" 1379 <Project xmlns='http://schemas.microsoft.com/developer/msbuild/2003' > 1380 <ItemGroup> 1381 <i Include='i1' Condition=""'%(Identity)'=='i1'/> 1382 </ItemGroup> 1383 </Project> 1384 "; 1385 1386 GetOneItem(content); 1387 } 1388 ); 1389 } 1390 /// <summary> 1391 /// Two items should each get their own values for built-in metadata 1392 /// </summary> 1393 [Fact] BuiltInMetadataTwoItems()1394 public void BuiltInMetadataTwoItems() 1395 { 1396 string content = @" 1397 <Project xmlns='http://schemas.microsoft.com/developer/msbuild/2003' > 1398 <ItemGroup> 1399 <i Include='i1.cpp;" + (NativeMethodsShared.IsWindows ? @"c:\bar\i2.cpp" : "/bar/i2.cpp") + @"'> 1400 <m>%(Filename).obj</m> 1401 </i> 1402 </ItemGroup> 1403 </Project> 1404 "; 1405 1406 IList<ProjectItem> items = ObjectModelHelpers.GetItems(content); 1407 1408 Assert.Equal(@"i1.obj", items[0].GetMetadataValue("m")); 1409 Assert.Equal(@"i2.obj", items[1].GetMetadataValue("m")); 1410 } 1411 1412 /// <summary> 1413 /// Items from another list, but with different metadata 1414 /// </summary> 1415 [Fact] DifferentMetadataItemsFromOtherList()1416 public void DifferentMetadataItemsFromOtherList() 1417 { 1418 string content = @" 1419 <Project xmlns='http://schemas.microsoft.com/developer/msbuild/2003' > 1420 <ItemGroup> 1421 <h Include='h0'> 1422 <m>m1</m> 1423 </h> 1424 <h Include='h1'/> 1425 1426 <i Include='@(h)'> 1427 <m>%(m)</m> 1428 </i> 1429 </ItemGroup> 1430 </Project> 1431 "; 1432 1433 IList<ProjectItem> items = ObjectModelHelpers.GetItems(content); 1434 1435 Assert.Equal(@"m1", items[0].GetMetadataValue("m")); 1436 Assert.Equal(String.Empty, items[1].GetMetadataValue("m")); 1437 } 1438 1439 /// <summary> 1440 /// Items from another list, but with different metadata 1441 /// </summary> 1442 [Fact] DifferentBuiltInMetadataItemsFromOtherList()1443 public void DifferentBuiltInMetadataItemsFromOtherList() 1444 { 1445 string content = @" 1446 <Project xmlns='http://schemas.microsoft.com/developer/msbuild/2003' > 1447 <ItemGroup> 1448 <h Include='h0.x'/> 1449 <h Include='h1.y'/> 1450 1451 <i Include='@(h)'> 1452 <m>%(extension)</m> 1453 </i> 1454 </ItemGroup> 1455 </Project> 1456 "; 1457 1458 IList<ProjectItem> items = ObjectModelHelpers.GetItems(content); 1459 1460 Assert.Equal(@".x", items[0].GetMetadataValue("m")); 1461 Assert.Equal(@".y", items[1].GetMetadataValue("m")); 1462 } 1463 1464 /// <summary> 1465 /// Two items coming from a transform 1466 /// </summary> 1467 [Fact] BuiltInMetadataTransformInInclude()1468 public void BuiltInMetadataTransformInInclude() 1469 { 1470 string content = @" 1471 <Project xmlns='http://schemas.microsoft.com/developer/msbuild/2003' > 1472 <ItemGroup> 1473 <h Include='h0'/> 1474 <h Include='h1'/> 1475 1476 <i Include=""@(h->'%(Identity).baz')""> 1477 <m>%(Filename)%(Extension).obj</m> 1478 </i> 1479 </ItemGroup> 1480 </Project> 1481 "; 1482 1483 IList<ProjectItem> items = ObjectModelHelpers.GetItems(content); 1484 1485 Assert.Equal(@"h0.baz.obj", items[0].GetMetadataValue("m")); 1486 Assert.Equal(@"h1.baz.obj", items[1].GetMetadataValue("m")); 1487 } 1488 1489 /// <summary> 1490 /// Transform in the metadata value; no bare metadata involved 1491 /// </summary> 1492 [Fact] BuiltInMetadataTransformInMetadataValue()1493 public void BuiltInMetadataTransformInMetadataValue() 1494 { 1495 string content = @" 1496 <Project xmlns='http://schemas.microsoft.com/developer/msbuild/2003' > 1497 <ItemGroup> 1498 <h Include='h0'/> 1499 <h Include='h1'/> 1500 <i Include='i0'/> 1501 <i Include='i1;i2'> 1502 <m>@(i);@(h->'%(Filename)')</m> 1503 </i> 1504 </ItemGroup> 1505 </Project> 1506 "; 1507 1508 IList<ProjectItem> items = ObjectModelHelpers.GetItems(content); 1509 1510 Assert.Equal(@"i0;h0;h1", items[1].GetMetadataValue("m")); 1511 Assert.Equal(@"i0;h0;h1", items[2].GetMetadataValue("m")); 1512 } 1513 1514 /// <summary> 1515 /// Transform in the metadata value; bare metadata involved 1516 /// </summary> 1517 [Fact] BuiltInMetadataTransformInMetadataValueBareMetadataPresent()1518 public void BuiltInMetadataTransformInMetadataValueBareMetadataPresent() 1519 { 1520 string content = @" 1521 <Project xmlns='http://schemas.microsoft.com/developer/msbuild/2003' > 1522 <ItemGroup> 1523 <h Include='h0'/> 1524 <h Include='h1'/> 1525 <i Include='i0.x'/> 1526 <i Include='i1.y;i2'> 1527 <m>@(i);@(h->'%(Filename)');%(Extension)</m> 1528 </i> 1529 </ItemGroup> 1530 </Project> 1531 "; 1532 1533 IList<ProjectItem> items = ObjectModelHelpers.GetItems(content); 1534 1535 Assert.Equal(@"i0.x;h0;h1;.y", items[1].GetMetadataValue("m")); 1536 Assert.Equal(@"i0.x;h0;h1;", items[2].GetMetadataValue("m")); 1537 } 1538 1539 /// <summary> 1540 /// Metadata on items can refer to item lists 1541 /// </summary> 1542 [Fact] MetadataValueReferringToItems()1543 public void MetadataValueReferringToItems() 1544 { 1545 string content = @" 1546 <Project xmlns='http://schemas.microsoft.com/developer/msbuild/2003' > 1547 <ItemGroup> 1548 <h Include='h0'/> 1549 <i Include='i0'/> 1550 <i Include='i1'> 1551 <m1>@(h);@(i)</m1> 1552 </i> 1553 </ItemGroup> 1554 </Project> 1555 "; 1556 1557 IList<ProjectItem> items = ObjectModelHelpers.GetItems(content); 1558 1559 Assert.Equal("h0;i0", items[1].GetMetadataValue("m1")); 1560 } 1561 1562 /// <summary> 1563 /// Metadata on items' conditions can refer to item lists 1564 /// </summary> 1565 [Fact] MetadataConditionReferringToItems()1566 public void MetadataConditionReferringToItems() 1567 { 1568 string content = @" 1569 <Project xmlns='http://schemas.microsoft.com/developer/msbuild/2003' > 1570 <ItemGroup> 1571 <h Include='h0'/> 1572 <i Include='i0'/> 1573 <i Include='i1'> 1574 <m1 Condition=""'@(h)'=='h0' and '@(i)'=='i0'"">v1</m1> 1575 <m2 Condition=""'@(h)'!='h0' or '@(i)'!='i0'"">v2</m2> 1576 </i> 1577 </ItemGroup> 1578 </Project> 1579 "; 1580 1581 IList<ProjectItem> items = ObjectModelHelpers.GetItems(content); 1582 1583 Assert.Equal("v1", items[1].GetMetadataValue("m1")); 1584 Assert.Equal(String.Empty, items[1].GetMetadataValue("m2")); 1585 } 1586 1587 /// <summary> 1588 /// Metadata on items' conditions can refer to other metadata 1589 /// </summary> 1590 [Fact] MetadataConditionReferringToMetadataOnSameItem()1591 public void MetadataConditionReferringToMetadataOnSameItem() 1592 { 1593 string content = @" 1594 <Project xmlns='http://schemas.microsoft.com/developer/msbuild/2003' > 1595 <ItemGroup> 1596 <i Include='i1'> 1597 <m0>0</m0> 1598 <m1 Condition=""'%(m0)'=='0'"">1</m1> 1599 <m2 Condition=""'%(m0)'=='3'"">2</m2> 1600 </i> 1601 </ItemGroup> 1602 </Project> 1603 "; 1604 1605 IList<ProjectItem> items = ObjectModelHelpers.GetItems(content); 1606 1607 Assert.Equal("0", items[0].GetMetadataValue("m0")); 1608 Assert.Equal("1", items[0].GetMetadataValue("m1")); 1609 Assert.Equal(String.Empty, items[0].GetMetadataValue("m2")); 1610 } 1611 1612 /// <summary> 1613 /// Remove a metadatum 1614 /// </summary> 1615 [Fact] RemoveMetadata()1616 public void RemoveMetadata() 1617 { 1618 Project project = new Project(); 1619 ProjectItem item = project.AddItem("i", "i1")[0]; 1620 item.SetMetadataValue("m", "m1"); 1621 project.ReevaluateIfNecessary(); 1622 1623 bool found = item.RemoveMetadata("m"); 1624 1625 Assert.Equal(true, found); 1626 Assert.Equal(true, project.IsDirty); 1627 Assert.Equal(String.Empty, item.GetMetadataValue("m")); 1628 Assert.Equal(0, Helpers.Count(item.Xml.Metadata)); 1629 } 1630 1631 /// <summary> 1632 /// Attempt to remove a metadatum originating from an item definition. 1633 /// Should fail if it was not overridden. 1634 /// </summary> 1635 [Fact] RemoveItemDefinitionMetadataMasked()1636 public void RemoveItemDefinitionMetadataMasked() 1637 { 1638 ProjectRootElement xml = ProjectRootElement.Create(); 1639 xml.AddItemDefinition("i").AddMetadata("m", "m1"); 1640 xml.AddItem("i", "i1").AddMetadata("m", "m2"); 1641 Project project = new Project(xml); 1642 ProjectItem item = Helpers.GetFirst(project.GetItems("i")); 1643 1644 bool found = item.RemoveMetadata("m"); 1645 Assert.Equal(true, found); 1646 Assert.Equal(0, item.DirectMetadataCount); 1647 Assert.Equal(0, Helpers.Count(item.DirectMetadata)); 1648 Assert.Equal("m1", item.GetMetadataValue("m")); // Now originating from definition! 1649 Assert.Equal(true, project.IsDirty); 1650 Assert.Equal(0, item.Xml.Count); 1651 } 1652 1653 /// <summary> 1654 /// Attempt to remove a metadatum originating from an item definition. 1655 /// Should fail if it was not overridden. 1656 /// </summary> 1657 [Fact] RemoveItemDefinitionMetadataNotMasked()1658 public void RemoveItemDefinitionMetadataNotMasked() 1659 { 1660 Assert.Throws<InvalidOperationException>(() => 1661 { 1662 ProjectRootElement xml = ProjectRootElement.Create(); 1663 xml.AddItemDefinition("i").AddMetadata("m", "m1"); 1664 xml.AddItem("i", "i1"); 1665 Project project = new Project(xml); 1666 ProjectItem item = Helpers.GetFirst(project.GetItems("i")); 1667 1668 item.RemoveMetadata("m"); // Should throw 1669 } 1670 ); 1671 } 1672 /// <summary> 1673 /// Remove a nonexistent metadatum 1674 /// </summary> 1675 [Fact] RemoveNonexistentMetadata()1676 public void RemoveNonexistentMetadata() 1677 { 1678 Project project = new Project(); 1679 ProjectItem item = project.AddItem("i", "i1")[0]; 1680 project.ReevaluateIfNecessary(); 1681 1682 bool found = item.RemoveMetadata("m"); 1683 1684 Assert.Equal(false, found); 1685 Assert.Equal(false, project.IsDirty); 1686 } 1687 1688 /// <summary> 1689 /// Tests removing built-in metadata. 1690 /// </summary> 1691 [Fact] RemoveBuiltInMetadata()1692 public void RemoveBuiltInMetadata() 1693 { 1694 Assert.Throws<ArgumentException>(() => 1695 { 1696 ProjectRootElement xml = ProjectRootElement.Create(); 1697 xml.AddItem("i", "i1"); 1698 Project project = new Project(xml); 1699 ProjectItem item = Helpers.GetFirst(project.GetItems("i")); 1700 1701 // This should throw 1702 item.RemoveMetadata("FullPath"); 1703 } 1704 ); 1705 } 1706 /// <summary> 1707 /// Simple rename 1708 /// </summary> 1709 [Fact] Rename()1710 public void Rename() 1711 { 1712 Project project = new Project(); 1713 ProjectItem item = project.AddItem("i", "i1")[0]; 1714 project.ReevaluateIfNecessary(); 1715 1716 // populate built in metadata cache for this item, to verify the cache is cleared out by the rename 1717 Assert.Equal("i1", item.GetMetadataValue("FileName")); 1718 1719 item.Rename("i2"); 1720 1721 Assert.Equal("i2", item.Xml.Include); 1722 Assert.Equal("i2", item.EvaluatedInclude); 1723 Assert.Equal(true, project.IsDirty); 1724 Assert.Equal("i2", item.GetMetadataValue("FileName")); 1725 } 1726 1727 /// <summary> 1728 /// Verifies that renaming a ProjectItem whose xml backing is a wildcard doesn't corrupt 1729 /// the MSBuild evaluation data. 1730 /// </summary> 1731 [Fact] 1732 [Trait("Category", "netcore-osx-failing")] 1733 [Trait("Category", "netcore-linux-failing")] RenameItemInProjectWithWildcards()1734 public void RenameItemInProjectWithWildcards() 1735 { 1736 string projectDirectory = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); 1737 Directory.CreateDirectory(projectDirectory); 1738 try 1739 { 1740 string sourceFile = Path.Combine(projectDirectory, "a.cs"); 1741 string renamedSourceFile = Path.Combine(projectDirectory, "b.cs"); 1742 File.Create(sourceFile).Dispose(); 1743 var project = new Project(); 1744 project.AddItem("File", "*.cs"); 1745 project.FullPath = Path.Combine(projectDirectory, "test.proj"); // assign a path so the wildcards can lock onto something. 1746 project.ReevaluateIfNecessary(); 1747 1748 var projectItem = project.Items.Single(); 1749 Assert.Equal(Path.GetFileName(sourceFile), projectItem.EvaluatedInclude); 1750 Assert.Same(projectItem, project.GetItemsByEvaluatedInclude(projectItem.EvaluatedInclude).Single()); 1751 projectItem.Rename(Path.GetFileName(renamedSourceFile)); 1752 File.Move(sourceFile, renamedSourceFile); // repro w/ or w/o this 1753 project.ReevaluateIfNecessary(); 1754 projectItem = project.Items.Single(); 1755 Assert.Equal(Path.GetFileName(renamedSourceFile), projectItem.EvaluatedInclude); 1756 Assert.Same(projectItem, project.GetItemsByEvaluatedInclude(projectItem.EvaluatedInclude).Single()); 1757 } 1758 finally 1759 { 1760 FileUtilities.DeleteWithoutTrailingBackslash(projectDirectory, recursive: true); 1761 } 1762 } 1763 1764 /// <summary> 1765 /// Change item type 1766 /// </summary> 1767 [Fact] ChangeItemType()1768 public void ChangeItemType() 1769 { 1770 Project project = new Project(); 1771 ProjectItem item = project.AddItem("i", "i1")[0]; 1772 project.ReevaluateIfNecessary(); 1773 1774 item.ItemType = "j"; 1775 1776 Assert.Equal("j", item.ItemType); 1777 Assert.Equal(true, project.IsDirty); 1778 } 1779 1780 /// <summary> 1781 /// Change item type to invalid value 1782 /// </summary> 1783 [Fact] ChangeItemTypeInvalid()1784 public void ChangeItemTypeInvalid() 1785 { 1786 Assert.Throws<ArgumentException>(() => 1787 { 1788 Project project = new Project(); 1789 ProjectItem item = project.AddItem("i", "i1")[0]; 1790 project.ReevaluateIfNecessary(); 1791 1792 item.ItemType = "|"; 1793 } 1794 ); 1795 } 1796 /// <summary> 1797 /// Attempt to rename imported item should fail 1798 /// </summary> 1799 [Fact] RenameImported()1800 public void RenameImported() 1801 { 1802 Assert.Throws<InvalidOperationException>(() => 1803 { 1804 string file = null; 1805 1806 try 1807 { 1808 file = Microsoft.Build.Shared.FileUtilities.GetTemporaryFile(); 1809 Project import = new Project(); 1810 import.AddItem("i", "i1"); 1811 import.Save(file); 1812 1813 ProjectRootElement xml = ProjectRootElement.Create(); 1814 xml.AddImport(file); 1815 Project project = new Project(xml); 1816 1817 ProjectItem item = Helpers.GetFirst(project.GetItems("i")); 1818 1819 item.Rename("i2"); 1820 } 1821 finally 1822 { 1823 File.Delete(file); 1824 } 1825 } 1826 ); 1827 } 1828 /// <summary> 1829 /// Attempt to set metadata on imported item should fail 1830 /// </summary> 1831 [Fact] SetMetadataImported()1832 public void SetMetadataImported() 1833 { 1834 Assert.Throws<InvalidOperationException>(() => 1835 { 1836 string file = null; 1837 1838 try 1839 { 1840 file = Microsoft.Build.Shared.FileUtilities.GetTemporaryFile(); 1841 Project import = new Project(); 1842 import.AddItem("i", "i1"); 1843 import.Save(file); 1844 1845 ProjectRootElement xml = ProjectRootElement.Create(); 1846 xml.AddImport(file); 1847 Project project = new Project(xml); 1848 1849 ProjectItem item = Helpers.GetFirst(project.GetItems("i")); 1850 1851 item.SetMetadataValue("m", "m0"); 1852 } 1853 finally 1854 { 1855 File.Delete(file); 1856 } 1857 } 1858 ); 1859 } 1860 /// <summary> 1861 /// Attempt to remove metadata on imported item should fail 1862 /// </summary> 1863 [Fact] RemoveMetadataImported()1864 public void RemoveMetadataImported() 1865 { 1866 Assert.Throws<InvalidOperationException>(() => 1867 { 1868 string file = null; 1869 1870 try 1871 { 1872 file = Microsoft.Build.Shared.FileUtilities.GetTemporaryFile(); 1873 Project import = new Project(); 1874 ProjectItem item = import.AddItem("i", "i1")[0]; 1875 item.SetMetadataValue("m", "m0"); 1876 import.Save(file); 1877 1878 ProjectRootElement xml = ProjectRootElement.Create(); 1879 xml.AddImport(file); 1880 Project project = new Project(xml); 1881 1882 item = Helpers.GetFirst(project.GetItems("i")); 1883 1884 item.RemoveMetadata("m"); 1885 } 1886 finally 1887 { 1888 File.Delete(file); 1889 } 1890 } 1891 ); 1892 } 1893 1894 [Fact] SetDirectMetadataShouldEvaluateMetadataValue()1895 public void SetDirectMetadataShouldEvaluateMetadataValue() 1896 { 1897 var projectContents = 1898 @"<Project> 1899 <PropertyGroup> 1900 <P>p</P> 1901 </PropertyGroup> 1902 <ItemGroup> 1903 <Foo Include=`f1;f2`/> 1904 <I Include=`i`/> 1905 </ItemGroup> 1906 </Project>".Cleanup(); 1907 1908 using (var env = TestEnvironment.Create()) 1909 { 1910 var project = ObjectModelHelpers.CreateInMemoryProject(env.CreateProjectCollection().Collection, projectContents, null, null); 1911 1912 var metadata = project.GetItems("I").FirstOrDefault().SetMetadataValue("M", "$(P);@(Foo)", true); 1913 1914 Assert.Equal("p;f1;f2", metadata.EvaluatedValue); 1915 Assert.Equal("$(P);@(Foo)", metadata.Xml.Value); 1916 } 1917 } 1918 1919 [Fact] SetDirectMetadataWhenSameMetadataComesFromDefinitionGroupShouldAddDirectMetadata()1920 public void SetDirectMetadataWhenSameMetadataComesFromDefinitionGroupShouldAddDirectMetadata() 1921 { 1922 var projectContents = 1923 @"<Project> 1924 <ItemDefinitionGroup> 1925 <I> 1926 <M>V</M> 1927 </I> 1928 </ItemDefinitionGroup> 1929 <ItemGroup> 1930 <I Include=`i`/> 1931 </ItemGroup> 1932 </Project>".Cleanup(); 1933 1934 using (var env = TestEnvironment.Create()) 1935 { 1936 var project = ObjectModelHelpers.CreateInMemoryProject(env.CreateProjectCollection().Collection, projectContents, null, null); 1937 1938 var item = project.GetItems("I").FirstOrDefault(); 1939 var metadata = item.SetMetadataValue("M", "V", true); 1940 1941 Assert.Equal("M", metadata.Name); 1942 Assert.Equal("V", metadata.EvaluatedValue); 1943 1944 Assert.Equal(1, item.Xml.Metadata.Count); 1945 1946 ProjectMetadataElement metadataElement = item.Xml.Metadata.FirstOrDefault(); 1947 Assert.Equal("M", metadataElement.Name); 1948 Assert.Equal("V", metadataElement.Value); 1949 } 1950 } 1951 1952 [Fact] SetDirectMetadataShouldAffectAllSiblingItems()1953 public void SetDirectMetadataShouldAffectAllSiblingItems() 1954 { 1955 var projectContents = 1956 @"<Project> 1957 <ItemGroup> 1958 <Foo Include=`f1;f2`/> 1959 <I Include=`*.cs;@(Foo);i1`> 1960 <M1>V1</M1> 1961 </I> 1962 </ItemGroup> 1963 </Project>".Cleanup(); 1964 1965 using (var env = TestEnvironment.Create()) 1966 { 1967 var testProject = env.CreateTestProjectWithFiles(projectContents.Cleanup(), new[] {"a.cs"}); 1968 1969 var project = new Project(testProject.ProjectFile, new Dictionary<string, string>(), MSBuildConstants.CurrentToolsVersion, env.CreateProjectCollection().Collection); 1970 1971 var items = project.GetItems("I"); 1972 1973 Assert.Equal(4, items.Count); 1974 1975 Assert.False(project.IsDirty); 1976 1977 items.First().SetMetadataValue("M2", "V2", true); 1978 1979 Assert.True(project.IsDirty); 1980 1981 Assert.Equal(2, project.Xml.AllChildren.OfType<ProjectItemElement>().Count()); 1982 1983 foreach (var item in items) 1984 { 1985 var metadata = item.Metadata; 1986 1987 Assert.Equal(2, metadata.Count); 1988 1989 var m1 = metadata.ElementAt(0); 1990 Assert.Equal("M1", m1.Name); 1991 Assert.Equal("V1", m1.EvaluatedValue); 1992 1993 var m2 = metadata.ElementAt(1); 1994 Assert.Equal("M2", m2.Name); 1995 Assert.Equal("V2", m2.EvaluatedValue); 1996 } 1997 1998 var metadataElements = items.First().Xml.Metadata; 1999 2000 Assert.Equal(2, metadataElements.Count); 2001 2002 var me1 = metadataElements.ElementAt(0); 2003 Assert.Equal("M1", me1.Name); 2004 Assert.Equal("V1", me1.Value); 2005 2006 var me2 = metadataElements.ElementAt(1); 2007 Assert.Equal("M2", me2.Name); 2008 Assert.Equal("V2", me2.Value); 2009 } 2010 } 2011 2012 [Fact] SetDirectMetadataShouldUpdateAlreadyExistingDirectMetadata()2013 public void SetDirectMetadataShouldUpdateAlreadyExistingDirectMetadata() 2014 { 2015 var projectContents = 2016 @"<Project> 2017 <ItemGroup> 2018 <Foo Include=`f1;f2`/> 2019 <I Include=`*.cs;@(Foo);i1`> 2020 <M1>V1</M1> 2021 </I> 2022 </ItemGroup> 2023 </Project>".Cleanup(); 2024 2025 using (var env = TestEnvironment.Create()) 2026 { 2027 var testProject = env.CreateTestProjectWithFiles(projectContents.Cleanup(), new[] { "a.cs" }); 2028 2029 var project = new Project(testProject.ProjectFile, new Dictionary<string, string>(), MSBuildConstants.CurrentToolsVersion, env.CreateProjectCollection().Collection); 2030 2031 var items = project.GetItems("I"); 2032 2033 Assert.Equal(4, items.Count); 2034 2035 Assert.False(project.IsDirty); 2036 2037 items.First().SetMetadataValue("M1", "V2", true); 2038 2039 Assert.True(project.IsDirty); 2040 2041 Assert.Equal(2, project.Xml.AllChildren.OfType<ProjectItemElement>().Count()); 2042 2043 foreach (var item in items) 2044 { 2045 var metadata = item.Metadata; 2046 2047 Assert.Equal(1, metadata.Count); 2048 2049 var m1 = metadata.ElementAt(0); 2050 Assert.Equal("M1", m1.Name); 2051 Assert.Equal("V2", m1.EvaluatedValue); 2052 } 2053 2054 var metadataElements = items.First().Xml.Metadata; 2055 2056 Assert.Equal(1, metadataElements.Count); 2057 2058 var me1 = metadataElements.ElementAt(0); 2059 Assert.Equal("M1", me1.Name); 2060 Assert.Equal("V2", me1.Value); 2061 } 2062 } 2063 2064 // TODO: Should remove tests go in project item tests, project item instance tests, or both? 2065 [Fact] Remove()2066 public void Remove() 2067 { 2068 IList<ProjectItem> items = ObjectModelHelpers.GetItemsFromFragment( 2069 "<i Include='a;b' />" + 2070 "<i Remove='b;c' />" 2071 ); 2072 2073 Assert.Equal(1, items.Count); 2074 Assert.Equal("a", items[0].EvaluatedInclude); 2075 } 2076 2077 [Fact] RemoveGlob()2078 public void RemoveGlob() 2079 { 2080 IList<ProjectItem> items = ObjectModelHelpers.GetItemsFromFragment( 2081 @"<i Include='a.txt;b.cs;bin\foo.cs' />" + 2082 @"<i Remove='bin\**' />" 2083 ); 2084 2085 Assert.Equal(2, items.Count); 2086 Assert.Equal(@"a.txt;b.cs", string.Join(";", items.Select(i => i.EvaluatedInclude))); ; 2087 } 2088 2089 [Fact] RemoveItemReference()2090 public void RemoveItemReference() 2091 { 2092 IList<ProjectItem> items = ObjectModelHelpers.GetItemsFromFragment( 2093 @"<i Include='a;b;c;d' />" + 2094 @"<j Include='b;d' />" + 2095 @"<i Remove='@(j)' />" 2096 ); 2097 2098 Assert.Equal(2, items.Count); 2099 Assert.Equal(@"a;c", string.Join(";", items.Select(i => i.EvaluatedInclude))); ; 2100 } 2101 2102 [Theory] 2103 [InlineData(@"1.foo;.\2.foo;.\.\3.foo", @"1.foo;.\2.foo;.\.\3.foo")] 2104 [InlineData(@"1.foo;.\2.foo;.\.\3.foo", @".\1.foo;.\.\2.foo;.\.\.\3.foo")] RemoveShouldMatchNonCanonicPaths(string include, string remove)2105 public void RemoveShouldMatchNonCanonicPaths(string include, string remove) 2106 { 2107 var content = @" 2108 <i Include='" + include + @"'> 2109 <m1>m1_contents</m1> 2110 <m2>m2_contents</m2> 2111 </i> 2112 <i Remove='" + remove + @"'/>"; 2113 2114 IList<ProjectItem> items = ObjectModelHelpers.GetItemsFromFragment(content); 2115 2116 Assert.Empty(items); 2117 } 2118 2119 [Fact] RemoveShouldRespectCondition()2120 public void RemoveShouldRespectCondition() 2121 { 2122 var projectContents = ObjectModelHelpers.FormatProjectContentsWithItemGroupFragment( 2123 @"<i Include='a;b;c' />" + 2124 @"<i Condition='0 == 1' Remove='b' />" + 2125 @"<i Condition='1 == 1' Remove='c' />" 2126 ); 2127 2128 var project = ObjectModelHelpers.CreateInMemoryProject(projectContents); 2129 2130 Assert.Equal(@"a;b", string.Join(";", project.Items.Select(i => i.EvaluatedInclude))); 2131 } 2132 2133 /// <summary> 2134 /// See comment for details: https://github.com/Microsoft/msbuild/issues/1475#issuecomment-275520394 2135 /// </summary> 2136 [Fact(Skip = "https://github.com/Microsoft/msbuild/issues/1616")] RemoveWithConditionShouldNotApplyOnItemsIgnoringCondition()2137 public void RemoveWithConditionShouldNotApplyOnItemsIgnoringCondition() 2138 { 2139 var projectContents = ObjectModelHelpers.FormatProjectContentsWithItemGroupFragment( 2140 @"<i Include='a;b;c;d' />" + 2141 @"<i Condition='0 == 1' Remove='b' />" + 2142 @"<i Condition='1 == 1' Remove='c' />" + 2143 @"<i Remove='d' />" 2144 ); 2145 2146 var project = ObjectModelHelpers.CreateInMemoryProject(projectContents); 2147 2148 Assert.Equal(@"a;b;c", string.Join(";", project.ItemsIgnoringCondition.Select(i => i.EvaluatedInclude))); 2149 } 2150 2151 [Fact] UpdateMetadataShouldAddOrReplace()2152 public void UpdateMetadataShouldAddOrReplace() 2153 { 2154 string content = @"<i Include='a;b'> 2155 <m1>m1_contents</m1> 2156 <m2>m2_contents</m2> 2157 <m3>m3_contents</m3> 2158 </i> 2159 <i Update='a'> 2160 <m1>updated</m1> 2161 <m2></m2> 2162 <m4>added</m4> 2163 </i>"; 2164 2165 IList<ProjectItem> items = ObjectModelHelpers.GetItemsFromFragment(content); 2166 2167 ObjectModelHelpers.AssertItemHasMetadata( 2168 new Dictionary<string, string> 2169 { 2170 {"m1", "updated"}, 2171 {"m2", ""}, 2172 {"m3", "m3_contents"}, 2173 {"m4", "added"} 2174 } 2175 , items[0]); 2176 2177 ObjectModelHelpers.AssertItemHasMetadata( 2178 new Dictionary<string, string> 2179 { 2180 {"m1", "m1_contents"}, 2181 {"m2", "m2_contents"}, 2182 {"m3", "m3_contents"} 2183 } 2184 , items[1]); 2185 } 2186 2187 [Fact] UpdateShouldRespectCondition()2188 public void UpdateShouldRespectCondition() 2189 { 2190 string projectContents = @"<i Include='a;b;c'> 2191 <m1>m1_contents</m1> 2192 </i> 2193 <i Update='a' Condition='1 == 1'> 2194 <m1>from_true</m1> 2195 </i> 2196 <i Update='b' Condition='1 == 0'> 2197 <m1>from_false_item</m1> 2198 </i> 2199 <i Update='c'> 2200 <m1 Condition='1 == 0'>from_false_metadata</m1> 2201 </i>"; 2202 2203 var project = ObjectModelHelpers.CreateInMemoryProject(ObjectModelHelpers.FormatProjectContentsWithItemGroupFragment(projectContents)); 2204 2205 var expectedInitial = new Dictionary<string, string> 2206 { 2207 {"m1", "m1_contents"} 2208 }; 2209 2210 var expectedUpdateFromTrue = new Dictionary<string, string> 2211 { 2212 {"m1", "from_true"} 2213 }; 2214 2215 var items = project.Items.ToList(); 2216 2217 ObjectModelHelpers.AssertItemHasMetadata(expectedUpdateFromTrue, items[0]); 2218 ObjectModelHelpers.AssertItemHasMetadata(expectedInitial, items[1]); 2219 ObjectModelHelpers.AssertItemHasMetadata(expectedInitial, items[2]); 2220 } 2221 2222 /// <summary> 2223 /// See comment for details: https://github.com/Microsoft/msbuild/issues/1475#issuecomment-275520394 2224 /// Conditions on metadata on appear to be respected even for items ignoring condition (don't know why, but that's what the code does). 2225 /// </summary> 2226 [Fact(Skip = "https://github.com/Microsoft/msbuild/issues/1616")] UpdateWithConditionShouldNotApplyOnItemsIgnoringCondition()2227 public void UpdateWithConditionShouldNotApplyOnItemsIgnoringCondition() 2228 { 2229 string projectContents = @"<i Include='a;b;c;d'> 2230 <m1>m1_contents</m1> 2231 </i> 2232 <i Update='a' Condition='1 == 1'> 2233 <m1>from_true</m1> 2234 </i> 2235 <i Update='b' Condition='1 == 0'> 2236 <m1>from_false_item</m1> 2237 </i> 2238 <i Update='c'> 2239 <m1 Condition='1 == 0'>from_false_metadata</m1> 2240 </i> 2241 <i Update='d'> 2242 <m1>from_uncoditioned_update</m1> 2243 </i>"; 2244 2245 var project = ObjectModelHelpers.CreateInMemoryProject(ObjectModelHelpers.FormatProjectContentsWithItemGroupFragment(projectContents)); 2246 2247 var expectedInitial = new Dictionary<string, string> 2248 { 2249 {"m1", "m1_contents"} 2250 }; 2251 2252 var expectedUpdateFromUnconditionedElement = new Dictionary<string, string> 2253 { 2254 {"m1", "from_uncoditioned_update"} 2255 }; 2256 2257 var itemsIgnoringCondition = project.ItemsIgnoringCondition.ToList(); 2258 2259 ObjectModelHelpers.AssertItemHasMetadata(expectedInitial, itemsIgnoringCondition[0]); 2260 ObjectModelHelpers.AssertItemHasMetadata(expectedInitial, itemsIgnoringCondition[1]); 2261 ObjectModelHelpers.AssertItemHasMetadata(expectedInitial, itemsIgnoringCondition[2]); 2262 ObjectModelHelpers.AssertItemHasMetadata(expectedUpdateFromUnconditionedElement, itemsIgnoringCondition[3]); 2263 } 2264 2265 2266 [Fact] LastUpdateWins()2267 public void LastUpdateWins() 2268 { 2269 string content = @"<i Include='a'> 2270 <m1>m1_contents</m1> 2271 </i> 2272 <i Update='a'> 2273 <m1>first</m1> 2274 </i> 2275 <i Update='a'> 2276 <m1>second</m1> 2277 </i>"; 2278 2279 IList<ProjectItem> items = ObjectModelHelpers.GetItemsFromFragment(content); 2280 2281 var expectedUpdate = new Dictionary<string, string> 2282 { 2283 {"m1", "second"} 2284 }; 2285 2286 ObjectModelHelpers.AssertItemHasMetadata(expectedUpdate, items[0]); 2287 } 2288 2289 [Fact] UpdateWithNoMetadataShouldNotAffectItems()2290 public void UpdateWithNoMetadataShouldNotAffectItems() 2291 { 2292 string content = @"<i Include='a;b'> 2293 <m1>m1_contents</m1> 2294 <m2>m2_contents</m2> 2295 <m3>m3_contents</m3> 2296 </i> 2297 <i Update='a'> 2298 </i>"; 2299 2300 IList<ProjectItem> items = ObjectModelHelpers.GetItemsFromFragment(content); 2301 2302 var expectedMetadata = new Dictionary<string, string> 2303 { 2304 {"m1", "m1_contents"}, 2305 {"m2", "m2_contents"}, 2306 {"m3", "m3_contents"} 2307 }; 2308 2309 Assert.Equal(2, items.Count); 2310 2311 ObjectModelHelpers.AssertItemHasMetadata(expectedMetadata, items[0]); 2312 ObjectModelHelpers.AssertItemHasMetadata(expectedMetadata, items[1]); 2313 } 2314 2315 [Fact] UpdateOnNonExistingItemShouldDoNothing()2316 public void UpdateOnNonExistingItemShouldDoNothing() 2317 { 2318 string content = @"<i Include='a;b'> 2319 <m1>m1_contents</m1> 2320 <m2>m2_contents</m2> 2321 </i> 2322 <i Update='c'> 2323 <m1>updated</m1> 2324 <m2></m2> 2325 <m3>added</m3> 2326 </i>"; 2327 2328 IList<ProjectItem> items = ObjectModelHelpers.GetItemsFromFragment(content); 2329 2330 Assert.Equal(2, items.Count); 2331 2332 var expectedMetadata = new Dictionary<string, string> 2333 { 2334 {"m1", "m1_contents"}, 2335 {"m2", "m2_contents"}, 2336 }; 2337 2338 ObjectModelHelpers.AssertItemHasMetadata(expectedMetadata, items[0]); 2339 ObjectModelHelpers.AssertItemHasMetadata(expectedMetadata, items[1]); 2340 } 2341 2342 [Fact] UpdateOnEmptyStringShouldThrow()2343 public void UpdateOnEmptyStringShouldThrow() 2344 { 2345 string content = @"<i Include='a;b'> 2346 <m1>m1_contents</m1> 2347 <m2>m2_contents</m2> 2348 </i> 2349 <i Update=''> 2350 <m1>updated</m1> 2351 <m2></m2> 2352 <m3>added</m3> 2353 </i>"; 2354 2355 var exception = Assert.Throws<InvalidProjectFileException>(() => 2356 { 2357 IList<ProjectItem> items = ObjectModelHelpers.GetItemsFromFragment(content); 2358 }); 2359 2360 Assert.Equal("The required attribute \"Update\" is empty or missing from the element <i>.", exception.Message); 2361 } 2362 2363 // Complex metadata: metadata references from the same item; item transforms; correct binding of metadata with same name but different item qualifiers 2364 [Fact] UpdateShouldSupportComplexMetadata()2365 public void UpdateShouldSupportComplexMetadata() 2366 { 2367 string content = @" 2368 <i1 Include='x'> 2369 <m1>%(Identity)</m1> 2370 </i1> 2371 <i2 Include='a;b'> 2372 <m1>m1_contents</m1> 2373 <m2>m2_contents</m2> 2374 </i2> 2375 <i2 Update='a;b'> 2376 <m1>%(Identity)</m1> 2377 <m2>%(m1)@(i1 -> '%(m1)')</m2> 2378 </i2>"; 2379 2380 IList<ProjectItem> items = ObjectModelHelpers.GetItemsFromFragment(content, true); 2381 2382 Assert.Equal(3, items.Count); 2383 2384 var expectedMetadataX = new Dictionary<string, string> 2385 { 2386 {"m1", "x"}, 2387 }; 2388 2389 var expectedMetadataA = new Dictionary<string, string> 2390 { 2391 {"m1", "a"}, 2392 {"m2", "ax"}, 2393 }; 2394 2395 var expectedMetadataB = new Dictionary<string, string> 2396 { 2397 {"m1", "b"}, 2398 {"m2", "bx"}, 2399 }; 2400 2401 ObjectModelHelpers.AssertItemHasMetadata(expectedMetadataX, items[0]); 2402 ObjectModelHelpers.AssertItemHasMetadata(expectedMetadataA, items[1]); 2403 ObjectModelHelpers.AssertItemHasMetadata(expectedMetadataB, items[2]); 2404 } 2405 2406 [Fact] UpdateShouldBeAbleToContainGlobs()2407 public void UpdateShouldBeAbleToContainGlobs() 2408 { 2409 var content = @"<i Include='*.foo'> 2410 <m1>m1_contents</m1> 2411 <m2>m2_contents</m2> 2412 </i> 2413 <i Update='*bar*foo'> 2414 <m1>updated</m1> 2415 <m2></m2> 2416 <m3>added</m3> 2417 </i>"; 2418 2419 var items = GetItemsFromFragmentWithGlobs(content, "a.foo", "b.foo", "bar1.foo", "bar2.foo"); 2420 2421 Assert.Equal(4, items.Count); 2422 2423 var expectedInitialMetadata = new Dictionary<string, string> 2424 { 2425 {"m1", "m1_contents"}, 2426 {"m2", "m2_contents"}, 2427 }; 2428 2429 var expectedUpdatedMetadata = new Dictionary<string, string> 2430 { 2431 {"m1", "updated"}, 2432 {"m2", ""}, 2433 {"m3", "added"}, 2434 }; 2435 2436 ObjectModelHelpers.AssertItemHasMetadata(expectedInitialMetadata, items[0]); 2437 ObjectModelHelpers.AssertItemHasMetadata(expectedInitialMetadata, items[1]); 2438 ObjectModelHelpers.AssertItemHasMetadata(expectedUpdatedMetadata, items[2]); 2439 ObjectModelHelpers.AssertItemHasMetadata(expectedUpdatedMetadata, items[3]); 2440 } 2441 2442 [Fact] UpdateShouldBeAbleToContainItemReferences()2443 public void UpdateShouldBeAbleToContainItemReferences() 2444 { 2445 var content = @"<i1 Include='x;y'> 2446 <m1>m1_contents</m1> 2447 <m2>m2_contents</m2> 2448 </i1> 2449 <i1 Update='@(i1)'> 2450 <m1>m1_updated</m1> 2451 <m2>m2_updated</m2> 2452 </i1> 2453 <i2 Include='a;y'> 2454 <m1>m1_i2_contents</m1> 2455 <m2>m2_i2_contents</m2> 2456 </i2> 2457 <i2 Update='@(i1)'> 2458 <m1>m1_i2_updated</m1> 2459 </i2>"; 2460 2461 IList<ProjectItem> items = ObjectModelHelpers.GetItemsFromFragment(content, true); 2462 2463 Assert.Equal(4, items.Count); 2464 2465 var expected_i1 = new Dictionary<string, string> 2466 { 2467 {"m1", "m1_updated"}, 2468 {"m2", "m2_updated"}, 2469 }; 2470 2471 var expected_i2_a = new Dictionary<string, string> 2472 { 2473 {"m1", "m1_i2_contents"}, 2474 {"m2", "m2_i2_contents"} 2475 }; 2476 2477 var expected_i2_y = new Dictionary<string, string> 2478 { 2479 {"m1", "m1_i2_updated"}, 2480 {"m2", "m2_i2_contents"} 2481 }; 2482 2483 ObjectModelHelpers.AssertItemHasMetadata(expected_i1, items[0]); 2484 ObjectModelHelpers.AssertItemHasMetadata(expected_i1, items[1]); 2485 ObjectModelHelpers.AssertItemHasMetadata(expected_i2_a, items[2]); 2486 ObjectModelHelpers.AssertItemHasMetadata(expected_i2_y, items[3]); 2487 } 2488 2489 [Fact] UpdateShouldBeAbleToContainProperties()2490 public void UpdateShouldBeAbleToContainProperties() 2491 { 2492 var content = @" 2493 <Project xmlns='http://schemas.microsoft.com/developer/msbuild/2003' > 2494 <PropertyGroup> 2495 <P>a</P> 2496 </PropertyGroup> 2497 <ItemGroup> 2498 <i Include='a;b;c'> 2499 <m1>m1_contents</m1> 2500 <m2>m2_contents</m2> 2501 </i> 2502 <i Update='$(P);b'> 2503 <m1>m1_updated</m1> 2504 <m2>m2_updated</m2> 2505 </i> 2506 </ItemGroup> 2507 </Project>" 2508 ; 2509 2510 IList<ProjectItem> items = ObjectModelHelpers.GetItems(content); 2511 2512 Assert.Equal(3, items.Count); 2513 2514 var expectedInitial = new Dictionary<string, string> 2515 { 2516 {"m1", "m1_contents"}, 2517 {"m2", "m2_contents"} 2518 }; 2519 2520 var expectedUpdated = new Dictionary<string, string> 2521 { 2522 {"m1", "m1_updated"}, 2523 {"m2", "m2_updated"} 2524 }; 2525 2526 ObjectModelHelpers.AssertItemHasMetadata(expectedUpdated, items[0]); 2527 ObjectModelHelpers.AssertItemHasMetadata(expectedUpdated, items[1]); 2528 ObjectModelHelpers.AssertItemHasMetadata(expectedInitial, items[2]); 2529 } 2530 2531 [Fact] UpdateAndRemoveShouldUseCaseInsensitiveMatching()2532 public void UpdateAndRemoveShouldUseCaseInsensitiveMatching() 2533 { 2534 var content = @" 2535 <i Include='x;y'> 2536 <m1>m1_contents</m1> 2537 <m2>m2_contents</m2> 2538 </i> 2539 <i Update='X'> 2540 <m1>m1_updated</m1> 2541 <m2>m2_updated</m2> 2542 </i> 2543 <i Remove='Y'/>"; 2544 2545 IList<ProjectItem> items = ObjectModelHelpers.GetItemsFromFragment(content); 2546 2547 Assert.Equal(1, items.Count); 2548 2549 var expectedUpdated = new Dictionary<string, string> 2550 { 2551 {"m1", "m1_updated"}, 2552 {"m2", "m2_updated"}, 2553 }; 2554 2555 ObjectModelHelpers.AssertItemHasMetadata(expectedUpdated, items[0]); 2556 } 2557 2558 public static IEnumerable<Object[]> UpdateAndRemoveShouldWorkWithEscapedCharactersTestData 2559 { 2560 get 2561 2562 { 2563 var expectedMetadata = new[] 2564 { 2565 new Dictionary<string, string> {{"m", "contents"}}, 2566 new Dictionary<string, string> {{"m", "updated"}} 2567 }; 2568 2569 // escaped value matches and nonescaped value include 2570 yield return new object[] 2571 { 2572 ItemWithIncludeUpdateAndRemove, 2573 "i;u;r", 2574 "%75", 2575 "%72", 2576 new[] {"i", "u"}, 2577 expectedMetadata 2578 }; 2579 2580 // escaped value matches and escaped value include 2581 yield return new object[] 2582 { 2583 ItemWithIncludeUpdateAndRemove, 2584 "i;%75;%72", 2585 "%75", 2586 "%72", 2587 new[] {"i", "u"}, 2588 expectedMetadata 2589 }; 2590 2591 // unescaped value matches and escaped value include 2592 yield return new object[] 2593 { 2594 ItemWithIncludeUpdateAndRemove, 2595 "i;%75;%72", 2596 "u", 2597 "r", 2598 new[] {"i", "u"}, 2599 expectedMetadata 2600 }; 2601 2602 // escaped glob matches and nonescaped value include 2603 yield return new object[] 2604 { 2605 ItemWithIncludeUpdateAndRemove, 2606 "i;u;r", 2607 "*%75*", 2608 "*%72*", 2609 new[] {"i", "u"}, 2610 expectedMetadata 2611 }; 2612 2613 // escaped glob matches and escaped value include 2614 yield return new object[] 2615 { 2616 ItemWithIncludeUpdateAndRemove, 2617 "i;%75;%72", 2618 "*%75*", 2619 "*%72*", 2620 new[] {"i", "u"}, 2621 expectedMetadata 2622 }; 2623 2624 // escaped matching items as globs containing escaped wildcards; treated as normal values 2625 yield return new object[] 2626 { 2627 ItemWithIncludeUpdateAndRemove, 2628 "i;u;r;%2A%75%2A;%2A%72%2A", 2629 "%2A%75%2A", 2630 "%2A%72%2A", 2631 new[] {"i", "u", "r", "*u*"}, 2632 new[] 2633 { 2634 new Dictionary<string, string> {{"m", "contents"}}, 2635 new Dictionary<string, string> {{"m", "contents"}}, 2636 new Dictionary<string, string> {{"m", "contents"}}, 2637 new Dictionary<string, string> {{"m", "updated"}} 2638 } 2639 }; 2640 } 2641 } 2642 2643 [Theory] 2644 [MemberData(nameof(UpdateAndRemoveShouldWorkWithEscapedCharactersTestData))] UpdateAndRemoveShouldWorkWithEscapedCharacters(string projectContents, string include, string update, string remove, string[] expectedInclude, Dictionary<string, string>[] expectedMetadata)2645 public void UpdateAndRemoveShouldWorkWithEscapedCharacters(string projectContents, string include, string update, string remove, string[] expectedInclude, Dictionary<string, string>[] expectedMetadata) 2646 { 2647 var formattedProjectContents = string.Format(projectContents, include, update, remove); 2648 ObjectModelHelpers.AssertItemEvaluationFromProject(formattedProjectContents, new string[0], expectedInclude, expectedMetadata); 2649 } 2650 2651 [Theory] 2652 [InlineData(@"1.foo;.\2.foo;.\.\3.foo", @"1.foo;.\2.foo;.\.\3.foo")] 2653 [InlineData(@"1.foo;.\2.foo;.\.\3.foo", @".\1.foo;.\.\2.foo;.\.\.\3.foo")] UpdateShouldMatchNonCanonicPaths(string include, string update)2654 public void UpdateShouldMatchNonCanonicPaths(string include, string update) 2655 { 2656 var content = @" 2657 <i Include='" + include + @"'> 2658 <m1>m1_contents</m1> 2659 <m2>m2_contents</m2> 2660 </i> 2661 <i Update='" + update + @"'> 2662 <m1>m1_updated</m1> 2663 <m2>m2_updated</m2> 2664 </i>"; 2665 2666 IList<ProjectItem> items = ObjectModelHelpers.GetItemsFromFragment(content); 2667 2668 var expectedUpdated = new Dictionary<string, string> 2669 { 2670 {"m1", "m1_updated"}, 2671 {"m2", "m2_updated"}, 2672 }; 2673 2674 foreach (var item in items) 2675 { 2676 ObjectModelHelpers.AssertItemHasMetadata(expectedUpdated, item); 2677 } 2678 } 2679 GetItemsFromFragmentWithGlobs(string itemGroupFragment, params string[] globFiles)2680 private static List<ProjectItem> GetItemsFromFragmentWithGlobs(string itemGroupFragment, params string[] globFiles) 2681 { 2682 var formattedProjectContents = ObjectModelHelpers.FormatProjectContentsWithItemGroupFragment(itemGroupFragment); 2683 2684 List<ProjectItem> itemsFromFragmentWithGlobs; 2685 2686 using (var env = TestEnvironment.Create()) 2687 { 2688 var testProject = env.CreateTestProjectWithFiles(formattedProjectContents, globFiles); 2689 itemsFromFragmentWithGlobs = Helpers.MakeList(new Project(testProject.ProjectFile).GetItems("i")); 2690 } 2691 2692 return itemsFromFragmentWithGlobs; 2693 } 2694 2695 /// <summary> 2696 /// Get the item of type "i" using the item Xml fragment provided. 2697 /// If there is more than one, fail. 2698 /// </summary> GetOneItemFromFragment(string fragment)2699 private static ProjectItem GetOneItemFromFragment(string fragment) 2700 { 2701 IList<ProjectItem> items = ObjectModelHelpers.GetItemsFromFragment(fragment); 2702 2703 Assert.Equal(1, items.Count); 2704 return items[0]; 2705 } 2706 2707 /// <summary> 2708 /// Get the item of type "i" in the project provided. 2709 /// If there is more than one, fail. 2710 /// </summary> GetOneItem(string content)2711 private static ProjectItem GetOneItem(string content) 2712 { 2713 IList<ProjectItem> items = ObjectModelHelpers.GetItems(content); 2714 2715 Assert.Equal(1, items.Count); 2716 return items[0]; 2717 } 2718 2719 /// <summary> 2720 /// Item metadata "Filename" should not depends on platform specific slashes. 2721 /// </summary> 2722 [Fact] FileNameMetadataEvaluationShouldNotDependsFromPlatformSpecificSlashes()2723 public void FileNameMetadataEvaluationShouldNotDependsFromPlatformSpecificSlashes() 2724 { 2725 using (var env = TestEnvironment.Create()) 2726 using (var projectCollection = new ProjectCollection()) 2727 { 2728 var testFiles = env.CreateTestProjectWithFiles(@"<?xml version=`1.0` encoding=`utf-8`?> 2729 <Project ToolsVersion=`msbuilddefaulttoolsversion` DefaultTargets=`Validate` xmlns=`msbuildnamespace`> 2730 <ItemGroup> 2731 <A Include=`A\B\C\D.cs` /> 2732 <B Include=`@(A->'%(Filename)_test.ext')` /> 2733 </ItemGroup> 2734 </Project>"); 2735 var project = new Project(testFiles.ProjectFile, new Dictionary<string, string>(), null, projectCollection); 2736 var buildManager = BuildManager.DefaultBuildManager; 2737 var projectInstance = buildManager.GetProjectInstanceForBuild(project); 2738 var itemB = projectInstance.Items.Single(i => i.ItemType == "B").EvaluatedInclude; 2739 itemB.ShouldBe("D_test.ext"); 2740 } 2741 } 2742 } 2743 } 2744