You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
Merging from /usr/John_Barrett: Added UE4 UScript/Blueprint Virtual Machine reflection class, new stack tracing and log tracing code, fixed and verified fixes for many broken unit tests; full list of changes:
- Added class for allowing reflection of UObjects/UStructs/UProperties/etc., so that properties can be referenced dynamically by string, instead of statically
- Allows game code to be referenced without direct linking
- Allows unit tests to break without blocking compiles, allows easier backwards compatability, and easier archiving of old unit tests
- Very useful for quick debug code; may tie it into the console with autocomplete at some stage
- Example:
FGuid* EntryItemGuidRef = (FGuid*)(void*)((FVMReflection(UnitPC.Get())->*"WorldInventory"->*"Inventory"->*ItemsPropName)["FFortItemEntry"][0]->*"ItemGuid")["FGuid"];
- Added a new stack tracing manager, which can be used by unit tests through GTraceManager, or through the whole engine with the StackTrace console command (see UnitTestManager.cpp for full command list)
- Allows conditional/filtered stack traces to be inserted anywhere into the code (categorized by name), for debugging purposes
- Once-off stack trace example:
GEngine->Exec(NULL, TEXT("StackTrace TraceName"));
- Multiple/accumulated stack trace examples:
GEngine->Exec(NULL, TEXT("StackTrace TraceName Add -Log"));
GEngine->Exec(NULL, TEXT("StackTrace TraceName Dump"));
- Added a log hook for the stack tracing manager, and a LogTrace command, which allows stack traces to be dumped whenever a specific log is encountered (see UnitTestManager.cpp for full command list)
- Example, for debugging the cause of a disconnect:
"LogTrace AddPartial UNetConnection::Close: Name: IpConnection_"
- Added -UnitTestCap=x commandline parameter, to limit the maximum number of unit tests that can run at once
- Added AllowedClientActors/AllowedClientRPCs to ClientUnitTest, to allow better whitelisting of accepted actors/RPC's
- Fixed many broken unit tests
- Adjusted actor replication blocking hook, to allow blocking of all instances of actor replication, instead of just actor channels
- Made console command results, auto-focus the Console tab, if the current tab won't display the results
- Fixed blocking automation testing, when the current game project doesn't support unit tests (doesn't have a unit test environment setup)
- Disable recursive killing of unit test child processes, while TerminateProc has a bug that kills all killable Windows processes
- Updated crash callstack detection
[CL 2565239 by John Barrett in Main branch]
This commit is contained in:
committed by
Shambler@OldUnreal.com
parent
dac8dc638e
commit
5d70d237c5
@@ -7,8 +7,8 @@
|
||||
"CreatedBy" : "Epic Games, Inc.",
|
||||
"CreatedByURL" : "http://epicgames.com",
|
||||
"EngineVersion" : "4.2.0",
|
||||
"Description": "A unit testing framework for testing the UE4 netcode, primarily for bugs and exploits",
|
||||
"Category": "Networking",
|
||||
"Description" : "A unit testing framework for testing the UE4 netcode, primarily for bugs and exploits",
|
||||
"Category" : "Networking",
|
||||
|
||||
"Modules" :
|
||||
[
|
||||
|
||||
@@ -63,8 +63,8 @@ enum class EUnitTestFlags : uint32 // NOTE: If you change from uint32, you need
|
||||
IgnoreDisconnect = 0x00080000, // Whether or not minimal/fake client disconnects, should be treated as a unit test failure
|
||||
|
||||
/** Unit test events */
|
||||
NotifyAllowNetActor = 0x00100000, // Whether or not to trigger 'NotifyAllowNetActor', which whitelist actor channels by class
|
||||
NotifyNetActors = 0x00200000, // Whether or not to trigger a 'NotifyNetActor' event, AFTER creation of actor channel actor
|
||||
NotifyProcessEvent = 0x00400000, // Whether or not to trigger 'NotifyScriptProcessEvent' for every executed local function
|
||||
|
||||
/** Debugging */
|
||||
CaptureReceivedRaw = 0x01000000, // Whether or not to capture raw (clientside) packet receives
|
||||
@@ -72,8 +72,8 @@ enum class EUnitTestFlags : uint32 // NOTE: If you change from uint32, you need
|
||||
DumpReceivedRaw = 0x04000000, // Whether or not to also hex-dump the raw packet receives to the log/log-window
|
||||
DumpSendRaw = 0x08000000, // Whether or not to also hex-dump the raw packet sends to the log/log-window
|
||||
DumpControlMessages = 0x10000000, // Whether or not to dump control channel messages, and their raw hex content
|
||||
DumpReceivedRPC = 0x20000000, // Whether or not to dump RPC receives
|
||||
DumpSendRPC = 0x40000000, // Whether or not to dump RPC sends
|
||||
DumpReceivedRPC = 0x20000000, // Whether or not to dump RPC receives (with LogNetTraffic, detects ProcessEvent RPC fail)
|
||||
DumpSendRPC = 0x40000000 // Whether or not to dump RPC sends
|
||||
};
|
||||
|
||||
// Required for bitwise operations with the above enum
|
||||
@@ -107,8 +107,8 @@ inline FString GetUnitTestFlagName(EUnitTestFlags Flag)
|
||||
EUTF_CASE(IgnoreServerCrash);
|
||||
EUTF_CASE(IgnoreClientCrash);
|
||||
EUTF_CASE(IgnoreDisconnect);
|
||||
EUTF_CASE(NotifyAllowNetActor);
|
||||
EUTF_CASE(NotifyNetActors);
|
||||
EUTF_CASE(NotifyProcessEvent);
|
||||
EUTF_CASE(CaptureReceivedRaw);
|
||||
EUTF_CASE(CaptureSendRaw);
|
||||
EUTF_CASE(DumpReceivedRaw);
|
||||
@@ -300,6 +300,12 @@ protected:
|
||||
/** The (non-URL) commandline parameters clients should be launched with */
|
||||
FString BaseClientParameters;
|
||||
|
||||
/** Actors the server is allowed replicate to client (requires AllowActors flag). Use NotifyAllowNetActor for conditional allows. */
|
||||
TArray<UClass*> AllowedClientActors;
|
||||
|
||||
/** Clientside RPC's that should be allowed to execute (requires NotifyProcessEvent flag; other flags also allow specific RPC's) */
|
||||
TArray<FString> AllowedClientRPCs;
|
||||
|
||||
|
||||
/** Runtime variables */
|
||||
protected:
|
||||
@@ -413,12 +419,13 @@ public:
|
||||
virtual void NotifyHandleClientPlayer(APlayerController* PC, UNetConnection* Connection);
|
||||
|
||||
/**
|
||||
* Override this, to receive notification BEFORE an actor channel actor has been created (allowing you to block, based on class)
|
||||
* Override this, to receive notification BEFORE a replicated actor has been created (allowing you to block, based on class)
|
||||
*
|
||||
* @param ActorClass The actor class that is about to be created
|
||||
* @param bActorChannel Whether or not this actor is being created within an actor channel
|
||||
* @return Whether or not to allow creation of that actor
|
||||
*/
|
||||
virtual bool NotifyAllowNetActor(UClass* ActorClass);
|
||||
virtual bool NotifyAllowNetActor(UClass* ActorClass, bool bActorChannel);
|
||||
|
||||
/**
|
||||
* Override this, to receive notification AFTER an actor channel actor has been created
|
||||
@@ -471,10 +478,9 @@ public:
|
||||
* @param Actor The actor the event is being executed on
|
||||
* @param Function The script function being executed
|
||||
* @param Parameters The raw unparsed parameters, being passed into the function
|
||||
* @param HookOrigin Reference to the unit test that the event is associated with
|
||||
* @return Whether or not to block the event from executing
|
||||
*/
|
||||
virtual bool NotifyScriptProcessEvent(AActor* Actor, UFunction* Function, void* Parameters, void* HookOrigin);
|
||||
virtual bool NotifyScriptProcessEvent(AActor* Actor, UFunction* Function, void* Parameters);
|
||||
#endif
|
||||
|
||||
/**
|
||||
@@ -549,12 +555,14 @@ public:
|
||||
/**
|
||||
* Sends the specified RPC for the specified actor, and verifies that the RPC was sent (triggering a unit test failure if not)
|
||||
*
|
||||
* @param Target The actor which will send the RPC
|
||||
* @param FunctionName The name of the RPC
|
||||
* @param Parms The RPC parameters (same as would be specified to ProcessEvent)
|
||||
* @return Whether or not the RPC was sent successfully
|
||||
* @param Target The actor which will send the RPC
|
||||
* @param FunctionName The name of the RPC
|
||||
* @param Parms The RPC parameters (same as would be specified to ProcessEvent)
|
||||
* @param ParmsSize The size of the RPC parameters, for verifying binary compatibility
|
||||
* @param ParmsSizeCorrection Some parameters are compressed to a different size. Verify Parms matches, and use this to correct.
|
||||
* @return Whether or not the RPC was sent successfully
|
||||
*/
|
||||
bool SendRPCChecked(AActor* Target, const TCHAR* FunctionName, void* Parms);
|
||||
bool SendRPCChecked(AActor* Target, const TCHAR* FunctionName, void* Parms, int16 ParmsSize, int16 ParmsSizeCorrection=0);
|
||||
|
||||
/**
|
||||
* As above, except the RPC is called within a lambda
|
||||
@@ -608,10 +616,12 @@ protected:
|
||||
/**
|
||||
* Whether or not all 'requirements' flag conditions have been met
|
||||
*
|
||||
* @return Whether or not all requirements are met
|
||||
* @param bIgnoreCustom If true, checks all requirements other than custom requirements
|
||||
* @return Whether or not all requirements are met
|
||||
*/
|
||||
bool HasAllRequirements();
|
||||
bool HasAllRequirements(bool bIgnoreCustom=false);
|
||||
|
||||
public:
|
||||
/**
|
||||
* Optionally, if the 'RequireCustom' flag is set, this returns whether custom conditions have been met.
|
||||
*
|
||||
@@ -633,7 +643,7 @@ protected:
|
||||
*/
|
||||
virtual ELogType GetExpectedLogTypes() override;
|
||||
|
||||
|
||||
protected:
|
||||
virtual bool ExecuteUnitTest() override;
|
||||
|
||||
virtual void CleanupUnitTest() override;
|
||||
|
||||
@@ -49,6 +49,10 @@ class ANUTActor : public AActor, public FSelfRegisteringExec
|
||||
{
|
||||
GENERATED_UCLASS_BODY()
|
||||
|
||||
private:
|
||||
/** The name of the beacon net driver */
|
||||
FName BeaconDriverName;
|
||||
|
||||
public:
|
||||
/** The value of World.RealTimeSeconds as of the last time the client was marked as still alive */
|
||||
float LastAliveTime;
|
||||
@@ -67,7 +71,11 @@ public:
|
||||
|
||||
virtual void PostActorCreated() override;
|
||||
|
||||
#if TARGET_UE4_CL < CL_CONSTNETCONN
|
||||
virtual UNetConnection* GetNetConnection() override;
|
||||
#else
|
||||
virtual UNetConnection* GetNetConnection() const override;
|
||||
#endif
|
||||
|
||||
bool NotifyControlMessage(UNetConnection* Connection, uint8 MessageType, FInBunch& Bunch);
|
||||
|
||||
|
||||
@@ -46,12 +46,13 @@ class UUnitTestNetConnection : public UIpConnection
|
||||
DECLARE_DELEGATE_TwoParams(FReceivedRawPacketDel, void* /*Data*/, int32& /*Count*/);
|
||||
|
||||
/**
|
||||
* Delegate for notifying on (and optionally blocking) actor channel creation
|
||||
* Delegate for notifying on (and optionally blocking) replicated actor creation
|
||||
*
|
||||
* @param ActorClass The class of the actor being replicated
|
||||
* @return Whether or not to allow creation of the actor channel
|
||||
* @param bActorChannel Whether or not this actor creation is from an actor channel
|
||||
* @return Whether or not to allow creation of the actor
|
||||
*/
|
||||
DECLARE_DELEGATE_RetVal_OneParam(bool, FOnActorChannelSpawn, UClass* /*ActorClass*/);
|
||||
DECLARE_DELEGATE_RetVal_TwoParams(bool, FOnReplicatedActorSpawn, UClass* /*ActorClass*/, bool /*bActorChannel*/);
|
||||
|
||||
|
||||
/** Delegate for hooking LowLevelSend */
|
||||
@@ -60,8 +61,8 @@ class UUnitTestNetConnection : public UIpConnection
|
||||
/** Delegate for hooking ReceivedRawPacket */
|
||||
FReceivedRawPacketDel ReceivedRawPacketDel;
|
||||
|
||||
/** Delegate for notifying on actor channel creation */
|
||||
FOnActorChannelSpawn ActorChannelSpawnDel;
|
||||
/** Delegate for notifying on replicated actor creation */
|
||||
FOnReplicatedActorSpawn ReplicatedActorSpawnDel;
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ class UUnitTestPackageMap : public UPackageMapClient
|
||||
TSharedPtr<FNetGUIDCache> InNetGUIDCache)
|
||||
: UPackageMapClient(ObjectInitializer, InConnection, InNetGUIDCache)
|
||||
, bWithinSerializeNewActor(false)
|
||||
, bPendingArchetypeSpawn(false)
|
||||
{
|
||||
}
|
||||
#endif
|
||||
@@ -29,5 +30,8 @@ class UUnitTestPackageMap : public UPackageMapClient
|
||||
public:
|
||||
/** Whether or not we are currently within execution of SerializeNewActor */
|
||||
bool bWithinSerializeNewActor;
|
||||
|
||||
/** Whether or not SerializeNewActor is about to spawn an actor, from an archetype */
|
||||
bool bPendingArchetypeSpawn;
|
||||
};
|
||||
|
||||
|
||||
@@ -137,6 +137,9 @@ protected:
|
||||
/** The unit test environment (not set until the current games unit test module is loaded - not set at all, if no such module) */
|
||||
static FUnitTestEnvironment* UnitEnv;
|
||||
|
||||
/** The null unit test environment - for unit tests which support all games, due to requiring no game-specific features */
|
||||
static FUnitTestEnvironment* NullUnitEnv;
|
||||
|
||||
/** The time of the last NetTick event */
|
||||
double LastNetTick;
|
||||
|
||||
@@ -227,7 +230,14 @@ public:
|
||||
|
||||
FString CurGame = FApp::GetGameName();
|
||||
|
||||
Result = ExpectedResult[CurGame];
|
||||
if (ExpectedResult.Contains(CurGame))
|
||||
{
|
||||
Result = ExpectedResult[CurGame];
|
||||
}
|
||||
else if (ExpectedResult.Contains(TEXT("NullUnitEnv")))
|
||||
{
|
||||
Result = ExpectedResult[TEXT("NullUnitEnv")];
|
||||
}
|
||||
|
||||
return Result;
|
||||
}
|
||||
@@ -305,7 +315,7 @@ public:
|
||||
/**
|
||||
* Executes the main unit test
|
||||
*
|
||||
* @return Whether or not the unit test was executed successfully
|
||||
* @return Whether or not the unit test kicked off execution successfully
|
||||
*/
|
||||
virtual bool ExecuteUnitTest() PURE_VIRTUAL(UUnitTest::ExecuteUnitTest, return false;)
|
||||
|
||||
|
||||
@@ -14,7 +14,20 @@
|
||||
* "UE4Editor.exe shootergame -run=NetcodeUnitTest.UnitTestCommandlet"
|
||||
*
|
||||
* Parameters:
|
||||
* "-UnitTest=UnitTestName" - Launches only the specified unit test
|
||||
* -UnitTest=UnitTestName
|
||||
* - Launches only the specified unit test
|
||||
*
|
||||
* -UnitTestNoAutoClose
|
||||
* - Sets the default option for the 'AutoClose' button to False
|
||||
*
|
||||
* -UnitTestServerParms="CommandlineParameters"
|
||||
* - Adds additional commandline parameters to unit test server instances (useful for e.g. unsuppressing specific logs)
|
||||
*
|
||||
* -UnitTestClientParms="CommandlineParameters"
|
||||
* - Adds additional commandline parameters to unit test client instances
|
||||
*
|
||||
* -UnitTestCap=x
|
||||
* - Caps the maximum number of unit tests that can run at the same time
|
||||
*/
|
||||
UCLASS()
|
||||
class UUnitTestCommandlet : public UCommandlet
|
||||
|
||||
@@ -262,6 +262,11 @@ public:
|
||||
* FOutputDevice methods
|
||||
*/
|
||||
|
||||
// We're hiding UObject::Serialize() by declaring this. That's OK, but Clang will warn about it.
|
||||
#if defined(__clang__)
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Woverloaded-virtual"
|
||||
#endif
|
||||
/**
|
||||
* Write text to the console
|
||||
*
|
||||
@@ -269,10 +274,6 @@ public:
|
||||
* @param Verbosity The log level verbosity for the message
|
||||
* @param Category The log category for the message
|
||||
*/
|
||||
#if defined(__clang__) // We're hiding UObject::Serialize() by declaring this. That's OK, but Clang will warn about it.
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Woverloaded-virtual"
|
||||
#endif
|
||||
virtual void Serialize(const TCHAR* Data, ELogVerbosity::Type Verbosity, const class FName& Category) override;
|
||||
#if defined(__clang__)
|
||||
#pragma clang diagnostic pop
|
||||
|
||||
@@ -0,0 +1,166 @@
|
||||
// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "UnitTest.h"
|
||||
|
||||
#include "VMReflection.generated.h"
|
||||
|
||||
/**
|
||||
* Internal unit test for verifying the functionality of the UScript/BP VM reflection helper
|
||||
*/
|
||||
UCLASS()
|
||||
class UVMReflection : public UUnitTest
|
||||
{
|
||||
GENERATED_UCLASS_BODY()
|
||||
|
||||
public:
|
||||
virtual bool ExecuteUnitTest() override;
|
||||
};
|
||||
|
||||
/**
|
||||
* Test classes for testing different types/combinations of property reflection
|
||||
*/
|
||||
|
||||
UCLASS()
|
||||
class UVMTestClassA : public UObject
|
||||
{
|
||||
GENERATED_UCLASS_BODY()
|
||||
|
||||
public:
|
||||
UPROPERTY()
|
||||
UObject* AObjectRef;
|
||||
|
||||
UPROPERTY()
|
||||
uint8 ByteProp;
|
||||
|
||||
UPROPERTY()
|
||||
uint16 UInt16Prop;
|
||||
|
||||
UPROPERTY()
|
||||
uint32 UInt32Prop;
|
||||
|
||||
UPROPERTY()
|
||||
uint64 UInt64Prop;
|
||||
|
||||
UPROPERTY()
|
||||
int8 Int8Prop;
|
||||
|
||||
UPROPERTY()
|
||||
int16 Int16Prop;
|
||||
|
||||
UPROPERTY()
|
||||
int32 Int32Prop;
|
||||
|
||||
UPROPERTY()
|
||||
int64 Int64Prop;
|
||||
|
||||
UPROPERTY()
|
||||
float FloatProp;
|
||||
|
||||
UPROPERTY()
|
||||
double DoubleProp;
|
||||
|
||||
UPROPERTY()
|
||||
bool bBoolPropA;
|
||||
|
||||
UPROPERTY()
|
||||
bool bBoolPropB;
|
||||
|
||||
UPROPERTY()
|
||||
bool bBoolPropC;
|
||||
|
||||
UPROPERTY()
|
||||
bool bBoolPropD;
|
||||
|
||||
UPROPERTY()
|
||||
bool bBoolPropE;
|
||||
|
||||
UPROPERTY()
|
||||
FName NameProp;
|
||||
|
||||
UPROPERTY()
|
||||
FString StringProp;
|
||||
|
||||
UPROPERTY()
|
||||
FText TextProp;
|
||||
|
||||
|
||||
UPROPERTY()
|
||||
uint8 BytePropArray[4];
|
||||
|
||||
UPROPERTY()
|
||||
UObject* ObjectPropArray[2];
|
||||
|
||||
UPROPERTY()
|
||||
TArray<uint8> DynBytePropArray;
|
||||
|
||||
UPROPERTY()
|
||||
TArray<bool> DynBoolPropArray;
|
||||
|
||||
UPROPERTY()
|
||||
TArray<UObject*> DynObjectPropArray;
|
||||
|
||||
UPROPERTY()
|
||||
TArray<FName> DynNamePropArray;
|
||||
|
||||
UPROPERTY()
|
||||
TArray<double> DynDoublePropArray;
|
||||
|
||||
UPROPERTY()
|
||||
TArray<float> DynFloatPropArray;
|
||||
|
||||
UPROPERTY()
|
||||
TArray<int16> DynInt16PropArray;
|
||||
|
||||
UPROPERTY()
|
||||
TArray<int64> DynInt64PropArray;
|
||||
|
||||
UPROPERTY()
|
||||
TArray<int8> DynInt8PropArray;
|
||||
|
||||
UPROPERTY()
|
||||
TArray<int32> DynIntPropArray;
|
||||
|
||||
UPROPERTY()
|
||||
TArray<uint16> DynUInt16PropArray;
|
||||
|
||||
UPROPERTY()
|
||||
TArray<uint32> DynUIntPropArray;
|
||||
|
||||
UPROPERTY()
|
||||
TArray<uint64> DynUInt64PropArray;
|
||||
|
||||
UPROPERTY()
|
||||
TArray<FString> DynStringPropArray;
|
||||
|
||||
UPROPERTY()
|
||||
TArray<FText> DynTextPropArray;
|
||||
|
||||
UPROPERTY()
|
||||
TArray<UClass*> DynClassPropArray;
|
||||
|
||||
UPROPERTY()
|
||||
TArray<APawn*> DynPawnPropArray;
|
||||
|
||||
|
||||
UPROPERTY()
|
||||
FVector StructProp;
|
||||
|
||||
UPROPERTY()
|
||||
FVector StructPropArray[2];
|
||||
|
||||
UPROPERTY()
|
||||
TArray<FVector> DynStructPropArray;
|
||||
};
|
||||
|
||||
UCLASS()
|
||||
class UVMTestClassB : public UObject
|
||||
{
|
||||
GENERATED_UCLASS_BODY()
|
||||
|
||||
public:
|
||||
UPROPERTY()
|
||||
UObject* BObjectRef;
|
||||
};
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
#include "UnitTestEnvironment.h"
|
||||
#include "Net/NUTUtilNet.h"
|
||||
#include "NUTUtilDebug.h"
|
||||
#include "NUTUtilReflection.h"
|
||||
|
||||
#include "LogWindowManager.h"
|
||||
#include "SLogWindow.h"
|
||||
@@ -35,6 +36,8 @@ UClientUnitTest::UClientUnitTest(const FObjectInitializer& ObjectInitializer)
|
||||
, BaseServerParameters(TEXT(""))
|
||||
, BaseClientURL(TEXT(""))
|
||||
, BaseClientParameters(TEXT(""))
|
||||
, AllowedClientActors()
|
||||
, AllowedClientRPCs()
|
||||
, ActiveProcesses()
|
||||
, ServerHandle(NULL)
|
||||
, ServerAddress(TEXT(""))
|
||||
@@ -105,7 +108,7 @@ void UClientUnitTest::NotifyHandleClientPlayer(APlayerController* PC, UNetConnec
|
||||
}
|
||||
}
|
||||
|
||||
bool UClientUnitTest::NotifyAllowNetActor(UClass* ActorClass)
|
||||
bool UClientUnitTest::NotifyAllowNetActor(UClass* ActorClass, bool bActorChannel)
|
||||
{
|
||||
bool bAllow = false;
|
||||
|
||||
@@ -126,6 +129,24 @@ bool UClientUnitTest::NotifyAllowNetActor(UClass* ActorClass)
|
||||
bAllow = true;
|
||||
}
|
||||
|
||||
if (!!(UnitTestFlags & EUnitTestFlags::RequireBeacon) && ActorClass->IsChildOf(AOnlineBeaconClient::StaticClass()) &&
|
||||
UnitBeacon == NULL)
|
||||
{
|
||||
bAllow = true;
|
||||
}
|
||||
|
||||
if (!bAllow && AllowedClientActors.Num() > 0)
|
||||
{
|
||||
const auto CheckIsChildOf =
|
||||
[&](const UClass* CurEntry)
|
||||
{
|
||||
return ActorClass->IsChildOf(CurEntry);
|
||||
};
|
||||
|
||||
// Use 'ContainsByPredicate' as iterator
|
||||
bAllow = AllowedClientActors.ContainsByPredicate(CheckIsChildOf);
|
||||
}
|
||||
|
||||
return bAllow;
|
||||
}
|
||||
|
||||
@@ -133,14 +154,18 @@ void UClientUnitTest::NotifyNetActor(UActorChannel* ActorChannel, AActor* Actor)
|
||||
{
|
||||
if (!UnitNUTActor.IsValid())
|
||||
{
|
||||
// Set this even if not required, as it's needed for some UI elements to function
|
||||
UnitNUTActor = Cast<ANUTActor>(Actor);
|
||||
|
||||
ResetTimeout(TEXT("NotifyNetActor - UnitNUTActor"));
|
||||
|
||||
if (UnitNUTActor.IsValid() && !!(UnitTestFlags & EUnitTestFlags::RequireNUTActor) && HasAllRequirements())
|
||||
if (UnitNUTActor.IsValid())
|
||||
{
|
||||
ResetTimeout(TEXT("ExecuteClientUnitTest (NotifyNetActor - UnitNUTActor)"));
|
||||
ExecuteClientUnitTest();
|
||||
ResetTimeout(TEXT("NotifyNetActor - UnitNUTActor"));
|
||||
|
||||
if (!!(UnitTestFlags & EUnitTestFlags::RequireNUTActor) && HasAllRequirements())
|
||||
{
|
||||
ResetTimeout(TEXT("ExecuteClientUnitTest (NotifyNetActor - UnitNUTActor)"));
|
||||
ExecuteClientUnitTest();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -273,26 +298,26 @@ void UClientUnitTest::ReceivedControlBunch(FInBunch& Bunch)
|
||||
}
|
||||
|
||||
#if !UE_BUILD_SHIPPING
|
||||
bool UClientUnitTest::NotifyScriptProcessEvent(AActor* Actor, UFunction* Function, void* Parameters, void* HookOrigin)
|
||||
bool UClientUnitTest::NotifyScriptProcessEvent(AActor* Actor, UFunction* Function, void* Parameters)
|
||||
{
|
||||
bool bBlockEvent = false;
|
||||
|
||||
// Handle UnitTestFlags that require RPC monitoring
|
||||
if (!(UnitTestFlags & EUnitTestFlags::AcceptRPCs) || !!(UnitTestFlags & EUnitTestFlags::DumpReceivedRPC) ||
|
||||
if (AllowedClientRPCs.Num() > 0 || !(UnitTestFlags & EUnitTestFlags::AcceptRPCs) || !!(UnitTestFlags & EUnitTestFlags::DumpReceivedRPC) ||
|
||||
!!(UnitTestFlags & EUnitTestFlags::RequirePawn))
|
||||
{
|
||||
bool bNetClientRPC = !!(Function->FunctionFlags & FUNC_Net) && !!(Function->FunctionFlags & FUNC_NetClient);
|
||||
|
||||
if (bNetClientRPC)
|
||||
{
|
||||
FString FuncName = Function->GetName();
|
||||
|
||||
// Whether or not to force acceptance of this RPC
|
||||
bool bForceAccept = false;
|
||||
|
||||
// Handle detection and proper setup of the PlayerController's pawn
|
||||
if (!!(UnitTestFlags & EUnitTestFlags::RequirePawn) && !bUnitPawnSetup && UnitPC != NULL)
|
||||
{
|
||||
FString FuncName = Function->GetName();
|
||||
|
||||
if (FuncName == TEXT("ClientRestart"))
|
||||
{
|
||||
UNIT_LOG(ELogType::StatusImportant, TEXT("Got ClientRestart"));
|
||||
@@ -337,17 +362,33 @@ bool UClientUnitTest::NotifyScriptProcessEvent(AActor* Actor, UFunction* Functio
|
||||
}
|
||||
|
||||
|
||||
bForceAccept = bForceAccept && AllowedClientRPCs.Contains(FuncName);
|
||||
|
||||
// Block RPC's, if they are not accepted
|
||||
if (!(UnitTestFlags & EUnitTestFlags::AcceptRPCs) && !bForceAccept)
|
||||
{
|
||||
UNIT_LOG(, TEXT("Blocking receive RPC '%s' for actor '%s'"), *Function->GetName(), *Actor->GetFullName());
|
||||
FString FuncParms = NUTUtilRefl::FunctionParmsToString(Function, Parameters);
|
||||
|
||||
UNIT_LOG(, TEXT("Blocking receive RPC '%s' for actor '%s'"), *FuncName, *Actor->GetFullName());
|
||||
|
||||
if (FuncParms.Len() > 0)
|
||||
{
|
||||
UNIT_LOG(, TEXT(" '%s' parameters: %s"), *FuncName, *FuncParms);
|
||||
}
|
||||
|
||||
bBlockEvent = true;
|
||||
}
|
||||
|
||||
if (!!(UnitTestFlags & EUnitTestFlags::DumpReceivedRPC) && !bBlockEvent)
|
||||
{
|
||||
UNIT_LOG(ELogType::StatusDebug, TEXT("Received RPC '%s' for actor '%s'"), *Function->GetName(), *Actor->GetFullName());
|
||||
FString FuncParms = NUTUtilRefl::FunctionParmsToString(Function, Parameters);
|
||||
|
||||
UNIT_LOG(ELogType::StatusDebug, TEXT("Received RPC '%s' for actor '%s'"), *FuncName, *Actor->GetFullName());
|
||||
|
||||
if (FuncParms.Len() > 0)
|
||||
{
|
||||
UNIT_LOG(, TEXT(" '%s' parameters: %s"), *FuncName, *FuncParms);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -383,36 +424,37 @@ void UClientUnitTest::NotifyProcessLog(TWeakPtr<FUnitTestProcess> InProcess, con
|
||||
const TArray<FString>* ServerStartProgressLogs = NULL;
|
||||
const TArray<FString>* ServerReadyLogs = NULL;
|
||||
const TArray<FString>* ServerTimeoutResetLogs = NULL;
|
||||
const TArray<FString>* ClientTimeoutResetLogs = NULL;
|
||||
|
||||
UnitEnv->GetServerProgressLogs(ServerStartProgressLogs, ServerReadyLogs, ServerTimeoutResetLogs);
|
||||
UnitEnv->GetClientProgressLogs(ClientTimeoutResetLogs);
|
||||
|
||||
// Using 'ContainsByPredicate' as an iterator
|
||||
FString MatchedLine;
|
||||
|
||||
const auto SearchInLogLine =
|
||||
[&](const FString& ProgressLine)
|
||||
{
|
||||
bool bFound = false;
|
||||
|
||||
for (auto CurLine : InLogLines)
|
||||
{
|
||||
if (CurLine.Contains(ProgressLine))
|
||||
{
|
||||
MatchedLine = CurLine;
|
||||
bFound = true;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return bFound;
|
||||
};
|
||||
|
||||
// If launching a server, delay joining by the fake client, until the server has fully setup, and reset the unit test timeout,
|
||||
// each time there is a server log event, that indicates progress in starting up
|
||||
if (!!(UnitTestFlags & EUnitTestFlags::LaunchServer) && ServerHandle.IsValid() && InProcess.HasSameObject(ServerHandle.Pin().Get()))
|
||||
{
|
||||
// Using 'ContainsByPredicate' as an iterator
|
||||
FString MatchedLine;
|
||||
|
||||
const auto SearchInLogLine =
|
||||
[&](const FString& ProgressLine)
|
||||
{
|
||||
bool bFound = false;
|
||||
|
||||
for (auto CurLine : InLogLines)
|
||||
{
|
||||
if (CurLine.Contains(ProgressLine))
|
||||
{
|
||||
MatchedLine = CurLine;
|
||||
bFound = true;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return bFound;
|
||||
};
|
||||
|
||||
if (UnitConn == NULL || UnitConn->State == EConnectionState::USOCK_Pending)
|
||||
{
|
||||
if (ServerReadyLogs->ContainsByPredicate(SearchInLogLine))
|
||||
@@ -445,7 +487,16 @@ void UClientUnitTest::NotifyProcessLog(TWeakPtr<FUnitTestProcess> InProcess, con
|
||||
}
|
||||
}
|
||||
|
||||
// @todo JohnB: Consider adding a progress-checker for launched clients as well
|
||||
if (!!(UnitTestFlags & EUnitTestFlags::LaunchClient) && ClientHandle.IsValid() && InProcess.HasSameObject(ClientHandle.Pin().Get()))
|
||||
{
|
||||
if (ClientTimeoutResetLogs->Num() > 0)
|
||||
{
|
||||
if (ClientTimeoutResetLogs->ContainsByPredicate(SearchInLogLine))
|
||||
{
|
||||
ResetTimeout(FString(TEXT("ClientTimeoutReset: ")) + MatchedLine, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// @todo JohnB: Consider also, adding a way to communicate with launched clients,
|
||||
// to reset their connection timeout upon server progress, if they fully startup before the server does
|
||||
@@ -568,12 +619,19 @@ bool UClientUnitTest::NotifyConsoleCommandRequest(FString CommandContext, FStrin
|
||||
}
|
||||
else
|
||||
{
|
||||
UNIT_LOG(, TEXT("Can't execute command '%s', it's in the 'bad commands' list (i.e. probably crashes)"), *Command);
|
||||
UNIT_LOG(ELogType::OriginConsole,
|
||||
TEXT("Can't execute command '%s', it's in the 'bad commands' list (i.e. probably crashes)"), *Command);
|
||||
}
|
||||
}
|
||||
else if (CommandContext == TEXT("Server"))
|
||||
{
|
||||
// @todo JohnB: Perhaps add extra checks here, to be sure we're ready to send console commands?
|
||||
//
|
||||
// UPDATE: Yes, this is a good idea, because if the client hasn't gotten to the correct login stage
|
||||
// (NMT_Join or such, need to check when server rejects non-login control commands),
|
||||
// then it leads to an early disconnect when you try to spam-send a command early.
|
||||
//
|
||||
// It's easy to test this, just type in a command before join, and hold down enter on the edit box to spam it.
|
||||
if (UnitConn != NULL)
|
||||
{
|
||||
FOutBunch* ControlChanBunch = NUTNet::CreateChannelBunch(ControlBunchSequence, UnitConn, CHTYPE_Control, 0);
|
||||
@@ -589,25 +647,25 @@ bool UClientUnitTest::NotifyConsoleCommandRequest(FString CommandContext, FStrin
|
||||
NUTNet::SendControlBunch(UnitConn, *ControlChanBunch);
|
||||
|
||||
|
||||
UNIT_LOG(, TEXT("Sent command '%s' to server."), *Command);
|
||||
UNIT_LOG(ELogType::OriginConsole, TEXT("Sent command '%s' to server."), *Command);
|
||||
|
||||
bHandled = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
UNIT_LOG(, TEXT("Failed to send console command '%s', no server connection."), *Command);
|
||||
UNIT_LOG(ELogType::OriginConsole, TEXT("Failed to send console command '%s', no server connection."), *Command);
|
||||
}
|
||||
}
|
||||
else if (CommandContext == TEXT("Client"))
|
||||
{
|
||||
// @todo JohnB
|
||||
|
||||
UNIT_LOG(, TEXT("Client console commands not yet implemented"));
|
||||
UNIT_LOG(ELogType::OriginConsole, TEXT("Client console commands not yet implemented"));
|
||||
}
|
||||
else
|
||||
{
|
||||
UNIT_LOG(ELogType::StatusFailure, TEXT("Unknown console command context '%s' - this is a bug that should be fixed."),
|
||||
*CommandContext);
|
||||
UNIT_LOG(ELogType::StatusFailure | ELogType::OriginConsole,
|
||||
TEXT("Unknown console command context '%s' - this is a bug that should be fixed."), *CommandContext);
|
||||
}
|
||||
|
||||
return bHandled;
|
||||
@@ -632,7 +690,8 @@ void UClientUnitTest::GetCommandContextList(TArray<TSharedPtr<FString>>& OutList
|
||||
}
|
||||
|
||||
|
||||
bool UClientUnitTest::SendRPCChecked(AActor* Target, const TCHAR* FunctionName, void* Parms)
|
||||
bool UClientUnitTest::SendRPCChecked(AActor* Target, const TCHAR* FunctionName, void* Parms, int16 ParmsSize,
|
||||
int16 ParmsSizeCorrection/*=0*/)
|
||||
{
|
||||
bool bSuccess = false;
|
||||
UFunction* TargetFunc = Target->FindFunction(FName(FunctionName));
|
||||
@@ -641,7 +700,15 @@ bool UClientUnitTest::SendRPCChecked(AActor* Target, const TCHAR* FunctionName,
|
||||
|
||||
if (TargetFunc != NULL)
|
||||
{
|
||||
Target->ProcessEvent(TargetFunc, Parms);
|
||||
if (TargetFunc->ParmsSize == ParmsSize + ParmsSizeCorrection)
|
||||
{
|
||||
Target->ProcessEvent(TargetFunc, Parms);
|
||||
}
|
||||
else
|
||||
{
|
||||
UNIT_LOG(ELogType::StatusFailure, TEXT("Failed to send RPC '%s', mismatched parameters: '%i' vs '%i' (%i - %i)."),
|
||||
FunctionName, TargetFunc->ParmsSize, ParmsSize + ParmsSizeCorrection, ParmsSize, -ParmsSizeCorrection);
|
||||
}
|
||||
}
|
||||
|
||||
bSuccess = PostSendRPC(FunctionName, Target);
|
||||
@@ -698,6 +765,7 @@ bool UClientUnitTest::PostSendRPC(FString RPCName, AActor* Target/*=NULL*/)
|
||||
}
|
||||
|
||||
|
||||
// @todo JohnB: This should be changed to work as compile-time conditionals, if possible
|
||||
bool UClientUnitTest::ValidateUnitTestSettings(bool bCDOCheck/*=false*/)
|
||||
{
|
||||
bool bSuccess = Super::ValidateUnitTestSettings();
|
||||
@@ -712,6 +780,9 @@ bool UClientUnitTest::ValidateUnitTestSettings(bool bCDOCheck/*=false*/)
|
||||
UNIT_ASSERT(!(UnitTestFlags & EUnitTestFlags::LaunchClient) || BaseClientParameters.Len() > 0);
|
||||
|
||||
|
||||
// You can't specify an allowed actors whitelist, without the AcceptActors flag
|
||||
UNIT_ASSERT(AllowedClientActors.Num() == 0 || !!(UnitTestFlags & EUnitTestFlags::AcceptActors));
|
||||
|
||||
// If you require a player/NUTActor, you need to accept actor channels
|
||||
UNIT_ASSERT((!(UnitTestFlags & EUnitTestFlags::AcceptPlayerController) && !(UnitTestFlags & EUnitTestFlags::RequireNUTActor)) ||
|
||||
!!(UnitTestFlags & EUnitTestFlags::AcceptActors));
|
||||
@@ -723,9 +794,23 @@ bool UClientUnitTest::ValidateUnitTestSettings(bool bCDOCheck/*=false*/)
|
||||
// If you require a pawn, you must require a PlayerController
|
||||
UNIT_ASSERT(!(UnitTestFlags & EUnitTestFlags::RequirePawn) || !!(UnitTestFlags & EUnitTestFlags::RequirePlayerController));
|
||||
|
||||
// If you require a pawn, you must enable NotifyProcessEvent
|
||||
UNIT_ASSERT(!(UnitTestFlags & EUnitTestFlags::RequirePawn) || !!(UnitTestFlags & EUnitTestFlags::NotifyProcessEvent));
|
||||
|
||||
// If you want to dump received RPC's, you need to hook NotifyProcessEvent
|
||||
UNIT_ASSERT(!(UnitTestFlags & EUnitTestFlags::DumpReceivedRPC) || !!(UnitTestFlags & EUnitTestFlags::NotifyProcessEvent));
|
||||
|
||||
// You can't whitelist client RPC's (i.e. unblock whitelisted RPC's), unless all RPC's are blocked by default
|
||||
UNIT_ASSERT(!(UnitTestFlags & EUnitTestFlags::AcceptRPCs) || AllowedClientRPCs.Num() == 0);
|
||||
|
||||
#if UE_BUILD_SHIPPING
|
||||
// You can't detect pawns in shipping builds, as you need to hook ProcessEvent for RPC notifications
|
||||
UNIT_ASSERT(!(UnitTestFlags & EUnitTestFlags::RequirePawn));
|
||||
// You can't hook ProcessEvent or block RPCs in shipping builds, as the main engine hook is not available in shipping; soft-fail
|
||||
if (!!(UnitTestFlags & EUnitTestFlags::NotifyProcessEvent) || !(UnitTestFlags & EUnitTestFlags::AcceptRPCs))
|
||||
{
|
||||
UNIT_LOG(ELogType::StatusFailure | ELogType::StyleBold, TEXT("Unit tests run in shipping mode, can't hook ProcessEvent."));
|
||||
|
||||
bSuccess = false;
|
||||
}
|
||||
#endif
|
||||
|
||||
// If you require a pawn, validate the existence of certain RPC's that are needed for pawn setup and verification
|
||||
@@ -768,16 +853,6 @@ bool UClientUnitTest::ValidateUnitTestSettings(bool bCDOCheck/*=false*/)
|
||||
// Don't require a beacon, if you're not connecting to a beacon
|
||||
UNIT_ASSERT(!(UnitTestFlags & EUnitTestFlags::RequireBeacon) || !!(UnitTestFlags & EUnitTestFlags::BeaconConnect));
|
||||
|
||||
// In shipping builds, you MUST accept RPC's, as there is no way to filter them out (can't hook ProcessEvent); soft-fail
|
||||
#if UE_BUILD_SHIPPING
|
||||
if (!(UnitTestFlags & EUnitTestFlags::AcceptRPCs))
|
||||
{
|
||||
UNIT_LOG(ELogType::StatusFailure | ELogType::StyleBold, TEXT("Unit tests run in shipping mode, must accept RPC's"));
|
||||
|
||||
bSuccess = false;
|
||||
}
|
||||
#endif
|
||||
|
||||
// Don't specify server-dependent flags, if not auto-launching a server
|
||||
UNIT_ASSERT(!(UnitTestFlags & EUnitTestFlags::LaunchClient) || !!(UnitTestFlags & EUnitTestFlags::LaunchServer));
|
||||
|
||||
@@ -787,9 +862,6 @@ bool UClientUnitTest::ValidateUnitTestSettings(bool bCDOCheck/*=false*/)
|
||||
// If DumpSendRaw is set, make sure hooking of SendRawPacket is enabled too
|
||||
UNIT_ASSERT(!(UnitTestFlags & EUnitTestFlags::DumpSendRaw) || !!(UnitTestFlags & EUnitTestFlags::CaptureSendRaw));
|
||||
|
||||
// You can't get net actor allow notifications, unless you accept actors
|
||||
UNIT_ASSERT(!(UnitTestFlags & EUnitTestFlags::NotifyAllowNetActor) || !!(UnitTestFlags & EUnitTestFlags::AcceptActors));
|
||||
|
||||
// You can't get net actor notifications, unless you accept actors
|
||||
UNIT_ASSERT(!(UnitTestFlags & EUnitTestFlags::NotifyNetActors) || !!(UnitTestFlags & EUnitTestFlags::AcceptActors));
|
||||
|
||||
@@ -842,7 +914,7 @@ EUnitTestFlags UClientUnitTest::GetMetRequirements()
|
||||
return ReturnVal;
|
||||
}
|
||||
|
||||
bool UClientUnitTest::HasAllRequirements()
|
||||
bool UClientUnitTest::HasAllRequirements(bool bIgnoreCustom/*=false*/)
|
||||
{
|
||||
bool bReturnVal = true;
|
||||
|
||||
@@ -854,6 +926,11 @@ bool UClientUnitTest::HasAllRequirements()
|
||||
|
||||
EUnitTestFlags RequiredFlags = (UnitTestFlags & EUnitTestFlags::RequirementsMask);
|
||||
|
||||
if (bIgnoreCustom)
|
||||
{
|
||||
RequiredFlags &= ~EUnitTestFlags::RequireCustom;
|
||||
}
|
||||
|
||||
if ((RequiredFlags & GetMetRequirements()) != RequiredFlags)
|
||||
{
|
||||
bReturnVal = false;
|
||||
@@ -990,7 +1067,7 @@ bool UClientUnitTest::ConnectFakeClient(FUniqueNetIdRepl* InNetID/*=NULL*/)
|
||||
|
||||
bool bFailedScriptHook = false;
|
||||
|
||||
if (!(UnitTestFlags & EUnitTestFlags::AcceptRPCs))
|
||||
if (!(UnitTestFlags & EUnitTestFlags::AcceptRPCs) || !!(UnitTestFlags & EUnitTestFlags::NotifyProcessEvent))
|
||||
{
|
||||
#if !UE_BUILD_SHIPPING
|
||||
AddProcessEventCallback(this, &UClientUnitTest::InternalScriptProcessEvent);
|
||||
@@ -1049,10 +1126,7 @@ bool UClientUnitTest::ConnectFakeClient(FUniqueNetIdRepl* InNetID/*=NULL*/)
|
||||
CurUnitConn->LowLevelSendDel.BindUObject(this, &UClientUnitTest::NotifySendRawPacket);
|
||||
}
|
||||
|
||||
if (!!(UnitTestFlags & EUnitTestFlags::NotifyAllowNetActor))
|
||||
{
|
||||
CurUnitConn->ActorChannelSpawnDel.BindUObject(this, &UClientUnitTest::NotifyAllowNetActor);
|
||||
}
|
||||
CurUnitConn->ReplicatedActorSpawnDel.BindUObject(this, &UClientUnitTest::NotifyAllowNetActor);
|
||||
|
||||
// If you don't have to wait for any requirements, execute immediately
|
||||
if (!(UnitTestFlags & (EUnitTestFlags::RequirementsMask)))
|
||||
@@ -1296,10 +1370,11 @@ void UClientUnitTest::ShutdownUnitTestProcess(TSharedPtr<FUnitTestProcess> InHan
|
||||
FString LogMsg = FString::Printf(TEXT("Shutting down process '%s'."), *InHandle->ProcessTag);
|
||||
|
||||
UNIT_LOG(ELogType::StatusImportant, TEXT("%s"), *LogMsg);
|
||||
UNIT_STATUS_LOG(ELogType::StatusVerbose, TEXT("%s"), *LogMsg)
|
||||
UNIT_STATUS_LOG(ELogType::StatusVerbose, TEXT("%s"), *LogMsg);
|
||||
|
||||
|
||||
FPlatformProcess::TerminateProc(InHandle->ProcessHandle, true);
|
||||
// @todo JohnB: Restore 'true' here, once the issue where killing child processes sometimes kills all processes, is fixed
|
||||
FPlatformProcess::TerminateProc(InHandle->ProcessHandle);//, true);
|
||||
|
||||
#if TARGET_UE4_CL < CL_CLOSEPROC
|
||||
InHandle->ProcessHandle.Close();
|
||||
@@ -1463,8 +1538,15 @@ void UClientUnitTest::CheckOutputForError(TSharedPtr<FUnitTestProcess> InProcess
|
||||
|
||||
if (InProcess->ErrorLogStage != EErrorLogStage::ELS_NoError)
|
||||
{
|
||||
// Regex pattern for matching callstack logs - matches: " (0x000007fefe22cacd) + 0 bytes ["
|
||||
const FRegexPattern CallstackPattern(TEXT("\\s\\(0x[0-9,a-f]+\\) \\+ [0-9]+ bytes \\["));
|
||||
// Regex pattern for matching callstack logs - matches:
|
||||
// " (0x000007fefe22cacd) + 0 bytes"
|
||||
// " {0x000007fefe22cacd} + 0 bytes"
|
||||
const FRegexPattern CallstackPattern(TEXT("\\s[\\(|\\{]0x[0-9,a-f]+[\\)|\\}] \\+ [0-9]+ bytes"));
|
||||
|
||||
// Matches:
|
||||
// "ntldll.dll"
|
||||
const FRegexPattern AltCallstackPattern(TEXT("^[a-z,A-Z,0-9,\\-,_]+\\.[exe|dll]"));
|
||||
|
||||
|
||||
// Check for the beginning of description logs
|
||||
if (InProcess->ErrorLogStage == EErrorLogStage::ELS_ErrorStart &&
|
||||
@@ -1478,8 +1560,9 @@ void UClientUnitTest::CheckOutputForError(TSharedPtr<FUnitTestProcess> InProcess
|
||||
InProcess->ErrorLogStage == EErrorLogStage::ELS_ErrorCallstack)
|
||||
{
|
||||
FRegexMatcher CallstackMatcher(CallstackPattern, CurLine);
|
||||
FRegexMatcher AltCallstackMatcher(AltCallstackPattern, CurLine);
|
||||
|
||||
if (CallstackMatcher.FindNext())
|
||||
if (CallstackMatcher.FindNext() || AltCallstackMatcher.FindNext())
|
||||
{
|
||||
InProcess->ErrorLogStage = EErrorLogStage::ELS_ErrorCallstack;
|
||||
}
|
||||
@@ -1539,7 +1622,7 @@ bool UClientUnitTest::InternalScriptProcessEvent(AActor* Actor, UFunction* Funct
|
||||
{
|
||||
UNIT_EVENT_BEGIN(OriginUnitTest);
|
||||
|
||||
bBlockEvent = OriginUnitTest->NotifyScriptProcessEvent(Actor, Function, Parameters, HookOrigin);
|
||||
bBlockEvent = OriginUnitTest->NotifyScriptProcessEvent(Actor, Function, Parameters);
|
||||
|
||||
UNIT_EVENT_END;
|
||||
}
|
||||
|
||||
@@ -8,13 +8,18 @@
|
||||
#include "NUTUtilNet.h"
|
||||
#include "NUTUtil.h"
|
||||
|
||||
#if TARGET_UE4_CL < CL_BEACONHOST
|
||||
const FName NAME_BeaconDriver = FName(TEXT("BeaconDriver"));
|
||||
#else
|
||||
const FName NAME_BeaconDriver = FName(TEXT("BeaconNetDriver"));
|
||||
#endif
|
||||
|
||||
IMPLEMENT_CONTROL_CHANNEL_MESSAGE(NUTControl);
|
||||
|
||||
|
||||
ANUTActor::ANUTActor(const FObjectInitializer& ObjectInitializer)
|
||||
: Super(ObjectInitializer)
|
||||
, BeaconDriverName(NAME_None)
|
||||
, LastAliveTime(0.f)
|
||||
, bMonitorForBeacon(false)
|
||||
{
|
||||
@@ -46,7 +51,11 @@ void ANUTActor::PostActorCreated()
|
||||
}
|
||||
}
|
||||
|
||||
#if TARGET_UE4_CL < CL_CONSTNETCONN
|
||||
UNetConnection* ANUTActor::GetNetConnection()
|
||||
#else
|
||||
UNetConnection* ANUTActor::GetNetConnection() const
|
||||
#endif
|
||||
{
|
||||
UNetConnection* ReturnVal = Super::GetNetConnection();
|
||||
|
||||
@@ -62,7 +71,7 @@ UNetConnection* ANUTActor::GetNetConnection() const
|
||||
}
|
||||
// If this is the server, set based on beacon driver client connection.
|
||||
// Only the server has a net driver of name NAME_BeaconDriver (clientside the name is chosen dynamically)
|
||||
else if (NetDriver != NULL && NetDriver->NetDriverName == NAME_BeaconDriver && NetDriver->ClientConnections.Num() > 0)
|
||||
else if (NetDriver != NULL && NetDriver->NetDriverName == BeaconDriverName && NetDriver->ClientConnections.Num() > 0)
|
||||
{
|
||||
ReturnVal = NetDriver->ClientConnections[0];
|
||||
}
|
||||
@@ -174,7 +183,7 @@ bool ANUTActor::NotifyControlMessage(UNetConnection* Connection, uint8 MessageTy
|
||||
{
|
||||
UE_LOG(LogUnitTest, Log, TEXT("Successfully summoned actor of class '%s'"), *SpawnClassName);
|
||||
|
||||
if (bForceBeginPlay)
|
||||
if (bForceBeginPlay && !NewActor->HasActorBegunPlay())
|
||||
{
|
||||
UE_LOG(LogUnitTest, Log, TEXT("Forcing call to 'BeginPlay' on newly spawned actor."));
|
||||
|
||||
@@ -371,7 +380,26 @@ void ANUTActor::Tick(float DeltaSeconds)
|
||||
// Monitor for the beacon net driver, so it can be hooked
|
||||
if (bMonitorForBeacon)
|
||||
{
|
||||
#if TARGET_UE4_CL < CL_BEACONHOST
|
||||
UNetDriver* BeaconDriver = GEngine->FindNamedNetDriver(CurWorld, NAME_BeaconDriver);
|
||||
#else
|
||||
// Somehow, the beacon driver name got messed up in a subsequent checkin, so now has to be found manually
|
||||
UNetDriver* BeaconDriver = NULL;
|
||||
|
||||
FWorldContext* CurContext = GEngine->GetWorldContextFromWorld(CurWorld);
|
||||
|
||||
if (CurContext != NULL)
|
||||
{
|
||||
for (auto CurDriverRef : CurContext->ActiveNetDrivers)
|
||||
{
|
||||
if (CurDriverRef.NetDriverDef->DefName == NAME_BeaconDriver)
|
||||
{
|
||||
BeaconDriver = CurDriverRef.NetDriver;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// Only hook when a client is connected
|
||||
if (BeaconDriver != NULL && BeaconDriver->ClientConnections.Num() > 0)
|
||||
@@ -385,7 +413,8 @@ void ANUTActor::Tick(float DeltaSeconds)
|
||||
Role = ROLE_None;
|
||||
SetReplicates(false);
|
||||
|
||||
NetDriverName = NAME_BeaconDriver;
|
||||
BeaconDriverName = BeaconDriver->NetDriverName;
|
||||
NetDriverName = BeaconDriverName;
|
||||
|
||||
Role = ROLE_Authority;
|
||||
SetReplicates(true);
|
||||
|
||||
@@ -11,6 +11,14 @@
|
||||
#include "Net/NUTUtilNet.h"
|
||||
|
||||
|
||||
/**
|
||||
* Globals
|
||||
*/
|
||||
|
||||
FStackTraceManager GTraceManager;
|
||||
FLogStackTraceManager GLogTraceManager;
|
||||
|
||||
|
||||
/**
|
||||
* FScopedLog
|
||||
*/
|
||||
@@ -124,10 +132,10 @@ FScopedLog::~FScopedLog()
|
||||
|
||||
#if !UE_BUILD_SHIPPING
|
||||
/**
|
||||
* FScopedProcessEventLog
|
||||
* FProcessEventHookBase
|
||||
*/
|
||||
|
||||
FScopedProcessEventLog::FScopedProcessEventLog()
|
||||
FProcessEventHookBase::FProcessEventHookBase()
|
||||
: OrigEventHook()
|
||||
{
|
||||
if (AActor::ProcessEventDelegate.IsBound())
|
||||
@@ -135,35 +143,242 @@ FScopedProcessEventLog::FScopedProcessEventLog()
|
||||
OrigEventHook = AActor::ProcessEventDelegate;
|
||||
}
|
||||
|
||||
AActor::ProcessEventDelegate.BindRaw(this, &FScopedProcessEventLog::ProcessEventHook);
|
||||
AActor::ProcessEventDelegate.BindRaw(this, &FProcessEventHookBase::InternalProcessEventHook);
|
||||
}
|
||||
|
||||
FScopedProcessEventLog::~FScopedProcessEventLog()
|
||||
FProcessEventHookBase::~FProcessEventHookBase()
|
||||
{
|
||||
AActor::ProcessEventDelegate = OrigEventHook;
|
||||
OrigEventHook.Unbind();
|
||||
}
|
||||
|
||||
bool FScopedProcessEventLog::ProcessEventHook(AActor* Actor, UFunction* Function, void* Parameters)
|
||||
bool FProcessEventHookBase::InternalProcessEventHook(AActor* Actor, UFunction* Function, void* Parameters)
|
||||
{
|
||||
bool bReturnVal = false;
|
||||
|
||||
UE_LOG(LogUnitTest, Log, TEXT("FScopedProcessEventLog: Actor: %s, Function: %s"),
|
||||
(Actor != NULL ? *Actor->GetName() : TEXT("NULL")),
|
||||
*Function->GetName());
|
||||
|
||||
|
||||
// If there was originally already a ProcessEvent hook in place, transparently pass on the event, so it's not disrupted
|
||||
if (OrigEventHook.IsBound())
|
||||
{
|
||||
bReturnVal = OrigEventHook.Execute(Actor, Function, Parameters);
|
||||
}
|
||||
|
||||
ProcessEventHook(Actor, Function, Parameters);
|
||||
|
||||
return bReturnVal;
|
||||
}
|
||||
|
||||
void FScopedProcessEventLog::ProcessEventHook(AActor* Actor, UFunction* Function, void* Parameters)
|
||||
{
|
||||
UE_LOG(LogUnitTest, Log, TEXT("FScopedProcessEventLog: Actor: %s, Function: %s"),
|
||||
(Actor != NULL ? *Actor->GetName() : TEXT("NULL")),
|
||||
*Function->GetName());
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
/**
|
||||
* FNUTStackTrace
|
||||
*/
|
||||
|
||||
FNUTStackTrace::FNUTStackTrace(FString InTraceName)
|
||||
: TraceName(InTraceName)
|
||||
, Tracker()
|
||||
{
|
||||
Tracker.ResetTracking();
|
||||
}
|
||||
|
||||
FNUTStackTrace::~FNUTStackTrace()
|
||||
{
|
||||
Tracker.ResetTracking();
|
||||
}
|
||||
void FNUTStackTrace::Enable()
|
||||
{
|
||||
if (!IsTrackingEnabled())
|
||||
{
|
||||
Tracker.ToggleTracking();
|
||||
}
|
||||
}
|
||||
|
||||
void FNUTStackTrace::Disable()
|
||||
{
|
||||
if (IsTrackingEnabled())
|
||||
{
|
||||
Tracker.ToggleTracking();
|
||||
}
|
||||
}
|
||||
|
||||
void FNUTStackTrace::AddTrace(bool bLogAdd/*=false*/)
|
||||
{
|
||||
if (IsTrackingEnabled())
|
||||
{
|
||||
if (bLogAdd)
|
||||
{
|
||||
UE_LOG(LogUnitTest, Log, TEXT("Adding stack trace for TraceName '%s'."), *TraceName);
|
||||
}
|
||||
|
||||
Tracker.CaptureStackTrace(TRACE_IGNORE_DEPTH);
|
||||
}
|
||||
}
|
||||
|
||||
void FNUTStackTrace::Dump(bool bKeepTraceHistory/*=false*/)
|
||||
{
|
||||
UE_LOG(LogUnitTest, Log, TEXT("Dumping tracked stack traces for TraceName '%s':"), *TraceName);
|
||||
|
||||
Tracker.DumpStackTraces(0, *GLog);
|
||||
|
||||
if (!bKeepTraceHistory)
|
||||
{
|
||||
Tracker.ResetTracking();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* FStackTraceManager
|
||||
*/
|
||||
|
||||
FStackTraceManager::FStackTraceManager()
|
||||
: Traces()
|
||||
{
|
||||
}
|
||||
|
||||
FStackTraceManager::~FStackTraceManager()
|
||||
{
|
||||
for (auto It=Traces.CreateConstIterator(); It; ++It)
|
||||
{
|
||||
FNUTStackTrace* Trace = It->Value;
|
||||
|
||||
if (Trace != NULL)
|
||||
{
|
||||
delete Trace;
|
||||
}
|
||||
}
|
||||
|
||||
Traces.Empty();
|
||||
}
|
||||
|
||||
void FStackTraceManager::Enable(FString TraceName)
|
||||
{
|
||||
FNUTStackTrace* Trace = GetOrCreateTrace(TraceName);
|
||||
|
||||
Trace->Enable();
|
||||
}
|
||||
|
||||
void FStackTraceManager::Disable(FString TraceName)
|
||||
{
|
||||
FNUTStackTrace* Trace = GetTrace(TraceName);
|
||||
|
||||
if (Trace != NULL)
|
||||
{
|
||||
Trace->Disable();
|
||||
}
|
||||
else
|
||||
{
|
||||
UE_LOG(LogUnitTest, Log, TEXT("Trace disable: No trace tracking found for TraceName '%s'."), *TraceName);
|
||||
}
|
||||
}
|
||||
|
||||
void FStackTraceManager::AddTrace(FString TraceName, bool bLogAdd/*=false*/, bool bDump/*=false*/, bool bStartDisabled/*=false*/)
|
||||
{
|
||||
bool bIsNewTrace = false;
|
||||
FNUTStackTrace* Trace = GetOrCreateTrace(TraceName, &bIsNewTrace);
|
||||
|
||||
if (bIsNewTrace)
|
||||
{
|
||||
if (bStartDisabled)
|
||||
{
|
||||
Trace->Disable();
|
||||
}
|
||||
else
|
||||
{
|
||||
Trace->Enable();
|
||||
}
|
||||
}
|
||||
|
||||
if (Trace->IsTrackingEnabled())
|
||||
{
|
||||
Trace->AddTrace(bLogAdd);
|
||||
|
||||
if (bDump)
|
||||
{
|
||||
Trace->Dump(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FStackTraceManager::Dump(FString TraceName, bool bKeepTraceHistory/*=false*/, bool bKeepTracking/*=true*/)
|
||||
{
|
||||
FNUTStackTrace* Trace = GetTrace(TraceName);
|
||||
|
||||
if (Trace != NULL)
|
||||
{
|
||||
Trace->Dump(bKeepTraceHistory);
|
||||
|
||||
if (!bKeepTracking)
|
||||
{
|
||||
delete Trace;
|
||||
Traces.FindAndRemoveChecked(TraceName);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
UE_LOG(LogUnitTest, Log, TEXT("No trace tracking found for TraceName '%s'."), *TraceName);
|
||||
}
|
||||
}
|
||||
|
||||
void FStackTraceManager::Clear(FString TraceName)
|
||||
{
|
||||
FNUTStackTrace* Trace = GetTrace(TraceName);
|
||||
|
||||
if (Trace != NULL)
|
||||
{
|
||||
delete Trace;
|
||||
Traces.FindAndRemoveChecked(TraceName);
|
||||
}
|
||||
else
|
||||
{
|
||||
UE_LOG(LogUnitTest, Log, TEXT("No trace tracking found for TraceName '%s'."), *TraceName);
|
||||
}
|
||||
}
|
||||
|
||||
void FStackTraceManager::DumpAll(bool bKeepTraceHistory/*=false*/, bool bKeepTracking/*=true*/)
|
||||
{
|
||||
UE_LOG(LogUnitTest, Log, TEXT("Dumping all tracked stack traces:"));
|
||||
|
||||
for (auto It=Traces.CreateIterator(); It; ++It)
|
||||
{
|
||||
FNUTStackTrace* Trace = It->Value;
|
||||
|
||||
if (Trace != NULL)
|
||||
{
|
||||
Trace->Dump(bKeepTraceHistory);
|
||||
|
||||
if (!bKeepTracking)
|
||||
{
|
||||
delete Trace;
|
||||
It.RemoveCurrent();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FStackTraceManager::TraceAndDump(FString TraceName)
|
||||
{
|
||||
FNUTStackTrace* Trace = GetTrace(TraceName);
|
||||
|
||||
if (Trace == NULL || Trace->IsTrackingEnabled())
|
||||
{
|
||||
UE_LOG(LogUnitTest, Log, TEXT("Dumping once-off stack trace for TraceName '%s':"), *TraceName);
|
||||
|
||||
FStackTracker TempTracker(NULL, NULL, true);
|
||||
|
||||
TempTracker.CaptureStackTrace(TRACE_IGNORE_DEPTH);
|
||||
TempTracker.DumpStackTraces(0, *GLog);
|
||||
TempTracker.ResetTracking();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* NUTDebug
|
||||
*/
|
||||
@@ -271,3 +486,4 @@ FString NUTDebug::HexDump(const TArray<uint8>& InBytes, bool bDumpASCII/*=true*/
|
||||
return ReturnValue;
|
||||
}
|
||||
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -405,7 +405,12 @@ bool NUTNet::CreateFakePlayer(UWorld* InWorld, UNetDriver*& InNetDriver, FString
|
||||
else
|
||||
{
|
||||
// Then send NMT_Login
|
||||
#if TARGET_UE4_CL < CL_CONSTUNIQUEID
|
||||
TSharedPtr<FUniqueNetId> DudPtr = MakeShareable(new FUniqueNetIdString(TEXT("Dud")));
|
||||
#else
|
||||
TSharedPtr<const FUniqueNetId> DudPtr = MakeShareable(new FUniqueNetIdString(TEXT("Dud")));
|
||||
#endif
|
||||
|
||||
FUniqueNetIdRepl PlayerUID(DudPtr);
|
||||
FString BlankStr = TEXT("");
|
||||
FString ConnectURL = UUnitTest::UnitEnv->GetDefaultClientConnectURL();
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
UUnitTestPackageMap::UUnitTestPackageMap(const FObjectInitializer& ObjectInitializer)
|
||||
: Super(ObjectInitializer)
|
||||
, bWithinSerializeNewActor(false)
|
||||
, bPendingArchetypeSpawn(false)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -25,24 +26,41 @@ bool UUnitTestPackageMap::SerializeObject(FArchive& Ar, UClass* Class, UObject*&
|
||||
|
||||
bReturnVal = Super::SerializeObject(Ar, Class, Obj, OutNetGUID);
|
||||
|
||||
// This indicates that the new actor channel archetype has just been serialized;
|
||||
// this is the first place we know what actor type the actor channel is initializing, but BEFORE it is spawned
|
||||
if (GIsInitializingActorChan && bWithinSerializeNewActor && Class == UObject::StaticClass() && Obj != NULL)
|
||||
if (bWithinSerializeNewActor)
|
||||
{
|
||||
UUnitTestNetConnection* UnitConn = Cast<UUnitTestNetConnection>(GActiveReceiveUnitConnection);
|
||||
bool bAllowActorChan = true;
|
||||
|
||||
if (UnitConn != NULL && UnitConn->ActorChannelSpawnDel.IsBound())
|
||||
// This indicates that SerializeObject has failed to find an existing instance when trying to serialize an actor,
|
||||
// so it will be spawned clientside later on (after the archetype is serialized) instead.
|
||||
// These spawns count as undesired clientside code execution, so filter them through NotifyAllowNetActor.
|
||||
if (Class == AActor::StaticClass() && Obj == NULL)
|
||||
{
|
||||
bAllowActorChan = UnitConn->ActorChannelSpawnDel.Execute(Obj->GetClass());
|
||||
bPendingArchetypeSpawn = true;
|
||||
}
|
||||
|
||||
if (!bAllowActorChan)
|
||||
// This indicates that a new actor archetype has just been serialized (which may or may not be during actor channel init);
|
||||
// this is the first place we know the type of a replicated actor (in an actor channel or otherwise), but BEFORE it is spawned
|
||||
else if ((GIsInitializingActorChan || bPendingArchetypeSpawn) && Class == UObject::StaticClass() && Obj != NULL)
|
||||
{
|
||||
Obj = NULL;
|
||||
UUnitTestNetConnection* UnitConn = Cast<UUnitTestNetConnection>(GActiveReceiveUnitConnection);
|
||||
bool bAllowActor = false;
|
||||
|
||||
// NULL the control channel, to break code that would disconnect the client (control chan is recovered, in ReceivedBunch)
|
||||
Connection->Channels[0] = NULL;
|
||||
if (UnitConn != NULL && UnitConn->ReplicatedActorSpawnDel.IsBound())
|
||||
{
|
||||
bAllowActor = UnitConn->ReplicatedActorSpawnDel.Execute(Obj->GetClass(), GIsInitializingActorChan);
|
||||
}
|
||||
|
||||
if (!bAllowActor)
|
||||
{
|
||||
UE_LOG(LogUnitTest, Log,
|
||||
TEXT("Blocking replication/spawning of actor on client (add to NotifyAllowNetActor if required)."));
|
||||
|
||||
UE_LOG(LogUnitTest, Log, TEXT(" ActorChannel: %s, Class: %s, Archetype: %s"),
|
||||
(GIsInitializingActorChan ? TEXT("true") : TEXT("false")), *Obj->GetClass()->GetFullName(),
|
||||
*Obj->GetFullName());
|
||||
|
||||
Obj = NULL;
|
||||
|
||||
// NULL the control channel, to break code that would disconnect the client (control chan is recovered, in ReceivedBunch)
|
||||
Connection->Channels[0] = NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,11 +72,21 @@ bool UUnitTestPackageMap::SerializeNewActor(FArchive& Ar, class UActorChannel* C
|
||||
bool bReturnVal = false;
|
||||
|
||||
bWithinSerializeNewActor = true;
|
||||
bPendingArchetypeSpawn = false;
|
||||
|
||||
bReturnVal = Super::SerializeNewActor(Ar, Channel, Actor);
|
||||
|
||||
bPendingArchetypeSpawn = false;
|
||||
bWithinSerializeNewActor = false;
|
||||
|
||||
// If we are initializing an actor channel, then make this returns false, to block PostNetInit from being called on the actor,
|
||||
// which in turn, blocks BeginPlay being called (this can lead to actor component initialization, which can trigger garbage
|
||||
// collection issues down the line)
|
||||
if (GIsInitializingActorChan)
|
||||
{
|
||||
bReturnVal = false;
|
||||
}
|
||||
|
||||
return bReturnVal;
|
||||
}
|
||||
|
||||
|
||||
@@ -571,17 +571,17 @@ TSharedRef<FTabManager::FLayout> SLogWidget::InitializeTabLayout(const FArgument
|
||||
{
|
||||
// Initialize the LogTabs array (which includes labels/tooltips, for each log type)
|
||||
LogTabs.Add(MakeShareable(new
|
||||
FLogTabInfo(TEXT("Summary"), TEXT("Filter for the most notable log entries."), ELogType::StatusImportant)));
|
||||
FLogTabInfo(TEXT("Summary"), TEXT("Filter for the most notable log entries."), ELogType::StatusImportant, 10)));
|
||||
|
||||
if (Args._bStatusWidget)
|
||||
{
|
||||
LogTabs.Add(MakeShareable(new
|
||||
FLogTabInfo(TEXT("Advanced Summary"), TEXT("Filter for the most notable log entries, with extra/advanced information."),
|
||||
ELogType::StatusImportant | ELogType::StatusVerbose | ELogType::StatusAdvanced)));
|
||||
ELogType::StatusImportant | ELogType::StatusVerbose | ELogType::StatusAdvanced, 20)));
|
||||
}
|
||||
|
||||
LogTabs.Add(MakeShareable(new
|
||||
FLogTabInfo(TEXT("All"), TEXT("No filters - all log output it shown."), ELogType::All)));
|
||||
FLogTabInfo(TEXT("All"), TEXT("No filters - all log output it shown."), ELogType::All, 30)));
|
||||
|
||||
if (!Args._bStatusWidget)
|
||||
{
|
||||
@@ -605,12 +605,12 @@ TSharedRef<FTabManager::FLayout> SLogWidget::InitializeTabLayout(const FArgument
|
||||
bool bOpenDebugTab = ((Args._ExpectedFilters & ELogType::StatusDebug) == ELogType::StatusDebug);
|
||||
|
||||
LogTabs.Add(MakeShareable(new
|
||||
FLogTabInfo(TEXT("Debug"), TEXT("Filter for debug log entries."), ELogType::StatusDebug, bOpenDebugTab)));
|
||||
|
||||
LogTabs.Add(MakeShareable(new
|
||||
FLogTabInfo(TEXT("Console"), TEXT("Filter for local console command results."), ELogType::OriginConsole, false)));
|
||||
FLogTabInfo(TEXT("Debug"), TEXT("Filter for debug log entries."), ELogType::StatusDebug, 5, bOpenDebugTab)));
|
||||
}
|
||||
|
||||
LogTabs.Add(MakeShareable(new
|
||||
FLogTabInfo(TEXT("Console"), TEXT("Filter for local console command results."), ELogType::OriginConsole, 5, false)));
|
||||
|
||||
|
||||
// Initialize the tab manager, stack and layout
|
||||
TSharedRef<SDockTab> DudTab = SNew(SLockedTab);
|
||||
@@ -1005,13 +1005,19 @@ TSharedPtr<FLogTabInfo> SLogWidget::GetActiveTabInfo() const
|
||||
}
|
||||
|
||||
|
||||
void SLogWidget::AddLine(ELogType LogType, TSharedRef<FString> LogLine, FSlateColor LogColor/*=FSlateColor::UseForeground()*/)
|
||||
void SLogWidget::AddLine(ELogType LogType, TSharedRef<FString> LogLine, FSlateColor LogColor/*=FSlateColor::UseForeground()*/,
|
||||
bool bTakeTabFocus/*=false*/)
|
||||
{
|
||||
TSharedRef<FLogLine> CurLogEntry = MakeShareable(new FLogLine(LogType, LogLine, LogColor));
|
||||
|
||||
// Add the line to the master list
|
||||
LogLines.Add(CurLogEntry);
|
||||
|
||||
TSharedPtr<FLogTabInfo> ActiveTab = GetActiveTabInfo();
|
||||
|
||||
bool bLineInTabFocus = ActiveTab.IsValid() && !!(ActiveTab->Filter & LogType);
|
||||
TSharedPtr<FLogTabInfo> FocusTab = NULL;
|
||||
|
||||
// Then add it to each log tab, if it passes that tabs filter
|
||||
for (auto CurTabInfo : LogTabs)
|
||||
{
|
||||
@@ -1020,8 +1026,6 @@ void SLogWidget::AddLine(ELogType LogType, TSharedRef<FString> LogLine, FSlateCo
|
||||
// If the tab is not presently open, open it now
|
||||
if (!CurTabInfo->bTabOpen && LogTabManager.IsValid())
|
||||
{
|
||||
TSharedPtr<FLogTabInfo> ActiveTab = GetActiveTabInfo();
|
||||
|
||||
LogTabManager->InvokeTab(CurTabInfo->TabIdName);
|
||||
|
||||
// The new tab has stolen focus, now restore the old tabs focus
|
||||
@@ -1030,6 +1034,16 @@ void SLogWidget::AddLine(ELogType LogType, TSharedRef<FString> LogLine, FSlateCo
|
||||
CurTabInfo->bTabOpen = true;
|
||||
}
|
||||
|
||||
// If the line is requesting focus, but is not currently in focus, select a tab for focusing
|
||||
if (bTakeTabFocus && !bLineInTabFocus)
|
||||
{
|
||||
if (CurTabInfo != ActiveTab && (!FocusTab.IsValid() || CurTabInfo->Priority < FocusTab->Priority))
|
||||
{
|
||||
FocusTab = CurTabInfo;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
CurTabInfo->TabLogLines.Add(CurLogEntry);
|
||||
|
||||
|
||||
@@ -1043,6 +1057,12 @@ void SLogWidget::AddLine(ELogType LogType, TSharedRef<FString> LogLine, FSlateCo
|
||||
CurLogListView->RequestListRefresh();
|
||||
}
|
||||
}
|
||||
|
||||
// If a focus change is required, perform it
|
||||
if (FocusTab.IsValid() && LogTabManager.IsValid())
|
||||
{
|
||||
LogTabManager->InvokeTab(FocusTab->TabIdName);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
|
||||
|
||||
FUnitTestEnvironment* UUnitTest::UnitEnv = NULL;
|
||||
FUnitTestEnvironment* UUnitTest::NullUnitEnv = NULL;
|
||||
|
||||
|
||||
/**
|
||||
@@ -221,7 +222,9 @@ void UUnitTest::NotifyLocalLog(ELogType LogType, const TCHAR* Data, ELogVerbosit
|
||||
CurLogColor = FLinearColor(1.f, 1.f, 0.f);
|
||||
}
|
||||
|
||||
LogWidget->AddLine(LogType, MakeShareable(new FString(LogLine)), CurLogColor);
|
||||
bool bRequestFocus = !!(LogOrigin & ELogType::FocusMask);
|
||||
|
||||
LogWidget->AddLine(LogType, MakeShareable(new FString(LogLine)), CurLogColor, bRequestFocus);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,6 +24,10 @@ void FUnitTestEnvironment::AddUnitTestEnvironment(FString Game, FUnitTestEnviron
|
||||
{
|
||||
UUnitTest::UnitEnv = Env;
|
||||
}
|
||||
else if (Game == TEXT("NullUnitEnv"))
|
||||
{
|
||||
UUnitTest::NullUnitEnv = Env;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,44 +36,59 @@ FString FUnitTestEnvironment::GetDefaultMap(EUnitTestFlags UnitTestFlags)
|
||||
return TEXT("");
|
||||
}
|
||||
|
||||
FString FUnitTestEnvironment::GetDefaultServerParameters()
|
||||
FString FUnitTestEnvironment::GetDefaultServerParameters(FString InLogCmds/*=TEXT("")*/, FString InExecCmds/*=TEXT("")*/)
|
||||
{
|
||||
FString ReturnVal = TEXT("");
|
||||
FString CmdLineServerParms;
|
||||
FString FullLogCmds = TEXT("");
|
||||
FString FullLogCmds = InLogCmds;
|
||||
FString FullExecCmds = InExecCmds;
|
||||
|
||||
if (NUTUtil::ParseValue(FCommandLine::Get(), TEXT("UnitTestServerParms="), CmdLineServerParms))
|
||||
{
|
||||
// LogCmds needs to be specially merged, since it may be specified in multiple places
|
||||
FString LogCmds;
|
||||
|
||||
if (FParse::Value(*CmdLineServerParms, TEXT("-LogCmds="), LogCmds, false))
|
||||
{
|
||||
if (FullLogCmds.Len() > 0)
|
||||
auto ParseCmds =
|
||||
[&](const TCHAR* CmdsParm, FString& OutFullCmds)
|
||||
{
|
||||
FullLogCmds += TEXT(",");
|
||||
}
|
||||
FString Cmds;
|
||||
|
||||
FullLogCmds += LogCmds.TrimQuotes();
|
||||
if (FParse::Value(*CmdLineServerParms, CmdsParm, Cmds, false))
|
||||
{
|
||||
if (OutFullCmds.Len() > 0)
|
||||
{
|
||||
OutFullCmds += TEXT(",");
|
||||
}
|
||||
|
||||
// Now remove the original LogCmds entry
|
||||
FString SearchStr = FString::Printf(TEXT("-LogCmds=\"%s\""), *LogCmds.TrimQuotes());
|
||||
OutFullCmds += Cmds.TrimQuotes();
|
||||
|
||||
CmdLineServerParms = CmdLineServerParms.Replace(*SearchStr, TEXT(""), ESearchCase::IgnoreCase);
|
||||
}
|
||||
// Now remove the original *Cmds entry
|
||||
FString SearchStr = FString::Printf(TEXT("%s\"%s\""), CmdsParm, *Cmds.TrimQuotes());
|
||||
|
||||
CmdLineServerParms = CmdLineServerParms.Replace(*SearchStr, TEXT(""), ESearchCase::IgnoreCase);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// LogCmds and ExecCmds need to be specially merged, since they may be specified in muliple places
|
||||
// (e.g. within unit tests, and using UnitTestServerParms)
|
||||
ParseCmds(TEXT("-LogCmds="), FullLogCmds);
|
||||
ParseCmds(TEXT("-ExecCmds="), FullExecCmds);
|
||||
|
||||
|
||||
ReturnVal += TEXT(" ");
|
||||
ReturnVal += CmdLineServerParms;
|
||||
}
|
||||
|
||||
SetupDefaultServerParameters(ReturnVal, FullLogCmds);
|
||||
SetupDefaultServerParameters(ReturnVal, FullLogCmds, FullExecCmds);
|
||||
|
||||
if (FullLogCmds.Len() > 0)
|
||||
{
|
||||
ReturnVal += FString::Printf(TEXT(" -LogCmds=\"%s\""), *FullLogCmds);
|
||||
}
|
||||
|
||||
if (FullExecCmds.Len() > 0)
|
||||
{
|
||||
ReturnVal += FString::Printf(TEXT(" -ExecCmds=\"%s\""), *FullExecCmds);
|
||||
}
|
||||
|
||||
return ReturnVal;
|
||||
}
|
||||
|
||||
@@ -118,6 +137,9 @@ void FUnitTestEnvironment::GetServerProgressLogs(const TArray<FString>*& OutStar
|
||||
|
||||
// Logs which should trigger a timeout reset for all games
|
||||
TimeoutResetLogs.Add(TEXT("LogStaticMesh: Building static mesh "));
|
||||
TimeoutResetLogs.Add(TEXT("LogMaterial: Missing cached shader map for material "));
|
||||
TimeoutResetLogs.Add(TEXT("Dumping tracked stack traces for TraceName '"));
|
||||
TimeoutResetLogs.Add(TEXT("Dumping once-off stack trace for TraceName '"));
|
||||
|
||||
InitializeServerProgressLogs(StartProgressLogs, ReadyLogs, TimeoutResetLogs);
|
||||
|
||||
@@ -129,3 +151,23 @@ void FUnitTestEnvironment::GetServerProgressLogs(const TArray<FString>*& OutStar
|
||||
OutTimeoutResetLogs = &TimeoutResetLogs;
|
||||
}
|
||||
|
||||
void FUnitTestEnvironment::GetClientProgressLogs(const TArray<FString>*& OutTimeoutResetLogs)
|
||||
{
|
||||
static TArray<FString> TimeoutResetLogs;
|
||||
static bool bSetupLogs = false;
|
||||
|
||||
// @todo JohnB: See what logs are common between client/server, and perhaps create a third generic progress log function for them
|
||||
|
||||
if (!bSetupLogs)
|
||||
{
|
||||
// Logs which should trigger a timeout reset for all games
|
||||
TimeoutResetLogs.Add(TEXT("LogMaterial: Missing cached shader map for material "));
|
||||
|
||||
InitializeClientProgressLogs(TimeoutResetLogs);
|
||||
|
||||
bSetupLogs = true;
|
||||
}
|
||||
|
||||
OutTimeoutResetLogs = &TimeoutResetLogs;
|
||||
}
|
||||
|
||||
|
||||
@@ -12,9 +12,10 @@
|
||||
#include "SLogWindow.h"
|
||||
#include "SLogDialog.h"
|
||||
|
||||
#include "NUTUtil.h"
|
||||
#include "NUTUtilDebug.h"
|
||||
#include "NUTUtilNet.h"
|
||||
#include "NUTUtilProfiler.h"
|
||||
#include "NUTUtil.h"
|
||||
|
||||
// @todo JohnB: Add an overall-timer, and then start debugging the memory management in more detail
|
||||
|
||||
@@ -146,14 +147,15 @@ bool UUnitTestManager::QueueUnitTest(UClass* UnitTestClass, bool bRequeued/*=fal
|
||||
UnitTestClass != UClientUnitTest::StaticClass();
|
||||
|
||||
UUnitTest* UnitTestDefault = (bValidUnitTestClass ? Cast<UUnitTest>(UnitTestClass->GetDefaultObject()) : NULL);
|
||||
bool bSupportsAllGames = (bValidUnitTestClass ? UnitTestDefault->GetSupportedGames().Contains("NullUnitEnv") : false);
|
||||
|
||||
bValidUnitTestClass = UnitTestDefault != NULL;
|
||||
|
||||
|
||||
if (bValidUnitTestClass && UUnitTest::UnitEnv != NULL)
|
||||
if (bValidUnitTestClass && (UUnitTest::UnitEnv != NULL || bSupportsAllGames))
|
||||
{
|
||||
FString UnitTestName = UnitTestDefault->GetUnitTestName();
|
||||
bool bCurrentGameSupported = UnitTestDefault->GetSupportedGames().Contains(FApp::GetGameName());
|
||||
bool bCurrentGameSupported = bSupportsAllGames || UnitTestDefault->GetSupportedGames().Contains(FApp::GetGameName());
|
||||
|
||||
if (bCurrentGameSupported)
|
||||
{
|
||||
@@ -219,8 +221,20 @@ bool UUnitTestManager::QueueUnitTest(UClass* UnitTestClass, bool bRequeued/*=fal
|
||||
}
|
||||
else if (UUnitTest::UnitEnv == NULL)
|
||||
{
|
||||
STATUS_LOG(ELogType::StatusError | ELogType::StyleBold,
|
||||
TEXT("No unit test environment found (need to load unit test environment module for this game, or create it)."));
|
||||
ELogType StatusType = ELogType::StyleBold;
|
||||
|
||||
// @todo JohnB: This should be enabled by default instead, so automation testing does give an error when a game doesn't
|
||||
// have a unit test environment. You should probably setup a whitelist of 'known-unsupported-games' somewhere,
|
||||
// to keep track of what games need support added, without breaking automation testing (but without breaking the
|
||||
// flow of setting-up/running automation tests)
|
||||
if (!GIsAutomationTesting)
|
||||
{
|
||||
StatusType |= ELogType::StatusError;
|
||||
}
|
||||
|
||||
STATUS_LOG(StatusType,
|
||||
TEXT("No unit test environment found (need to load unit test environment module for this game '%s', or create it)."),
|
||||
FApp::GetGameName());
|
||||
}
|
||||
|
||||
return bSuccess;
|
||||
@@ -318,6 +332,12 @@ bool UUnitTestManager::WithinUnitTestLimits(UClass* PendingUnitTest/*=NULL*/)
|
||||
// Check max unit test count
|
||||
bReturnVal = !bCapUnitTestCount || ActiveUnitTests.Num() < MaxUnitTestCount;
|
||||
|
||||
int32 CommandlineCap = 0;
|
||||
|
||||
if (bReturnVal && FParse::Value(FCommandLine::Get(), TEXT("UnitTestCap="), CommandlineCap) && CommandlineCap > 0)
|
||||
{
|
||||
bReturnVal = ActiveUnitTests.Num() < CommandlineCap;
|
||||
}
|
||||
|
||||
// Limit the number of first-run unit tests (which don't have any stats gathered), to MaxUnitTestCount, even if !bCapUnitTestCount.
|
||||
// If any first-run unit tests have had to be aborted, this might signify a problem, so make the cap very strict (two at a time)
|
||||
@@ -1027,7 +1047,7 @@ void UUnitTestManager::OpenStatusWindow()
|
||||
{
|
||||
FString LogLine = FOutputDevice::FormatLogLine(Verbosity, Category, V);
|
||||
|
||||
STATUS_LOG_BASE(, TEXT("%s"), *LogLine);
|
||||
STATUS_LOG_BASE(ELogType::OriginConsole, TEXT("%s"), *LogLine);
|
||||
});
|
||||
|
||||
|
||||
@@ -1293,54 +1313,78 @@ bool UUnitTestManager::Exec(UWorld* InWorld, const TCHAR* Cmd, FOutputDevice& Ar
|
||||
// Debug unit test commands
|
||||
else if (UnitTestName == TEXT("debug"))
|
||||
{
|
||||
if (InWorld != NULL)
|
||||
{
|
||||
UUnitTest** TargetUnitTestRef = ActiveUnitTests.FindByPredicate(
|
||||
[&InWorld](const UUnitTest* InElement)
|
||||
{
|
||||
auto CurUnitTest = Cast<UClientUnitTest>(InElement);
|
||||
|
||||
return CurUnitTest != NULL && CurUnitTest->UnitWorld == InWorld;
|
||||
});
|
||||
|
||||
UClientUnitTest* TargetUnitTest = (TargetUnitTestRef != NULL ? Cast<UClientUnitTest>(*TargetUnitTestRef) : NULL);
|
||||
|
||||
if (TargetUnitTest != NULL)
|
||||
UUnitTest** TargetUnitTestRef = (InWorld != NULL ? ActiveUnitTests.FindByPredicate(
|
||||
[&InWorld](const UUnitTest* InElement)
|
||||
{
|
||||
if (FParse::Command(&Cmd, TEXT("Requirements")))
|
||||
auto CurUnitTest = Cast<UClientUnitTest>(InElement);
|
||||
|
||||
return CurUnitTest != NULL && CurUnitTest->UnitWorld == InWorld;
|
||||
})
|
||||
: NULL);
|
||||
|
||||
UClientUnitTest* TargetUnitTest = (TargetUnitTestRef != NULL ? Cast<UClientUnitTest>(*TargetUnitTestRef) : NULL);
|
||||
|
||||
// Alternatively, if a unit test has not launched started connecting to a server, its world may not be setup,
|
||||
// so can detect by checking the active log unit test too
|
||||
if (TargetUnitTest == NULL && GActiveLogUnitTest != NULL)
|
||||
{
|
||||
TargetUnitTest = Cast<UClientUnitTest>(GActiveLogUnitTest);
|
||||
}
|
||||
|
||||
if (TargetUnitTest != NULL)
|
||||
{
|
||||
if (FParse::Command(&Cmd, TEXT("Requirements")))
|
||||
{
|
||||
EUnitTestFlags RequirementsFlags = (TargetUnitTest->UnitTestFlags & EUnitTestFlags::RequirementsMask);
|
||||
EUnitTestFlags MetRequirements = TargetUnitTest->GetMetRequirements();
|
||||
|
||||
// Iterate over the requirements mask flag bits
|
||||
FString RequiredBits = TEXT("");
|
||||
FString MetBits = TEXT("");
|
||||
FString FailBits = TEXT("");
|
||||
TArray<FString> FlagResults;
|
||||
EUnitTestFlags FirstFlag =
|
||||
(EUnitTestFlags)(1UL << (31 - FMath::CountLeadingZeros((uint32)EUnitTestFlags::RequirementsMask)));
|
||||
|
||||
for (EUnitTestFlags CurFlag=FirstFlag; !!(CurFlag & EUnitTestFlags::RequirementsMask);
|
||||
CurFlag = (EUnitTestFlags)((uint32)CurFlag >> 1))
|
||||
{
|
||||
EUnitTestFlags RequirementsFlags = (TargetUnitTest->UnitTestFlags & EUnitTestFlags::RequirementsMask);
|
||||
EUnitTestFlags MetRequirements = TargetUnitTest->GetMetRequirements();
|
||||
bool bCurFlagReq = !!(CurFlag & RequirementsFlags);
|
||||
bool bCurFlagSet = !!(CurFlag & MetRequirements);
|
||||
|
||||
// Iterate over the requirements mask flag bits
|
||||
FString RequiredBits = TEXT("");
|
||||
FString MetBits = TEXT("");
|
||||
FString FailBits = TEXT("");
|
||||
TArray<FString> FlagResults;
|
||||
EUnitTestFlags FirstFlag =
|
||||
(EUnitTestFlags)(1UL << (31 - FMath::CountLeadingZeros((uint32)EUnitTestFlags::RequirementsMask)));
|
||||
RequiredBits += (bCurFlagReq ? TEXT("1") : TEXT("0"));
|
||||
MetBits += (bCurFlagSet ? TEXT("1") : TEXT("0"));
|
||||
FailBits += ((bCurFlagReq && !bCurFlagSet) ? TEXT("1") : TEXT("0"));
|
||||
|
||||
for (EUnitTestFlags CurFlag=FirstFlag; !!(CurFlag & EUnitTestFlags::RequirementsMask);
|
||||
CurFlag = (EUnitTestFlags)((uint32)CurFlag >> 1))
|
||||
{
|
||||
bool bCurFlagReq = !!(CurFlag & RequirementsFlags);
|
||||
bool bCurFlagSet = !!(CurFlag & MetRequirements);
|
||||
FlagResults.Add(FString::Printf(TEXT(" - %s: Required: %i, Set: %i, Failed: %i"), *GetUnitTestFlagName(CurFlag),
|
||||
(uint32)bCurFlagReq, (uint32)bCurFlagSet, (uint32)(bCurFlagReq && !bCurFlagSet)));
|
||||
}
|
||||
|
||||
RequiredBits += (bCurFlagReq ? TEXT("1") : TEXT("0"));
|
||||
MetBits += (bCurFlagSet ? TEXT("1") : TEXT("0"));
|
||||
FailBits += ((bCurFlagReq && !bCurFlagSet) ? TEXT("1") : TEXT("0"));
|
||||
Ar.Logf(TEXT("Requirements flags for unit test '%s': Required: %s, Set: %s, Failed: %s"),
|
||||
*TargetUnitTest->GetUnitTestName(), *RequiredBits, *MetBits, *FailBits);
|
||||
|
||||
FlagResults.Add(FString::Printf(TEXT(" - %s: Required: %i, Set: %i, Failed: %i"), *GetUnitTestFlagName(CurFlag),
|
||||
(uint32)bCurFlagReq, (uint32)bCurFlagSet, (uint32)(bCurFlagReq && !bCurFlagSet)));
|
||||
}
|
||||
for (auto CurResult : FlagResults)
|
||||
{
|
||||
Ar.Logf(*CurResult);
|
||||
}
|
||||
}
|
||||
else if (FParse::Command(&Cmd, TEXT("ForceReady")))
|
||||
{
|
||||
if (TargetUnitTest != NULL && !!(TargetUnitTest->UnitTestFlags & EUnitTestFlags::LaunchServer) &&
|
||||
TargetUnitTest->ServerHandle.IsValid() && TargetUnitTest->UnitPC == NULL)
|
||||
{
|
||||
Ar.Logf(TEXT("Forcing unit test '%s' as ready to connect client."), *TargetUnitTest->GetUnitTestName());
|
||||
|
||||
Ar.Logf(TEXT("Requirements flags for unit test '%s': Required: %s, Set: %s, Failed: %s"),
|
||||
*TargetUnitTest->GetUnitTestName(), *RequiredBits, *MetBits, *FailBits);
|
||||
TargetUnitTest->ConnectFakeClient();
|
||||
}
|
||||
}
|
||||
else if (FParse::Command(&Cmd, TEXT("Disconnect")))
|
||||
{
|
||||
if (TargetUnitTest != NULL && TargetUnitTest->UnitConn != NULL)
|
||||
{
|
||||
Ar.Logf(TEXT("Forcing unit test '%s' to disconnect."), *TargetUnitTest->GetUnitTestName());
|
||||
|
||||
for (auto CurResult : FlagResults)
|
||||
{
|
||||
Ar.Logf(*CurResult);
|
||||
}
|
||||
TargetUnitTest->UnitConn->Close();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1490,8 +1534,9 @@ void UUnitTestManager::Serialize(const TCHAR* Data, ELogVerbosity::Type Verbosit
|
||||
}
|
||||
|
||||
TSharedRef<FString> LogLineRef = MakeShareable(LogLine);
|
||||
bool bRequestFocus = !!(CurLogType & ELogType::FocusMask);
|
||||
|
||||
LogWidget->AddLine(CurLogType, LogLineRef, StatusColor);
|
||||
LogWidget->AddLine(CurLogType, LogLineRef, StatusColor, bRequestFocus);
|
||||
|
||||
|
||||
if (bSetTypeColor)
|
||||
@@ -1610,6 +1655,141 @@ static bool UnitTestExec(UWorld* InWorld, const TCHAR* Cmd, FOutputDevice& Ar)
|
||||
|
||||
return true;
|
||||
}
|
||||
/**
|
||||
* Special 'StackTrace' command, for adding complex arbitrary stack tracing, as a debugging method.
|
||||
*
|
||||
* Usage: (NOTE: Replace 'TraceName' as desired, to help identify traces in logs)
|
||||
*
|
||||
* Once-off stack trace/dump:
|
||||
* GEngine->Exec(NULL, TEXT("StackTrace TraceName"));
|
||||
*
|
||||
*
|
||||
* Multiple tracked stack traces: (grouped by TraceName)
|
||||
* - Add a stack trace to tracking: ('-Log' also logs that a stack trace was added, and '-Dump' immediately dumps it to log,
|
||||
* '-StartDisabled' only begins tracking once the 'enable' command below is called)
|
||||
* GEngine->Exec(NULL, TEXT("StackTrace TraceName Add"));
|
||||
* GEngine->Exec(NULL, TEXT("StackTrace TraceName Add -Log"));
|
||||
* GEngine->Exec(NULL, TEXT("StackTrace TraceName Add -Log -Dump"));
|
||||
* GEngine->Exec(NULL, TEXT("StackTrace TraceName Add -StartDisabled"));
|
||||
*
|
||||
* - Dump collected/tracked stack traces: (also removes from tracking by default, unless -Keep is added)
|
||||
* GEngine->Exec(NULL, TEXT("StackTrace TraceName Dump"));
|
||||
* GEngine->Exec(NULL, TEXT("StackTrace TraceName Dump -Keep"));
|
||||
*
|
||||
* - Temporarily disable tracking: (NOTE: Dump must be used with -Keep to use this)
|
||||
* GEngine->Exec(NULL, TEXT("StackTrace TraceName Disable"));
|
||||
*
|
||||
* - Enable/re-enable tracking: (usually not necessary, unless tracking was previously disabled)
|
||||
* GEngine->Exec(NULL, TEXT("StackTrace TraceName Enable"));
|
||||
*
|
||||
*
|
||||
* Additional commands:
|
||||
* - Dump all active stack traces (optionally skip resetting built up stack traces, and optionally stop all active traces)
|
||||
* "StackTrace DumpAll"
|
||||
* "StackTrace DumpAll -NoReset"
|
||||
* "StackTrace DumpAll -Stop"
|
||||
*/
|
||||
else if (FParse::Command(&Cmd, TEXT("StackTrace")))
|
||||
{
|
||||
FString TraceName;
|
||||
|
||||
if (FParse::Command(&Cmd, TEXT("DumpAll")))
|
||||
{
|
||||
bool bKeepTraceHistory = FParse::Param(Cmd, TEXT("KEEPHISTORY"));
|
||||
bool bStopTracking = FParse::Param(Cmd, TEXT("STOP"));
|
||||
|
||||
GTraceManager.DumpAll(bKeepTraceHistory, !bStopTracking);
|
||||
}
|
||||
else if (FParse::Token(Cmd, TraceName, true))
|
||||
{
|
||||
if (FParse::Command(&Cmd, TEXT("Enable")))
|
||||
{
|
||||
GTraceManager.Enable(TraceName);
|
||||
}
|
||||
else if (FParse::Command(&Cmd, TEXT("Disable")))
|
||||
{
|
||||
GTraceManager.Disable(TraceName);
|
||||
}
|
||||
else if (FParse::Command(&Cmd, TEXT("Add")))
|
||||
{
|
||||
bool bLogAdd = FParse::Param(Cmd, TEXT("LOG"));
|
||||
bool bDump = FParse::Param(Cmd, TEXT("DUMP"));
|
||||
bool bStartDisabled = FParse::Param(Cmd, TEXT("STARTDISABLED"));
|
||||
|
||||
GTraceManager.AddTrace(TraceName, bLogAdd, bDump, bStartDisabled);
|
||||
}
|
||||
else if (FParse::Command(&Cmd, TEXT("Dump")))
|
||||
{
|
||||
GTraceManager.Dump(TraceName);
|
||||
}
|
||||
// If no subcommands above are specified, assume this is a once-off stack trace dump
|
||||
// @todo JohnB: This will also capture mistyped commands, so find a way to detect that
|
||||
else
|
||||
{
|
||||
GTraceManager.TraceAndDump(TraceName);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Ar.Logf(TEXT("Need to specify TraceName, i.e. 'StackTrace TraceName'"));
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Special 'LogTrace' command, which ties into the stack tracking code as used by the 'StackTrace' command.
|
||||
* Every time a matching log entry is encountered, a stack trace is dumped.
|
||||
*
|
||||
* NOTE: Does not track the category or verbosity of log entries
|
||||
*
|
||||
* Usage: (NOTE: Replace 'LogLine' with the log text to be tracked)
|
||||
*
|
||||
* - Add an exact log line to tracking (case sensitive, and must match length too):
|
||||
* GEngine->Exec(NULL, TEXT("LogTrace Add LogLine"));
|
||||
*
|
||||
* - Add a partial log line to tracking (case insensitive, and can match substrings):
|
||||
* GEngine->Exec(NULL, TEXT("LogTrace AddPartial LogLine"));
|
||||
*
|
||||
* - Dump accumulated log entries, for a specified log line, and clears it from tracing:
|
||||
* GEngine->Exec(NULL, TEXT("LogTrace Dump LogLine"));
|
||||
*
|
||||
* - Clear the specified log line from tracing:
|
||||
* GEngine->Exec(NULL, TEXT("LogTrace Clear LogLine"));
|
||||
*
|
||||
* - Clear all log lines from tracing:
|
||||
* GEngine->Exec(NULL, TEXT("LogTrace ClearAll"));
|
||||
* GEngine->Exec(NULL, TEXT("LogTrace ClearAll -Dump"));
|
||||
*/
|
||||
else if (FParse::Command(&Cmd, TEXT("LogTrace")))
|
||||
{
|
||||
FString LogLine = TEXT("NotSet");
|
||||
|
||||
if (FParse::Command(&Cmd, TEXT("Add")) && (LogLine = Cmd).Len() > 0)
|
||||
{
|
||||
GLogTraceManager.AddLogTrace(LogLine, false);
|
||||
}
|
||||
else if (FParse::Command(&Cmd, TEXT("AddPartial")) && (LogLine = Cmd).Len() > 0)
|
||||
{
|
||||
GLogTraceManager.AddLogTrace(LogLine, true);
|
||||
}
|
||||
else if (FParse::Command(&Cmd, TEXT("Dump")) && (LogLine = Cmd).Len() > 0)
|
||||
{
|
||||
GLogTraceManager.ClearLogTrace(LogLine, true);
|
||||
}
|
||||
else if (FParse::Command(&Cmd, TEXT("Clear")) && (LogLine = Cmd).Len() > 0)
|
||||
{
|
||||
GLogTraceManager.ClearLogTrace(LogLine, false);
|
||||
}
|
||||
else if (FParse::Command(&Cmd, TEXT("ClearAll")))
|
||||
{
|
||||
bool bDump = FParse::Param(Cmd, TEXT("DUMP"));
|
||||
|
||||
GLogTraceManager.ClearAll(bDump);
|
||||
}
|
||||
// If LogLine is now zero-length instead of 'NotSet', that means a valid command was encountered, but no LogLine specified
|
||||
else if (LogLine.Len() == 0)
|
||||
{
|
||||
Ar.Logf(TEXT("Need to specify a log line for tracing."));
|
||||
}
|
||||
}
|
||||
|
||||
return bReturnVal;
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user