1 #region Copyright & License Information
2 /*
3  * Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
4  * This file is part of OpenRA, which is free software. It is made
5  * available to you under the terms of the GNU General Public License
6  * as published by the Free Software Foundation, either version 3 of
7  * the License, or (at your option) any later version. For more
8  * information, see COPYING.
9  */
10 #endregion
11 
12 using System.Collections.Generic;
13 using System.IO;
14 using NUnit.Framework;
15 using OpenRA.Support;
16 
17 namespace OpenRA.Test
18 {
19 	[TestFixture]
20 	public class VariableExpressionTest
21 	{
22 		IReadOnlyDictionary<string, int> testValues = new ReadOnlyDictionary<string, int>(new Dictionary<string, int>()
23 		{
24 			{ "t", 5 },
25 			{ "t-1", 7 },
26 			{ "one", 1 },
27 			{ "five", 5 }
28 		});
29 
AssertFalse(string expression)30 		void AssertFalse(string expression)
31 		{
32 			Assert.False(new BooleanExpression(expression).Evaluate(testValues), expression);
33 		}
34 
AssertTrue(string expression)35 		void AssertTrue(string expression)
36 		{
37 			Assert.True(new BooleanExpression(expression).Evaluate(testValues), expression);
38 		}
39 
AssertValue(string expression, int value)40 		void AssertValue(string expression, int value)
41 		{
42 			Assert.AreEqual(value, new IntegerExpression(expression).Evaluate(testValues), expression);
43 		}
44 
AssertParseFailure(string expression)45 		void AssertParseFailure(string expression)
46 		{
47 			Assert.Throws(typeof(InvalidDataException), () => new IntegerExpression(expression).Evaluate(testValues), expression);
48 		}
49 
AssertParseFailure(string expression, string errorMessage)50 		void AssertParseFailure(string expression, string errorMessage)
51 		{
52 			var actualErrorMessage = Assert.Throws(typeof(InvalidDataException),
53 				() => new IntegerExpression(expression).Evaluate(testValues),
54 				expression).Message;
55 			Assert.AreEqual(errorMessage, actualErrorMessage, expression + "   ===>   " + actualErrorMessage);
56 		}
57 
58 		[TestCase(TestName = "Numbers")]
TestNumbers()59 		public void TestNumbers()
60 		{
61 			AssertParseFailure("1a", "Number 1 and variable merged at index 0");
62 			AssertValue("0", 0);
63 			AssertValue("1", 1);
64 			AssertValue("12", 12);
65 			AssertValue("-1", -1);
66 			AssertValue("-12", -12);
67 		}
68 
69 		[TestCase(TestName = "Variables")]
TestVariables()70 		public void TestVariables()
71 		{
72 			AssertValue("one", 1);
73 			AssertValue("five", 5);
74 		}
75 
76 		[TestCase(TestName = "Boolean Constants")]
TestBoolConsts()77 		public void TestBoolConsts()
78 		{
79 			AssertValue(" true", 1);
80 			AssertValue(" true ", 1);
81 			AssertValue("true", 1);
82 			AssertValue("false", 0);
83 			AssertValue("tru", 0);
84 			AssertValue("fals", 0);
85 			AssertValue("tr", 0);
86 			AssertValue("fal", 0);
87 		}
88 
89 		[TestCase(TestName = "Booleans")]
TestBooleans()90 		public void TestBooleans()
91 		{
92 			AssertValue("false", 0);
93 			AssertValue("true", 1);
94 		}
95 
96 		[TestCase(TestName = "AND operation")]
TestAnd()97 		public void TestAnd()
98 		{
99 			AssertTrue("true && true");
100 			AssertFalse("false && false");
101 			AssertFalse("true && false");
102 			AssertFalse("false && true");
103 			AssertValue("2 && false", 0);
104 			AssertValue("false && 2", 0);
105 			AssertValue("3 && 2", 1);
106 			AssertValue("2 && 3", 1);
107 		}
108 
109 		[TestCase(TestName = "OR operation")]
TestOR()110 		public void TestOR()
111 		{
112 			AssertTrue("true || true");
113 			AssertFalse("false || false");
114 			AssertTrue("true || false");
115 			AssertTrue("false || true");
116 			AssertValue("2 || false", 1);
117 			AssertValue("false || 2", 1);
118 			AssertValue("3 || 2", 1);
119 			AssertValue("2 || 3", 1);
120 		}
121 
122 		[TestCase(TestName = "Equals operation")]
TestEquals()123 		public void TestEquals()
124 		{
125 			AssertTrue("true == true");
126 			AssertTrue("false == false");
127 			AssertFalse("true == false");
128 			AssertFalse("false == true");
129 			AssertTrue("1 == 1");
130 			AssertTrue("0 == 0");
131 			AssertFalse("1 == 0");
132 			AssertTrue("1 == true");
133 			AssertFalse("1 == false");
134 			AssertTrue("0 == false");
135 			AssertFalse("0 == true");
136 			AssertValue("12 == 12", 1);
137 			AssertValue("1 == 12", 0);
138 		}
139 
140 		[TestCase(TestName = "Not-equals operation")]
TestNotEquals()141 		public void TestNotEquals()
142 		{
143 			AssertFalse("true != true");
144 			AssertFalse("false != false");
145 			AssertTrue("true != false");
146 			AssertTrue("false != true");
147 			AssertValue("1 != 2", 1);
148 			AssertValue("1 != 1", 0);
149 			AssertFalse("1 != true");
150 			AssertFalse("0 != false");
151 			AssertTrue("1 != false");
152 			AssertTrue("0 != true");
153 		}
154 
155 		[TestCase(TestName = "NOT operation")]
TestNOT()156 		public void TestNOT()
157 		{
158 			AssertValue("!true", 0);
159 			AssertValue("!false", 1);
160 			AssertValue("!!true", 1);
161 			AssertValue("!!false", 0);
162 			AssertValue("!0", 1);
163 			AssertValue("!1", 0);
164 			AssertValue("!5", 0);
165 			AssertValue("!!5", 1);
166 			AssertValue("!-5", 0);
167 		}
168 
169 		[TestCase(TestName = "Relation operations")]
TestRelations()170 		public void TestRelations()
171 		{
172 			AssertValue("2 < 5", 1);
173 			AssertValue("0 < 5", 1);
174 			AssertValue("5 < 2", 0);
175 			AssertValue("5 < 5", 0);
176 			AssertValue("-5 < 0", 1);
177 			AssertValue("-2 < -5", 0);
178 			AssertValue("-5 < -2", 1);
179 			AssertValue("-5 < -5", 0);
180 			AssertValue("-7 < 5", 1);
181 			AssertValue("0 <= 5", 1);
182 			AssertValue("2 <= 5", 1);
183 			AssertValue("5 <= 2", 0);
184 			AssertValue("5 <= 5", 1);
185 			AssertValue("5 <= 0", 0);
186 			AssertValue("-2 <= -5", 0);
187 			AssertValue("-5 <= -2", 1);
188 			AssertValue("-5 <= -5", 1);
189 			AssertValue("-7 <= 5", 1);
190 			AssertValue("0 <= -5", 0);
191 			AssertValue("-5 <= 0", 1);
192 			AssertValue("5 > 2", 1);
193 			AssertValue("0 > 5", 0);
194 			AssertValue("2 > 5", 0);
195 			AssertValue("5 > 5", 0);
196 			AssertValue("5 > 0", 1);
197 			AssertValue("-2 > -5", 1);
198 			AssertValue("-7 > -5", 0);
199 			AssertValue("-5 > -5", 0);
200 			AssertValue("-4 > -5", 1);
201 			AssertValue("5 >= 0", 1);
202 			AssertValue("0 >= 5", 0);
203 			AssertValue("5 >= 2", 1);
204 			AssertValue("2 >= 5", 0);
205 			AssertValue("5 >= 5", 1);
206 			AssertValue("-5 >= 0", 0);
207 			AssertValue("0 >= -5", 1);
208 			AssertValue("-7 >= 5", 0);
209 			AssertValue("-5 >= -5", 1);
210 			AssertValue("-4 >= -5", 1);
211 		}
212 
213 		[TestCase(TestName = "Relation Mixed Precedence")]
TestRelationMixedPrecedence()214 		public void TestRelationMixedPrecedence()
215 		{
216 			AssertValue("5 <= 5 && 2 > 1", 1);
217 			AssertValue("5 > 5 || 2 > 1", 1);
218 			AssertValue("5 > 5 || 1 > 1", 0);
219 			AssertValue("5 <= 5 == 2 > 1", 1);
220 			AssertValue("5 > 5 == 2 > 1", 0);
221 			AssertValue("5 > 5 == 1 > 1", 1);
222 			AssertValue("5 <= 5 != 2 > 1", 0);
223 			AssertValue("5 > 5 != 2 > 1", 1);
224 			AssertValue("5 > 5 != 1 > 1", 0);
225 			AssertValue("5 > 5 != 1 >= 1", 1);
226 		}
227 
228 		[TestCase(TestName = "AND-OR Precedence")]
TestAndOrPrecedence()229 		public void TestAndOrPrecedence()
230 		{
231 			AssertTrue("true && false || true");
232 			AssertFalse("false || false && true");
233 			AssertTrue("true && !true || !false");
234 			AssertFalse("false || !true && !false");
235 		}
236 
237 		[TestCase(TestName = "Parenthesis")]
TestParens()238 		public void TestParens()
239 		{
240 			AssertTrue("(true)");
241 			AssertTrue("((true))");
242 			AssertFalse("(false)");
243 			AssertFalse("((false))");
244 		}
245 
246 		[TestCase(TestName = "Arithmetic")]
TestArithmetic()247 		public void TestArithmetic()
248 		{
249 			AssertValue("~0", ~0);
250 			AssertValue("-0", 0);
251 			AssertValue("-a", 0);
252 			AssertValue("-true", -1);
253 			AssertValue("~-0", -1);
254 			AssertValue("2 + 3", 5);
255 			AssertValue("2 + 0", 2);
256 			AssertValue("2 + 3", 5);
257 			AssertValue("5 - 3", 2);
258 			AssertValue("5 - -3", 8);
259 			AssertValue("5 - 0", 5);
260 			AssertValue("2 * 3", 6);
261 			AssertValue("2 * 0", 0);
262 			AssertValue("2 * -3", -6);
263 			AssertValue("-2 * 3", -6);
264 			AssertValue("-2 * -3", 6);
265 			AssertValue("6 / 3", 2);
266 			AssertValue("7 / 3", 2);
267 			AssertValue("-6 / 3", -2);
268 			AssertValue("6 / -3", -2);
269 			AssertValue("-6 / -3", 2);
270 			AssertValue("8 / 3", 2);
271 			AssertValue("6 % 3", 0);
272 			AssertValue("7 % 3", 1);
273 			AssertValue("8 % 3", 2);
274 			AssertValue("7 % 0", 0);
275 			AssertValue("-7 % 3", -1);
276 			AssertValue("7 % -3", 1);
277 			AssertValue("-7 % -3", -1);
278 			AssertValue("8 / 0", 0);
279 		}
280 
281 		[TestCase(TestName = "Arithmetic Mixed")]
TestArithmeticMixed()282 		public void TestArithmeticMixed()
283 		{
284 			AssertValue("~~0", 0);
285 			AssertValue("-~0", 1);
286 			AssertValue("~- 0", -1);
287 			AssertValue("2 * 3 + 4", 10);
288 			AssertValue("2 * 3 - 4", 2);
289 			AssertValue("2 + 3 * 4", 14);
290 			AssertValue("2 + 3 % 4", 5);
291 			AssertValue("2 + 3 / 4", 2);
292 			AssertValue("2 * 3 / 4", 1);
293 			AssertValue("8 / 2 == 4", 1);
294 			AssertValue("~2 + ~3", -7);
295 			AssertValue("~(~2 + ~3)", 6);
296 		}
297 
298 		[TestCase(TestName = "Hyphen")]
TestHyphen()299 		public void TestHyphen()
300 		{
301 			AssertValue("t-1", 7);
302 			AssertValue("-t-1", -7);
303 			AssertValue("t - 1", 4);
304 			AssertValue("-1", -1);
305 		}
306 
307 		[TestCase(TestName = "Parenthesis and mixed operations")]
TestMixedParens()308 		public void TestMixedParens()
309 		{
310 			AssertTrue("(!false)");
311 			AssertTrue("!(false)");
312 			AssertFalse("!(!false)");
313 			AssertTrue("(true) || (false)");
314 			AssertTrue("true && (false || true)");
315 			AssertTrue("(true && false) || true");
316 			AssertTrue("!(true && false) || false");
317 			AssertTrue("((true != true) == false) && true");
318 			AssertFalse("(true != false) == false && true");
319 			AssertTrue("true || ((true != false) != !(false && true))");
320 			AssertFalse("((true != false) != !(false && true))");
321 		}
322 
323 		[TestCase(TestName = "Test parser errors")]
TestParseErrors()324 		public void TestParseErrors()
325 		{
326 			AssertParseFailure("()", "Empty parenthesis at index 0");
327 			AssertParseFailure("! && true", "Missing value or sub-expression or there is an extra operator `!` at index 0 or `&&` at index 2");
328 			AssertParseFailure("(true", "Unclosed opening parenthesis at index 0");
329 			AssertParseFailure(")true", "Unmatched closing parenthesis at index 0");
330 			AssertParseFailure("false)", "Unmatched closing parenthesis at index 5");
331 			AssertParseFailure("false(", "Missing binary operation before `(` at index 5");
332 			AssertParseFailure("(", "Missing value or sub-expression at end for `(` operator");
333 			AssertParseFailure(")", "Unmatched closing parenthesis at index 0");
334 			AssertParseFailure("false!", "Missing binary operation before `!` at index 5");
335 			AssertParseFailure("true false", "Missing binary operation before `false` at index 5");
336 			AssertParseFailure("true & false", "Unexpected character '&' at index 5 - should it be `&&`?");
337 			AssertParseFailure("true | false", "Unexpected character '|' at index 5 - should it be `||`?");
338 			AssertParseFailure("true : false", "Invalid character ':' at index 5");
339 			AssertParseFailure("true & false && !", "Unexpected character '&' at index 5 - should it be `&&`?");
340 			AssertParseFailure("(true && !)", "Missing value or sub-expression or there is an extra operator `!` at index 9 or `)` at index 10");
341 			AssertParseFailure("&& false", "Missing value or sub-expression at beginning for `&&` operator");
342 			AssertParseFailure("false ||", "Missing value or sub-expression at end for `||` operator");
343 			AssertParseFailure("1 <", "Missing value or sub-expression at end for `<` operator");
344 			AssertParseFailure("-1a", "Number -1 and variable merged at index 0");
345 		}
346 
347 		[TestCase(TestName = "Test hyphen parser errors")]
TestParseHyphenErrors()348 		public void TestParseHyphenErrors()
349 		{
350 			AssertParseFailure("-", "Missing value or sub-expression at end for `-` operator");
351 			AssertParseFailure("-1-1", "Missing binary operation before `-1` at index 2");
352 			AssertParseFailure("5-1", "Missing binary operation before `-1` at index 1");
353 			AssertParseFailure("6 -1", "Missing binary operation before `-1` at index 2");
354 			AssertParseFailure("t -1", "Missing binary operation before `-1` at index 2");
355 		}
356 
357 		[TestCase(TestName = "Test mixed charaters at end of identifier parser errors")]
TestParseMixedEndErrors()358 		public void TestParseMixedEndErrors()
359 		{
360 			AssertParseFailure("t- 1", "Invalid identifier end character at index 1 for `t-`");
361 			AssertParseFailure("t-", "Invalid identifier end character at index 1 for `t-`");
362 			AssertParseFailure("t. 1", "Invalid identifier end character at index 1 for `t.`");
363 			AssertParseFailure("t.", "Invalid identifier end character at index 1 for `t.`");
364 			AssertParseFailure("t@ 1", "Invalid identifier end character at index 1 for `t@`");
365 			AssertParseFailure("t@", "Invalid identifier end character at index 1 for `t@`");
366 			AssertParseFailure("t$ 1", "Invalid identifier end character at index 1 for `t$`");
367 			AssertParseFailure("t$", "Invalid identifier end character at index 1 for `t$`");
368 		}
369 
370 		[TestCase(TestName = "Test binary operator whitespace parser errors")]
TestParseSpacedBinaryOperatorErrors()371 		public void TestParseSpacedBinaryOperatorErrors()
372 		{
373 			// `t-1` is valid variable name and `t- 1` starts with an invalid variable name.
374 			// `6 -1`, `6-1`, `t -1` contain `-1` and are missing a binary operator.
375 			AssertParseFailure("6- 1", "Missing whitespace at index 2, before `-` operator.");
376 
377 			AssertParseFailure("6+ 1", "Missing whitespace at index 2, before `+` operator.");
378 			AssertParseFailure("t+ 1", "Missing whitespace at index 2, before `+` operator.");
379 			AssertParseFailure("6 +1", "Missing whitespace at index 3, after `+` operator.");
380 			AssertParseFailure("t +1", "Missing whitespace at index 3, after `+` operator.");
381 			AssertParseFailure("6+1", "Missing whitespace at index 2, before `+` operator.");
382 			AssertParseFailure("t+1", "Missing whitespace at index 2, before `+` operator.");
383 
384 			AssertParseFailure("6* 1", "Missing whitespace at index 2, before `*` operator.");
385 			AssertParseFailure("t* 1", "Missing whitespace at index 2, before `*` operator.");
386 			AssertParseFailure("6 *1", "Missing whitespace at index 3, after `*` operator.");
387 			AssertParseFailure("t *1", "Missing whitespace at index 3, after `*` operator.");
388 			AssertParseFailure("6*1", "Missing whitespace at index 2, before `*` operator.");
389 			AssertParseFailure("t*1", "Missing whitespace at index 2, before `*` operator.");
390 
391 			AssertParseFailure("6/ 1", "Missing whitespace at index 2, before `/` operator.");
392 			AssertParseFailure("t/ 1", "Missing whitespace at index 2, before `/` operator.");
393 			AssertParseFailure("6 /1", "Missing whitespace at index 3, after `/` operator.");
394 			AssertParseFailure("t /1", "Missing whitespace at index 3, after `/` operator.");
395 			AssertParseFailure("6/1", "Missing whitespace at index 2, before `/` operator.");
396 			AssertParseFailure("t/1", "Missing whitespace at index 2, before `/` operator.");
397 
398 			AssertParseFailure("6% 1", "Missing whitespace at index 2, before `%` operator.");
399 			AssertParseFailure("t% 1", "Missing whitespace at index 2, before `%` operator.");
400 			AssertParseFailure("6 %1", "Missing whitespace at index 3, after `%` operator.");
401 			AssertParseFailure("t %1", "Missing whitespace at index 3, after `%` operator.");
402 			AssertParseFailure("6%1", "Missing whitespace at index 2, before `%` operator.");
403 			AssertParseFailure("t%1", "Missing whitespace at index 2, before `%` operator.");
404 
405 			AssertParseFailure("6< 1", "Missing whitespace at index 2, before `<` operator.");
406 			AssertParseFailure("t< 1", "Missing whitespace at index 2, before `<` operator.");
407 			AssertParseFailure("6 <1", "Missing whitespace at index 3, after `<` operator.");
408 			AssertParseFailure("t <1", "Missing whitespace at index 3, after `<` operator.");
409 			AssertParseFailure("6<1", "Missing whitespace at index 2, before `<` operator.");
410 			AssertParseFailure("t<1", "Missing whitespace at index 2, before `<` operator.");
411 
412 			AssertParseFailure("6> 1", "Missing whitespace at index 2, before `>` operator.");
413 			AssertParseFailure("t> 1", "Missing whitespace at index 2, before `>` operator.");
414 			AssertParseFailure("6 >1", "Missing whitespace at index 3, after `>` operator.");
415 			AssertParseFailure("t >1", "Missing whitespace at index 3, after `>` operator.");
416 			AssertParseFailure("6>1", "Missing whitespace at index 2, before `>` operator.");
417 			AssertParseFailure("t>1", "Missing whitespace at index 2, before `>` operator.");
418 
419 			AssertParseFailure("6&& 1", "Missing whitespace at index 3, before `&&` operator.");
420 			AssertParseFailure("t&& 1", "Missing whitespace at index 3, before `&&` operator.");
421 			AssertParseFailure("6 &&1", "Missing whitespace at index 4, after `&&` operator.");
422 			AssertParseFailure("t &&1", "Missing whitespace at index 4, after `&&` operator.");
423 			AssertParseFailure("6&&1", "Missing whitespace at index 3, before `&&` operator.");
424 			AssertParseFailure("t&&1", "Missing whitespace at index 3, before `&&` operator.");
425 
426 			AssertParseFailure("6|| 1", "Missing whitespace at index 3, before `||` operator.");
427 			AssertParseFailure("t|| 1", "Missing whitespace at index 3, before `||` operator.");
428 			AssertParseFailure("6 ||1", "Missing whitespace at index 4, after `||` operator.");
429 			AssertParseFailure("t ||1", "Missing whitespace at index 4, after `||` operator.");
430 			AssertParseFailure("6||1", "Missing whitespace at index 3, before `||` operator.");
431 			AssertParseFailure("t||1", "Missing whitespace at index 3, before `||` operator.");
432 
433 			AssertParseFailure("6== 1", "Missing whitespace at index 3, before `==` operator.");
434 			AssertParseFailure("t== 1", "Missing whitespace at index 3, before `==` operator.");
435 			AssertParseFailure("6 ==1", "Missing whitespace at index 4, after `==` operator.");
436 			AssertParseFailure("t ==1", "Missing whitespace at index 4, after `==` operator.");
437 			AssertParseFailure("6==1", "Missing whitespace at index 3, before `==` operator.");
438 			AssertParseFailure("t==1", "Missing whitespace at index 3, before `==` operator.");
439 
440 			AssertParseFailure("6!= 1", "Missing whitespace at index 3, before `!=` operator.");
441 			AssertParseFailure("t!= 1", "Missing whitespace at index 3, before `!=` operator.");
442 			AssertParseFailure("6 !=1", "Missing whitespace at index 4, after `!=` operator.");
443 			AssertParseFailure("t !=1", "Missing whitespace at index 4, after `!=` operator.");
444 			AssertParseFailure("6!=1", "Missing whitespace at index 3, before `!=` operator.");
445 			AssertParseFailure("t!=1", "Missing whitespace at index 3, before `!=` operator.");
446 
447 			AssertParseFailure("6<= 1", "Missing whitespace at index 3, before `<=` operator.");
448 			AssertParseFailure("t<= 1", "Missing whitespace at index 3, before `<=` operator.");
449 			AssertParseFailure("6 <=1", "Missing whitespace at index 4, after `<=` operator.");
450 			AssertParseFailure("t <=1", "Missing whitespace at index 4, after `<=` operator.");
451 			AssertParseFailure("6<=1", "Missing whitespace at index 3, before `<=` operator.");
452 			AssertParseFailure("t<=1", "Missing whitespace at index 3, before `<=` operator.");
453 
454 			AssertParseFailure("6>= 1", "Missing whitespace at index 3, before `>=` operator.");
455 			AssertParseFailure("t>= 1", "Missing whitespace at index 3, before `>=` operator.");
456 			AssertParseFailure("6 >=1", "Missing whitespace at index 4, after `>=` operator.");
457 			AssertParseFailure("t >=1", "Missing whitespace at index 4, after `>=` operator.");
458 			AssertParseFailure("6>=1", "Missing whitespace at index 3, before `>=` operator.");
459 			AssertParseFailure("t>=1", "Missing whitespace at index 3, before `>=` operator.");
460 		}
461 
462 		[TestCase(TestName = "Undefined symbols are treated as `false` (0) values")]
TestUndefinedSymbols()463 		public void TestUndefinedSymbols()
464 		{
465 			AssertFalse("undef1 || undef2");
466 			AssertValue("undef1", 0);
467 			AssertValue("undef1 + undef2", 0);
468 		}
469 	}
470 }
471