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