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.

255 lines
7.5KB

  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 GamecraftModdingAPI.App;
  11. using GamecraftModdingAPI.Tasks;
  12. using GamecraftModdingAPI.Utility;
  13. namespace GamecraftModdingAPI.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 = "GamecraftModdingAPI_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.Lean.EveryFrameStepRunner_RUNS_IN_TIME_STOPPED_AND_RUNNING); };
  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. m.Invoke(null, new object[0]);
  78. }
  79. }
  80. }
  81. state = "EnteringMenu";
  82. }
  83. private static IEnumerator<TaskContract> MenuTests()
  84. {
  85. yield return Yield.It;
  86. // menu tests
  87. foreach (Type t in testTypes)
  88. {
  89. foreach (MethodBase m in t.GetMethods())
  90. {
  91. APITestCaseAttribute a = m.GetCustomAttribute<APITestCaseAttribute>();
  92. if (a != null && a.TestType == TestType.Menu)
  93. {
  94. m.Invoke(null, new object[0]);
  95. yield return Yield.It;
  96. }
  97. }
  98. }
  99. // load game
  100. yield return GoToGameTests().Continue();
  101. }
  102. private static IEnumerator<TaskContract> GoToGameTests()
  103. {
  104. Client app = new Client();
  105. int oldLength = 0;
  106. while (app.MyGames.Length == 0 || oldLength != app.MyGames.Length)
  107. {
  108. oldLength = app.MyGames.Length;
  109. yield return new WaitForSecondsEnumerator(1).Continue();
  110. }
  111. yield return Yield.It;
  112. app.MyGames[0].EnterGame();
  113. // returning from a new game without saving will hard lock GC (it's an invalid state)
  114. //Game newGame = Game.NewGame();
  115. //yield return new WaitForSecondsEnumerator(5).Continue(); // wait for sync
  116. //newGame.EnterGame();
  117. }
  118. private static IEnumerator<TaskContract> GameTests()
  119. {
  120. yield return Yield.It;
  121. Game currentGame = Game.CurrentGame();
  122. // in-game tests
  123. yield return new WaitForSecondsEnumerator(5).Continue(); // wait for game to finish loading
  124. foreach (Type t in testTypes)
  125. {
  126. foreach (MethodBase m in t.GetMethods())
  127. {
  128. APITestCaseAttribute a = m.GetCustomAttribute<APITestCaseAttribute>();
  129. if (a != null && a.TestType == TestType.Game)
  130. {
  131. m.Invoke(null, new object[0]);
  132. yield return Yield.It;
  133. }
  134. }
  135. }
  136. currentGame.ToggleTimeMode();
  137. yield return new WaitForSecondsEnumerator(5).Continue();
  138. // simulation tests
  139. foreach (Type t in testTypes)
  140. {
  141. foreach (MethodBase m in t.GetMethods())
  142. {
  143. APITestCaseAttribute a = m.GetCustomAttribute<APITestCaseAttribute>();
  144. if (a != null && a.TestType == TestType.SimulationMode)
  145. {
  146. m.Invoke(null, new object[0]);
  147. yield return Yield.It;
  148. }
  149. }
  150. }
  151. currentGame.ToggleTimeMode();
  152. yield return new WaitForSecondsEnumerator(5).Continue();
  153. // build tests
  154. foreach (Type t in testTypes)
  155. {
  156. foreach (MethodBase m in t.GetMethods())
  157. {
  158. APITestCaseAttribute a = m.GetCustomAttribute<APITestCaseAttribute>();
  159. if (a != null && a.TestType == TestType.EditMode)
  160. {
  161. m.Invoke(null, new object[0]);
  162. yield return Yield.It;
  163. }
  164. }
  165. }
  166. // exit game
  167. yield return new WaitForSecondsEnumerator(5).Continue();
  168. yield return ReturnToMenu().Continue();
  169. }
  170. private static IEnumerator<TaskContract> ReturnToMenu()
  171. {
  172. Logging.MetaLog("Returning to main menu");
  173. yield return Yield.It;
  174. Game.CurrentGame().ExitGame();
  175. }
  176. private static IEnumerator<TaskContract> TearDown()
  177. {
  178. yield return new WaitForSecondsEnumerator(5).Continue();
  179. Logging.MetaLog("Tearing down test run");
  180. // dispose tests here
  181. foreach (Type t in testTypes)
  182. {
  183. foreach (MethodBase m in t.GetMethods())
  184. {
  185. if (m.GetCustomAttribute<APITestTearDownAttribute>() != null)
  186. {
  187. m.Invoke(null, new object[0]);
  188. yield return Yield.It;
  189. }
  190. }
  191. }
  192. // finish up
  193. Assert.CallsComplete();
  194. timer.Stop();
  195. string verdict = _testsPassed ? "--- PASSED :) ---" : "--- FAILED :( ---";
  196. Assert.Log($"VERDICT: {verdict} ({_testsCountPassed}/{_testsCountFailed}/{_testsCount} P/F/T in {timer.ElapsedMilliseconds}ms)");
  197. yield return Yield.It;
  198. // end game
  199. Logging.MetaLog("Completed test run: " + verdict);
  200. yield return Yield.It;
  201. Assert.CloseLog();
  202. if (AutoShutdown) Application.Quit();
  203. }
  204. private static void FindTests(Assembly asm)
  205. {
  206. testTypes = new List<Type>();
  207. foreach (Type t in asm.GetTypes())
  208. {
  209. if (t.GetCustomAttribute<APITestClassAttribute>() != null)
  210. {
  211. testTypes.Add(t);
  212. }
  213. }
  214. }
  215. /// <summary>
  216. /// Runs the tests.
  217. /// </summary>
  218. /// <param name="asm">Assembly to search for tests. When set to null, this uses the GamecraftModdingAPI assembly. </param>
  219. public static void RunTests(Assembly asm = null)
  220. {
  221. if (asm == null) asm = Assembly.GetExecutingAssembly();
  222. FindTests(asm);
  223. Logging.MetaLog("Starting test run");
  224. // log metadata
  225. Assert.Log($"Unity {Application.unityVersion}");
  226. Assert.Log($"Gamecraft {Application.version}");
  227. Assert.Log($"GamecraftModdingAPI {Assembly.GetExecutingAssembly().GetName().Version}");
  228. Assert.Log($"Testing {asm.GetName().Name} {asm.GetName().Version}");
  229. Assert.Log($"START: --- {DateTime.Now.ToString()} --- ({testTypes.Count} tests classes detected)");
  230. StartUp();
  231. Logging.MetaLog("Test StartUp complete");
  232. }
  233. }
  234. }