// Copyright Epic Games, Inc. All Rights Reserved. #pragma once #include "HAL/Platform.h" #define ENABLE_PIE_NETWORK_TEST WITH_EDITOR && (WITH_DEV_AUTOMATION_TESTS || WITH_PERF_AUTOMATION_TESTS) #if ENABLE_PIE_NETWORK_TEST #include "CQTest.h" #include "CQTestSettings.h" #include "PIENetworkTestStateRestorer.h" #include "Editor.h" #include "Engine/NetDriver.h" #include "Engine/PackageMapClient.h" #include "Net/NetIDVariant.h" #include "UnrealEdGlobals.h" #if UE_WITH_IRIS #include "Iris/ReplicationSystem/ReplicationSystem.h" #include "Iris/ReplicationSystem/ObjectReplicationBridge.h" #endif /* //Example boiler plate #include "CQTest.h" #include "Components/PIENetworkComponent.h" #include "GameFramework/GameModeBase.h" #if ENABLE_PIE_NETWORK_TEST NETWORK_TEST_CLASS(MyFixtureName, "Network.Example") { // Deriving from the FBasePIENetworkComponentState to add objects to be tested in each state struct DerivedState : public FBasePIENetworkComponentState { APawn* ReplicatedPawn; }; FPIENetworkComponent Network{ TestRunner, TestCommandBuilder, bInitializing }; BEFORE_EACH() { FNetworkComponentBuilder() .WithClients(2) .WithGameInstanceClass(UGameInstance::StaticClass()) .WithGameMode(AGameModeBase::StaticClass()) .Build(Network); } TEST_METHOD(SpawnAndReplicatePawn_WithReplicatedPawn_ProvidesPawnToClients) { Network.SpawnAndReplicate() .ThenServer([this](DerivedState& ServerState) { ASSERT_THAT(IsNotNull(ServerState.ReplicatedPawn)); }) .ThenClients([this](DerivedState& ClientState) { ASSERT_THAT(IsNotNull(ClientState.ReplicatedPawn)); }); } }; #endif // ENABLE_PIE_NETWORK_TEST */ /** * Struct which handles the PIE session's world and network information. * Both the server and client will keep a reference to their own respective state which will be tested against. */ struct FBasePIENetworkComponentState { /** Default virtual destructor. */ virtual ~FBasePIENetworkComponentState() = default; /** Reference to the session's world. */ UWorld* World = nullptr; /** Used by the server to reference the connections to the client. */ TArray ClientConnections; /** Used by the client to reference location within the `ClientConnections` array on the server. */ int32 ClientIndex = INDEX_NONE; /** Used by the server for creating and validation of client instances. */ int32 ClientCount = 2; /** Used by the server to create a PIE session as a dedicated or listen server. */ bool bIsDedicatedServer = true; /** Used to track spawned and replicated actors across client and server PIE sessions. */ TSet LocallySpawnedActors{}; }; /** * Component which creates and initializes the server and client network connections between the PIE sessions. * The component also provides methods around the CommandBuilder for adding latent commands. */ class FBasePIENetworkComponent { public: /** * Construct the BasePIENetworkComponent. * * @param InTestRunner - Pointer to the TestRunner used for test reporting. * @param InCommandBuilder - Reference to the latent command manager. * @param IsInitializing - Bool specifying if the Automation framework is still being initialized. * @param Timeout - Duration that the latent command can execute before reporting an error. * * @note Requires that the `FBasePIENetworkComponent` is built using the `FNetworkComponentBuilder` as this will setup the server and client `FBasePIENetworkComponentState` * @note If no value is provided for the timeout, value will be pulled from the `TestFramework.CQTest.CommandTimeout.Network` CVar. */ CQTEST_API FBasePIENetworkComponent(FAutomationTestBase* InTestRunner, FTestCommandBuilder& InCommandBuilder, bool IsInitializing, TOptional Timeout = CQTest::DefaultTimeout); /** * Enqueues a function to the latent command manager for execution on a subsequent frame. * * @param Action - Function to be executed. * * @return a reference to this */ CQTEST_API FBasePIENetworkComponent& Then(TFunction Action); /** * Enqueues a function to the latent command manager for execution on a subsequent frame. * * @param Description - Description of the provided function to be logged. * @param Action - Function to be executed. * * @return a reference to this */ CQTEST_API FBasePIENetworkComponent& Then(const TCHAR* Description, TFunction Action); /** * Enqueues a function to the latent command manager for execution on a subsequent frame. * * @param Action - Function to be executed. * * @return a reference to this */ CQTEST_API FBasePIENetworkComponent& Do(TFunction Action); /** * Enqueues a function to the latent command manager for execution on a subsequent frame. * * @param Description - Description of the provided function to be logged. * @param Action - Function to be executed. * * @return a reference to this */ CQTEST_API FBasePIENetworkComponent& Do(const TCHAR* Description, TFunction Action); /** * Enqueues a function to the latent command manager for execution until completion or timeout. * * @param Query - Function to be executed. * @param Timeout - Duration that the latent command can execute before reporting an error. * * @return a reference to this * * @note Will continue to execute until the function provided evaluates to `true` or the duration exceeds the specified timeout. * @note If no value is provided for the timeout, value will be pulled from the `TestFramework.CQTest.CommandTimeout.Network` CVar. */ CQTEST_API FBasePIENetworkComponent& Until(TFunction Query, TOptional Timeout = CQTest::DefaultTimeout); /** * Enqueues a function to the latent command manager for execution until completion or timeout. * * @param Description - Description of the provided function to be logged. * @param Query - Function to be executed. * @param Timeout - Duration that the latent command can execute before reporting an error. * * @return a reference to this * * @note Will continue to execute until the function provided evaluates to `true` or the duration exceeds the specified timeout. * @note If no value is provided for the timeout, value will be pulled from the `TestFramework.CQTest.CommandTimeout.Network` CVar. */ CQTEST_API FBasePIENetworkComponent& Until(const TCHAR* Description, TFunction Query, TOptional Timeout = CQTest::DefaultTimeout); /** * Enqueues a function to the latent command manager for execution until completion or timeout. * * @param Query - Function to be executed. * @param Timeout - Duration that the latent command can execute before reporting an error. * * @return a reference to this * * @note Will continue to execute until the function provided evaluates to `true` or the duration exceeds the specified timeout. * @note If no value is provided for the timeout, value will be pulled from the `TestFramework.CQTest.CommandTimeout.Network` CVar. */ CQTEST_API FBasePIENetworkComponent& StartWhen(TFunction Query, TOptional Timeout = CQTest::DefaultTimeout); /** * Enqueues a function to the latent command manager for execution until completion or timeout. * * @param Description - Description of the provided function to be logged. * @param Query - Function to be executed. * @param Timeout - Duration that the latent command can execute before reporting an error. * * @return a reference to this * * @note Will continue to execute until the function provided evaluates to `true` or the duration exceeds the specified timeout. * @note If no value is provided for the timeout, value will be pulled from the `TestFramework.CQTest.CommandTimeout.Network` CVar. */ CQTEST_API FBasePIENetworkComponent& StartWhen(const TCHAR* Description, TFunction Query, TOptional Timeout = CQTest::DefaultTimeout); protected: /** Prepares the active PIE sessions to be stopped. */ CQTEST_API void StopPie(); /** Setup settings for a network session and start PIE sessions for both the server and client with the network settings applied. */ CQTEST_API void StartPie(); /** * Fetch all of the worlds that will be used for the networked session. * * @return true if an error was encountered or if the PIE sessions were created with the correct network settings, false otherwise. * * @note Method is expected to be used within the `Until` latent command to then wait until the worlds are ready for use. */ CQTEST_API bool SetWorlds(); /** Sets the packet settings used to simulate packet behavior over a network. */ CQTEST_API void SetPacketSettings() const; /** Connects all available clients to the server. */ CQTEST_API void ConnectClientsToServer(); /** * Go through all of the client connections to make sure they are connected and ready. * * @return true if an error was encountered or if the connections all have a valid controller, false otherwise. * * @note Method is expected to be used within the `Until` latent command to then wait until the worlds are ready for use. */ CQTEST_API bool AwaitClientsReady() const; /** Restore back to the state prior to test execution. */ CQTEST_API void RestoreState(); /** * Creates a timeout to be used. * * @param Timeout - Duration that the latent command can execute before reporting an error. * * @return either the FTimespan specified or the value from the `TestFramework.CQTest.CommandTimeout.Network` CVar. */ CQTEST_API FTimespan MakeTimeout(TOptional Timeout); TUniquePtr ServerState = nullptr; TArray> ClientStates; FAutomationTestBase* TestRunner = nullptr; FTestCommandBuilder* CommandBuilder = nullptr; FPacketSimulationSettings* PacketSimulationSettings = nullptr; TSubclassOf GameMode = TSubclassOf(nullptr); FPIENetworkTestStateRestorer StateRestorer; TMap SpawnedActors{}; }; template class FNetworkComponentBuilder; /** * Component which creates and initializes the server and client network connections between the PIE sessions. * Expands on the `FBasePIENetworkComponent` by providing separate methods to add latent commands for the server and clients. */ template class FPIENetworkComponent : public FBasePIENetworkComponent { public: /** * Construct the PIENetworkComponent. * * @param InTestRunner - Pointer to the TestRunner used for test reporting. * @param InCommandBuilder - Reference to the latent command manager. * @param IsInitializing - Bool specifying if the Automation framework is still being initialized. * * @note Requires that the `FPIENetworkComponent` is built using the `FNetworkComponentBuilder` as this will setup the server and client `NetworkDataType` states */ FPIENetworkComponent(FAutomationTestBase* InTestRunner, FTestCommandBuilder& InCommandBuilder, bool IsInitializing) : FBasePIENetworkComponent(InTestRunner, InCommandBuilder, IsInitializing) {} /** * Enqueues a function to the latent command manager for execution on a subsequent frame on the server. * * @param Action - Function to be executed. * * @return a reference to this */ FPIENetworkComponent& ThenServer(TFunction Action); /** * Enqueues a function to the latent command manager for execution on a subsequent frame on the server. * * @param Description - Description of the provided function to be logged. * @param Action - Function to be executed. * * @return a reference to this */ FPIENetworkComponent& ThenServer(const TCHAR* Description, TFunction Action); /** * Enqueues a function to the latent command manager for execution on a subsequent frame on all clients. * * @param Action - Function to be executed. * * @return a reference to this */ FPIENetworkComponent& ThenClients(TFunction Action); /** * Enqueues a function to the latent command manager for execution on a subsequent frame on all clients. * * @param Description - Description of the provided function to be logged. * @param Action - Function to be executed. * * @return a reference to this */ FPIENetworkComponent& ThenClients(const TCHAR* Description, TFunction Action); /** * Enqueues a function to the latent command manager for execution on a subsequent frame on a given client. * * @param ClientIndex - Index of the connected client. * @param Action - Function to be executed. * * @return a reference to this */ FPIENetworkComponent& ThenClient(int32 ClientIndex, TFunction Action); /** * Enqueues a function to the latent command manager for execution on a subsequent frame on a given client. * * @param Description - Description of the provided function to be logged. * @param ClientIndex - Index of the connected client. * @param Action - Function to be executed. * * @return a reference to this */ FPIENetworkComponent& ThenClient(const TCHAR* Description, int32 ClientIndex, TFunction Action); /** * Enqueues a function to the latent command manager for execution until completion or timeout on the server. * * @param Query - Function to be executed. * @param Timeout - Duration that the latent command can execute before reporting an error. * * @return a reference to this * * @note If no value is provided for the timeout, value will be pulled from the `TestFramework.CQTest.CommandTimeout.Network` CVar. */ FPIENetworkComponent& UntilServer(TFunction Query, TOptional Timeout = CQTest::DefaultTimeout); /** * Enqueues a function to the latent command manager for execution until completion or timeout on the server. * * @param Description - Description of the latent command * @param Query - Function to be executed. * @param Timeout - Duration that the latent command can execute before reporting an error. * * @return a reference to this * * @note If no value is provided for the timeout, value will be pulled from the `TestFramework.CQTest.CommandTimeout.Network` CVar. */ FPIENetworkComponent& UntilServer(const TCHAR* Description, TFunction Query, TOptional Timeout = CQTest::DefaultTimeout); /** * Enqueues a function to the latent command manager for execution until completion or timeout on all clients. * * @param Query - Function to be executed. * @param Timeout - Duration that the latent command can execute before reporting an error. * * @return a reference to this * * @note If no value is provided for the timeout, value will be pulled from the `TestFramework.CQTest.CommandTimeout.Network` CVar. */ FPIENetworkComponent& UntilClients(TFunction Query, TOptional Timeout = CQTest::DefaultTimeout); /** * Enqueues a function to the latent command manager for execution until completion or timeout on all clients. * * @param Description - Description of the latent command * @param Query - Function to be executed. * @param Timeout - Duration that the latent command can execute before reporting an error. * * @return a reference to this * * @note If no value is provided for the timeout, value will be pulled from the `TestFramework.CQTest.CommandTimeout.Network` CVar. */ FPIENetworkComponent& UntilClients(const TCHAR* Description, TFunction Query, TOptional Timeout = CQTest::DefaultTimeout); /** * Enqueues a function to the latent command manager for execution until completion or timeout on a given client. * * @param ClientIndex - Index of the connected client. * @param Query - Function to be executed. * @param Timeout - Duration that the latent command can execute before reporting an error. * * @return a reference to this * * @note If no value is provided for the timeout, value will be pulled from the `TestFramework.CQTest.CommandTimeout.Network` CVar. */ FPIENetworkComponent& UntilClient(int32 ClientIndex, TFunction Query, TOptional Timeout = CQTest::DefaultTimeout); /** * Enqueues a function to the latent command manager for execution until completion or timeout on a given client. * * @param Description - Description of the latent command * @param ClientIndex - Index of the connected client. * @param Query - Function to be executed. * @param Timeout - Duration that the latent command can execute before reporting an error. * * @return a reference to this * * @note If no value is provided for the timeout, value will be pulled from the `TestFramework.CQTest.CommandTimeout.Network` CVar. */ FPIENetworkComponent& UntilClient(const TCHAR* Description, int32 ClientIndex, TFunction Query, TOptional Timeout = CQTest::DefaultTimeout); /** * Has a new client request a late join and syncs the server and other clients with the new client PIE session. * * @param Timeout - Duration that the latent command can execute before reporting an error. * * @return a reference to this * * @note If no value is provided for the timeout, value will be pulled from the `TestFramework.CQTest.CommandTimeout.Network` CVar. */ FPIENetworkComponent& ThenClientJoins(TOptional Timeout = CQTest::DefaultTimeout); /** * Spawns an actor on the server and replicates to the clients. * * @param Timeout - Duration that the latent command can execute before reporting an error. * * @return a reference to this * * @note If no value is provided for the timeout, value will be pulled from the `TestFramework.CQTest.CommandTimeout.Network` CVar. */ template FPIENetworkComponent& SpawnAndReplicate(TOptional Timeout = CQTest::DefaultTimeout); /** * Spawns an actor on the server and replicates to the clients. * * @param SpawnParameters - Parameters that can be used when spawning an Actor. * @param Timeout - Duration that the latent command can execute before reporting an error. * * @return a reference to this * * @note If no value is provided for the timeout, value will be pulled from the `TestFramework.CQTest.CommandTimeout.Network` CVar. */ template FPIENetworkComponent& SpawnAndReplicate(const FActorSpawnParameters& SpawnParameters, TOptional Timeout = CQTest::DefaultTimeout); /** * Spawns an actor on the server and replicates to the clients. * * @param BeforeReplicate - Function to be called after Actor has been spawned, but before being replicated to the other clients. * @param Timeout - Duration that the latent command can execute before reporting an error. * * @return a reference to this * * @note If no value is provided for the timeout, value will be pulled from the `TestFramework.CQTest.CommandTimeout.Network` CVar. */ template FPIENetworkComponent& SpawnAndReplicate(TFunction BeforeReplicate, TOptional Timeout = CQTest::DefaultTimeout); /** * Spawns an actor on the server and replicates to the connected clients. * * @param SpawnParameters - Parameters that can be used when spawning an Actor. * @param BeforeReplicate - Function to be called after Actor has been spawned, but before being replicated to the other clients. * @param Timeout - Duration that the latent command can execute before reporting an error. * * @return a reference to this * * @note If no value is provided for the timeout, value will be pulled from the `TestFramework.CQTest.CommandTimeout.Network` CVar. */ template FPIENetworkComponent& SpawnAndReplicate(const FActorSpawnParameters& SpawnParameters, TFunction BeforeReplicate, TOptional Timeout = CQTest::DefaultTimeout); private: friend class FNetworkComponentBuilder; /** * Spawns an actor on the server. * * @param SpawnParameters - Parameters that can be used when spawning an Actor. * @param BeforeReplicate - Function to be called after Actor has been spawned. * @param Timeout - Duration that the latent command can execute before reporting an error. * * @return a reference to this * * @note If no value is provided for the timeout, value will be pulled from the `TestFramework.CQTest.CommandTimeout.Network` CVar. */ template FPIENetworkComponent& SpawnOnServer(const FActorSpawnParameters& SpawnParameters, TFunction BeforeReplicate, TOptional Timeout = CQTest::DefaultTimeout); /** * Replicates all Actors to the connected client. * * @param ClientState - Client state to receive the updated Actor. * * @return true if all Actors have been successfully replicated on this client */ bool ReplicateToClients(NetworkDataType& ClientState); }; /** * Helper object used to setup and build the `FPIENetworkComponent`. */ template class FNetworkComponentBuilder { public: /** * Construct the NetworkComponentBuilder. * * @note Will assert if the `NetworkDataType` used is not of type or derived from `FBasePIENetworkComponentState` */ FNetworkComponentBuilder(); /** * Specifies the number of clients to be used. * * @param ClientCount - Number of clients not including the listen server. * * @return a reference to this * * @note Number of clients should exclude the server, even if using the server as a listen server. */ FNetworkComponentBuilder& WithClients(int32 ClientCount); /** * Build the server as a dedicated server. * * @return a reference to this */ FNetworkComponentBuilder& AsDedicatedServer(); /** * Build the server as a listen server. * * @return a reference to this */ FNetworkComponentBuilder& AsListenServer(); /** * Build the FPIENetworkComponent to use the provided packet simulation settings. * * @param InPacketSimulationSettings - Settings used to simulate packet behavior over a network. * * @return a reference to this */ FNetworkComponentBuilder& WithPacketSimulationSettings(FPacketSimulationSettings* InPacketSimulationSettings); /** * Build the FPIENetworkComponent to use the provided game mode. * * @param InGameMode - GameMode used for the networked session. * * @return a reference to this */ FNetworkComponentBuilder& WithGameMode(TSubclassOf InGameMode); /** * Build the FPIENetworkComponent to restore back to the provided game instance. * * @param InGameInstanceClass - GameInstance to restore back to. * * @return a reference to this */ FNetworkComponentBuilder& WithGameInstanceClass(FSoftClassPath InGameInstanceClass); /** * Build the FPIENetworkComponent with the provided data. * * @param OutNetwork - Reference to the FPIENetworkComponent. */ void Build(FPIENetworkComponent& OutNetwork); private: FPacketSimulationSettings* PacketSimulationSettings = nullptr; TSubclassOf GameMode = TSubclassOf(nullptr); FSoftClassPath GameInstanceClass = FSoftClassPath{}; int32 ClientCount = 2; bool bIsDedicatedServer = true; }; constexpr static EAutomationTestFlags NetworkTestContext = EAutomationTestFlags::EditorContext | EAutomationTestFlags::ProductFilter; /** Macro to quickly create tests which will only run within the Editor. */ #define NETWORK_TEST_CLASS(_ClassName, _TestDir) TEST_CLASS_WITH_FLAGS(_ClassName, _TestDir, NetworkTestContext) #include "Impl/PIENetworkComponent.inl" #endif // ENABLE_PIE_NETWORK_TEST