// Copyright Epic Games, Inc. All Rights Reserved. using AutomatedPerfTesting; using Gauntlet; using System; using System.Collections.Generic; using System.Data; using System.Linq; using System.Reflection; /// /// This file defines the Test Bridge interface. In this context, a "Test Bridge" /// refers to the custom test session configuration and logic which needs to run /// before Gauntlet launches the test session on the target device. /// /// A Test Bridge may contain various attributes which enable composing and defining /// test-specific config and logic depending on the attribute parameters. These /// bridges are then instantiated for a given test if the test context parameters match /// the attribute parameters. /// /// Simple example would be defining Test Bridges for each Test Type in your project /// i.e. SequenceBridge, ReplayBridge, ProfileGoBridge etc. such that each bridge /// is only activated for their corresponding test type. /// namespace AutomatedPerfTest { /// /// Interface to be used if a project wants to configure the Automated Perf Test /// context, roles and/or config without making intrusive changes to the main /// node before launching the test. /// /// Ideally project/game specific command line parameters get set here. /// public interface ITestBridge { /// /// Initial configure call. This is invoked first. /// /// Test Context /// APT Config Base void Configure(UnrealTestContext Context, AutomatedPerfTestConfigBase Config) { } /// /// Configure client role /// /// Client Test Role /// Test Context /// APT Config Base void ConfigureClient(UnrealTestRole Role, UnrealTestContext Context, AutomatedPerfTestConfigBase Config) { } /// /// Configure server role. This is invoked optionally if a server role is required for the test. /// /// Server Test Role /// Test Context /// APT Config Base void ConfigureServer(UnrealTestRole Role, UnrealTestContext Context, AutomatedPerfTestConfigBase Config) { } /// /// If this is true, server role is instantiated and configured. /// /// bool IsServerRoleRequired() { return false; } } /// /// Null Test Bridge /// public class NullBridge : ITestBridge { } /// /// Static helper class to activate and invoke test bridge configuration /// functions. /// public static class AutoTestBridge { private static HashSet Bridges = new HashSet(); /// /// Initialize Test Bridge instances with given list of Bridge class names. /// /// Bridge names public static void Initialize(IEnumerable InConfigBridges) { foreach (string BridgeName in InConfigBridges) { ITestBridge ConfigBridge = GetConfigBridge(BridgeName.Trim()); AddBridge(ConfigBridge); } } public static void AddBridge(ITestBridge ConfigBridge) { if(ConfigBridge != null) { Bridges.Add(ConfigBridge); Log.Info($"Test Bridge Added: {ConfigBridge.GetType().Name}"); } } private static ITestBridge GetConfigBridge(string ConfigBridgeName) { if (!string.IsNullOrEmpty(ConfigBridgeName)) { Type BridgeType = Util.GetTypeWithInterface(ConfigBridgeName); if (BridgeType != null) { return Activator.CreateInstance(BridgeType) as ITestBridge; } } return null; } public static void Configure(UnrealTestContext Context, AutomatedPerfTestConfigBase Config) { Bridges.ToList().ForEach(Bridge => Bridge?.Configure(Context, Config)); } public static void ConfigureClient(UnrealTestRole Role, UnrealTestContext Context, AutomatedPerfTestConfigBase Config) { Bridges.ToList().ForEach(Bridge => Bridge?.ConfigureClient(Role, Context, Config)); } public static void ConfigureServer(UnrealTestRole Role, UnrealTestContext Context, AutomatedPerfTestConfigBase Config) { Bridges.ToList().ForEach(Bridge => Bridge?.ConfigureServer(Role, Context, Config)); } public static bool IsServerRoleRequired() { return Bridges.Where(B => B.IsServerRoleRequired()).Any(); } private static bool ValidateAttributes(Type TestBridge, string ProjectName = "") { EnableForProjectAttribute ProjectAttribute = TestBridge.GetCustomAttribute(); EnableForParamsAttribute Params = TestBridge.GetCustomAttribute(); AutoBridge BridgeAttribute = TestBridge.GetCustomAttribute(); IEnumerable AutoBridgeTestAttribute = TestBridge.GetCustomAttributes(typeof(AutoBridgeForTestAttribute<>)); bool Result = true; if (ProjectAttribute != null) { Result = ProjectAttribute.ProjectName == ProjectName; } if (Params != null) { foreach (KeyValuePair Args in Params.Params) { string Value = Globals.Params.ParseValue(Args.Key, ""); bool bContainsKey = Globals.Params.ParseParam(Args.Key); // Some params may be bool params and may only have the param key to // indicate if it should be activated. bool bIsBoolVal = string.IsNullOrEmpty(Value) && string.IsNullOrEmpty(Args.Value) && bContainsKey; bool bIsEqual = string.Equals(Value, Args.Value, StringComparison.OrdinalIgnoreCase); if (!bIsBoolVal && !bIsEqual) { Result = false; break; } } } return Result && (BridgeAttribute != null || AutoBridgeTestAttribute.Any()); } /// /// Registers valid Test Bridge types which will then be used to configure the test /// depending on the need of the test and/or project. /// Following attributes used for validation:
/// /// [AutoBridge] - This enables the bridge for all automated perf tests.
/// [AutoBridgeForTest[]] - This enables the bridge only /// for the specified test type. Multiple types are supported.
/// [EnableForProject] - Ensures the bridge is only enabled for a given project.
/// [EnableForParams] /// ///
/// Type of test being run in this given session /// Name of project. This should match the name in /// EnableForProjectAttribute to take effect. public static void ActivateTestBridges(Type CurrentTestType, string ProjectName = "") { Type[] TestBridgeTypes = Util.GetTypesWithInterface(); foreach(Type TestBridge in TestBridgeTypes) { AutoBridge BridgeAttribute = TestBridge.GetCustomAttribute(); IEnumerable AutoBridgeTestAttribute = TestBridge.GetCustomAttributes(typeof(AutoBridgeForTestAttribute<>)); // A bridge is valid if each present attribute is valid. If an attribute is null, we do not consider // it for validation. if (!ValidateAttributes(TestBridge, ProjectName)) { continue; } if (BridgeAttribute != null) { // If a Test Bridge is enabled for all test types, we simply add the bridge // and continue to the next bridge derived type. AddBridge(Activator.CreateInstance(TestBridge) as ITestBridge); continue; } // Register bridges which have indicated that they are applicable for a given type of // test. foreach (Attribute AutoBridge in AutoBridgeTestAttribute) { if (AutoBridge != null && AutoBridge.GetType().IsGenericType) { Type CandidateTestType = AutoBridge.GetType().GetGenericArguments().First(); if(CandidateTestType != null && CurrentTestType == CandidateTestType) { AddBridge(Activator.CreateInstance(TestBridge) as ITestBridge); } } } } } } /// /// Automatically activate given Test Bridge for all Automated Perf Tests. /// If this is the only attribute present, it will be added to all /// tests by default.
/// Example: [AutoBridge] ///
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] public class AutoBridge : Attribute { public AutoBridge() { } } /// /// Automatically activate given Test Bridge for given Automated Perf Test /// type. If this is the only attribute present, it will be added to /// all instances of a given test type.
/// Example: [AutoBridgeForTest[ReplayTest]] ///
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] public class AutoBridgeForTestAttribute : Attribute where T : IAutomatedPerfTest { public Type TestType { get; private set; } public AutoBridgeForTestAttribute() { TestType = typeof(T); } } /// /// Ensures test bridge is activated only for a given project. This ensures if /// a given bridge is very specific and/or does not apply to any other project /// it can only be activated for that project.
/// This should be combined with one of the AutoBridge attribute types to ensure /// auto-activation of the bridge.
/// Example: [EnableForProject("MyProjectName")] ///
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] public class EnableForProjectAttribute : Attribute { public string ProjectName { get; private set; } public EnableForProjectAttribute(string InProjectName) { ProjectName = InProjectName; } } /// /// If a test bridge only applies for a very specific set of parameters, /// they can be specified here.
/// Example: [EnableForParams("Mode=Test", "SomeBoolVal", ...)] ///
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] public class EnableForParamsAttribute : Attribute { public List> Params { get; private set; } public EnableForParamsAttribute(params string[] InParams) { Params = InParams .Select(P => P.Split('=')) .Select(S => new KeyValuePair(S[0], S.Length > 1 ? S[1] : null)) .ToList(); } } }