Files
UnrealEngine/Engine/Source/Editor/PropertyEditor/Private/EditConditionParserTests.cpp
2025-05-18 13:04:45 +08:00

958 lines
41 KiB
C++

// 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<EditConditionByteEnum> 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<UObject> UEditConditionTestObject::GetSoftClassPtrFunction() const
{
return SoftClassPtr;
}
TWeakObjectPtr<UObject> 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<UEditConditionTestObject>()->BoolProperty;
}
EditConditionTestEnum UEditConditionTestObject::StaticGetEnumFunction()
{
return UEditConditionTestObject::StaticClass()->GetDefaultObject<UEditConditionTestObject>()->EnumProperty;
}
TEnumAsByte<EditConditionByteEnum> UEditConditionTestObject::StaticGetByteEnumFunction()
{
return UEditConditionTestObject::StaticClass()->GetDefaultObject<UEditConditionTestObject>()->ByteEnumProperty;
}
double UEditConditionTestObject::StaticGetDoubleFunction()
{
return UEditConditionTestObject::StaticClass()->GetDefaultObject<UEditConditionTestObject>()->DoubleProperty;
}
int32 UEditConditionTestObject::StaticGetIntegerFunction()
{
return UEditConditionTestObject::StaticClass()->GetDefaultObject<UEditConditionTestObject>()->IntegerProperty;
}
uint8 UEditConditionTestObject::StaticGetUintBitfieldFunction()
{
return UEditConditionTestObject::StaticClass()->GetDefaultObject<UEditConditionTestObject>()->UintBitfieldProperty;
}
UObject* UEditConditionTestObject::StaticGetUObjectPtrFunction()
{
return UEditConditionTestObject::StaticClass()->GetDefaultObject<UEditConditionTestObject>()->UObjectPtr;
}
TSoftClassPtr<UObject> UEditConditionTestObject::StaticGetSoftClassPtrFunction()
{
return UEditConditionTestObject::StaticClass()->GetDefaultObject<UEditConditionTestObject>()->SoftClassPtr;
}
TWeakObjectPtr<UObject> UEditConditionTestObject::StaticGetWeakObjectPtrFunction()
{
return UEditConditionTestObject::StaticClass()->GetDefaultObject<UEditConditionTestObject>()->WeakObjectPtr;
}
#if WITH_DEV_AUTOMATION_TESTS
struct FTestEditConditionContext : IEditConditionContext
{
TMap<FString, bool> BoolValues;
TMap<FString, int64> IntegerValues;
TMap<FString, double> DoubleValues;
TMap<FString, FString> EnumValues;
FString EnumTypeName;
TMap<FString, int64> EnumTypeValues;
FTestEditConditionContext(){}
virtual ~FTestEditConditionContext() {}
virtual FName GetContextName() const
{
return "TestEditConditionContext";
}
virtual TOptional<bool> GetBoolValue(const FString& PropertyName, TWeakObjectPtr<UFunction> CachedFunction = nullptr) const override
{
if (const bool* Value = BoolValues.Find(PropertyName))
{
return *Value;
}
return TOptional<bool>();
}
virtual TOptional<int64> GetIntegerValue(const FString& PropertyName, TWeakObjectPtr<UFunction> CachedFunction = nullptr) const override
{
if (const int64* Value = IntegerValues.Find(PropertyName))
{
return *Value;
}
return TOptional<int64>();
}
virtual TOptional<double> GetNumericValue(const FString& PropertyName, TWeakObjectPtr<UFunction> CachedFunction = nullptr) const override
{
if (const double* Value = DoubleValues.Find(PropertyName))
{
return *Value;
}
return TOptional<double>();
}
virtual TOptional<FString> GetEnumValue(const FString& PropertyName, TWeakObjectPtr<UFunction> CachedFunction = nullptr) const override
{
if (const FString* Value = EnumValues.Find(PropertyName))
{
return *Value;
}
return TOptional<FString>();
}
virtual TOptional<int64> GetIntegerValueOfEnum(const FString& TypeName, const FString& ValueName) const override
{
check(TypeName == EnumTypeName);
if (const int64* Value = EnumTypeValues.Find(ValueName))
{
return *Value;
}
return TOptional<int64>();
}
virtual const TWeakObjectPtr<UFunction> GetFunction(const FString& FieldName) const override
{
return nullptr;
}
virtual TOptional<UObject*> GetPointerValue(const FString& PropertyName, TWeakObjectPtr<UFunction> CachedFunction = nullptr) const override
{
return TOptional<UObject*>();
}
virtual TOptional<FString> GetTypeName(const FString& PropertyName, TWeakObjectPtr<UFunction> CachedFunction = nullptr) const override
{
TOptional<FString> 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<FEditConditionExpression> 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<EditConditionParserTokens::FPropertyToken>();
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<FEditConditionExpression> Parsed = Parser.Parse(Expression);
if (!Parsed.IsValid())
{
ensureMsgf(false, TEXT("Failed to parse expression: %s"), *Expression);
return false;
}
TValueOrError<bool, FText> 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<FEditConditionExpression> Parsed = Parser.Parse(Expression);
if (!Parsed.IsValid())
{
ensureMsgf(false, TEXT("Failed to parse expression: %s"), *Expression);
return false;
}
TValueOrError<bool, FText> 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<UEditConditionTestObject>();
TestObject->AddToRoot();
TSharedPtr<FObjectPropertyNode> ObjectNode(new FObjectPropertyNode);
ObjectNode->AddObject(TestObject);
FPropertyNodeInitParams InitParams;
ObjectNode->InitNode(InitParams);
TOptional<bool> bResult;
bool bAllResults = true;
// enum comparison
{
static const FName EnumPropertyName = TEXT("EnumProperty");
TSharedPtr<FPropertyNode> 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<FPropertyNode> PropertyNode = ObjectNode->FindChildPropertyNode(BoolPropertyName, true);
FEditConditionContext Context(*PropertyNode.Get());
bAllResults &= RunVoidTests(Context);
}
// bool comparison
{
static const FName BoolPropertyName(TEXT("BoolProperty"));
TSharedPtr<FPropertyNode> PropertyNode = ObjectNode->FindChildPropertyNode(BoolPropertyName, true);
FEditConditionContext Context(*PropertyNode.Get());
TestObject->BoolProperty = true;
bAllResults &= RunBoolTests(Context);
}
// double comparison
{
static const FName DoublePropertyName(TEXT("DoubleProperty"));
TSharedPtr<FPropertyNode> 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<FPropertyNode> PropertyNode = ObjectNode->FindChildPropertyNode(IntegerPropertyName, true);
FEditConditionContext Context(*PropertyNode.Get());
TestObject->IntegerProperty = 5;
bAllResults &= RunNumericTests(Context);
}
{
static const FName DoublePropertyName(TEXT("DoubleProperty"));
TSharedPtr<FPropertyNode> 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<UEditConditionTestObject>();
TestObject->AddToRoot();
TSharedPtr<FObjectPropertyNode> ObjectNode(new FObjectPropertyNode);
ObjectNode->AddObject(TestObject);
FPropertyNodeInitParams InitParams;
ObjectNode->InitNode(InitParams);
static const FName BoolPropertyName(TEXT("BoolProperty"));
TSharedPtr<FPropertyNode> 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<UEditConditionTestObject>();
TestObject->AddToRoot();
TSharedPtr<FObjectPropertyNode> ObjectNode(new FObjectPropertyNode);
ObjectNode->AddObject(TestObject);
FPropertyNodeInitParams InitParams;
ObjectNode->InitNode(InitParams);
static const FName BoolPropertyName(TEXT("BoolProperty"));
TSharedPtr<FPropertyNode> 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<UEditConditionTestObject>();
TestObject->AddToRoot();
TSharedPtr<FObjectPropertyNode> ObjectNode(new FObjectPropertyNode);
ObjectNode->AddObject(TestObject);
FPropertyNodeInitParams InitParams;
ObjectNode->InitNode(InitParams);
static const FName BoolPropertyName(TEXT("BoolProperty"));
TSharedPtr<FPropertyNode> PropertyNode = ObjectNode->FindChildPropertyNode(BoolPropertyName, true);
FEditConditionContext Context(*PropertyNode.Get());
FEditConditionParser Parser;
{
TSharedPtr<FEditConditionExpression> Expression = Parser.Parse(FString(TEXT("BoolProperty")));
const FBoolProperty* Property = Context.GetSingleBoolProperty(Expression);
TestNotNull(TEXT("Bool"), Property);
}
{
TSharedPtr<FEditConditionExpression> 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