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.

303 lines
8.3KB

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