// Copyright Epic Games, Inc. All Rights Reserved. #include "EditConditionParserTests.h" #include "EditConditionParser.h" #include "EditConditionContext.h" #include "ObjectPropertyNode.h" #include "Misc/AutomationTest.h" UEditConditionTestObject::UEditConditionTestObject(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) { } void UEditConditionTestObject::VoidFunction() const { ensureAlwaysMsgf(false, TEXT("Error. Should not be executing a void function in edit conditions.")); } bool UEditConditionTestObject::GetBoolFunction() const { return BoolProperty; } EditConditionTestEnum UEditConditionTestObject::GetEnumFunction() const { return EnumProperty; } TEnumAsByte UEditConditionTestObject::GetByteEnumFunction() const { return ByteEnumProperty; } double UEditConditionTestObject::GetDoubleFunction() const { return DoubleProperty; } int32 UEditConditionTestObject::GetIntegerFunction() const { return IntegerProperty; } uint8 UEditConditionTestObject::GetUintBitfieldFunction() const { return UintBitfieldProperty; } UObject* UEditConditionTestObject::GetUObjectPtrFunction() const { return UObjectPtr; } TSoftClassPtr UEditConditionTestObject::GetSoftClassPtrFunction() const { return SoftClassPtr; } TWeakObjectPtr UEditConditionTestObject::GetWeakObjectPtrFunction() const { return WeakObjectPtr; } void UEditConditionTestObject::StaticVoidFunction() { ensureAlwaysMsgf(false, TEXT("Error. Should not be executing a void function in edit conditions.")); } bool UEditConditionTestObject::StaticGetBoolFunction() { return UEditConditionTestObject::StaticClass()->GetDefaultObject()->BoolProperty; } EditConditionTestEnum UEditConditionTestObject::StaticGetEnumFunction() { return UEditConditionTestObject::StaticClass()->GetDefaultObject()->EnumProperty; } TEnumAsByte UEditConditionTestObject::StaticGetByteEnumFunction() { return UEditConditionTestObject::StaticClass()->GetDefaultObject()->ByteEnumProperty; } double UEditConditionTestObject::StaticGetDoubleFunction() { return UEditConditionTestObject::StaticClass()->GetDefaultObject()->DoubleProperty; } int32 UEditConditionTestObject::StaticGetIntegerFunction() { return UEditConditionTestObject::StaticClass()->GetDefaultObject()->IntegerProperty; } uint8 UEditConditionTestObject::StaticGetUintBitfieldFunction() { return UEditConditionTestObject::StaticClass()->GetDefaultObject()->UintBitfieldProperty; } UObject* UEditConditionTestObject::StaticGetUObjectPtrFunction() { return UEditConditionTestObject::StaticClass()->GetDefaultObject()->UObjectPtr; } TSoftClassPtr UEditConditionTestObject::StaticGetSoftClassPtrFunction() { return UEditConditionTestObject::StaticClass()->GetDefaultObject()->SoftClassPtr; } TWeakObjectPtr UEditConditionTestObject::StaticGetWeakObjectPtrFunction() { return UEditConditionTestObject::StaticClass()->GetDefaultObject()->WeakObjectPtr; } #if WITH_DEV_AUTOMATION_TESTS struct FTestEditConditionContext : IEditConditionContext { TMap BoolValues; TMap IntegerValues; TMap DoubleValues; TMap EnumValues; FString EnumTypeName; TMap EnumTypeValues; FTestEditConditionContext(){} virtual ~FTestEditConditionContext() {} virtual FName GetContextName() const { return "TestEditConditionContext"; } virtual TOptional GetBoolValue(const FString& PropertyName, TWeakObjectPtr CachedFunction = nullptr) const override { if (const bool* Value = BoolValues.Find(PropertyName)) { return *Value; } return TOptional(); } virtual TOptional GetIntegerValue(const FString& PropertyName, TWeakObjectPtr CachedFunction = nullptr) const override { if (const int64* Value = IntegerValues.Find(PropertyName)) { return *Value; } return TOptional(); } virtual TOptional GetNumericValue(const FString& PropertyName, TWeakObjectPtr CachedFunction = nullptr) const override { if (const double* Value = DoubleValues.Find(PropertyName)) { return *Value; } return TOptional(); } virtual TOptional GetEnumValue(const FString& PropertyName, TWeakObjectPtr CachedFunction = nullptr) const override { if (const FString* Value = EnumValues.Find(PropertyName)) { return *Value; } return TOptional(); } virtual TOptional GetIntegerValueOfEnum(const FString& TypeName, const FString& ValueName) const override { check(TypeName == EnumTypeName); if (const int64* Value = EnumTypeValues.Find(ValueName)) { return *Value; } return TOptional(); } virtual const TWeakObjectPtr GetFunction(const FString& FieldName) const override { return nullptr; } virtual TOptional GetPointerValue(const FString& PropertyName, TWeakObjectPtr CachedFunction = nullptr) const override { return TOptional(); } virtual TOptional GetTypeName(const FString& PropertyName, TWeakObjectPtr CachedFunction = nullptr) const override { TOptional Result; if (BoolValues.Find(PropertyName) != nullptr) { Result = TEXT("bool"); } else if (DoubleValues.Find(PropertyName) != nullptr) { Result = TEXT("double"); } else if (EnumValues.Find(PropertyName) != nullptr) { Result = EnumTypeName; } return Result; } void SetupBool(const FString& PropertyName, bool Value) { BoolValues.Add(PropertyName, Value); } void SetupInteger(const FString& PropertyName, int64 Value) { IntegerValues.Add(PropertyName, Value); } void SetupDouble(const FString& PropertyName, double Value) { DoubleValues.Add(PropertyName, Value); } void SetupEnum(const FString& PropertyName, const FString& Value) { EnumValues.Add(PropertyName, Value); } void SetupEnumType(const FString& EnumType) { EnumTypeName = EnumType; } void SetupEnumTypeValue(const FString& Name, int64 Value) { EnumTypeValues.Add(Name, Value); } }; static bool CanParse(const FEditConditionParser& Parser, const FString& Expression, int32 ExpectedTokens, int32 ExpectedProperties) { TSharedPtr Parsed = Parser.Parse(Expression); if (!Parsed.IsValid()) { ensureMsgf(false, TEXT("Failed to parse expression: %s"), *Expression); return false; } int PropertyCount = 0; for (const auto& Token : Parsed->Tokens) { const EditConditionParserTokens::FPropertyToken* PropertyToken = Token.Node.Cast(); if (PropertyToken != nullptr) { ++PropertyCount; } } if (Parsed->Tokens.Num() != ExpectedTokens || PropertyCount != ExpectedProperties) { ensureMsgf(false, TEXT("Parsing produced an invalid number of tokens or properties. Expression: %s"), *Expression); return false; } return true; } IMPLEMENT_SIMPLE_AUTOMATION_TEST(FEditConditionParser_Parse, "EditConditionParser.Parse", EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter) bool FEditConditionParser_Parse::RunTest(const FString& Parameters) { FEditConditionParser Parser; bool bResult = true; bResult &= CanParse(Parser, TEXT("false"), 1, 0); bResult &= CanParse(Parser, TEXT("TRUE"), 1, 0); bResult &= CanParse(Parser, TEXT("fAlsE"), 1, 0); bResult &= CanParse(Parser, TEXT("true == false"), 3, 0); bResult &= CanParse(Parser, TEXT("BoolProperty"), 1, 1); bResult &= CanParse(Parser, TEXT("!BoolProperty"), 2, 1); bResult &= CanParse(Parser, TEXT("BoolProperty == true"), 3, 1); bResult &= CanParse(Parser, TEXT("BoolProperty == false"), 3, 1); bResult &= CanParse(Parser, TEXT("GetBoolFunction"), 1, 1); bResult &= CanParse(Parser, TEXT("!GetBoolFunction"), 2, 1); bResult &= CanParse(Parser, TEXT("GetBoolFunction == true"), 3, 1); bResult &= CanParse(Parser, TEXT("GetBoolFunction == false"), 3, 1); bResult &= CanParse(Parser, TEXT("PropertyEditor.EditConditionTestObject.StaticGetBoolFunction"), 1, 1); bResult &= CanParse(Parser, TEXT("!PropertyEditor.EditConditionTestObject.StaticGetBoolFunction"), 2, 1); bResult &= CanParse(Parser, TEXT("PropertyEditor.EditConditionTestObject.StaticGetBoolFunction == true"), 3, 1); bResult &= CanParse(Parser, TEXT("PropertyEditor.EditConditionTestObject.StaticGetBoolFunction == false"), 3, 1); bResult &= CanParse(Parser, TEXT("IntProperty == 0"), 3, 1); bResult &= CanParse(Parser, TEXT("IntProperty != 0"), 3, 1); bResult &= CanParse(Parser, TEXT("IntProperty > 0"), 3, 1); bResult &= CanParse(Parser, TEXT("IntProperty < 0"), 3, 1); bResult &= CanParse(Parser, TEXT("IntProperty <= 0"), 3, 1); bResult &= CanParse(Parser, TEXT("IntProperty >= 0"), 3, 1); bResult &= CanParse(Parser, TEXT("Foo > Bar"), 3, 2); bResult &= CanParse(Parser, TEXT("Foo && Bar"), 3, 2); bResult &= CanParse(Parser, TEXT("Foo || Bar"), 3, 2); bResult &= CanParse(Parser, TEXT("Foo == Bar + 5"), 5, 2); bResult &= CanParse(Parser, TEXT("Foo == Bar - 5"), 5, 2); bResult &= CanParse(Parser, TEXT("Foo == Bar * 5"), 5, 2); bResult &= CanParse(Parser, TEXT("Foo == Bar / 5"), 5, 2); bResult &= CanParse(Parser, TEXT("Enum == EType::Value"), 3, 1); bResult &= CanParse(Parser, TEXT("Enum != EType::Value"), 3, 1); bResult &= CanParse(Parser, TEXT("Enum != EType::Value && BoolProperty"), 5, 2); bResult &= CanParse(Parser, TEXT("Enum == EType::Value || BoolProperty == false"), 7, 2); bResult &= CanParse(Parser, TEXT("Enum != EType::Value || BoolProperty == bFoo"), 7, 3); bResult &= CanParse(Parser, TEXT("Enum != EType::Value && GetBoolFunction"), 5, 2); bResult &= CanParse(Parser, TEXT("Enum == EType::Value || GetBoolFunction == false"), 7, 2); bResult &= CanParse(Parser, TEXT("Enum != EType::Value || GetBoolFunction == bFoo"), 7, 3); bResult &= CanParse(Parser, TEXT("Enum != EType::Value && PropertyEditor.EditConditionTestObject.StaticGetBoolFunction"), 5, 2); bResult &= CanParse(Parser, TEXT("Enum == EType::Value || PropertyEditor.EditConditionTestObject.StaticGetBoolFunction == false"), 7, 2); bResult &= CanParse(Parser, TEXT("Enum != EType::Value || PropertyEditor.EditConditionTestObject.StaticGetBoolFunction == bFoo"), 7, 3); bResult &= CanParse(Parser, TEXT("Enum == EType::Value && Foo != 5"), 7, 2); bResult &= CanParse(Parser, TEXT("Enum != EType::Value && Foo == Bar"), 7, 3); bResult &= CanParse(Parser, TEXT("PointerProperty == nullptr"), 3, 1); bResult &= CanParse(Parser, TEXT("PointerProperty != nullptr"), 3, 1); bResult &= CanParse(Parser, TEXT("Flags & EType::Value"), 3, 1); bResult &= CanParse(Parser, TEXT("Flags & EType::Value == false"), 5, 1); return bResult; } static bool CanEvaluate(const FEditConditionParser& Parser, const IEditConditionContext& Context, const FString& Expression, bool Expected) { TSharedPtr Parsed = Parser.Parse(Expression); if (!Parsed.IsValid()) { ensureMsgf(false, TEXT("Failed to parse expression: %s"), *Expression); return false; } TValueOrError Result = Parser.Evaluate(*Parsed.Get(), Context); if (!Result.IsValid()) { ensureMsgf(false, TEXT("Expression failed to evaluate: %s, Error: %s"), *Expression, *Result.GetError().ToString()); return false; } if (Result.GetValue() != Expected) { auto BoolToString = [](bool Value) { return Value ? TEXT("true") : TEXT("false"); }; ensureMsgf(false, TEXT("Expression evaluated to unexpected value: %s, Expected: %s, Actual: %s"), *Expression, BoolToString(Expected), BoolToString(Result.GetValue())); return false; } return true; } /** For expressions we expect / wish to be considered mal-formed */ static bool CanNotEvaluate(const FEditConditionParser& Parser, const IEditConditionContext& Context, const FString& Expression) { TSharedPtr Parsed = Parser.Parse(Expression); if (!Parsed.IsValid()) { ensureMsgf(false, TEXT("Failed to parse expression: %s"), *Expression); return false; } TValueOrError Result = Parser.Evaluate(*Parsed.Get(), Context); if (Result.IsValid()) { ensureMsgf(false, TEXT("Error. Expected expression to fail evaluation: %s"), *Expression); return false; } return true; } static bool RunVoidTests(const IEditConditionContext& Context) { FEditConditionParser Parser; bool bResult = true; bResult &= CanNotEvaluate(Parser, Context, TEXT("VoidFunction")); bResult &= CanNotEvaluate(Parser, Context, TEXT("!VoidFunction")); bResult &= CanNotEvaluate(Parser, Context, TEXT("VoidFunction == true")); bResult &= CanNotEvaluate(Parser, Context, TEXT("VoidFunction == false")); bResult &= CanNotEvaluate(Parser, Context, TEXT("VoidFunction == VoidFunction")); bResult &= CanNotEvaluate(Parser, Context, TEXT("VoidFunction != VoidFunction")); bResult &= CanNotEvaluate(Parser, Context, TEXT("VoidFunction != true")); bResult &= CanNotEvaluate(Parser, Context, TEXT("VoidFunction != false")); bResult &= CanNotEvaluate(Parser, Context, TEXT("PropertyEditor.EditConditionTestObject.StaticVoidFunction")); bResult &= CanNotEvaluate(Parser, Context, TEXT("!PropertyEditor.EditConditionTestObject.StaticVoidFunction")); bResult &= CanNotEvaluate(Parser, Context, TEXT("PropertyEditor.EditConditionTestObject.StaticVoidFunction == true")); bResult &= CanNotEvaluate(Parser, Context, TEXT("PropertyEditor.EditConditionTestObject.StaticVoidFunction == false")); bResult &= CanNotEvaluate(Parser, Context, TEXT("PropertyEditor.EditConditionTestObject.StaticVoidFunction == PropertyEditor.EditConditionTestObject.StaticVoidFunction")); bResult &= CanNotEvaluate(Parser, Context, TEXT("PropertyEditor.EditConditionTestObject.StaticVoidFunction != PropertyEditor.EditConditionTestObject.StaticVoidFunction")); bResult &= CanNotEvaluate(Parser, Context, TEXT("PropertyEditor.EditConditionTestObject.StaticVoidFunction != true")); bResult &= CanNotEvaluate(Parser, Context, TEXT("PropertyEditor.EditConditionTestObject.StaticVoidFunction != false")); return bResult; } static bool RunBoolTests(const IEditConditionContext& Context) { FEditConditionParser Parser; bool bResult = true; bResult &= CanEvaluate(Parser, Context, TEXT("true"), true); bResult &= CanEvaluate(Parser, Context, TEXT("TRUE"), true); bResult &= CanEvaluate(Parser, Context, TEXT("false"), false); bResult &= CanEvaluate(Parser, Context, TEXT("False"), false); bResult &= CanEvaluate(Parser, Context, TEXT("fAlSe"), false); bResult &= CanEvaluate(Parser, Context, TEXT("!true"), false); bResult &= CanEvaluate(Parser, Context, TEXT("!false"), true); bResult &= CanEvaluate(Parser, Context, TEXT("BoolProperty"), true); bResult &= CanEvaluate(Parser, Context, TEXT("!BoolProperty"), false); bResult &= CanEvaluate(Parser, Context, TEXT("BoolProperty == true"), true); bResult &= CanEvaluate(Parser, Context, TEXT("BoolProperty == false"), false); bResult &= CanEvaluate(Parser, Context, TEXT("BoolProperty == BoolProperty"), true); bResult &= CanEvaluate(Parser, Context, TEXT("BoolProperty != BoolProperty"), false); bResult &= CanEvaluate(Parser, Context, TEXT("BoolProperty != true"), false); bResult &= CanEvaluate(Parser, Context, TEXT("BoolProperty != false"), true); bResult &= CanEvaluate(Parser, Context, TEXT("GetBoolFunction"), true); bResult &= CanEvaluate(Parser, Context, TEXT("!GetBoolFunction"), false); bResult &= CanEvaluate(Parser, Context, TEXT("GetBoolFunction == true"), true); bResult &= CanEvaluate(Parser, Context, TEXT("GetBoolFunction == false"), false); bResult &= CanEvaluate(Parser, Context, TEXT("GetBoolFunction == GetBoolFunction"), true); bResult &= CanEvaluate(Parser, Context, TEXT("GetBoolFunction != GetBoolFunction"), false); bResult &= CanEvaluate(Parser, Context, TEXT("GetBoolFunction != true"), false); bResult &= CanEvaluate(Parser, Context, TEXT("GetBoolFunction != false"), true); bResult &= CanEvaluate(Parser, Context, TEXT("PropertyEditor.EditConditionTestObject.StaticGetBoolFunction"), false); bResult &= CanEvaluate(Parser, Context, TEXT("!PropertyEditor.EditConditionTestObject.StaticGetBoolFunction"), true); bResult &= CanEvaluate(Parser, Context, TEXT("PropertyEditor.EditConditionTestObject.StaticGetBoolFunction == true"), false); bResult &= CanEvaluate(Parser, Context, TEXT("PropertyEditor.EditConditionTestObject.StaticGetBoolFunction == false"), true); bResult &= CanEvaluate(Parser, Context, TEXT("PropertyEditor.EditConditionTestObject.StaticGetBoolFunction == PropertyEditor.EditConditionTestObject.StaticGetBoolFunction"), true); bResult &= CanEvaluate(Parser, Context, TEXT("PropertyEditor.EditConditionTestObject.StaticGetBoolFunction != PropertyEditor.EditConditionTestObject.StaticGetBoolFunction"), false); bResult &= CanEvaluate(Parser, Context, TEXT("PropertyEditor.EditConditionTestObject.StaticGetBoolFunction != true"), true); bResult &= CanEvaluate(Parser, Context, TEXT("PropertyEditor.EditConditionTestObject.StaticGetBoolFunction != false"), false); bResult &= CanEvaluate(Parser, Context, TEXT("true && true"), true); bResult &= CanEvaluate(Parser, Context, TEXT("true && false"), false); bResult &= CanEvaluate(Parser, Context, TEXT("false && true"), false); bResult &= CanEvaluate(Parser, Context, TEXT("false && false"), false); bResult &= CanEvaluate(Parser, Context, TEXT("true && true && true"), true); bResult &= CanEvaluate(Parser, Context, TEXT("true && true && false"), false); bResult &= CanEvaluate(Parser, Context, TEXT("BoolProperty && BoolProperty"), true); bResult &= CanEvaluate(Parser, Context, TEXT("BoolProperty && false"), false); bResult &= CanEvaluate(Parser, Context, TEXT("false && BoolProperty"), false); bResult &= CanEvaluate(Parser, Context, TEXT("GetBoolFunction && GetBoolFunction"), true); bResult &= CanEvaluate(Parser, Context, TEXT("GetBoolFunction && false"), false); bResult &= CanEvaluate(Parser, Context, TEXT("false && GetBoolFunction"), false); bResult &= CanEvaluate(Parser, Context, TEXT("PropertyEditor.EditConditionTestObject.StaticGetBoolFunction && PropertyEditor.EditConditionTestObject.StaticGetBoolFunction"), false); bResult &= CanEvaluate(Parser, Context, TEXT("PropertyEditor.EditConditionTestObject.StaticGetBoolFunction && false"), false); bResult &= CanEvaluate(Parser, Context, TEXT("false && PropertyEditor.EditConditionTestObject.StaticGetBoolFunction"), false); bResult &= CanEvaluate(Parser, Context, TEXT("true || true"), true); bResult &= CanEvaluate(Parser, Context, TEXT("true || false"), true); bResult &= CanEvaluate(Parser, Context, TEXT("false || true"), true); bResult &= CanEvaluate(Parser, Context, TEXT("false || false"), false); bResult &= CanEvaluate(Parser, Context, TEXT("true || true || true"), true); bResult &= CanEvaluate(Parser, Context, TEXT("true || true || false"), true); bResult &= CanEvaluate(Parser, Context, TEXT("BoolProperty || BoolProperty"), true); bResult &= CanEvaluate(Parser, Context, TEXT("BoolProperty || false"), true); bResult &= CanEvaluate(Parser, Context, TEXT("false || BoolProperty"), true); bResult &= CanEvaluate(Parser, Context, TEXT("GetBoolFunction || GetBoolFunction"), true); bResult &= CanEvaluate(Parser, Context, TEXT("GetBoolFunction || false"), true); bResult &= CanEvaluate(Parser, Context, TEXT("false || GetBoolFunction"), true); bResult &= CanEvaluate(Parser, Context, TEXT("PropertyEditor.EditConditionTestObject.StaticGetBoolFunction || PropertyEditor.EditConditionTestObject.StaticGetBoolFunction"), false); bResult &= CanEvaluate(Parser, Context, TEXT("PropertyEditor.EditConditionTestObject.StaticGetBoolFunction || false"), false); bResult &= CanEvaluate(Parser, Context, TEXT("false || PropertyEditor.EditConditionTestObject.StaticGetBoolFunction"), false); return bResult; } IMPLEMENT_SIMPLE_AUTOMATION_TEST(FEditConditionParser_EvaluateVoidFails, "EditConditionParser.EvaluateVoidFails", EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter) bool FEditConditionParser_EvaluateVoidFails::RunTest(const FString& Parameters) { // We only expect errors first time, since the API only logs first time static bool bExpectErrors = true; if (bExpectErrors) { AddExpectedError(TEXT("EditCondition attempted to use an invalid operand"), EAutomationExpectedErrorFlags::Contains, 2); bExpectErrors = false; } FTestEditConditionContext TestContext; return RunVoidTests(TestContext); } IMPLEMENT_SIMPLE_AUTOMATION_TEST(FEditConditionParser_EvaluateBool, "EditConditionParser.EvaluateBool", EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter) bool FEditConditionParser_EvaluateBool::RunTest(const FString& Parameters) { FTestEditConditionContext TestContext; TestContext.SetupBool(TEXT("BoolProperty"), true); TestContext.SetupBool(TEXT("GetBoolFunction"), true); TestContext.SetupBool(TEXT("PropertyEditor.EditConditionTestObject.StaticGetBoolFunction"), false); return RunBoolTests(TestContext); } static bool RunNumericTests(const IEditConditionContext& Context) { FEditConditionParser Parser; bool bResult = true; bResult &= CanEvaluate(Parser, Context, TEXT("5 == 5"), true); bResult &= CanEvaluate(Parser, Context, TEXT("5.0 == 5.0"), true); bResult &= CanEvaluate(Parser, Context, TEXT("DoubleProperty == 5.0"), true); bResult &= CanEvaluate(Parser, Context, TEXT("DoubleProperty == 5"), true); bResult &= CanEvaluate(Parser, Context, TEXT("DoubleProperty == DoubleProperty"), true); bResult &= CanEvaluate(Parser, Context, TEXT("DoubleProperty != 5.0"), false); bResult &= CanEvaluate(Parser, Context, TEXT("DoubleProperty != 6.0"), true); bResult &= CanEvaluate(Parser, Context, TEXT("DoubleProperty != 6"), true); bResult &= CanEvaluate(Parser, Context, TEXT("DoubleProperty != DoubleProperty"), false); bResult &= CanEvaluate(Parser, Context, TEXT("DoubleProperty > 4.5"), true); bResult &= CanEvaluate(Parser, Context, TEXT("DoubleProperty > 5"), false); bResult &= CanEvaluate(Parser, Context, TEXT("DoubleProperty > 6"), false); bResult &= CanEvaluate(Parser, Context, TEXT("DoubleProperty > DoubleProperty"), false); bResult &= CanEvaluate(Parser, Context, TEXT("DoubleProperty < 4.5"), false); bResult &= CanEvaluate(Parser, Context, TEXT("DoubleProperty < 5"), false); bResult &= CanEvaluate(Parser, Context, TEXT("DoubleProperty < 6"), true); bResult &= CanEvaluate(Parser, Context, TEXT("DoubleProperty < DoubleProperty"), false); bResult &= CanEvaluate(Parser, Context, TEXT("DoubleProperty >= 4.5"), true); bResult &= CanEvaluate(Parser, Context, TEXT("DoubleProperty >= 5"), true); bResult &= CanEvaluate(Parser, Context, TEXT("DoubleProperty >= 6"), false); bResult &= CanEvaluate(Parser, Context, TEXT("DoubleProperty >= DoubleProperty"), true); bResult &= CanEvaluate(Parser, Context, TEXT("DoubleProperty <= 4.5"), false); bResult &= CanEvaluate(Parser, Context, TEXT("DoubleProperty <= 5"), true); bResult &= CanEvaluate(Parser, Context, TEXT("DoubleProperty <= 6"), true); bResult &= CanEvaluate(Parser, Context, TEXT("DoubleProperty <= DoubleProperty"), true); bResult &= CanEvaluate(Parser, Context, TEXT("DoubleProperty == 2 + 3"), true); bResult &= CanEvaluate(Parser, Context, TEXT("DoubleProperty == 6 - 1"), true); bResult &= CanEvaluate(Parser, Context, TEXT("DoubleProperty == 2.5 * 2"), true); bResult &= CanEvaluate(Parser, Context, TEXT("DoubleProperty == 10 / 2"), true); return bResult; } IMPLEMENT_SIMPLE_AUTOMATION_TEST(FEditConditionParser_EvaluateDouble, "EditConditionParser.EvaluateDouble", EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter) bool FEditConditionParser_EvaluateDouble::RunTest(const FString& Parameters) { FTestEditConditionContext TestContext; TestContext.SetupDouble(TEXT("DoubleProperty"), 5.0); return RunNumericTests(TestContext); } static bool RunEnumTests(const IEditConditionContext& Context, const FString& EnumName, const FString& PropertyName) { FEditConditionParser Parser; bool bResult = true; bResult &= CanEvaluate(Parser, Context, EnumName + TEXT("::First == ") + EnumName + TEXT("::First"), true); bResult &= CanEvaluate(Parser, Context, EnumName + TEXT("::First == ") + EnumName + TEXT("::Second"), false); bResult &= CanEvaluate(Parser, Context, EnumName + TEXT("::First != ") + EnumName + TEXT("::First"), false); bResult &= CanEvaluate(Parser, Context, EnumName + TEXT("::First != ") + EnumName + TEXT("::Second"), true); bResult &= CanEvaluate(Parser, Context, PropertyName + TEXT(" == ") + PropertyName, true); bResult &= CanEvaluate(Parser, Context, PropertyName + TEXT(" != ") + PropertyName, false); bResult &= CanEvaluate(Parser, Context, PropertyName + TEXT(" == ") + EnumName + TEXT("::First"), true); bResult &= CanEvaluate(Parser, Context, EnumName + TEXT("::First == ") + PropertyName, true); bResult &= CanEvaluate(Parser, Context, PropertyName + TEXT(" == ") + EnumName + TEXT("::Second"), false); bResult &= CanEvaluate(Parser, Context, PropertyName + TEXT(" != ") + EnumName + TEXT("::Second"), true); bResult &= CanEvaluate(Parser, Context, PropertyName + TEXT(" != ") + EnumName + TEXT("::First"), false); bResult &= CanEvaluate(Parser, Context, EnumName + TEXT("::Second != ") + PropertyName, true); return bResult; } IMPLEMENT_SIMPLE_AUTOMATION_TEST(FEditConditionParser_EvaluateEnum, "EditConditionParser.EvaluateEnum", EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter) bool FEditConditionParser_EvaluateEnum::RunTest(const FString& Parameters) { FTestEditConditionContext TestContext; const FString EnumType = TEXT("EditConditionTestEnum"); TestContext.SetupEnumType(EnumType); const FString PropertyName = TEXT("EnumProperty"); TestContext.SetupEnum(PropertyName, TEXT("First")); return RunEnumTests(TestContext, EnumType, PropertyName); } IMPLEMENT_SIMPLE_AUTOMATION_TEST(FEditConditionParser_EvaluateBitFlags, "EditConditionParser.EvaluateBitFlags", EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter) bool FEditConditionParser_EvaluateBitFlags::RunTest(const FString& Parameters) { FTestEditConditionContext TestContext; const FString EnumType = TEXT("TestEnum"); TestContext.SetupEnumType(EnumType); TestContext.SetupEnumTypeValue(TEXT("Nil"), 0); TestContext.SetupEnumTypeValue(TEXT("One"), 1 << 0); TestContext.SetupEnumTypeValue(TEXT("Two"), 1 << 1); TestContext.SetupEnumTypeValue(TEXT("Four"), 1 << 2); const FString PropertyName = TEXT("FlagsProperty"); FEditConditionParser Parser; bool bResult = true; TestContext.SetupInteger(PropertyName, 0); bResult &= CanEvaluate(Parser, TestContext, TEXT("FlagsProperty & TestEnum::Nil"), false); bResult &= CanEvaluate(Parser, TestContext, TEXT("FlagsProperty & TestEnum::One"), false); bResult &= CanEvaluate(Parser, TestContext, TEXT("FlagsProperty & TestEnum::Two"), false); bResult &= CanEvaluate(Parser, TestContext, TEXT("FlagsProperty & TestEnum::Four"), false); bResult &= CanEvaluate(Parser, TestContext, TEXT("FlagsProperty & TestEnum::One == false"), true); bResult &= CanEvaluate(Parser, TestContext, TEXT("FlagsProperty & TestEnum::Two == false"), true); bResult &= CanEvaluate(Parser, TestContext, TEXT("FlagsProperty & TestEnum::Four == false"), true); TestContext.SetupInteger(PropertyName, 1); bResult &= CanEvaluate(Parser, TestContext, TEXT("FlagsProperty & TestEnum::Nil"), false); bResult &= CanEvaluate(Parser, TestContext, TEXT("FlagsProperty & TestEnum::One"), true); bResult &= CanEvaluate(Parser, TestContext, TEXT("FlagsProperty & TestEnum::Two"), false); bResult &= CanEvaluate(Parser, TestContext, TEXT("FlagsProperty & TestEnum::Four"), false); TestContext.SetupInteger(PropertyName, 2); bResult &= CanEvaluate(Parser, TestContext, TEXT("FlagsProperty & TestEnum::Nil"), false); bResult &= CanEvaluate(Parser, TestContext, TEXT("FlagsProperty & TestEnum::One"), false); bResult &= CanEvaluate(Parser, TestContext, TEXT("FlagsProperty & TestEnum::Two"), true); bResult &= CanEvaluate(Parser, TestContext, TEXT("FlagsProperty & TestEnum::Four"), false); TestContext.SetupInteger(PropertyName, 3); bResult &= CanEvaluate(Parser, TestContext, TEXT("FlagsProperty & TestEnum::Nil"), false); bResult &= CanEvaluate(Parser, TestContext, TEXT("FlagsProperty & TestEnum::One"), true); bResult &= CanEvaluate(Parser, TestContext, TEXT("FlagsProperty & TestEnum::Two"), true); bResult &= CanEvaluate(Parser, TestContext, TEXT("FlagsProperty & TestEnum::Four"), false); TestContext.SetupInteger(PropertyName, 5); bResult &= CanEvaluate(Parser, TestContext, TEXT("FlagsProperty & TestEnum::Nil"), false); bResult &= CanEvaluate(Parser, TestContext, TEXT("FlagsProperty & TestEnum::One"), true); bResult &= CanEvaluate(Parser, TestContext, TEXT("FlagsProperty & TestEnum::Two"), false); bResult &= CanEvaluate(Parser, TestContext, TEXT("FlagsProperty & TestEnum::Four"), true); TestContext.SetupInteger(PropertyName, 7); bResult &= CanEvaluate(Parser, TestContext, TEXT("FlagsProperty & TestEnum::Nil"), false); bResult &= CanEvaluate(Parser, TestContext, TEXT("FlagsProperty & TestEnum::One"), true); bResult &= CanEvaluate(Parser, TestContext, TEXT("FlagsProperty & TestEnum::Two"), true); bResult &= CanEvaluate(Parser, TestContext, TEXT("FlagsProperty & TestEnum::Four"), true); bResult &= CanEvaluate(Parser, TestContext, TEXT("FlagsProperty & TestEnum::One == false"), false); bResult &= CanEvaluate(Parser, TestContext, TEXT("FlagsProperty & TestEnum::Two == false"), false); bResult &= CanEvaluate(Parser, TestContext, TEXT("FlagsProperty & TestEnum::Four == false"), false); bResult &= CanEvaluate(Parser, TestContext, TEXT("FlagsProperty & TestEnum::One == true"), true); bResult &= CanEvaluate(Parser, TestContext, TEXT("FlagsProperty & TestEnum::Two == true"), true); bResult &= CanEvaluate(Parser, TestContext, TEXT("FlagsProperty & TestEnum::Four == true"), true); return bResult; } IMPLEMENT_SIMPLE_AUTOMATION_TEST(FEditConditionParser_EvaluateUObject, "EditConditionParser.EvaluateUObject", EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter) bool FEditConditionParser_EvaluateUObject::RunTest(const FString& Parameters) { UEditConditionTestObject* TestObject = NewObject(); TestObject->AddToRoot(); TSharedPtr ObjectNode(new FObjectPropertyNode); ObjectNode->AddObject(TestObject); FPropertyNodeInitParams InitParams; ObjectNode->InitNode(InitParams); TOptional bResult; bool bAllResults = true; // enum comparison { static const FName EnumPropertyName = TEXT("EnumProperty"); TSharedPtr PropertyNode = ObjectNode->FindChildPropertyNode(EnumPropertyName, true); FEditConditionContext Context(*PropertyNode.Get()); TestObject->EnumProperty = EditConditionTestEnum::First; TestObject->ByteEnumProperty = EditConditionByteEnum::First; static const FString EnumType = TEXT("EditConditionTestEnum"); bAllResults &= RunEnumTests(Context, EnumType, EnumPropertyName.ToString()); static const FString ByteEnumType = TEXT("EditConditionByteEnum"); static const FString ByteEnumPropertyName = TEXT("ByteEnumProperty"); bAllResults &= RunEnumTests(Context, ByteEnumType, ByteEnumPropertyName); } // void expression failure { // We only expect errors first time, since the API only logs first time static bool bExpectErrors = true; if (bExpectErrors) { AddExpectedError(TEXT("EditCondition parsing failed"), EAutomationExpectedErrorFlags::Contains, 1); AddExpectedError(TEXT("EditCondition attempted to use an invalid operand"), EAutomationExpectedErrorFlags::Contains, 2); bExpectErrors = false; } // Using bool property for context since doesn't matter as long as parent is same (due to evaluation failing) static const FName BoolPropertyName(TEXT("BoolProperty")); TSharedPtr PropertyNode = ObjectNode->FindChildPropertyNode(BoolPropertyName, true); FEditConditionContext Context(*PropertyNode.Get()); bAllResults &= RunVoidTests(Context); } // bool comparison { static const FName BoolPropertyName(TEXT("BoolProperty")); TSharedPtr PropertyNode = ObjectNode->FindChildPropertyNode(BoolPropertyName, true); FEditConditionContext Context(*PropertyNode.Get()); TestObject->BoolProperty = true; bAllResults &= RunBoolTests(Context); } // double comparison { static const FName DoublePropertyName(TEXT("DoubleProperty")); TSharedPtr PropertyNode = ObjectNode->FindChildPropertyNode(DoublePropertyName, true); FEditConditionContext Context(*PropertyNode.Get()); TestObject->DoubleProperty = 5.0; bAllResults &= RunNumericTests(Context); } // integer comparison { static const FName IntegerPropertyName(TEXT("IntegerProperty")); TSharedPtr PropertyNode = ObjectNode->FindChildPropertyNode(IntegerPropertyName, true); FEditConditionContext Context(*PropertyNode.Get()); TestObject->IntegerProperty = 5; bAllResults &= RunNumericTests(Context); } { static const FName DoublePropertyName(TEXT("DoubleProperty")); TSharedPtr PropertyNode = ObjectNode->FindChildPropertyNode(DoublePropertyName, true); FEditConditionContext Context(*PropertyNode.Get()); TestEqual(TEXT("Boolean Type Name"), Context.GetTypeName(TEXT("BoolProperty")).GetValue(), TEXT("bool")); TestEqual(TEXT("Enum Type Name"), Context.GetTypeName(TEXT("EnumProperty")).GetValue(), TEXT("EditConditionTestEnum")); TestEqual(TEXT("Byte Enum Type Name"), Context.GetTypeName(TEXT("ByteEnumProperty")).GetValue(), TEXT("EditConditionByteEnum")); TestEqual(TEXT("Double Type Name"), Context.GetTypeName(TEXT("DoubleProperty")).GetValue(), TEXT("double")); } TestObject->RemoveFromRoot(); return bAllResults; } IMPLEMENT_SIMPLE_AUTOMATION_TEST(FEditConditionParser_EvaluatePointers, "EditConditionParser.EvaluatePointers", EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter) bool FEditConditionParser_EvaluatePointers::RunTest(const FString& Parameters) { UEditConditionTestObject* TestObject = NewObject(); TestObject->AddToRoot(); TSharedPtr ObjectNode(new FObjectPropertyNode); ObjectNode->AddObject(TestObject); FPropertyNodeInitParams InitParams; ObjectNode->InitNode(InitParams); static const FName BoolPropertyName(TEXT("BoolProperty")); TSharedPtr PropertyNode = ObjectNode->FindChildPropertyNode(BoolPropertyName, true); FEditConditionContext Context(*PropertyNode.Get()); bool bAllResults = true; FEditConditionParser Parser; { TestObject->UObjectPtr = nullptr; bAllResults &= CanEvaluate(Parser, Context, TEXT("UObjectPtr != nullptr"), false); bAllResults &= CanEvaluate(Parser, Context, TEXT("UObjectPtr == nullptr"), true); TestObject->UObjectPtr = TestObject; bAllResults &= CanEvaluate(Parser, Context, TEXT("UObjectPtr != nullptr"), true); bAllResults &= CanEvaluate(Parser, Context, TEXT("UObjectPtr == nullptr"), false); } { TestObject->SoftClassPtr = nullptr; bAllResults &= CanEvaluate(Parser, Context, TEXT("SoftClassPtr != nullptr"), false); bAllResults &= CanEvaluate(Parser, Context, TEXT("SoftClassPtr == nullptr"), true); TestObject->SoftClassPtr = TestObject; bAllResults &= CanEvaluate(Parser, Context, TEXT("SoftClassPtr != nullptr"), true); bAllResults &= CanEvaluate(Parser, Context, TEXT("SoftClassPtr == nullptr"), false); } { TestObject->WeakObjectPtr = nullptr; bAllResults &= CanEvaluate(Parser, Context, TEXT("WeakObjectPtr != nullptr"), false); bAllResults &= CanEvaluate(Parser, Context, TEXT("WeakObjectPtr == nullptr"), true); TestObject->WeakObjectPtr = TestObject; bAllResults &= CanEvaluate(Parser, Context, TEXT("WeakObjectPtr != nullptr"), true); bAllResults &= CanEvaluate(Parser, Context, TEXT("WeakObjectPtr == nullptr"), false); } { // equality & inequality bAllResults &= CanEvaluate(Parser, Context, TEXT("UObjectPtr == SoftClassPtr"), true); bAllResults &= CanEvaluate(Parser, Context, TEXT("SoftClassPtr == UObjectPtr"), true); bAllResults &= CanEvaluate(Parser, Context, TEXT("WeakObjectPtr != UObjectPtr"), false); bAllResults &= CanEvaluate(Parser, Context, TEXT("UObjectPtr != WeakObjectPtr"), false); bAllResults &= CanEvaluate(Parser, Context, TEXT("SoftClassPtr == WeakObjectPtr"), true); bAllResults &= CanEvaluate(Parser, Context, TEXT("WeakObjectPtr == UObjectPtr"), true); TestObject->UObjectPtr = nullptr; bAllResults &= CanEvaluate(Parser, Context, TEXT("UObjectPtr != SoftClassPtr"), true); bAllResults &= CanEvaluate(Parser, Context, TEXT("WeakObjectPtr != UObjectPtr"), true); bAllResults &= CanEvaluate(Parser, Context, TEXT("WeakObjectPtr == UObjectPtr"), false); } TestObject->RemoveFromRoot(); return bAllResults; } IMPLEMENT_SIMPLE_AUTOMATION_TEST(FEditConditionParser_Grouping, "EditConditionParser.Grouping", EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter) bool FEditConditionParser_Grouping::RunTest(const FString& Parameters) { UEditConditionTestObject* TestObject = NewObject(); TestObject->AddToRoot(); TSharedPtr ObjectNode(new FObjectPropertyNode); ObjectNode->AddObject(TestObject); FPropertyNodeInitParams InitParams; ObjectNode->InitNode(InitParams); static const FName BoolPropertyName(TEXT("BoolProperty")); TSharedPtr PropertyNode = ObjectNode->FindChildPropertyNode(BoolPropertyName, true); FEditConditionContext Context(*PropertyNode.Get()); bool bAllResults = true; FEditConditionParser Parser; bAllResults &= CanEvaluate(Parser, Context, TEXT("(true)"), true); bAllResults &= CanEvaluate(Parser, Context, TEXT("(true) == true"), true); bAllResults &= CanEvaluate(Parser, Context, TEXT("(true) == false"), false); bAllResults &= CanEvaluate(Parser, Context, TEXT("(true) == (true)"), true); bAllResults &= CanEvaluate(Parser, Context, TEXT("((true == false) == false) == true"), true); TestObject->DoubleProperty = 5.0; bAllResults &= CanEvaluate(Parser, Context, TEXT("(DoubleProperty == 5.0)"), true); bAllResults &= CanEvaluate(Parser, Context, TEXT("(DoubleProperty == 5.0) == false"), false); bAllResults &= CanEvaluate(Parser, Context, TEXT("(DoubleProperty == 5.0) == (true == true)"), true); TestObject->EnumProperty = EditConditionTestEnum::First; bAllResults &= CanEvaluate(Parser, Context, TEXT("(EnumProperty == EditConditionTestEnum::First) == true"), true); bAllResults &= CanEvaluate(Parser, Context, TEXT("(EnumProperty == EditConditionTestEnum::First) && (DoubleProperty == 5.0)"), true); bAllResults &= CanEvaluate(Parser, Context, TEXT("(EnumProperty == EditConditionTestEnum::Second) && (DoubleProperty == 5.0)"), false); bAllResults &= CanEvaluate(Parser, Context, TEXT("(EnumProperty == EditConditionTestEnum::Second) || (DoubleProperty == 5.0)"), true); { FTestEditConditionContext TestContext; const FString EnumType = TEXT("TestEnum"); TestContext.SetupEnumType(EnumType); TestContext.SetupEnumTypeValue(TEXT("Nil"), 0); TestContext.SetupEnumTypeValue(TEXT("One"), 1 << 0); TestContext.SetupInteger("FlagsProperty", 1); TestContext.SetupDouble("DoubleProperty", 5.0); bAllResults &= CanEvaluate(Parser, TestContext, TEXT("(FlagsProperty & TestEnum::Nil)"), false); bAllResults &= CanEvaluate(Parser, TestContext, TEXT("(FlagsProperty & TestEnum::One)"), true); bAllResults &= CanEvaluate(Parser, TestContext, TEXT("(FlagsProperty & TestEnum::One) && (DoubleProperty == 5.0)"), true); bAllResults &= CanEvaluate(Parser, TestContext, TEXT("(FlagsProperty & TestEnum::One) == false"), false); bAllResults &= CanEvaluate(Parser, TestContext, TEXT("!(FlagsProperty & TestEnum::One)"), false); bAllResults &= CanEvaluate(Parser, TestContext, TEXT("!(FlagsProperty & TestEnum::Nil)"), true); } TestObject->RemoveFromRoot(); return bAllResults; } IMPLEMENT_SIMPLE_AUTOMATION_TEST(FEditConditionParser_SingleBool, "EditConditionParser.SingleBool", EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter) bool FEditConditionParser_SingleBool::RunTest(const FString& Parameters) { UEditConditionTestObject* TestObject = NewObject(); TestObject->AddToRoot(); TSharedPtr ObjectNode(new FObjectPropertyNode); ObjectNode->AddObject(TestObject); FPropertyNodeInitParams InitParams; ObjectNode->InitNode(InitParams); static const FName BoolPropertyName(TEXT("BoolProperty")); TSharedPtr PropertyNode = ObjectNode->FindChildPropertyNode(BoolPropertyName, true); FEditConditionContext Context(*PropertyNode.Get()); FEditConditionParser Parser; { TSharedPtr Expression = Parser.Parse(FString(TEXT("BoolProperty"))); const FBoolProperty* Property = Context.GetSingleBoolProperty(Expression); TestNotNull(TEXT("Bool"), Property); } { TSharedPtr Expression = Parser.Parse(FString(TEXT("UintBitfieldProperty"))); const FBoolProperty* Property = Context.GetSingleBoolProperty(Expression); TestNotNull(TEXT("Uint Bitfield"), Property); } TestObject->RemoveFromRoot(); return true; } #endif // WITH_DEV_AUTOMATION_TESTS