2 // ExpressionParserTest.cs
5 // Atsushi Enomoto (atsushi@xamarin.com)
7 // Copyright (C) 2013 Xamarin Inc. (http://www.xamarin.com)
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:
17 // The above copyright notice and this permission notice shall be
18 // included in all copies or substantial portions of the Software.
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.
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;
41 namespace MonoTests.Microsoft.Build.Internal
44 public class ExpressionParserTest
46 string [] invalid_always = {
48 "$([DateTime.Now])", // fullname required
49 "$([System.DateTime.Now])", // member cannot be invoked with '.'
51 string [] invalid_as_boolean = {
63 "A", // must be evaluated as a boolean
64 "1", // ditto (no default conversion to bool)
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
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)",
102 string [] depends = {
103 // valid only if evaluated to boolean
109 public void EvaluateAsBoolean ()
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'>
114 <Foo Condition=""{0}"" Include='x' />
117 var xml = XmlReader.Create (new StringReader (string.Format (project_xml, expr)));
118 var root = ProjectRootElement.Create (xml);
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)
128 throw new Exception (string.Format ("unexpected exception to parse '{0}'", expr), ex);
134 public void EvaluateAsString ()
136 foreach (var expr in invalid_always.Concat (invalid_as_boolean).Concat (valid)) {
138 string project_xml = @"<Project xmlns='http://schemas.microsoft.com/developer/msbuild/2003'>
140 <Foo Include=""{0}"" />
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
147 } catch (Exception ex) {
148 if (!invalid_always.Contains (expr))
149 throw new Exception (string.Format ("failed to parse '{0}'", expr), ex);
155 public void EvaluatePropertyReferencesWithProperties ()
157 string project_xml = @"<Project xmlns='http://schemas.microsoft.com/developer/msbuild/2003'>
159 <Foo Condition=""$(foo)"" Include='x' />
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);
170 public void EvaluateItemReferences ()
172 string project_xml = @"<Project xmlns='http://schemas.microsoft.com/developer/msbuild/2003'>
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' />
179 var xml = XmlReader.Create (new StringReader (project_xml));
180 var root = ProjectRootElement.Create (xml);
185 public void EvaluateReferencesWithoutProperties ()
187 foreach (var expr in depends) {
188 string project_xml = @"<Project xmlns='http://schemas.microsoft.com/developer/msbuild/2003'>
190 <Foo Condition=""{0}"" Include='x' />
193 var xml = XmlReader.Create (new StringReader (string.Format (project_xml, expr)));
194 var root = ProjectRootElement.Create (xml);
197 Assert.Fail ("Parsing Condition '{0}' should fail", expr);
198 } catch (InvalidProjectFileException) {
205 public void SemicolonHandling ()
207 string project_xml = @"<Project xmlns='http://schemas.microsoft.com/developer/msbuild/2003'>
209 <Foo Condition=""'A;B'=='A;B'"">'A;B'</Foo>
212 <Bar Include='$(Foo)' />
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");
229 // the same as above except that ItemGroup goes first (and yet evaluated the same).
231 public void EvaluationOrderPropertiesPrecedesItems ()
233 string project_xml = @"<Project xmlns='http://schemas.microsoft.com/developer/msbuild/2003'>
235 <Bar Include='$(Foo)' />
238 <Foo Condition=""'A;B'=='A;B'"">'A;B'</Foo>
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");
256 [ExpectedException (typeof (InvalidProjectFileException))]
257 public void PropertyReferencesItem ()
259 string project_xml = @"<Project xmlns='http://schemas.microsoft.com/developer/msbuild/2003'>
261 <Bar Include='True' />
264 <Foo Condition='@(Bar)'>X</Foo><!-- not allowed -->
267 var xml = XmlReader.Create (new StringReader (project_xml));
268 var root = ProjectRootElement.Create (xml);
273 [ExpectedException (typeof (InvalidProjectFileException))]
274 public void SequentialPropertyReferenceNotAllowed ()
276 string xml = @"<Project xmlns='http://schemas.microsoft.com/developer/msbuild/2003'>
280 <C Condition=""$(A)$(B)==''"">z</C>
283 var reader = XmlReader.Create (new StringReader (xml));
284 var root = ProjectRootElement.Create (reader);
289 public void MetadataExpansion ()
291 string project_xml = @"<Project xmlns='http://schemas.microsoft.com/developer/msbuild/2003'>
296 <CreateItem Include='$(X)'>
297 <Output TaskParameter='Include' ItemName='I' />
299 <CreateProperty Value=""@(I->'%(Filename)%(Extension)')"">
300 <Output TaskParameter='Value' PropertyName='P' />
302 <Error Text=""Expected 'c.txt' but got '$(P)'"" Condition=""'$(P)'!='c.txt'"" />
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);
314 public void MultipleBinaryCondition ()
316 string cond = @"$(AndroidIncludeDebugSymbols) == '' And Exists ('$(_IntermediatePdbFile)') And '$(OS)' == 'Windows_NT'";
317 string project_xml = @"<Project xmlns='http://schemas.microsoft.com/developer/msbuild/2003'>
322 <CreateItem Include='$(X)'>
323 <Output TaskParameter='Include' ItemName='I' />
325 <CreateProperty Value=""@(I->'%(Filename)%(Extension)')"">
326 <Output TaskParameter='Value' PropertyName='P' />
328 <Error Text=""Expected 'c.txt' but got '$(P)'"" Condition=""" + cond + @""" />
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);
340 public void FunctionCall ()
342 string project_xml = @"<Project xmlns='http://schemas.microsoft.com/developer/msbuild/2003'>
344 <Warning Text=""$([Microsoft.Build.Utilities.ToolLocationHelper]::GetPathToStandardLibraries ('$(TargetFrameworkIdentifier)', '$(TargetFrameworkVersion)', '$(TargetFrameworkProfile)'))\mscorlib.dll'"" />
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);