A game about forced loneliness, made by TACStudios
1using System;
2using System.Collections.Generic;
3using UnityEditor.Networking.PlayerConnection;
4using UnityEditor.TestTools.TestRunner;
5using UnityEditor.TestTools.TestRunner.Api;
6using UnityEngine;
7using UnityEngine.Networking.PlayerConnection;
8using UnityEngine.TestRunner.TestLaunchers;
9
10namespace UnityEditor.TestRunner.TestLaunchers
11{
12 [Serializable]
13 internal class RemoteTestRunController : ScriptableSingleton<RemoteTestRunController>
14 {
15 internal const int k_HeartbeatTimeout = 60 * 10;
16
17 [SerializeField]
18 internal bool isRunning;
19
20 [SerializeField]
21 private bool m_RegisteredConnectionCallbacks;
22
23 [SerializeField]
24 private int m_HearbeatTimeOut;
25
26 private enum MessageType
27 {
28 TestStarted,
29 TestFinished,
30 RunStarted,
31 RunFinished
32 }
33 [Serializable]
34 private struct Message
35 {
36 public MessageEventArgs MessageArgs;
37 public MessageType Type;
38
39 public Message(MessageEventArgs messageArgs, MessageType type)
40 {
41 MessageArgs = messageArgs;
42 Type = type;
43 }
44 }
45
46 [SerializeField]
47 private List<Message> m_IncomingMessages = new List<Message>();
48
49 [SerializeField]
50 private bool m_RegisteredMessageCallback;
51
52 private TestTools.TestRunner.DelayedCallback m_TimeoutCallback;
53
54 public void Init(BuildTarget buildTarget, int heartbeatTimeout)
55 {
56 isRunning = true;
57 m_HearbeatTimeOut = heartbeatTimeout;
58 EditorConnection.instance.Initialize();
59 if (!m_RegisteredConnectionCallbacks)
60 {
61 EditorConnection.instance.Initialize();
62 DelegateEditorConnectionEvents();
63 }
64 }
65
66 private void DelegateEditorConnectionEvents()
67 {
68 m_RegisteredConnectionCallbacks = true;
69 //This is needed because RemoteTestResultReceiver is not a ScriptableObject
70 EditorConnection.instance.Register(PlayerConnectionMessageIds.playerAliveHeartbeat, PlayerAliveHeartbeat);
71
72 // When a message comes in, we should not immediately process it but instead enqueue it for processing later
73 // in the frame. The problem this solves is that Unity only reserves about 1ms worth of time every frame to
74 // process message from the player connection. When some tests run in a player, it can take the editor
75 // minutes to react to all messages we receive because we only do 1ms of processing, then render all of the
76 // editor etc. -- Instead, we use that 1ms time-window to enqueue messages and then react to them later
77 // during the frame. This reduces the waiting time from minutes to seconds.
78 EditorConnection.instance.Register(PlayerConnectionMessageIds.testStartedMessageId, args => EnqueueMessage(new Message(args, MessageType.TestStarted)));
79 EditorConnection.instance.Register(PlayerConnectionMessageIds.testFinishedMessageId, args => EnqueueMessage(new Message(args, MessageType.TestFinished)));
80 EditorConnection.instance.Register(PlayerConnectionMessageIds.runStartedMessageId, args => EnqueueMessage(new Message(args, MessageType.RunStarted)));
81 EditorConnection.instance.Register(PlayerConnectionMessageIds.runFinishedMessageId, args => EnqueueMessage(new Message(args, MessageType.RunFinished)));
82 }
83
84 private void FlushMessageQueue()
85 {
86 EditorApplication.update -= FlushMessageQueue;
87 m_RegisteredMessageCallback = false;
88 foreach (var msg in m_IncomingMessages)
89 {
90 switch (msg.Type)
91 {
92 case MessageType.TestFinished:
93 {
94 CallbacksDelegator.instance.TestFinishedRemotely(msg.MessageArgs.data);
95 break;
96 }
97 case MessageType.TestStarted:
98 {
99 CallbacksDelegator.instance.TestStartedRemotely(msg.MessageArgs.data);
100 break;
101 }
102 case MessageType.RunStarted:
103 {
104 RunStarted(msg.MessageArgs);
105 break;
106 }
107 case MessageType.RunFinished:
108 {
109 RunFinished(msg.MessageArgs);
110 break;
111 }
112 }
113 }
114 m_IncomingMessages.Clear();
115 }
116
117 private void EnqueueMessage(Message message)
118 {
119 m_TimeoutCallback?.Reset();
120 if (!m_RegisteredMessageCallback)
121 {
122 EditorApplication.update += FlushMessageQueue;
123 m_RegisteredMessageCallback = true;
124 }
125 m_IncomingMessages.Add(message);
126 }
127
128 private void RunStarted(MessageEventArgs messageEventArgs)
129 {
130 m_TimeoutCallback?.Reset();
131 CallbacksDelegator.instance.RunStartedRemotely(messageEventArgs.data);
132 }
133
134 private void RunFinished(MessageEventArgs messageEventArgs)
135 {
136 m_TimeoutCallback?.Clear();
137 EditorConnection.instance.Send(PlayerConnectionMessageIds.quitPlayerMessageId, null, messageEventArgs.playerId);
138 EditorConnection.instance.DisconnectAll();
139
140 CallbacksDelegator.instance.RunFinishedRemotely(messageEventArgs.data);
141 isRunning = false;
142 }
143
144 private void PlayerAliveHeartbeat(MessageEventArgs messageEventArgs)
145 {
146 m_TimeoutCallback?.Reset();
147 }
148
149 private void TimeoutCallback()
150 {
151 CallbacksDelegator.instance.RunFailed($"Test execution timed out. No activity received from the player in {m_HearbeatTimeOut} seconds.");
152 }
153
154 public void PostSuccessfulBuildAction()
155 {
156 m_TimeoutCallback = new TestTools.TestRunner.DelayedCallback(TimeoutCallback, m_HearbeatTimeOut);
157 }
158 }
159}