Merge pull request #1074 from esdrubal/bug18421
[mono.git] / mcs / class / Microsoft.Build / Test / Microsoft.Build.Internal / ExpressionParserTest.cs
1 //
2 // ExpressionParserTest.cs
3 //
4 // Author:
5 //   Atsushi Enomoto (atsushi@xamarin.com)
6 //
7 // Copyright (C) 2013 Xamarin Inc. (http://www.xamarin.com)
8 //
9 // Permission is hereby granted, free of charge, to any person obtaining
10 // a copy of this software and associated documentation files (the
11 // "Software"), to deal in the Software without restriction, including
12 // without limitation the rights to use, copy, modify, merge, publish,
13 // distribute, sublicense, and/or sell copies of the Software, and to
14 // permit persons to whom the Software is furnished to do so, subject to
15 // the following conditions:
16 // 
17 // The above copyright notice and this permission notice shall be
18 // included in all copies or substantial portions of the Software.
19 // 
20 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
21 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
22 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
23 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
24 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
25 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
26 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
27 //
28 using System;
29 using System.IO;
30 using System.Linq;
31 using System.Xml;
32 using Microsoft.Build.Construction;
33 using Microsoft.Build.Evaluation;
34 using NUnit.Framework;
35 using Microsoft.Build.Execution;
36 using Microsoft.Build.Exceptions;
37 using System.Collections.Generic;
38 using Microsoft.Build.Framework;
39 using Microsoft.Build.Logging;
40
41 namespace MonoTests.Microsoft.Build.Internal
42 {
43         [TestFixture]
44         public class ExpressionParserTest
45         {
46                         string [] invalid_always = {
47                                 "$(Foo..Bar)",
48                                 "$([DateTime.Now])", // fullname required
49                                 "$([System.DateTime.Now])", // member cannot be invoked with '.'
50                         };
51                         string [] invalid_as_boolean = {
52                                 "$",
53                                 "@",
54                                 "%",
55                                 "%-1",
56                                 "$(",
57                                 "%(",
58                                 "$)",
59                                 "%)",
60                                 "%24",
61                                 "()",
62                                 "{}",
63                                 "A", // must be evaluated as a boolean
64                                 "1", // ditto (no default conversion to bool)
65                                 "$ (foo) == ''",
66                                 "@ (foo) == ''",
67                                 "$(1)",
68                                 "$(Foo) == And", // reserved keyword 'and'
69                                 "$(Foo) == Or", // reserved keyword 'or'
70                                 "$(Foo) == $(Bar) == $(Baz)", // unexpected '=='
71                                 "$([System.DateTime]::Now)", // it is DateTime
72                                 "$([System.String]::Format('Tr'))$([System.String]::Format('ue'))", // only one expression is accepted
73                                 "$([System.String]::Format(null))", // causing ANE, wrapped by InvalidProjectFileException
74                                 "yep",
75                                 "nope",
76                                 "ONN",
77                                 "OFFF",
78                         };
79                         string [] valid = {
80                                 "'%24' == 0",
81                                 "true",
82                                 "fAlSe",
83                                 "(false)",
84                                 "A==A",
85                                 "A ==A",
86                                 "A== A",
87                                 "A=='A'",
88                                 "A==\tA",
89                                 "\tA== A",
90                                 "$([System.String]::Format('True'))",
91                                 "$([System.String]::Format('True', null))",
92                                 "False And True == True And True",
93                                 "True or True or False",
94                                 "(True or True or False)",
95                                 "True and False",
96                                 "(True) and (False)",
97                                 "yes",
98                                 "nO",
99                                 "oN",
100                                 "oFf",
101                         };
102                         string [] depends = {
103                                 // valid only if evaluated to boolean
104                                 "$(foo)",
105                                 "@(foo)",
106                         };
107                         
108                 [Test]
109                 public void EvaluateAsBoolean ()
110                 {
111                         foreach (var expr in invalid_always.Concat (invalid_as_boolean).Concat (valid)) {
112                                 string project_xml = @"<Project xmlns='http://schemas.microsoft.com/developer/msbuild/2003'>
113   <ItemGroup>
114     <Foo Condition=""{0}"" Include='x' />
115   </ItemGroup>
116 </Project>";
117                                 var xml = XmlReader.Create (new StringReader (string.Format (project_xml, expr)));
118                                 var root = ProjectRootElement.Create (xml);
119                                 try {
120                                         new Project (root);
121                                         if (invalid_as_boolean.Contains (expr) || invalid_always.Contains (expr))
122                                                 Assert.Fail ("Parsing Condition '{0}' should fail", expr);
123                                 } catch (Exception ex) {
124                                         if (valid.Contains (expr))
125                                                 throw new Exception (string.Format ("failed to parse '{0}'", expr), ex);
126                                         else if (ex is InvalidProjectFileException)
127                                                 continue;
128                                         throw new Exception (string.Format ("unexpected exception to parse '{0}'", expr), ex);
129                                 }
130                         }
131                 }
132                 
133                 [Test]
134                 public void EvaluateAsString ()
135                 {
136                         foreach (var expr in invalid_always.Concat (invalid_as_boolean).Concat (valid)) {
137                                 try {
138                                         string project_xml = @"<Project xmlns='http://schemas.microsoft.com/developer/msbuild/2003'>
139           <ItemGroup>
140             <Foo Include=""{0}"" />
141           </ItemGroup>
142         </Project>";
143                                         var xml = XmlReader.Create (new StringReader (string.Format (project_xml, expr)));
144                                         var root = ProjectRootElement.Create (xml);
145                                         // everything but 'invalid_always' should pass
146                                         new Project (root);
147                                 } catch (Exception ex) {
148                                         if (!invalid_always.Contains (expr))
149                                                 throw new Exception (string.Format ("failed to parse '{0}'", expr), ex);
150                                 }
151                         }
152                 }
153                 
154                 [Test]
155                 public void EvaluatePropertyReferencesWithProperties ()
156                 {
157                         string project_xml = @"<Project xmlns='http://schemas.microsoft.com/developer/msbuild/2003'>
158   <ItemGroup>
159     <Foo Condition=""$(foo)"" Include='x' />
160   </ItemGroup>
161 </Project>";
162                         var xml = XmlReader.Create (new StringReader (project_xml));
163                         var root = ProjectRootElement.Create (xml);
164                         var props = new Dictionary<string,string> ();
165                         props ["foo"] = "true";
166                         new Project (root, props, null);
167                 }
168                 
169                 [Test]
170                 public void EvaluateItemReferences ()
171                 {
172                         string project_xml = @"<Project xmlns='http://schemas.microsoft.com/developer/msbuild/2003'>
173   <ItemGroup>
174     <Foo Include='false' />
175     <!-- by the time Bar is evaluated, Foo is already evaluated and taken into consideration in expansion -->
176     <Bar Condition=""@(foo)"" Include='x' />
177   </ItemGroup>
178 </Project>";
179                         var xml = XmlReader.Create (new StringReader (project_xml));
180                         var root = ProjectRootElement.Create (xml);
181                         new Project (root);
182                 }
183                 
184                 [Test]
185                 public void EvaluateReferencesWithoutProperties ()
186                 {
187                         foreach (var expr in depends) {
188                                 string project_xml = @"<Project xmlns='http://schemas.microsoft.com/developer/msbuild/2003'>
189   <ItemGroup>
190     <Foo Condition=""{0}"" Include='x' />
191   </ItemGroup>
192 </Project>";
193                                 var xml = XmlReader.Create (new StringReader (string.Format (project_xml, expr)));
194                                 var root = ProjectRootElement.Create (xml);
195                                 try {
196                                         new Project (root);
197                                         Assert.Fail ("Parsing Condition '{0}' should fail", expr);
198                                 } catch (InvalidProjectFileException) {
199                                         continue;
200                                 }
201                         }
202                 }
203                 
204                 [Test]
205                 public void SemicolonHandling ()
206                 {
207                         string project_xml = @"<Project xmlns='http://schemas.microsoft.com/developer/msbuild/2003'>
208   <PropertyGroup>
209     <Foo Condition=""'A;B'=='A;B'"">'A;B'</Foo>
210   </PropertyGroup>
211   <ItemGroup>
212     <Bar Include='$(Foo)' />
213   </ItemGroup>
214 </Project>";
215                         var xml = XmlReader.Create (new StringReader (project_xml));
216                         var root = ProjectRootElement.Create (xml);
217                         var proj = new Project (root); // at this state property is parsed without error i.e. Condition evaluates fine.
218                         var prop = proj.GetProperty ("Foo");
219                         Assert.AreEqual ("'A;B'", prop.EvaluatedValue, "#1");
220                         var items = proj.GetItems ("Bar");
221                         Assert.AreEqual ("'A", items.First ().EvaluatedInclude, "#2");
222                         Assert.AreEqual ("$(Foo)", items.First ().UnevaluatedInclude, "#3");
223                         Assert.AreEqual (2, items.Count, "#4");
224                         Assert.AreEqual ("B'", items.Last ().EvaluatedInclude, "#5");
225                         Assert.AreEqual ("$(Foo)", items.Last ().UnevaluatedInclude, "#6");
226                         Assert.IsTrue (items.First ().Xml == items.Last ().Xml, "#7");
227                 }
228                 
229                 // the same as above except that ItemGroup goes first (and yet evaluated the same).
230                 [Test]
231                 public void EvaluationOrderPropertiesPrecedesItems ()
232                 {
233                         string project_xml = @"<Project xmlns='http://schemas.microsoft.com/developer/msbuild/2003'>
234   <ItemGroup>
235     <Bar Include='$(Foo)' />
236   </ItemGroup>
237   <PropertyGroup>
238     <Foo Condition=""'A;B'=='A;B'"">'A;B'</Foo>
239   </PropertyGroup>
240 </Project>";
241                         var xml = XmlReader.Create (new StringReader (project_xml));
242                         var root = ProjectRootElement.Create (xml);
243                         var proj = new Project (root); // at this state property is parsed without error i.e. Condition evaluates fine.
244                         var prop = proj.GetProperty ("Foo");
245                         Assert.AreEqual ("'A;B'", prop.EvaluatedValue, "#1");
246                         var items = proj.GetItems ("Bar");
247                         Assert.AreEqual ("'A", items.First ().EvaluatedInclude, "#2");
248                         Assert.AreEqual ("$(Foo)", items.First ().UnevaluatedInclude, "#3");
249                         Assert.AreEqual (2, items.Count, "#4");
250                         Assert.AreEqual ("B'", items.Last ().EvaluatedInclude, "#5");
251                         Assert.AreEqual ("$(Foo)", items.Last ().UnevaluatedInclude, "#6");
252                         Assert.IsTrue (items.First ().Xml == items.Last ().Xml, "#7");
253                 }
254                 
255                 [Test]
256                 [ExpectedException (typeof (InvalidProjectFileException))]
257                 public void PropertyReferencesItem ()
258                 {
259                         string project_xml = @"<Project xmlns='http://schemas.microsoft.com/developer/msbuild/2003'>
260   <ItemGroup>
261     <Bar Include='True' />
262   </ItemGroup>
263   <PropertyGroup>
264     <Foo Condition='@(Bar)'>X</Foo><!-- not allowed -->
265   </PropertyGroup>
266 </Project>";
267                         var xml = XmlReader.Create (new StringReader (project_xml));
268                         var root = ProjectRootElement.Create (xml);
269                         new Project (root);
270                 }
271                 
272                 [Test]
273                 [ExpectedException (typeof (InvalidProjectFileException))]
274                 public void SequentialPropertyReferenceNotAllowed ()
275                 {
276                         string xml = @"<Project xmlns='http://schemas.microsoft.com/developer/msbuild/2003'>
277   <PropertyGroup>
278     <A>x</A>
279     <B>y</B>
280     <C Condition=""$(A)$(B)==''"">z</C>
281   </PropertyGroup>
282 </Project>";
283                         var reader = XmlReader.Create (new StringReader (xml));
284                         var root = ProjectRootElement.Create (reader);
285                         new Project (root);
286                 }
287                 
288                 [Test]
289                 public void MetadataExpansion ()
290                 {
291                         string project_xml = @"<Project xmlns='http://schemas.microsoft.com/developer/msbuild/2003'>
292   <PropertyGroup>
293     <X>a/b/c.txt</X>
294   </PropertyGroup>
295   <Target Name='Foo'>
296     <CreateItem Include='$(X)'>
297       <Output TaskParameter='Include' ItemName='I' />
298     </CreateItem>
299     <CreateProperty Value=""@(I->'%(Filename)%(Extension)')"">
300       <Output TaskParameter='Value' PropertyName='P' />
301     </CreateProperty>
302     <Error Text=""Expected 'c.txt' but got '$(P)'"" Condition=""'$(P)'!='c.txt'"" />
303   </Target>
304 </Project>";
305                         var xml = XmlReader.Create (new StringReader (project_xml));
306                         var root = ProjectRootElement.Create (xml);
307                         var p = new ProjectInstance (root);
308                         var sw = new StringWriter ();
309                         var result = p.Build (new ILogger [] { new ConsoleLogger (LoggerVerbosity.Minimal, sw.WriteLine, null, null)});
310                         Assert.IsTrue (result, "#1: " + sw);
311                 }
312
313                 [Test]
314                 public void MultipleBinaryCondition ()
315                 {
316                         string cond = @"$(AndroidIncludeDebugSymbols) == '' And Exists ('$(_IntermediatePdbFile)') And '$(OS)' == 'Windows_NT'";
317                         string project_xml = @"<Project xmlns='http://schemas.microsoft.com/developer/msbuild/2003'>
318   <PropertyGroup>
319     <X>a/b/c.txt</X>
320   </PropertyGroup>
321   <Target Name='Foo'>
322     <CreateItem Include='$(X)'>
323       <Output TaskParameter='Include' ItemName='I' />
324     </CreateItem>
325     <CreateProperty Value=""@(I->'%(Filename)%(Extension)')"">
326       <Output TaskParameter='Value' PropertyName='P' />
327     </CreateProperty>
328     <Error Text=""Expected 'c.txt' but got '$(P)'"" Condition=""" + cond + @""" />
329   </Target>
330 </Project>";
331                         var xml = XmlReader.Create (new StringReader (project_xml));
332                         var root = ProjectRootElement.Create (xml);
333                         var p = new ProjectInstance (root);
334                         var sw = new StringWriter ();
335                         var result = p.Build (new ILogger [] { new ConsoleLogger (LoggerVerbosity.Minimal, sw.WriteLine, null, null)});
336                         Assert.IsTrue (result, "#1: " + sw);
337                 }
338
339                 [Test]
340                 public void FunctionCall ()
341                 {
342                         string project_xml = @"<Project xmlns='http://schemas.microsoft.com/developer/msbuild/2003'>
343   <Target Name='Foo'>
344     <Warning Text=""$([Microsoft.Build.Utilities.ToolLocationHelper]::GetPathToStandardLibraries ('$(TargetFrameworkIdentifier)', '$(TargetFrameworkVersion)', '$(TargetFrameworkProfile)'))\mscorlib.dll'"" />
345   </Target>
346 </Project>";
347                         var xml = XmlReader.Create (new StringReader (project_xml));
348                         var root = ProjectRootElement.Create (xml);
349                         var p = new ProjectInstance (root, null, "4.0", ProjectCollection.GlobalProjectCollection);
350                         var sw = new StringWriter ();
351                         var result = p.Build (new ILogger [] { new ConsoleLogger (LoggerVerbosity.Minimal, sw.WriteLine, null, null)});
352                         Assert.IsTrue (result, "#1: " + sw);
353                 }
354         }
355 }
356