A stable modding interface between Techblox and mods https://mod.exmods.org/
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

291 lines
7.9KB

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Diagnostics;
  4. using System.Reflection;
  5. using System.Linq; // welcome to the dark side
  6. using Svelto.Tasks;
  7. using Svelto.Tasks.Lean;
  8. using Svelto.Tasks.Enumerators;
  9. using UnityEngine;
  10. using TechbloxModdingAPI.App;
  11. using TechbloxModdingAPI.Tasks;
  12. using TechbloxModdingAPI.Utility;
  13. namespace TechbloxModdingAPI.Tests
  14. {
  15. /// <summary>
  16. /// API test system root class.
  17. /// </summary>
  18. public static class TestRoot
  19. {
  20. public static bool AutoShutdown = true;
  21. public const string ReportFile = "TechbloxModdingAPI_tests.log";
  22. private static bool _testsPassed = false;
  23. private static uint _testsCount = 0;
  24. private static uint _testsCountPassed = 0;
  25. private static uint _testsCountFailed = 0;
  26. private static string state = "StartingUp";
  27. private static Stopwatch timer;
  28. private static List<Type> testTypes = null;
  29. public static bool TestsPassed
  30. {
  31. get => _testsPassed;
  32. set
  33. {
  34. _testsPassed = _testsPassed && value;
  35. _testsCount++;
  36. if (value)
  37. {
  38. _testsCountPassed++;
  39. }
  40. else
  41. {
  42. _testsCountFailed++;
  43. }
  44. }
  45. }
  46. private static void StartUp()
  47. {
  48. // init
  49. timer = Stopwatch.StartNew();
  50. _testsPassed = true;
  51. _testsCount = 0;
  52. _testsCountPassed = 0;
  53. _testsCountFailed = 0;
  54. // flow control
  55. Game.Enter += (sender, args) => { GameTests().RunOn(RobocraftX.Schedulers.ClientLean.EveryFrameStepRunner_TimeRunningAndStopped); };
  56. Game.Exit += (s, a) => state = "ReturningFromGame";
  57. Client.EnterMenu += (sender, args) =>
  58. {
  59. if (state == "EnteringMenu")
  60. {
  61. MenuTests().RunOn(Scheduler.leanRunner);
  62. state = "EnteringGame";
  63. }
  64. if (state == "ReturningFromGame")
  65. {
  66. TearDown().RunOn(Scheduler.leanRunner);
  67. state = "ShuttingDown";
  68. }
  69. };
  70. // init tests here
  71. foreach (Type t in testTypes)
  72. {
  73. foreach (MethodBase m in t.GetMethods())
  74. {
  75. if (m.GetCustomAttribute<APITestStartUpAttribute>() != null)
  76. {
  77. try
  78. {
  79. m.Invoke(null, new object[0]);
  80. }
  81. catch (Exception e)
  82. {
  83. Assert.Fail($"Start up method '{m}' raised an exception: {e.ToString()}");
  84. }
  85. }
  86. }
  87. }
  88. state = "EnteringMenu";
  89. }
  90. private static IEnumerator<TaskContract> MenuTests()
  91. {
  92. yield return Yield.It;
  93. // menu tests
  94. foreach (Type t in testTypes)
  95. {
  96. foreach (MethodBase m in t.GetMethods())
  97. {
  98. APITestCaseAttribute a = m.GetCustomAttribute<APITestCaseAttribute>();
  99. if (a != null && a.TestType == TestType.Menu)
  100. {
  101. try
  102. {
  103. m.Invoke(null, new object[0]);
  104. }
  105. catch (Exception e)
  106. {
  107. Assert.Fail($"Menu test '{m}' raised an exception: {e.ToString()}");
  108. }
  109. yield return Yield.It;
  110. }
  111. }
  112. }
  113. // load game
  114. yield return GoToGameTests().Continue();
  115. }
  116. private static IEnumerator<TaskContract> GoToGameTests()
  117. {
  118. Client app = Client.Instance;
  119. int oldLength = 0;
  120. while (app.MyGames.Length == 0 || oldLength != app.MyGames.Length)
  121. {
  122. oldLength = app.MyGames.Length;
  123. yield return new WaitForSecondsEnumerator(1).Continue();
  124. }
  125. yield return Yield.It;
  126. try
  127. {
  128. app.MyGames[0].EnterGame();
  129. }
  130. catch (Exception e)
  131. {
  132. Console.WriteLine("Failed to go to game tests");
  133. Console.WriteLine(e);
  134. }
  135. /*Game newGame = Game.NewGame();
  136. yield return new WaitForSecondsEnumerator(5).Continue(); // wait for sync
  137. newGame.EnterGame();*/
  138. }
  139. private static IEnumerator<TaskContract> GameTests()
  140. {
  141. yield return Yield.It;
  142. Game currentGame = Game.CurrentGame();
  143. // in-game tests
  144. yield return new WaitForSecondsEnumerator(5).Continue(); // wait for game to finish loading
  145. var testTypesToRun = new[]
  146. {
  147. TestType.Game,
  148. TestType.SimulationMode,
  149. TestType.EditMode
  150. };
  151. for (var index = 0; index < testTypesToRun.Length; index++)
  152. {
  153. foreach (Type t in testTypes)
  154. {
  155. foreach (MethodBase m in t.GetMethods())
  156. {
  157. APITestCaseAttribute a = m.GetCustomAttribute<APITestCaseAttribute>();
  158. if (a == null || a.TestType != testTypesToRun[index]) continue;
  159. object ret = null;
  160. try
  161. {
  162. ret = m.Invoke(null, new object[0]);
  163. }
  164. catch (Exception e)
  165. {
  166. Assert.Fail($"{a.TestType} test '{m}' raised an exception: {e}");
  167. }
  168. if (ret is IEnumerator<TaskContract> enumerator)
  169. { //Support enumerator methods with added exception handling
  170. bool cont;
  171. do
  172. { //Can't use yield return in a try block...
  173. try
  174. { //And with Continue() exceptions aren't caught
  175. cont = enumerator.MoveNext();
  176. }
  177. catch (Exception e)
  178. {
  179. Assert.Fail($"{a.TestType} test '{m}' raised an exception: {e}");
  180. cont = false;
  181. }
  182. yield return Yield.It;
  183. } while (cont);
  184. }
  185. yield return Yield.It;
  186. }
  187. }
  188. if (index + 1 < testTypesToRun.Length) //Don't toggle on the last test
  189. currentGame.ToggleTimeMode();
  190. yield return new WaitForSecondsEnumerator(5).Continue();
  191. }
  192. // exit game
  193. yield return ReturnToMenu().Continue();
  194. }
  195. private static IEnumerator<TaskContract> ReturnToMenu()
  196. {
  197. Logging.MetaLog("Returning to main menu");
  198. yield return Yield.It;
  199. Game.CurrentGame().ExitGame();
  200. }
  201. private static IEnumerator<TaskContract> TearDown()
  202. {
  203. yield return new WaitForSecondsEnumerator(5).Continue();
  204. Logging.MetaLog("Tearing down test run");
  205. // dispose tests here
  206. foreach (Type t in testTypes)
  207. {
  208. foreach (MethodBase m in t.GetMethods())
  209. {
  210. if (m.GetCustomAttribute<APITestTearDownAttribute>() != null)
  211. {
  212. try
  213. {
  214. m.Invoke(null, new object[0]);
  215. }
  216. catch (Exception e)
  217. {
  218. Assert.Warn($"Tear down method '{m}' raised an exception: {e.ToString()}");
  219. }
  220. yield return Yield.It;
  221. }
  222. }
  223. }
  224. // finish up
  225. Assert.CallsComplete();
  226. timer.Stop();
  227. string verdict = _testsPassed ? "--- PASSED :) ---" : "--- FAILED :( ---";
  228. Assert.Log($"VERDICT: {verdict} ({_testsCountPassed}/{_testsCountFailed}/{_testsCount} P/F/T in {timer.ElapsedMilliseconds}ms)");
  229. yield return Yield.It;
  230. // end game
  231. Logging.MetaLog("Completed test run: " + verdict);
  232. yield return Yield.It;
  233. Assert.CloseLog();
  234. if (AutoShutdown) Application.Quit();
  235. }
  236. private static void FindTests(Assembly asm)
  237. {
  238. testTypes = new List<Type>();
  239. foreach (Type t in asm.GetTypes())
  240. {
  241. if (t.GetCustomAttribute<APITestClassAttribute>() != null)
  242. {
  243. testTypes.Add(t);
  244. }
  245. }
  246. }
  247. /// <summary>
  248. /// Runs the tests.
  249. /// </summary>
  250. /// <param name="asm">Assembly to search for tests. When set to null, this uses the TechbloxModdingAPI assembly. </param>
  251. public static void RunTests(Assembly asm = null)
  252. {
  253. if (asm == null) asm = Assembly.GetExecutingAssembly();
  254. FindTests(asm);
  255. Logging.MetaLog("Starting test run");
  256. // log metadata
  257. Assert.Log($"Unity {Application.unityVersion}");
  258. Assert.Log($"Techblox {Application.version}");
  259. Assert.Log($"TechbloxModdingAPI {Assembly.GetExecutingAssembly().GetName().Version}");
  260. Assert.Log($"Testing {asm.GetName().Name} {asm.GetName().Version}");
  261. Assert.Log($"START: --- {DateTime.Now.ToString()} --- ({testTypes.Count} tests classes detected)");
  262. StartUp();
  263. Logging.MetaLog("Test StartUp complete");
  264. }
  265. }
  266. }