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