2using System.Reflection;
3using System.Collections.Generic;
8using Microsoft.Xna.Framework;
15 public class TestForAttribute : System.Attribute
18 public TestForAttribute(
string name)
26 public class TestContext
28 public string Description =
"";
30 public TestContext(
string description =
"")
32 Description = description;
35 public override string ToString() => Description;
37 public static TestContext operator +(TestContext a, TestContext b)
38 =>
new TestContext(a.Description +
" | " + b.Description);
41 public class TestResult
43 public TestContext Context;
47 public Exception exception;
49 public void ToBeEqual(
object o) => State = Object.Equals(Result, o);
50 public void ToBeNotEqual(
object o) => State = !Object.Equals(Result, o);
51 public void ToThrow() => State = Error;
52 public void ToNotThrow() => State = !Error;
55 public static string GetTestClassName(
string name) => name +
"Test";
56 public static string GetTestMethodName(
string name) => name +
"Test";
57 public static bool IsTestable(MemberInfo member, Type original)
59 if (member is MethodInfo method)
62 method.IsSpecialName ||
64 method.DeclaringType != original
73 public static bool IsTestingMethod(MethodInfo mi) => mi.Name.EndsWith(
"Test") || Attribute.IsDefined(mi, typeof(TestForAttribute));
75 public static Dictionary<string, Type> FindAllNonTests()
77 Assembly TestAssembly = Assembly.GetAssembly(typeof(UnitTest));
79 Dictionary<string, Type> types =
new();
81 foreach (Type t
in TestAssembly.GetTypes().Where(t => !t.IsSubclassOf(typeof(UnitTest))))
83 if (t.IsSpecialName)
continue;
84 if (t.Name.StartsWith(
"<>"))
continue;
85 if (t == typeof(UnitTest))
continue;
86 if (t == typeof(TestForAttribute))
continue;
87 if (t == typeof(TestContext))
continue;
88 if (t == typeof(TestResult))
continue;
89 if (t.IsAssignableTo(typeof(IAssemblyPlugin)))
continue;
97 public static Dictionary<string, Type> FindAllTests()
99 Assembly TestAssembly = Assembly.GetAssembly(typeof(UnitTest));
101 Dictionary<string, Type> testClasses =
new();
103 foreach (Type t
in TestAssembly.GetTypes().Where(t => t.IsSubclassOf(typeof(UnitTest))))
105 TestForAttribute attribute = t.GetCustomAttribute<TestForAttribute>();
106 if (attribute !=
null)
108 testClasses[GetTestClassName(attribute.Name)] = t;
112 testClasses[t.Name] = t;
119 public static Dictionary<string, MethodInfo> FindAllTestMethods(Type testType)
121 Dictionary<string, MethodInfo> testMethods =
new();
123 foreach (MethodInfo mi
in testType.GetMethods(AccessTools.all))
125 if (IsTestingMethod(mi))
127 TestForAttribute attribute = mi.GetCustomAttribute<TestForAttribute>();
128 if (attribute !=
null)
130 testMethods[GetTestMethodName(attribute.Name)] = mi;
134 testMethods[mi.Name] = mi;
144 public static void Coverage(IEnumerable<Type> originalTypes)
146 Dictionary<string, Type> AllTests = FindAllTests();
148 foreach (Type original
in originalTypes)
150 if (original ==
null)
continue;
152 Type testType = AllTests.GetValueOrDefault(GetTestClassName(original.Name));
153 if (testType ==
null)
155 Log($
"No test class for type {original}");
159 Coverage(original, testType);
164 public static void Coverage(
string typeName)
166 if (typeName ==
null || typeName ==
"") { Log($
"for what?");
return; };
168 List<Type> TestAssemblyTypes = Assembly.GetAssembly(typeof(UnitTest)).GetTypes().ToList();
170 Type original =
null;
172 original ??= TestAssemblyTypes.Find(T => T.FullName.Equals(typeName, StringComparison.OrdinalIgnoreCase));
173 original ??= TestAssemblyTypes.Find(T => T.Name.Equals(typeName, StringComparison.OrdinalIgnoreCase));
175 if (original !=
null)
181 List<Type> BaroAssemblyTypes = Assembly.GetAssembly(typeof(GameMain)).GetTypes().ToList();
183 original ??= BaroAssemblyTypes.Find(T => T.FullName.Equals(typeName, StringComparison.OrdinalIgnoreCase));
184 original ??= BaroAssemblyTypes.Find(T => T.Name.Equals(typeName, StringComparison.OrdinalIgnoreCase));
189 public static void Coverage(Type original)
191 if (original ==
null) { Log($
"No such type");
return; };
193 Dictionary<string, Type> AllTests = FindAllTests();
195 Type testType = AllTests.GetValueOrDefault(GetTestClassName(original.Name));
196 if (testType ==
null)
198 Log($
"No test class for type {original}");
202 Coverage(original, testType);
206 public static void Coverage(Type original, Type testType)
208 if (original ==
null) { Log($
"original type is null");
return; }
209 if (testType ==
null) { Log($
"test type is null");
return; }
211 MethodInfo customIsTestable = testType.GetMethod(
"IsTestable", AccessTools.all);
212 Dictionary<string, MethodInfo> testMethods = FindAllTestMethods(testType);
214 Log($
"Coverage for {original.Name}:");
217 foreach (MemberInfo mi
in original.GetMembers(AccessTools.all))
219 bool testable =
true;
221 if (customIsTestable !=
null)
223 testable = Convert.ToBoolean(customIsTestable.Invoke(
null,
new object[] { mi, original }));
227 testable = IsTestable(mi, original);
230 if (!testable)
continue;
232 bool covered = testMethods.ContainsKey(GetTestMethodName(mi.Name));
234 if (covered) Log($
" {mi}", Color.Lime);
235 else Log($
" {mi}", Color.Gray);
238 catch (Exception e) { Log(e, Color.Orange); }
243 public static void RunAll()
245 IEnumerable<Type> AllTests = FindAllTests().Values;
246 if (AllTests.Count() == 0) Log($
"no tests");
247 foreach (Type T
in AllTests) { Run(T); }
250 public static void Run(
string name,
string method =
null)
252 Dictionary<string, Type> AllTests = FindAllTests();
254 if (String.Equals(
"all", name, StringComparison.OrdinalIgnoreCase))
256 if (AllTests.Count == 0)
262 foreach (Type T
in AllTests.Values) { Run(T); }
268 if (AllTests.ContainsKey(name))
270 Run(AllTests[name], method);
274 Log($
"{name} not found");
277 public static void Run<RawType>(
string method =
null) => Run(typeof(RawType), method);
278 public static void Run(Type T,
string method =
null)
280 if (!T.IsSubclassOf(typeof(UnitTest)))
282 Log($
"{T} is not a test!");
287 Log($
"------------------------");
289 UnitTest test = (UnitTest)Activator.CreateInstance(T);
294 test.Execute(method);
298 Log($
"{T} Execution failed with:\n{e}", Color.Yellow);
308 public List<TestResult> Results =
new List<TestResult>();
309 public TestContext Context =
new TestContext();
310 public virtual void Prepare() { }
312 public virtual void Finalize() { }
314 public virtual void Execute(
string method =
null)
318 IEnumerable<MethodInfo> methods = this.GetType().GetMethods().Where(mi => IsTestingMethod(mi));
320 foreach (MethodInfo mi
in methods)
322 Describe(mi.Name, () => mi.Invoke(
this,
new object[] { }));
327 MethodInfo mi = this.GetType().GetMethod(method);
328 mi ??= this.GetType().GetMethod(GetTestMethodName(method));
332 Describe(mi.Name, () => mi.Invoke(
this,
new object[] { }));
336 Log($
"method {method} in {this.GetType()} not found", Color.Orange);
342 public void Describe(
string description, Action test)
344 TestContext oldContext = Context;
345 Context = oldContext +
new TestContext(description);
347 Context = oldContext;
351 public TestResult Expect(Action test,
string context =
null)
353 TestResult result =
new TestResult();
354 if (context !=
null) result.Context = Context +
new TestContext(context);
355 result.Context ??= Context;
364 result.exception = e;
370 public TestResult Expect(Func<object> test,
string context =
null)
372 TestResult result =
new TestResult();
373 if (context !=
null) result.Context = Context +
new TestContext(context);
374 result.Context ??= Context;
378 result.Result = test();
383 result.exception = e;
390 public TestResult Expect(
object o,
string context =
null)
392 TestResult result =
new TestResult();
393 if (context !=
null) result.Context = Context +
new TestContext(context);
394 result.Context ??= Context;
402 public void PrintResults()
405 foreach (TestResult tr
in Results)
407 if (tr.State.HasValue && tr.State.Value) passed++;
410 if (tr.State.HasValue)
412 cl = tr.State.Value ? Color.Lime : Color.Red;
419 object result = tr.Error ? tr.exception.Message : tr.Result;
421 Log($
"{tr.Context} [{result}]", cl);
424 string conclusion = passed == Results.Count ?
"All Passed" :
"Passed";
426 Log($
"\n{passed}/{Results.Count} {conclusion}");
431 Context =
new TestContext(this.GetType().Name);
434 public static void Log(
object msg, Color? cl =
null)
437 LuaCsLogger.LogMessage($
"{msg ?? "null"}", cl * 0.8f, cl);