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