Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
75 commits
Select commit Hold shift + click to select a range
6d185e5
moving orchestration to this branch from test/multiprocess-testing/wip
SamuelBellomo Jun 27, 2021
4f6d799
moving base for multiprocess tests to this branch from test/multiproc…
SamuelBellomo Jun 27, 2021
efb083c
adding fixed testcoordinator
SamuelBellomo Jun 27, 2021
bdaf320
Merge branch 'test/multiprocess-tests/orchestration' into test/multip…
SamuelBellomo Jun 27, 2021
4ea4d4c
adding missing change
SamuelBellomo Jun 27, 2021
ea7cdcf
moving execute step in context to this branch from test/multiprocess-…
SamuelBellomo Jun 27, 2021
5803269
taking changes from wip branch
SamuelBellomo Jun 27, 2021
aa09b08
Merge branch 'test/multiprocess-tests/base-multiprocess-tests' into t…
SamuelBellomo Jun 27, 2021
c804d64
commenting out ExecuteStepInContext for better PR clarity
SamuelBellomo Jun 27, 2021
4165a10
Merge branch 'test/multiprocess-tests/base-multiprocess-tests' into t…
SamuelBellomo Jun 27, 2021
5a1d2ef
uncommenting here, this is where they should really be
SamuelBellomo Jun 27, 2021
23e435a
cleanup
SamuelBellomo Jun 27, 2021
c400776
Merge branch 'test/multiprocess-tests/orchestration' into test/multip…
SamuelBellomo Jun 27, 2021
5197a3c
Merge branch 'test/multiprocess-tests/base-multiprocess-tests' into t…
SamuelBellomo Jun 27, 2021
c38a957
better name
SamuelBellomo Jun 27, 2021
ad836c5
Merge branch 'test/multiprocess-tests/orchestration' into test/multip…
SamuelBellomo Jun 27, 2021
f60e75c
Merge branch 'test/multiprocess-tests/base-multiprocess-tests' into t…
SamuelBellomo Jun 27, 2021
60b7d43
removing comment and putting better name
SamuelBellomo Jun 27, 2021
b1656ae
consistent naming
SamuelBellomo Jul 5, 2021
4ddcd25
Apply suggestions from code review
SamuelBellomo Jul 5, 2021
55e6853
Applying suggestions
SamuelBellomo Jul 5, 2021
310a78d
Merge branch 'test/multiprocess-tests/orchestration' of github.com:Un…
SamuelBellomo Jul 5, 2021
3da130d
Merge branch 'test/multiprocess-tests/orchestration' into test/multip…
SamuelBellomo Jul 5, 2021
17c7e7d
naming
SamuelBellomo Jul 5, 2021
7221712
should be kept public for following PR
SamuelBellomo Jul 5, 2021
4d0c72f
Merge branch 'test/multiprocess-tests/orchestration' into test/multip…
SamuelBellomo Jul 5, 2021
4ff1206
apply rename
SamuelBellomo Jul 5, 2021
ad7addb
Merge branch 'test/multiprocess-tests/base-multiprocess-tests' into t…
SamuelBellomo Jul 5, 2021
5888f18
rename
SamuelBellomo Jul 5, 2021
eeae93c
using proper list
SamuelBellomo Jul 5, 2021
520c20d
better exception
SamuelBellomo Jul 5, 2021
3dad45e
Merge branch 'test/multiprocess-tests/base-multiprocess-tests' into t…
SamuelBellomo Jul 5, 2021
a3b98a0
Merge branch 'develop' into test/multiprocess-tests/orchestration
0xFA11 Jul 6, 2021
9885747
fix for unused method
SamuelBellomo Jul 6, 2021
0b37b63
Merge branch 'test/multiprocess-tests/orchestration' of github.com:Un…
SamuelBellomo Jul 6, 2021
3cf743a
Merge branch 'test/multiprocess-tests/orchestration' into test/multip…
SamuelBellomo Jul 6, 2021
2f74194
Merge branch 'test/multiprocess-tests/base-multiprocess-tests' into t…
SamuelBellomo Jul 6, 2021
f6309e9
fix/cleanup asmdefs again
0xFA11 Jul 6, 2021
cd809ec
Apply suggestions from code review
SamuelBellomo Jul 6, 2021
8db3ac3
Merge branch 'test/multiprocess-tests/orchestration' into test/multip…
SamuelBellomo Jul 6, 2021
933df82
Merge branch 'test/multiprocess-tests/base-multiprocess-tests' into t…
SamuelBellomo Jul 6, 2021
6f7469b
Merge branch 'develop' into test/multiprocess-tests/orchestration
0xFA11 Jul 8, 2021
29e05bb
no longer ignore '[Ss]treamingAssets/buildInfo.txt'
0xFA11 Jul 8, 2021
ed2519c
PR suggestions
SamuelBellomo Jul 8, 2021
b08561a
changing root menu
SamuelBellomo Jul 8, 2021
1c9fb6c
#
SamuelBellomo Jul 8, 2021
c6d3b4a
rename test scene
SamuelBellomo Jul 8, 2021
2aa372b
Update testproject/Assets/Tests/Runtime/MultiprocessRuntime/Helpers/B…
SamuelBellomo Jul 8, 2021
3b6bfd8
Merge branch 'test/multiprocess-tests/orchestration' of github.com:Un…
SamuelBellomo Jul 8, 2021
484700f
rename for test scene
SamuelBellomo Jul 8, 2021
0701716
Merge branch 'test/multiprocess-tests/orchestration' into test/multip…
SamuelBellomo Jul 8, 2021
cba7b19
proper rename for scene
SamuelBellomo Jul 8, 2021
bde44dd
Merge branch 'test/multiprocess-tests/base-multiprocess-tests' into t…
SamuelBellomo Jul 8, 2021
90fffe4
#
SamuelBellomo Jul 8, 2021
24e3608
Merge branch 'test/multiprocess-tests/orchestration' into test/multip…
SamuelBellomo Jul 8, 2021
544572f
Merge branch 'test/multiprocess-tests/base-multiprocess-tests' into t…
SamuelBellomo Jul 8, 2021
2c915e9
simpler flow
SamuelBellomo Jul 8, 2021
d083fb6
Merge branch 'test/multiprocess-tests/orchestration' into test/multip…
SamuelBellomo Jul 8, 2021
9a3bd9b
fixes
SamuelBellomo Jul 8, 2021
65b214c
Merge branch 'test/multiprocess-tests/orchestration' into test/multip…
SamuelBellomo Jul 8, 2021
945c7f3
Merge branch 'test/multiprocess-tests/base-multiprocess-tests' into t…
SamuelBellomo Jul 8, 2021
f34841f
Merge branch 'develop' into test/multiprocess-tests/base-multiprocess…
SamuelBellomo Jul 8, 2021
2fba2c4
Merge branch 'test/multiprocess-tests/base-multiprocess-tests' into t…
SamuelBellomo Jul 8, 2021
070f6af
formatting issues
SamuelBellomo Jul 9, 2021
d1aea4a
Merge branch 'test/multiprocess-tests/base-multiprocess-tests' into t…
SamuelBellomo Jul 9, 2021
8f8f7ec
name fix
SamuelBellomo Jul 9, 2021
5897561
format update
SamuelBellomo Jul 9, 2021
e64b12e
name fix
SamuelBellomo Jul 9, 2021
750a3b5
#
SamuelBellomo Jul 9, 2021
1e14bd4
Merge branch 'test/multiprocess-tests/base-multiprocess-tests' into t…
SamuelBellomo Jul 9, 2021
b15f3cd
Merge branch 'develop' into test/multiprocess-tests/base-multiprocess…
0xFA11 Jul 9, 2021
50398b3
fix for automation fail right now
SamuelBellomo Jul 9, 2021
3d4d863
Merge branch 'test/multiprocess-tests/base-multiprocess-tests' of git…
SamuelBellomo Jul 9, 2021
e18b84f
Merge branch 'test/multiprocess-tests/base-multiprocess-tests' into t…
SamuelBellomo Jul 9, 2021
8ab1f40
Merge branch 'develop' into test/multiprocess-tests/execute-step-in-c…
SamuelBellomo Jul 9, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,289 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using MLAPI;
using MLAPI.Messaging;
using NUnit.Framework;
using NUnit.Framework.Interfaces;
using UnityEngine;
using UnityEngine.TestTools;
using Debug = UnityEngine.Debug;

/// <summary>
/// Allows for context based delegate execution.
/// Can specify where you want that lambda executed (client side? server side?) and it'll automatically wait for the end
/// of a clientRPC server side and vice versa.
/// todo this could be used as an in-game tool too? for protocols that require a lot of back and forth?
/// </summary>
public class ExecuteStepInContext : CustomYieldInstruction
{
public enum StepExecutionContext
{
Server,
Clients
}

[AttributeUsage(AttributeTargets.Method)]
public class MultiprocessContextBasedTestAttribute : NUnitAttribute, IOuterUnityTestAction
{
public IEnumerator BeforeTest(ITest test)
{
yield return new WaitUntil(() => TestCoordinator.Instance != null && HasRegistered);
}

public IEnumerator AfterTest(ITest test)
{
yield break;
}
}

private StepExecutionContext m_ActionContext;
private Action<byte[]> m_StepToExecute;
private string m_CurrentActionId;

// as a remote worker, I store all available actions so I can execute them when triggered from RPCs
public static Dictionary<string, ExecuteStepInContext> AllActions = new Dictionary<string, ExecuteStepInContext>();
private static Dictionary<string, int> s_MethodIdCounter = new Dictionary<string, int>();

private NetworkManager m_NetworkManager;
private bool m_IsRegistering;
private List<Func<bool>> m_ClientIsFinishedChecks = new List<Func<bool>>();
private Func<bool> m_AdditionalIsFinishedWaiter;

private bool m_WaitMultipleUpdates;
private bool m_IgnoreTimeoutException;

private float m_StartTime;
private bool isTimingOut => Time.time - m_StartTime > TestCoordinator.MaxWaitTimeoutSec;
private bool shouldExecuteLocally => (m_ActionContext == StepExecutionContext.Server && m_NetworkManager.IsServer) || (m_ActionContext == StepExecutionContext.Clients && !m_NetworkManager.IsServer);

public static bool IsRegistering;
public static bool HasRegistered;
private static List<object> s_AllClientTestInstances = new List<object>(); // to keep an instance for each tests, so captured context in each step is kept

/// <summary>
/// This MUST be called at the beginning of each test in order to use context based steps.
/// Assumes this is called from same callsite as ExecuteStepInContext (and assumes this is called from IEnumerator, the method full name is unique
/// even with the same method name and different parameters).
/// This relies on the name to be unique for each generated IEnumerator state machines
/// </summary>
public static void InitializeContextSteps()
{
var callerMethod = new StackFrame(1).GetMethod();
var methodIdentifier = GetMethodIdentifier(callerMethod); // since this is called from IEnumerator, this should be a generated class, making it unique
s_MethodIdCounter[methodIdentifier] = 0;
}

private static string GetMethodIdentifier(MethodBase callerMethod)
{
return callerMethod.DeclaringType.FullName;
}

internal static void InitializeAllSteps()
{
// registering magically all context based steps
IsRegistering = true;
var registeredMethods = typeof(TestCoordinator).Assembly.GetTypes().SelectMany(t => t.GetMethods())
.Where(m => m.GetCustomAttributes(typeof(MultiprocessContextBasedTestAttribute), true).Length > 0)
.ToArray();
var typesWithContextMethods = new HashSet<Type>();
foreach (var method in registeredMethods)
{
typesWithContextMethods.Add(method.ReflectedType);
}

if (registeredMethods.Length == 0)
{
throw new Exception($"Couldn't find any registered methods for multiprocess testing. Is {nameof(TestCoordinator)} in same assembly as test methods?");
}

object[] GetParameterValuesToPassFunc(ParameterInfo[] parameterInfo)
{
object[] parametersToReturn = new object[parameterInfo.Length];
for (int i = 0; i < parameterInfo.Length; i++)
{
var paramType = parameterInfo[i].GetType();
object defaultObj = null;
if (paramType.IsValueType)
{
defaultObj = Activator.CreateInstance(paramType);
}

parametersToReturn[i] = defaultObj;
}

return parametersToReturn;
}

foreach (var contextType in typesWithContextMethods)
{
var allConstructors = contextType.GetConstructors();
if (allConstructors.Length > 1)
{
throw new NotImplementedException("Case not implemented where test has more than one constructor");
}

var instance = Activator.CreateInstance(contextType, allConstructors.Length > 0 ? GetParameterValuesToPassFunc(allConstructors[0].GetParameters()) : null);
s_AllClientTestInstances.Add(instance); // keeping that instance so tests can use captured local attributes

var typeMethodsWithContextSteps = new List<MethodInfo>();
foreach (var method in contextType.GetMethods())
{
if (method.GetCustomAttributes(typeof(MultiprocessContextBasedTestAttribute), true).Length > 0)
{
typeMethodsWithContextSteps.Add(method);
}
}

foreach (var method in typeMethodsWithContextSteps)
{
var parametersToPass = GetParameterValuesToPassFunc(method.GetParameters());
var enumerator = (IEnumerator)method.Invoke(instance, parametersToPass.ToArray());
while (enumerator.MoveNext()) { }
}
}

IsRegistering = false;
HasRegistered = true;
}

/// <summary>
/// Executes an action with the specified context. This allows writing tests all in the same sequential flow,
/// making it more readable. This allows not having to jump between static client methods and test method
/// </summary>
/// <param name="actionContext">context to use. for example, should execute client side? server side?</param>
/// <param name="stepToExecute">action to execute</param>
/// <param name="ignoreTimeoutException">waits for timeout and just finishes step execution silently</param>
/// <param name="paramToPass">parameters to pass to action</param>
/// <param name="networkManager"></param>
/// <param name="waitMultipleUpdates"> waits multiple frames before allowing the execution to continue. This means ClientFinishedServerRpc must be called manually</param>
/// <param name="additionalIsFinishedWaiter"></param>
public ExecuteStepInContext(StepExecutionContext actionContext, Action<byte[]> stepToExecute, bool ignoreTimeoutException = false, byte[] paramToPass = default, NetworkManager networkManager = null, bool waitMultipleUpdates = false, Func<bool> additionalIsFinishedWaiter = null)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit

Suggested change
public ExecuteStepInContext(StepExecutionContext actionContext, Action<byte[]> stepToExecute, bool ignoreTimeoutException = false, byte[] paramToPass = default, NetworkManager networkManager = null, bool waitMultipleUpdates = false, Func<bool> additionalIsFinishedWaiter = null)
public ExecuteStepInContext(StepExecutionContext actionContext, Action<byte[]> stepToExecuteAction, bool ignoreTimeoutException = false, byte[] paramToPass = default, NetworkManager networkManager = null, bool waitMultipleUpdates = false, Func<bool> additionalIsFinishedWaiterFunc = null)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not a fan of putting types in parameter names. mouse hover and auto complete in your IDE are there to help. Would you also rename ignoreTimeoutExceptionBool?

{
m_StartTime = Time.time;
m_IsRegistering = IsRegistering;
m_ActionContext = actionContext;
m_StepToExecute = stepToExecute;
m_WaitMultipleUpdates = waitMultipleUpdates;
m_IgnoreTimeoutException = ignoreTimeoutException;

if (additionalIsFinishedWaiter != null)
{
m_AdditionalIsFinishedWaiter = additionalIsFinishedWaiter;
}

if (networkManager == null)
{
networkManager = NetworkManager.Singleton;
}

m_NetworkManager = networkManager; // todo test using this for multiinstance tests too?

var callerMethod = new StackFrame(1).GetMethod(); // one skip frame for current method

var methodId = GetMethodIdentifier(callerMethod); // assumes called from IEnumerator MoveNext, which should be the case since we're a CustomYieldInstruction. This will return a generated class name which should be unique
if (!s_MethodIdCounter.ContainsKey(methodId))
{
s_MethodIdCounter[methodId] = 0;
}

string currentActionId = $"{methodId}-{s_MethodIdCounter[methodId]++}";
m_CurrentActionId = currentActionId;

if (m_IsRegistering)
{
Assert.That(AllActions, Does.Not.Contain(currentActionId)); // sanity check
AllActions[currentActionId] = this;
}
else
{
if (shouldExecuteLocally)
{
m_StepToExecute.Invoke(paramToPass);
}
else
{
if (networkManager.IsServer)
{
TestCoordinator.Instance.TriggerActionIdClientRpc(currentActionId, paramToPass,
clientRpcParams: new ClientRpcParams
{
Send = new ClientRpcSendParams { TargetClientIds = TestCoordinator.AllClientIdsExceptMine.ToArray() }
});
foreach (var clientId in TestCoordinator.AllClientIdsExceptMine)
{
m_ClientIsFinishedChecks.Add(TestCoordinator.ConsumeClientIsFinished(clientId));
}
}
else
{
throw new NotImplementedException();
}
}
}
}

public void Invoke(byte[] args)
{
m_StepToExecute.Invoke(args);
if (!m_WaitMultipleUpdates)
{
if (!m_NetworkManager.IsServer)
{
TestCoordinator.Instance.ClientFinishedServerRpc();
}
else
{
throw new NotImplementedException("todo implement");
}
}
}

public override bool keepWaiting
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

again, we apparently didn't write rules for property names (yet) but my gut tells me that it's first letter should've been capitalized.

Suggested change
public override bool keepWaiting
public override bool KeepWaiting

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unfortunately, this is overriding a property we don't control :(

{
get
{
if (isTimingOut)
{
if (m_IgnoreTimeoutException)
{
Debug.LogWarning($"Timeout ignored for action ID {m_CurrentActionId}");
return false;
}

throw new Exception($"timeout for Context Step with action ID {m_CurrentActionId}");
}

if (m_AdditionalIsFinishedWaiter != null)
{
var isFinished = m_AdditionalIsFinishedWaiter.Invoke();
if (!isFinished)
{
return true;
}
}

if (m_IsRegistering || shouldExecuteLocally || m_ClientIsFinishedChecks == null)
{
return false;
}

for (int i = m_ClientIsFinishedChecks.Count - 1; i >= 0; i--)
{
if (m_ClientIsFinishedChecks[i].Invoke())
{
m_ClientIsFinishedChecks.RemoveAt(i);
}
else
{
return true;
}
}

return false;
}
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading