|
- using System;
- using System.Collections.Generic;
- using System.Diagnostics;
- using System.Reflection;
- using System.Linq; // welcome to the dark side
-
- using Svelto.Tasks;
- using Svelto.Tasks.Lean;
- using Svelto.Tasks.Enumerators;
- using Svelto.Tasks.Lean.Unity;
- using UnityEngine;
-
- using TechbloxModdingAPI.App;
- using TechbloxModdingAPI.Tasks;
- using TechbloxModdingAPI.Utility;
-
- namespace TechbloxModdingAPI.Tests
- {
- /// <summary>
- /// API test system root class.
- /// </summary>
- public static class TestRoot
- {
- public static bool AutoShutdown = true;
-
- public const string ReportFile = "TechbloxModdingAPI_tests.log";
-
- private static bool _testsPassed = false;
-
- private static uint _testsCount = 0;
-
- private static uint _testsCountPassed = 0;
-
- private static uint _testsCountFailed = 0;
-
- private static string state = "StartingUp";
-
- private static Stopwatch timer;
-
- private static List<Type> testTypes = null;
-
- public static bool TestsPassed
- {
- get => _testsPassed;
- set
- {
- _testsPassed = _testsPassed && value;
- _testsCount++;
- if (value)
- {
- _testsCountPassed++;
- }
- else
- {
- _testsCountFailed++;
- }
- }
- }
-
- private static void StartUp()
- {
- // init
- timer = Stopwatch.StartNew();
- _testsPassed = true;
- _testsCount = 0;
- _testsCountPassed = 0;
- _testsCountFailed = 0;
- // flow control
- Game.Enter += (sender, args) => { GameTests().RunOn(new UpdateMonoRunner("TechbloxModdingAPITestRunner")); };
- Game.Exit += (s, a) => state = "ReturningFromGame";
- Client.EnterMenu += (sender, args) =>
- {
- if (state == "EnteringMenu")
- {
- MenuTests().RunOn(Scheduler.leanRunner);
- state = "EnteringGame";
- }
- if (state == "ReturningFromGame")
- {
- TearDown().RunOn(Scheduler.leanRunner);
- state = "ShuttingDown";
- }
- };
- // init tests here
- foreach (Type t in testTypes)
- {
- foreach (MethodBase m in t.GetMethods())
- {
- if (m.GetCustomAttribute<APITestStartUpAttribute>() != null)
- {
- try
- {
- m.Invoke(null, new object[0]);
- }
- catch (Exception e)
- {
- Assert.Fail($"Start up method '{m}' raised an exception: {e.ToString()}");
- }
- }
- }
- }
- state = "EnteringMenu";
- }
-
- private static IEnumerator<TaskContract> MenuTests()
- {
- yield return Yield.It;
- // menu tests
- foreach (Type t in testTypes)
- {
- foreach (MethodBase m in t.GetMethods())
- {
- APITestCaseAttribute a = m.GetCustomAttribute<APITestCaseAttribute>();
- if (a != null && a.TestType == TestType.Menu)
- {
- try
- {
- m.Invoke(null, new object[0]);
- }
- catch (Exception e)
- {
- Assert.Fail($"Menu test '{m}' raised an exception: {e.ToString()}");
- }
- yield return Yield.It;
- }
- }
- }
- // load game
- yield return GoToGameTests().Continue();
- }
-
- private static IEnumerator<TaskContract> GoToGameTests()
- {
- Client app = Client.Instance;
- int oldLength = 0;
- while (app.MyGames.Length == 0 || oldLength != app.MyGames.Length)
- {
- oldLength = app.MyGames.Length;
- yield return new WaitForSecondsEnumerator(1).Continue();
- }
- yield return Yield.It;
- try
- {
- app.MyGames[0].EnterGame();
- }
- catch (Exception e)
- {
- Console.WriteLine("Failed to go to game tests");
- Console.WriteLine(e);
- }
- /*Game newGame = Game.NewGame();
- yield return new WaitForSecondsEnumerator(5).Continue(); // wait for sync
- newGame.EnterGame();*/
- }
-
- private static IEnumerator<TaskContract> GameTests()
- {
- yield return Yield.It;
- Game currentGame = Game.CurrentGame();
- // in-game tests
- yield return new WaitForSecondsEnumerator(5).Continue(); // wait for game to finish loading
- var testTypesToRun = new[]
- {
- TestType.Game,
- TestType.SimulationMode,
- TestType.EditMode
- };
- for (var index = 0; index < testTypesToRun.Length; index++)
- {
- Logging.MetaLog($"Running test type {testTypesToRun[index]}");
- foreach (Type t in testTypes)
- {
- foreach (MethodBase m in t.GetMethods())
- {
- APITestCaseAttribute a = m.GetCustomAttribute<APITestCaseAttribute>();
- if (a == null || a.TestType != testTypesToRun[index]) continue;
-
- object ret = null;
- try
- {
- ret = m.Invoke(null, new object[0]);
- }
- catch (Exception e)
- {
- Assert.Fail($"{a.TestType} test '{m}' raised an exception: {e}");
- }
-
- if (ret is IEnumerator<TaskContract> enumerator)
- { //Support enumerator methods with added exception handling
- bool cont;
- do
- { //Can't use yield return in a try block...
- try
- { //And with Continue() exceptions aren't caught
- cont = enumerator.MoveNext();
- }
- catch (Exception e)
- {
- Assert.Fail($"{a.TestType} test '{m}' raised an exception: {e}");
- cont = false;
- }
-
- yield return Yield.It;
- } while (cont);
- }
-
- yield return Yield.It;
- }
- }
-
- if (index + 1 < testTypesToRun.Length) //Don't toggle on the last test
- {
- bool running = currentGame.IsTimeRunning;
- currentGame.ToggleTimeMode();
- while (running ? !currentGame.IsTimeStopped : !currentGame.IsTimeRunning)
- {
- Logging.MetaLog($"Waiting for time to {(running?"stop":"start")}...");
- yield return new WaitForSecondsEnumerator(1).Continue();
- }
- }
-
- yield return new WaitForSecondsEnumerator(5).Continue();
- }
- // exit game
- yield return ReturnToMenu().Continue();
- }
-
- private static IEnumerator<TaskContract> ReturnToMenu()
- {
- Logging.MetaLog("Returning to main menu");
- yield return Yield.It;
- Game.CurrentGame().ExitGame();
- }
-
- private static IEnumerator<TaskContract> TearDown()
- {
- yield return new WaitForSecondsEnumerator(5).Continue();
- Logging.MetaLog("Tearing down test run");
- // dispose tests here
- foreach (Type t in testTypes)
- {
- foreach (MethodBase m in t.GetMethods())
- {
- if (m.GetCustomAttribute<APITestTearDownAttribute>() != null)
- {
- try
- {
- m.Invoke(null, new object[0]);
- }
- catch (Exception e)
- {
- Assert.Warn($"Tear down method '{m}' raised an exception: {e.ToString()}");
- }
- yield return Yield.It;
- }
- }
- }
- // finish up
- Assert.CallsComplete();
- timer.Stop();
- string verdict = _testsPassed ? "--- PASSED :) ---" : "--- FAILED :( ---";
- Assert.Log($"VERDICT: {verdict} ({_testsCountPassed}/{_testsCountFailed}/{_testsCount} P/F/T in {timer.ElapsedMilliseconds}ms)");
- yield return Yield.It;
- // end game
- Logging.MetaLog("Completed test run: " + verdict);
- yield return Yield.It;
- Assert.CloseLog();
- if (AutoShutdown) Application.Quit();
- }
-
- private static void FindTests(Assembly asm)
- {
- testTypes = new List<Type>();
- foreach (Type t in asm.GetTypes())
- {
- if (t.GetCustomAttribute<APITestClassAttribute>() != null)
- {
- testTypes.Add(t);
- }
- }
- }
-
- /// <summary>
- /// Runs the tests.
- /// </summary>
- /// <param name="asm">Assembly to search for tests. When set to null, this uses the TechbloxModdingAPI assembly. </param>
- public static void RunTests(Assembly asm = null)
- {
- if (asm == null) asm = Assembly.GetExecutingAssembly();
- FindTests(asm);
- Logging.MetaLog("Starting test run");
- // log metadata
- Assert.Log($"Unity {Application.unityVersion}");
- Assert.Log($"Techblox {Application.version}");
- Assert.Log($"TechbloxModdingAPI {Assembly.GetExecutingAssembly().GetName().Version}");
- Assert.Log($"Testing {asm.GetName().Name} {asm.GetName().Version}");
- Assert.Log($"START: --- {DateTime.Now.ToString()} --- ({testTypes.Count} tests classes detected)");
- StartUp();
- Logging.MetaLog("Test StartUp complete");
- }
- }
- }
|