using System; using System.Collections.Concurrent; using System.IO; using System.Runtime.CompilerServices; using Unity.Mathematics; namespace TechbloxModdingAPI.Tests { /// /// API test system assertion utilities. /// public static class Assert { private static StreamWriter logFile = null; private static ConcurrentDictionary callbacks = new ConcurrentDictionary(); private const string PASS = "SUCCESS: "; private const string FAIL = "FAILURE: "; private const string WARN = "WARNING: "; private const string INFO = "DEBUG: "; /// /// Log a message to the test log. /// /// Message. /// Message ending. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Log(string msg, string end = "\n") { if (logFile == null) openTestLog(); logFile.Write(msg + end); logFile.Flush(); } /// /// Asserts that the event receives a callback... eventually. /// Add the eventhandler returned by this method to the relevant event. /// This does not assert that the callback happens under that event's intended circumstances. /// Add another event handler to assert specific circumstance requirements. /// /// The callback event handler. /// Event name. /// Event error message. /// The event handler callback argument object. public static EventHandler CallsBack(string eventName, string eventMsg = null) { if (eventMsg == null) eventMsg = $"expected callback to {eventName} but it never occurred..."; callbacks[eventName] = eventMsg; return (sender, args) => { string value = null; if (!callbacks.TryRemove(eventName, out value)) { Log(WARN + $"callback to {eventName} occurred again or a related error occurred... (Received '{args.ToString()}' from '{(sender == null ? (string)sender : sender.ToString())}')"); } Log(PASS + $"callback to {eventName} occurred... (Received '{args.ToString()}' from '{(sender == null ? (string)sender : sender.ToString())}')"); TestRoot.TestsPassed = true; }; } public static bool NotNull(T obj, string err = null, string success = null) { if (err == null) err = $"{nameof(T)} object was null."; if (success == null) success = $"{nameof(T)} '{obj}' not null"; if (obj == null) { Log(FAIL + err); TestRoot.TestsPassed = false; return false; } else { Log(PASS + success); TestRoot.TestsPassed = true; return true; } } public static bool Equal(T obj1, T obj2, string err = null, string success = null) { if (err == null) err = $"{nameof(T)} '{obj1}' is not equal to '{obj2}'."; if (success == null) success = $"{nameof(T)} '{obj1}' is equal to '{obj2}'."; if ((obj1 == null && obj2 == null) || (obj1 != null && obj2 != null && obj1.Equals(obj2) && obj2.Equals(obj1))) { // pass Log(PASS + success); TestRoot.TestsPassed = true; return true; } else { // fail Log(FAIL + err); TestRoot.TestsPassed = false; return false; } } public static bool Errorless(Action tryThis, string err = null, string success = null) { if (err == null) err = $"{tryThis} raised an exception: "; if (success == null) success = $"{tryThis} completed without raising an exception."; try { tryThis(); } catch (Exception e) { Log(FAIL + err + e); TestRoot.TestsPassed = false; return false; } TestRoot.TestsPassed = true; Log(PASS + success); return true; } public static bool Errorful(Action tryThis, string err = null, string success = null) where T : Exception { if (err == null) err = $"{tryThis} was expected to error but completed without errors."; if (success == null) success = $"{tryThis} completed with an expected error."; try { tryThis(); } catch (T e) { TestRoot.TestsPassed = true; Log(PASS + success + " " + e.GetType().Name + ": " + e.Message); return true; } Log(FAIL + err); TestRoot.TestsPassed = false; return false; } public static bool CloseTo(float a, float b, string err = null, string success = null, float delta = float.Epsilon) { if (err == null) err = $"{a} is not within {delta} of {b}."; if (success == null) success = $"{a} is close enough to {b}."; if (Math.Abs(a - b) > delta) { Log(FAIL + err); TestRoot.TestsPassed = false; return false; } else { TestRoot.TestsPassed = true; Log(PASS + success); return true; } } public static bool CloseTo(double a, double b, string err = null, string success = null, double delta = double.Epsilon) { if (err == null) err = $"{a} is not within {delta} of {b}."; if (success == null) success = $"{a} is close enough to {b}."; if (Math.Abs(a - b) > delta) { Log(FAIL + err); TestRoot.TestsPassed = false; return false; } else { TestRoot.TestsPassed = true; Log(PASS + success); return true; } } public static bool CloseTo(float3 a, float3 b, string err = null, string success = null, float delta = float.Epsilon) { if (err == null) err = $"{a} is not within {delta} of {b} in every direction."; if (success == null) success = $"{a} is close enough to {b}."; bool xClose = CloseTo(a.x, b.x, err, success, delta); bool yClose = CloseTo(a.y, b.y, err, success, delta); bool zClose = CloseTo(a.z, b.z, err, success, delta); if (xClose && yClose && zClose) { //TestRoot.TestsPassed = true; //Log(PASS + success); return true; } else { //Log(FAIL + err); //TestRoot.TestsPassed = false; return false; } } public static void Fail(string msg = null) { if (msg == null) msg = $"Manual test failure with no message provided."; Log(FAIL + msg); TestRoot.TestsPassed = false; } public static void Pass(string msg = null) { if (msg == null) msg = $"Manual test pass with no message provided."; Log(PASS + msg); TestRoot.TestsPassed = true; } public static void Warn(string msg = null) { if (msg == null) msg = $"Manual test warning with no message provided."; Log(WARN + msg); TestRoot.TestsPassed = true; } internal static void CallsComplete() { foreach(string key in callbacks.Keys) { Log(FAIL + callbacks[key]); TestRoot.TestsPassed = false; } } internal static void CloseLog() { if (logFile != null) logFile.Close(); } [MethodImpl(MethodImplOptions.AggressiveInlining)] private static void openTestLog() { logFile = File.CreateText(TestRoot.ReportFile); } } }