#include "SUDSExpression.h" #include "Misc/AutomationTest.h" UE_DISABLE_OPTIMIZATION IMPLEMENT_SIMPLE_AUTOMATION_TEST(FTestExpressions, "SUDSTest.TestExpressions", EAutomationTestFlags::EditorContext | EAutomationTestFlags::ClientContext | EAutomationTestFlags::ProductFilter) bool FTestExpressions::RunTest(const FString& Parameters) { FSUDSExpression Expr; TMap Variables; TMap GlobalVariables; Variables.Add("Six", 6); TestTrue("SimpleVarParse", Expr.ParseFromString("3 + 4 * {Six} + 1", nullptr)); TestEqual("Eval", Expr.Evaluate(Variables, GlobalVariables).GetIntValue(), 28); auto& RPN = Expr.GetQueue(); if (TestEqual("Queue len", RPN.Num(), 7)) { // RPN should be 3 4 6 * + 1 + TestEqual("Queue 0", RPN[0].GetOperandValue().GetIntValue(), 3); TestEqual("Queue 1", RPN[1].GetOperandValue().GetIntValue(), 4); TestEqual("Queue 2", RPN[2].GetOperandValue().GetVariableNameValue().ToString(), "Six"); TestEqual("Queue 3", RPN[3].GetType(), ESUDSExpressionItemType::Multiply); TestEqual("Queue 4", RPN[4].GetType(), ESUDSExpressionItemType::Add); TestEqual("Queue 5", RPN[5].GetOperandValue().GetIntValue(), 1); TestEqual("Queue 6", RPN[6].GetType(), ESUDSExpressionItemType::Add); } if (TestEqual("Variable count", Expr.GetVariableNames().Num(), 1)) { TestEqual("Variable name", Expr.GetVariableNames()[0].ToString(), "Six"); } TestTrue("Arithmetic", Expr.ParseFromString("-6.7 * 2 + (21.3 - 8) * 5", nullptr)); TestEqual("Eval", Expr.Evaluate(Variables, GlobalVariables).GetFloatValue(), 53.1f); // Modulo operator TestTrue("ModuloIntOperator", Expr.ParseFromString("11 % 5", nullptr)); TestEqual("Eval", Expr.Evaluate(Variables, GlobalVariables).GetIntValue(), 1); TestTrue("ModuloFloatOperator", Expr.ParseFromString("7.25 % 3.0", nullptr)); TestEqual("Eval", Expr.Evaluate(Variables, GlobalVariables).GetFloatValue(), 1.25f); // Explicit FSUDSValue(true) needed to avoid it using the int conversion by default Variables.Add("IsATest", FSUDSValue(true)); TestTrue("BoolSingleValueParse", Expr.ParseFromString("{IsATest}", nullptr)); TestTrue("Eval", Expr.Evaluate(Variables, GlobalVariables).GetBooleanValue()); if (TestEqual("Variable count", Expr.GetVariableNames().Num(), 1)) { TestEqual("Variable name", Expr.GetVariableNames()[0].ToString(), "IsATest"); } Variables.Add("SomethingFalse", FSUDSValue(false)); Variables.Add("SomethingTrue", FSUDSValue(true)); Variables.Add("SomethingElseFalse", FSUDSValue(false)); TestTrue("BoolCompound1", Expr.ParseFromString("!{SomethingFalse} && {SomethingTrue}", nullptr)); TestTrue("Eval", Expr.Evaluate(Variables, GlobalVariables).GetBooleanValue()); if (TestEqual("Variable count", Expr.GetVariableNames().Num(), 2)) { TestEqual("Variable name", Expr.GetVariableNames()[0].ToString(), "SomethingFalse"); TestEqual("Variable name", Expr.GetVariableNames()[1].ToString(), "SomethingTrue"); } TestTrue("BoolCompound2", Expr.ParseFromString("{SomethingFalse} || {SomethingTrue}", nullptr)); TestTrue("Eval", Expr.Evaluate(Variables, GlobalVariables).GetBooleanValue()); TestTrue("BoolCompound3", Expr.ParseFromString("{SomethingFalse} or {SomethingTrue}", nullptr)); TestTrue("Eval", Expr.Evaluate(Variables, GlobalVariables).GetBooleanValue()); // Test parentheses changing precedence & result // True result for successful parsing, but false for Eval unless we parenthesise TestTrue("BoolCompound4", Expr.ParseFromString("!{SomethingFalse} && {SomethingElseFalse} && {SomethingTrue}", nullptr)); TestFalse("Eval", Expr.Evaluate(Variables, GlobalVariables).GetBooleanValue()); if (TestEqual("Variable count", Expr.GetVariableNames().Num(), 3)) { TestEqual("Variable name", Expr.GetVariableNames()[0].ToString(), "SomethingFalse"); TestEqual("Variable name", Expr.GetVariableNames()[1].ToString(), "SomethingElseFalse"); TestEqual("Variable name", Expr.GetVariableNames()[2].ToString(), "SomethingTrue"); } TestTrue("BoolCompound5", Expr.ParseFromString("!({SomethingFalse} && {SomethingElseFalse}) && {SomethingTrue}", nullptr)); TestTrue("Eval", Expr.Evaluate(Variables, GlobalVariables).GetBooleanValue()); TestTrue("BoolCompound6", Expr.ParseFromString("not {SomethingFalse} and {SomethingElseFalse} and {SomethingTrue}", nullptr)); TestFalse("Eval", Expr.Evaluate(Variables, GlobalVariables).GetBooleanValue()); TestTrue("BoolCompound7", Expr.ParseFromString("not ({SomethingFalse} and {SomethingElseFalse}) and {SomethingTrue}", nullptr)); TestTrue("Eval", Expr.Evaluate(Variables, GlobalVariables).GetBooleanValue()); Variables.Add("Seven", 7); TestTrue("Comparisons", Expr.ParseFromString("{Six} == 6", nullptr)); TestTrue("Eval", Expr.Evaluate(Variables, GlobalVariables).GetBooleanValue()); TestTrue("Comparisons", Expr.ParseFromString("{Six} = 6", nullptr)); TestTrue("Eval", Expr.Evaluate(Variables, GlobalVariables).GetBooleanValue()); TestTrue("Comparisons", Expr.ParseFromString("{Six} >= 6", nullptr)); TestTrue("Eval", Expr.Evaluate(Variables, GlobalVariables).GetBooleanValue()); TestTrue("Comparisons", Expr.ParseFromString("{Six} > 6", nullptr)); TestFalse("Eval", Expr.Evaluate(Variables, GlobalVariables).GetBooleanValue()); TestTrue("Comparisons", Expr.ParseFromString("{Six} < 6", nullptr)); TestFalse("Eval", Expr.Evaluate(Variables, GlobalVariables).GetBooleanValue()); TestTrue("Comparisons", Expr.ParseFromString("{Six} <= 6", nullptr)); TestTrue("Eval", Expr.Evaluate(Variables, GlobalVariables).GetBooleanValue()); TestTrue("Comparisons", Expr.ParseFromString("{Six} < {Seven}", nullptr)); TestTrue("Eval", Expr.Evaluate(Variables, GlobalVariables).GetBooleanValue()); TestTrue("Comparisons", Expr.ParseFromString("{Seven} > {Six}", nullptr)); TestTrue("Eval", Expr.Evaluate(Variables, GlobalVariables).GetBooleanValue()); TestTrue("Comparisons", Expr.ParseFromString("{Seven} != {Six}", nullptr)); TestTrue("Eval", Expr.Evaluate(Variables, GlobalVariables).GetBooleanValue()); TestTrue("Comparisons", Expr.ParseFromString("{Seven} != {Seven}", nullptr)); TestFalse("Eval", Expr.Evaluate(Variables, GlobalVariables).GetBooleanValue()); // Mixed float/int comparisons Variables.Add("EightFloat", 8.1f); TestTrue("Comparisons", Expr.ParseFromString("{EightFloat} > 8", nullptr)); TestTrue("Eval", Expr.Evaluate(Variables, GlobalVariables).GetBooleanValue()); TestTrue("Comparisons", Expr.ParseFromString("{EightFloat} > {Seven}", nullptr)); TestTrue("Eval", Expr.Evaluate(Variables, GlobalVariables).GetBooleanValue()); TestTrue("Comparisons", Expr.ParseFromString("{Six} < {EightFloat}", nullptr)); TestTrue("Eval", Expr.Evaluate(Variables, GlobalVariables).GetBooleanValue()); // Fuzzy float comparisons Variables.Add("EightFloatPlusMargin", 8.1000002f); TestTrue("Comparisons", Expr.ParseFromString("{EightFloat} == 8.1", nullptr)); TestTrue("Eval", Expr.Evaluate(Variables, GlobalVariables).GetBooleanValue()); TestTrue("Comparisons", Expr.ParseFromString("{EightFloat} == 8.15", nullptr)); TestFalse("Eval", Expr.Evaluate(Variables, GlobalVariables).GetBooleanValue()); TestTrue("Comparisons", Expr.ParseFromString("{EightFloatPlusMargin} == 8.1", nullptr)); TestTrue("Eval", Expr.Evaluate(Variables, GlobalVariables).GetBooleanValue()); TestTrue("Comparisons", Expr.ParseFromString("{EightFloat} == 8.1000005", nullptr)); TestTrue("Eval", Expr.Evaluate(Variables, GlobalVariables).GetBooleanValue()); TestTrue("Comparisons", Expr.ParseFromString("{EightFloatPlusMargin} == 8.1000005", nullptr)); TestTrue("Eval", Expr.Evaluate(Variables, GlobalVariables).GetBooleanValue()); // Other type comparisons Variables.Add("SomeText", FText::FromString("Hello")); TestTrue("Comparisons", Expr.ParseFromString("{SomeText} == \"Hello\"", nullptr)); TestTrue("Eval", Expr.Evaluate(Variables, GlobalVariables).GetBooleanValue()); TestTrue("Comparisons", Expr.ParseFromString("{SomeText} == \"Hi\"", nullptr)); TestFalse("Eval", Expr.Evaluate(Variables, GlobalVariables).GetBooleanValue()); Variables.Add("Male", ETextGender::Masculine); Variables.Add("Female", ETextGender::Feminine); Variables.Add("AlsoFemale", ETextGender::Feminine); Variables.Add("Neuter", ETextGender::Neuter); TestTrue("Comparisons", Expr.ParseFromString("{Male} == masculine", nullptr)); TestTrue("Eval", Expr.Evaluate(Variables, GlobalVariables).GetBooleanValue()); TestTrue("Comparisons", Expr.ParseFromString("{Male} == Masculine", nullptr)); TestTrue("Eval", Expr.Evaluate(Variables, GlobalVariables).GetBooleanValue()); TestTrue("Comparisons", Expr.ParseFromString("{Male} == feminine", nullptr)); TestFalse("Eval", Expr.Evaluate(Variables, GlobalVariables).GetBooleanValue()); TestTrue("Comparisons", Expr.ParseFromString("{Male} == Feminine", nullptr)); TestFalse("Eval", Expr.Evaluate(Variables, GlobalVariables).GetBooleanValue()); TestTrue("Comparisons", Expr.ParseFromString("{Female} == Feminine", nullptr)); TestTrue("Eval", Expr.Evaluate(Variables, GlobalVariables).GetBooleanValue()); TestTrue("Comparisons", Expr.ParseFromString("{Female} != feminine", nullptr)); TestFalse("Eval", Expr.Evaluate(Variables, GlobalVariables).GetBooleanValue()); TestTrue("Comparisons", Expr.ParseFromString("{Female} == {AlsoFemale}", nullptr)); TestTrue("Eval", Expr.Evaluate(Variables, GlobalVariables).GetBooleanValue()); TestTrue("Comparisons", Expr.ParseFromString("{Female} != {Neuter}", nullptr)); TestTrue("Eval", Expr.Evaluate(Variables, GlobalVariables).GetBooleanValue()); TestTrue("Comparisons", Expr.ParseFromString("{Neuter} == neuter", nullptr)); TestTrue("Eval", Expr.Evaluate(Variables, GlobalVariables).GetBooleanValue()); TestTrue("Comparisons", Expr.ParseFromString("{Neuter} == Neuter", nullptr)); TestTrue("Eval", Expr.Evaluate(Variables, GlobalVariables).GetBooleanValue()); // Test local / global by defining the same variable GlobalVariables.Add("GlobalLocalTestInt", 3); Variables.Add("GlobalLocalTestInt", 20); TestTrue("LocalTest", Expr.ParseFromString("{GlobalLocalTestInt} == 20", nullptr)); TestTrue("Eval", Expr.Evaluate(Variables, GlobalVariables).GetBooleanValue()); TestTrue("GlobalTest", Expr.ParseFromString("{global.GlobalLocalTestInt} == 3", nullptr)); TestTrue("Eval", Expr.Evaluate(Variables, GlobalVariables).GetBooleanValue()); return true; }; IMPLEMENT_SIMPLE_AUTOMATION_TEST(FTestBadExpressions, "SUDSTest.TestBadExpressions", EAutomationTestFlags::EditorContext | EAutomationTestFlags::ClientContext | EAutomationTestFlags::ProductFilter) bool FTestBadExpressions::RunTest(const FString& Parameters) { FSUDSExpression Expr; TMap Variables; FString ParseError; TestFalse("Missing operand", Expr.ParseFromString(" + 1", &ParseError)); TestTrue("Correct error", ParseError.Contains("Bad expression")); TestFalse("Missing operand", Expr.ParseFromString("1 * ", &ParseError)); TestTrue("Correct error", ParseError.Contains("Bad expression")); TestFalse("Missing parenthesis", Expr.ParseFromString("(3 + 1", &ParseError)); TestTrue("Correct error", ParseError.Contains("Mismatched parentheses")); TestFalse("Missing parenthesis", Expr.ParseFromString("3 + 1)", &ParseError)); TestTrue("Correct error", ParseError.Contains("Mismatched parentheses")); TestFalse("Invalid symbol", Expr.ParseFromString("something + 1", &ParseError)); TestTrue("Correct error", ParseError.Contains("Bad expression")); return true; } UE_ENABLE_OPTIMIZATION