160 lines
6.1 KiB
C#
160 lines
6.1 KiB
C#
|
using System;
|
||
|
using System.Collections.Generic;
|
||
|
using UnityEditor.Networking.PlayerConnection;
|
||
|
using UnityEditor.TestTools.TestRunner;
|
||
|
using UnityEditor.TestTools.TestRunner.Api;
|
||
|
using UnityEngine;
|
||
|
using UnityEngine.Networking.PlayerConnection;
|
||
|
using UnityEngine.TestRunner.TestLaunchers;
|
||
|
|
||
|
namespace UnityEditor.TestRunner.TestLaunchers
|
||
|
{
|
||
|
[Serializable]
|
||
|
internal class RemoteTestRunController : ScriptableSingleton<RemoteTestRunController>
|
||
|
{
|
||
|
internal const int k_HeartbeatTimeout = 60 * 10;
|
||
|
|
||
|
[SerializeField]
|
||
|
internal bool isRunning;
|
||
|
|
||
|
[SerializeField]
|
||
|
private bool m_RegisteredConnectionCallbacks;
|
||
|
|
||
|
[SerializeField]
|
||
|
private int m_HearbeatTimeOut;
|
||
|
|
||
|
private enum MessageType
|
||
|
{
|
||
|
TestStarted,
|
||
|
TestFinished,
|
||
|
RunStarted,
|
||
|
RunFinished
|
||
|
}
|
||
|
[Serializable]
|
||
|
private struct Message
|
||
|
{
|
||
|
public MessageEventArgs MessageArgs;
|
||
|
public MessageType Type;
|
||
|
|
||
|
public Message(MessageEventArgs messageArgs, MessageType type)
|
||
|
{
|
||
|
MessageArgs = messageArgs;
|
||
|
Type = type;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
[SerializeField]
|
||
|
private List<Message> m_IncomingMessages = new List<Message>();
|
||
|
|
||
|
[SerializeField]
|
||
|
private bool m_RegisteredMessageCallback;
|
||
|
|
||
|
private TestTools.TestRunner.DelayedCallback m_TimeoutCallback;
|
||
|
|
||
|
public void Init(BuildTarget buildTarget, int heartbeatTimeout)
|
||
|
{
|
||
|
isRunning = true;
|
||
|
m_HearbeatTimeOut = heartbeatTimeout;
|
||
|
EditorConnection.instance.Initialize();
|
||
|
if (!m_RegisteredConnectionCallbacks)
|
||
|
{
|
||
|
EditorConnection.instance.Initialize();
|
||
|
DelegateEditorConnectionEvents();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private void DelegateEditorConnectionEvents()
|
||
|
{
|
||
|
m_RegisteredConnectionCallbacks = true;
|
||
|
//This is needed because RemoteTestResultReceiver is not a ScriptableObject
|
||
|
EditorConnection.instance.Register(PlayerConnectionMessageIds.playerAliveHeartbeat, PlayerAliveHeartbeat);
|
||
|
|
||
|
// When a message comes in, we should not immediately process it but instead enqueue it for processing later
|
||
|
// in the frame. The problem this solves is that Unity only reserves about 1ms worth of time every frame to
|
||
|
// process message from the player connection. When some tests run in a player, it can take the editor
|
||
|
// minutes to react to all messages we receive because we only do 1ms of processing, then render all of the
|
||
|
// editor etc. -- Instead, we use that 1ms time-window to enqueue messages and then react to them later
|
||
|
// during the frame. This reduces the waiting time from minutes to seconds.
|
||
|
EditorConnection.instance.Register(PlayerConnectionMessageIds.testStartedMessageId, args => EnqueueMessage(new Message(args, MessageType.TestStarted)));
|
||
|
EditorConnection.instance.Register(PlayerConnectionMessageIds.testFinishedMessageId, args => EnqueueMessage(new Message(args, MessageType.TestFinished)));
|
||
|
EditorConnection.instance.Register(PlayerConnectionMessageIds.runStartedMessageId, args => EnqueueMessage(new Message(args, MessageType.RunStarted)));
|
||
|
EditorConnection.instance.Register(PlayerConnectionMessageIds.runFinishedMessageId, args => EnqueueMessage(new Message(args, MessageType.RunFinished)));
|
||
|
}
|
||
|
|
||
|
private void FlushMessageQueue()
|
||
|
{
|
||
|
EditorApplication.update -= FlushMessageQueue;
|
||
|
m_RegisteredMessageCallback = false;
|
||
|
foreach (var msg in m_IncomingMessages)
|
||
|
{
|
||
|
switch (msg.Type)
|
||
|
{
|
||
|
case MessageType.TestFinished:
|
||
|
{
|
||
|
CallbacksDelegator.instance.TestFinishedRemotely(msg.MessageArgs.data);
|
||
|
break;
|
||
|
}
|
||
|
case MessageType.TestStarted:
|
||
|
{
|
||
|
CallbacksDelegator.instance.TestStartedRemotely(msg.MessageArgs.data);
|
||
|
break;
|
||
|
}
|
||
|
case MessageType.RunStarted:
|
||
|
{
|
||
|
RunStarted(msg.MessageArgs);
|
||
|
break;
|
||
|
}
|
||
|
case MessageType.RunFinished:
|
||
|
{
|
||
|
RunFinished(msg.MessageArgs);
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
m_IncomingMessages.Clear();
|
||
|
}
|
||
|
|
||
|
private void EnqueueMessage(Message message)
|
||
|
{
|
||
|
m_TimeoutCallback?.Reset();
|
||
|
if (!m_RegisteredMessageCallback)
|
||
|
{
|
||
|
EditorApplication.update += FlushMessageQueue;
|
||
|
m_RegisteredMessageCallback = true;
|
||
|
}
|
||
|
m_IncomingMessages.Add(message);
|
||
|
}
|
||
|
|
||
|
private void RunStarted(MessageEventArgs messageEventArgs)
|
||
|
{
|
||
|
m_TimeoutCallback?.Reset();
|
||
|
CallbacksDelegator.instance.RunStartedRemotely(messageEventArgs.data);
|
||
|
}
|
||
|
|
||
|
private void RunFinished(MessageEventArgs messageEventArgs)
|
||
|
{
|
||
|
m_TimeoutCallback?.Clear();
|
||
|
EditorConnection.instance.Send(PlayerConnectionMessageIds.quitPlayerMessageId, null, messageEventArgs.playerId);
|
||
|
EditorConnection.instance.DisconnectAll();
|
||
|
|
||
|
CallbacksDelegator.instance.RunFinishedRemotely(messageEventArgs.data);
|
||
|
isRunning = false;
|
||
|
}
|
||
|
|
||
|
private void PlayerAliveHeartbeat(MessageEventArgs messageEventArgs)
|
||
|
{
|
||
|
m_TimeoutCallback?.Reset();
|
||
|
}
|
||
|
|
||
|
private void TimeoutCallback()
|
||
|
{
|
||
|
CallbacksDelegator.instance.RunFailed($"Test execution timed out. No activity received from the player in {m_HearbeatTimeOut} seconds.");
|
||
|
}
|
||
|
|
||
|
public void PostSuccessfulBuildAction()
|
||
|
{
|
||
|
m_TimeoutCallback = new TestTools.TestRunner.DelayedCallback(TimeoutCallback, m_HearbeatTimeOut);
|
||
|
}
|
||
|
}
|
||
|
}
|