UnityGame/Library/PackageCache/com.unity.test-framework/UnityEditor.TestRunner/TestLaunchers/RemotePlayerTestController.cs
2024-10-27 10:53:47 +03:00

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);
}
}
}