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